The VB-TAPI 3 Answering Machine application is
written in Microsoft's Visual Basic 6.0. This is by no means a
commercial grade application, or it is merely a sample,
it works and accomplished its goal of getting me more familiar
with TAPI 3.0. You MUST be using Windows
2000 or a newer operating systems (e.g. XP), older Win9x, ME or NT 4
operating systems DO NOT have TAPI 3.0.
This program is written as an
exercise and is not intended for anything. You may freely use and
modify the source code contained in this product for use in your own
applications including commercial applications. However, you may not
redistribute any part of this archive or in any way derive financial
gain from this demo product without the express permission of its
author – Steven A. Frare.
Please do NOT redistribute
this archive, instead refer people to my website, where the latest
version can be downloaded free!
http://www.geocities.com/sfrare
No claim whatsoever is made
concerning the suitability of this source code product for any
particular task. Anyone who decides to use code from this sample does
so at their own risk and agrees not to hold the author or anyone
connected with this sample responsible for it's success or
failure.
Copyright (C) 2002, Steven A.
Frare all rights reserved
The application is
standalone, no .dll's or setup program. It simply requires
itself (VBTAPI3.exe) a greeting file in its program directory named
Greet.wav and it will create a subdirectory called Messages to hold
the recordings.
What this sample
demonstrates:
-
Enumerating TAPI
Addresses while filtering them based on TSP and
capabilities
-
Registering /
Un-registering for TAPI events
-
Accessing legacy C based
TAPI functions
-
Answering / Disconnecting
a call
-
Generating and detecting
DTMF digits
-
Getting Caller ID
information
-
Playing and Recording
.wav files through legacy TAPI devices and the desktop using
DirectSound8
Enumerating TAPI Addresses while filtering
them based on TSP and capabilities
Enumerating TAPI addresses is demonstrated in
the dlgSetup.frm code Private Sub FillDeviceList(). All
addresses are filtered first to make sure they are not using the Kernel-Mode Device Driver TSP, the NDIS Proxy TSP or the H.323 TSP's.
Then if the address passes that test it is checked to make sure it
supports TAPIMEDIATYPE_AUDIO for audio playback and record. All
addresses meeting this criteria are added to a drop down
list. Several other self explanatory settings are also set
here. If you don't understand what a setting is check the older
VB-TAPI page, it
has a primer
on an answering machine. Choosing Okay in this dialog saves the settings to the registry at:
HKEY_CURRENT_USER\Software\VB and VBA Program
Settings\VB-TAPI3\Settings
Registering / Un-registering for TAPI
events
Registering and un-registering for TAPI events
is done in the cmdOn_Click method in frmMain.frm code. The 'On'
button is a dual state button. When it reads 'On' it attempts to
register for TAPI events, get the DirectSoundDevice GUID's for
recording and playback and disable most of the buttons, while changing
the 'On' button caption to 'Off'. It registers for events
through a call to the RegisterForNotification sub. That sub will
unregister the address for events if it detects it has
already been registered
then call the TAPI 3.0 method RegisterCallNotifications method to register for all TAPI events.
Accessing legacy C based TAPI
functions
In the afore mentioned
cmdOn_Click sub the sample got the DirectSoundDevice GUID's for
recording and playback. To create a DirectSound capture or play
back device requires a GUID. For the default speaker and
microphone on the desktop this is easy, vbNullString will use the
default devices. For the legacy TAPI devices it is a bit of a
problem, which unfortunately boils down to a bit of a hack to get
around the problem.
There is no way that I can find to record to
or play from a file using TAPI 3.0 and Visual Basic... So a way
needs to be invented. The way used in this sample is to use
DirectSound8 to do the playing and recording. The TAPI address
can be correlated to the DirectSound device via the legacy device id,
which according to MSDN we can get from the
TAPI3Lib.ITLegacyCallMediaControl GetID method. MSDN states that
method is for use by automation clients, like VB, to use.
However not only is the method marked hidden, it contains an
unsupported type for automation clients as its second parameter and is
thus unusable in VB! So
now the hack... Simply write around One Hundred lines of code to
access legacy TAPI functions to get the ID in place of the one line of
code that should have worked! VBTAPI.bas contains those One
Hundred some odd lines to get the job done... The one useful
method VBTAPI.bas contains is GetDeviceId which returns the wave
device identifier based on the address index and a string that
describes the TAPI Device Class (e.g. "wave/out"). So when the
answering machine is turning on it calls GetLegacyWaveIDAsDXDeviceGUID
which calls the 'hack' method GetDeviceId then does a string compare
against the appropriate DirectSound device name. If the we
needed a "wave/out" device we may get a legacy id '2' then DirectSound
should have a device whose name is "WaveOut 2". A direct match,
a bit of a hack but it works. The application does this twice,
once for the record device and once for the play device, the GUID's
returned by GetLegacyWaveIDAsDXDeviceGUID are stored in form level
variables. Those GUID's are used when playing or
recording.
Answering / Disconnecting a call
Since we have already registered for events
there is much to do but what for the call notification event.
You will find all the events are delivered to the gobjTapiWithEvents_Event sub. In
particular for new calls we are interested in the TE_CALLNOTIFICATION event. When we
get it we make sure we aren't already on a call as this sample only
handles one call at a time, set the caller id labels on the form and
add references to the ITCallInfo
and ITBasicCallControl objects
exposed by the event. We also attempt to get caller id, explained
in more detail next. Once that is accomplished we expect to get
TE_ADDRESS events of type AE_RINGING and when the ring count is
greater than or equal to the ring counts put into the setup form a
flag is set and a timer enabled to answer the call. A timer is
used so that the TAPI thread is not held up while the call is
answered. You can see the sub tmrUpdate_Timer where this variable is
checked and if it is set the AM_Start sub is called which actually
answers the phone and plays the greeting. The state of the
playback is also monitored in tmrUpdate_Timer and when it is done we
start
recording.
Generating and detecting DTMF digits
When the call is answered the application also turns on digit
detection. It is not enough to have just set the flag to
include TE_DIGITEVENT when registering for call events, an
application must still explicitly turn on digit detection. So we
call:
gobjMediaControl.DetectDigits LINEDIGITMODE_DTMF
Where gobjMediaControl is a reference to an
ITLegacyCallMediaControl object that we set when we got the TE_CALLNOTIFICATION event above.
To disable digit detection is the same call except you pass zero as
the argument. To generate digits you pass a string of digits to
be generated to the ITLegacyCallMediaControl::generateDigits method
and tell the method how you want the digits generated, like this:
gobjMediaControl.GenerateDigits "*", LINEDIGITMODE_DTMF
Both the disabling of digit detection and the generation of digits
(in this case digit) can be seen in tmrUpdate_timer these are called
just before the application starts to record the call.
The answering machine application detects digits so that a person
can call in and get there messages remotely. When playing back
the greeting file every digit is caught in the TE_DIGITEVENT case of
the gobjTapiWithEvents_Event sub.
These digits are concatenated and a simple string compare is
done to see if the remote code entered matches the remote code that was
setup. If there is a match the greeting is stopped and the messages
start to play back. Now digit detection performs another
function, which is to duplicate the three playback controls on the
form. DTMF 1 is for 'Next', DTMF 2 is for 'Repeat' and DTMF 3
is for 'Stop', which will also hang up the call.
Getting Caller ID information
Caller ID caused me no end of grief, so I will
explain how it should work and how it works. First of all there
is a small subroutine to get the caller id info called, aptly
enough, GetCallerID which take an ITCallInfo object to query for the
caller
id strings. You
must be subscribed for caller id at your TelCo to get caller id information!
How it should work:
There should be two places where the
GetCallerID sub is called, once when the TE_CALLNOTIFICATION event is
sent then again if a TE_CALLINFOCHANGE event is detected with a cause
of CIC_CALLERID. This way we should be assured of getting it on
the original notification and then getting updates if the information
changes, which is likely to happen in the US
since the caller
id information is set between the first and second rings of a phone call.
How it does work:
The above steps worked fine in C++, in VB
with the Unimodem TSP they broke the application. In VB if
the ITCallInfo::CallInfoString method is called at a time when the
event TE_CALLINFOCHANGE did not occur the application would never
receive further new call information. That is it would work once then
it would just sit there. Additionally calling
the ITCallInfo::CallInfoString during TE_CALLNOTIFICATION made it so
that no TE_CALLINFOCHANGE events were ever delivered! You cannot
rely on TE_CALLINFOCHANGE events to occur, you must attempt to get the
caller id information during TE_CALLNOTIFICATION as that may be the
only time it is available. This is true with the Dialogic TSP
and the H.323 TSP. In particular with the H.323 TSP since you
don't ever get TE_CALLINFOCHANGE or AE_RINGING events. So to
make it work this application calls GetCallerInfo in three places,
TE_CALLNOTIFICATION, TE_CALLINFOCHANGE with cause equal to
CIC_CALLERID and TE_ADDRESS with
cause AE_RINGING. To get around not getting further new call
notifications the application re-registers for notifications after
every call. This bug can easily be reproduced on Win2K and WinXP
using the Incoming VB sample included in the platform SDK. Note
that it only happens when using
VB with the
Unimodem TSP. C++ works as expected, VB with the Dialogic TSP works as expected.
Playing and Recording .wav
files through legacy TAPI devices and the desktop using
DirectSound8
Playing a file with
DirectSound8 in VB is extremely simple! About the only thing
non-intuitive is to set the DSBUFFERDESC struct to contain the
flag DSBCAPS_GLOBALFOCUS as such:
dsBuf.lFlags=
DSBCAPS_GLOBALFOCUS
Failure to do that will cause
the sound to stop playing should application not have the input
focus. Other than that check the PlayFile sub for the very few
lines of of code it takes to play a file! It simply takes the
GUID of the device to play, which is already set up during the
cmdOn_Click method discussed above. For playing the status is
polled in tmrUpdate_timer. This can also be done with events; I
wanted to use both ways so I used events for recording and polling for
status during playing. I probably should have done that the
other way around since tmrUpdate_Timer got to be a mess after
awhile...
Recording is a little more
difficult but not too bad. Most of the recording code is in
basStream.bas. That code comes from a DirectSound7 example
streamto, which was broken but only took a little work to fix.
It uses two DirectX8 events. When event 0 is signaled the sound
buffer pointer is at the beginning of the buffer, meaning we just
started or we wrapped around. When event 1 is signaled the sound
buffer pointer is at the middle of the buffer. So for event 1
the application reads one half of the sound buffer, from the beginning
to the middle and appends that to a temp file. When event 0 is
signaled, and we haven't just started to record, the application reads
the second half of the buffer, from the middle to the end, and appends
that data to the temp file. This circular buffer routine
continues until StopRecording is called, the file is put into the
proper wave file format, and the DirectX objects are cleaned up.
The application now resets to go ready for the next call.
One note about sound playback
and recording, with a Dialogic TSP the sound was skipping, a sure sign
of buffer under run, so I needed to adjust the sound buffers for the
Dialogic Wave Drivers which can be done through Control Panel. I
adjusted the from 8192 bytes to 1024 bytes. The Dialogic sound
was a bit quiet to me.
That's it! Not too
bad. The caller id problem with VB and Unimodem is a bit of a
draw back, as well as the sound volume with Dialogic wave drivers
however I am a bit of a novice with DirectX so that may be
fixable. The obvious step of trying to control the volume is not
the solution.
Trouble
Unfortunately if you
have any questions I probably can't help. My test lab isn't that
great and my time demands are high. You can give it a shot if
you are really frustrated and e-mail me, but I have gone for greater
than six months without checking this e-mail account.
If you don't see any voice cards listed in
the setup drop down menu you probably do not have a voice modem, or
the voice modem is not setup correctly. This link to KenGolf.com looks
helpful in that case. Hopefully you read the first couple of
sentences of this document and are running under an Operating System
that has TAPI 3 (Win2K, WinXP or newer)
Have fun!
Steve