Fixed DevStudio 2003 build with memory check code.
[pwlib.git] / src / ptclib / vxml.cxx
blobbd1572350905b9cf1680bd94c81f04eb732ccfdb
1 /*
2 * vxml.cxx
4 * VXML engine for pwlib library
6 * Copyright (C) 2002 Equivalence Pty. Ltd.
8 * The contents of this file are subject to the Mozilla Public License
9 * Version 1.0 (the "License"); you may not use this file except in
10 * compliance with the License. You may obtain a copy of the License at
11 * http://www.mozilla.org/MPL/
13 * Software distributed under the License is distributed on an "AS IS"
14 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
15 * the License for the specific language governing rights and limitations
16 * under the License.
18 * The Original Code is Portable Windows Library.
20 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
22 * Contributor(s): ______________________________________.
24 * $Log$
25 * Revision 1.75 2007/09/08 11:34:28 rjongbloed
26 * Improved memory checking (leaks etc), especially when using MSVC debug library.
28 * Revision 1.74 2007/09/04 11:33:21 csoutheren
29 * Add PlayTone
30 * Add access to session variable table
32 * Revision 1.73 2007/04/10 05:08:48 rjongbloed
33 * Fixed issue with use of static C string variables in DLL environment,
34 * must use functional interface for correct initialisation.
36 * Revision 1.72 2007/04/04 01:51:38 rjongbloed
37 * Reviewed and adjusted PTRACE log levels
38 * Now follows 1=error,2=warn,3=info,4+=debug
40 * Revision 1.71 2007/04/02 05:29:54 rjongbloed
41 * Tidied some trace logs to assure all have a category (bit before a tab character) set.
43 * Revision 1.70 2007/01/31 06:05:32 csoutheren
44 * Allow disabling of VXML
45 * Ensure VXML compiles when PPipeChannel not enabled
47 * Revision 1.69 2006/08/10 03:53:19 csoutheren
48 * Apply 1532388 - Fix PVXML log message
49 * Thanks to Stanislav Brabec
51 * Revision 1.68 2006/06/21 03:28:44 csoutheren
52 * Various cleanups thanks for Frederic Heem
54 * Revision 1.67 2006/06/20 09:01:51 csoutheren
55 * Applied patch 1353851
56 * VXML unitialized autoDeleteTextToSpeech
57 * Thanks to Frederich
59 * Revision 1.66 2005/12/01 01:05:59 csoutheren
60 * Fixed uninitialised variable
62 * Revision 1.65 2005/11/30 12:47:41 csoutheren
63 * Removed tabs, reformatted some code, and changed tags for Doxygen
65 * Revision 1.64 2005/10/30 23:25:52 csoutheren
66 * Fixed formatting
67 * Removed throw() declarations (PWLib does not do exceptions)
68 * Removed duplicate destructor declarations and definitions
70 * Revision 1.63 2005/10/30 19:41:53 dominance
71 * fixed most of the warnings occuring during compilation
73 * Revision 1.62 2005/10/21 08:18:21 csoutheren
74 * Fixed Close operator
76 * Revision 1.61 2005/08/13 06:38:22 rjongbloed
77 * Fixed illegal code line, assigning to const object!
79 * Revision 1.60 2005/08/12 16:41:51 shorne
80 * A couple more small fixes thx. Nickolay V. Shmyrev
82 * Revision 1.59 2005/08/11 08:48:10 shorne
83 * Removed Close from PVXMLSession::Open method. Thanks Nickolay V. Shmyrev
85 * Revision 1.58 2005/05/12 13:40:45 csoutheren
86 * Fixed locking problems with currentPLayItem optimisation
88 * Revision 1.57 2005/05/12 05:28:36 csoutheren
89 * Optimised read loop and fixed problems with playing repeated continuous tones
91 * Revision 1.56 2005/04/21 05:28:58 csoutheren
92 * Fixed assert if recordable does not queue properly
94 * Revision 1.55 2005/03/19 02:52:54 csoutheren
95 * Fix warnings from gcc 4.1-20050313 shapshot
97 * Revision 1.54 2004/12/03 02:06:05 csoutheren
98 * Ensure FlushQueue called OnStop for queued elements
100 * Revision 1.53 2004/08/09 11:10:34 csoutheren
101 * Changed SetTextToSpeech to return ptr to new engine
103 * Revision 1.52 2004/07/28 02:01:51 csoutheren
104 * Removed deadlock in some call shutdown scenarios
106 * Revision 1.51 2004/07/27 05:26:46 csoutheren
107 * Fixed recording
109 * Revision 1.50 2004/07/27 00:00:41 csoutheren
110 * Allowed Close to set closed flag before attepting lock of channels
112 * Revision 1.49 2004/07/26 07:25:02 csoutheren
113 * Fixed another problem with thread starvation due to delaying inside a mutex lock
115 * Revision 1.48 2004/07/26 00:40:41 csoutheren
116 * Fixed thread starvation problem under Linux by splitting channelMutex
117 * into seperate read and write mutexes
119 * Revision 1.47 2004/07/23 00:59:26 csoutheren
120 * Check in latest changes
122 * Revision 1.46 2004/07/17 09:44:12 rjongbloed
123 * Fixed missing set of last write count if not actually writing frames.
125 * Revision 1.45 2004/07/15 03:12:42 csoutheren
126 * Migrated changes from crs_vxnml_devel branch into main trunk
128 * Revision 1.42.2.7 2004/07/13 08:13:05 csoutheren
129 * Lots of implementation of factory-based PWAVFile
131 * Revision 1.42.2.6 2004/07/12 08:30:17 csoutheren
132 * More fixes for abstract factory implementation of PWAVFile
134 * Revision 1.42.2.5 2004/07/08 04:58:11 csoutheren
135 * Exposed VXML playable classes to allow descendants
137 * Revision 1.42.2.4 2004/07/07 07:07:43 csoutheren
138 * Changed PWAVFile to use abstract factories (extensively)
139 * Removed redundant blocking/unblocking when using G.723.1
140 * More support for call transfer
142 * Revision 1.42.2.3 2004/07/06 01:38:57 csoutheren
143 * Changed PVXMLChannel to use PDelayChannel
144 * Fixed bug where played files were deleted after playing
146 * Revision 1.42.2.2 2004/07/02 07:22:40 csoutheren
147 * Updated for latest factory changes
149 * Revision 1.42.2.1 2004/06/20 11:18:03 csoutheren
150 * Rewrite of resource cacheing to cache text-to-speech output
152 * Revision 1.42 2004/06/19 07:21:08 csoutheren
153 * Change TTS engine registration to use abstract factory code
154 * Started disentanglement of PVXMLChannel from PVXMLSession
155 * Fixed problem with VXML session closing if played data file is not exact frame size multiple
156 * Allowed PVXMLSession to be used without a VXML script
157 * Changed PVXMLChannel to handle "file:" URLs
158 * Numerous other small improvements and optimisations
160 * Revision 1.41 2004/06/02 08:29:28 csoutheren
161 * Added new code from Andreas Sikkema to implement various VXML features
163 * Revision 1.40 2004/06/02 06:16:48 csoutheren
164 * Removed unnecessary buffer copying and removed potential busy loop
166 * Revision 1.39 2004/05/02 05:14:43 rjongbloed
167 * Fixed possible deadlock in shutdown of VXML channel/session.
169 * Revision 1.38 2004/04/24 06:27:56 rjongbloed
170 * Fixed GCC 3.4.0 warnings about PAssertNULL and improved recoverability on
171 * NULL pointer usage in various bits of code.
173 * Revision 1.37 2004/03/23 04:48:42 csoutheren
174 * Improved ability to start VXML scripts as needed
176 * Revision 1.36 2003/11/12 20:38:16 csoutheren
177 * Fixed problem with incorrect sense of ContentLength header detection thanks to Andreas Sikkema
179 * Revision 1.35 2003/05/14 01:12:53 rjongbloed
180 * Fixed test for SID frames in record silence detection on G.723.1A
182 * Revision 1.34 2003/04/23 11:54:53 craigs
183 * Added ability to record audio
185 * Revision 1.33 2003/04/10 04:19:43 robertj
186 * Fixed incorrect timing on G.723.1 (framed codec)
187 * Fixed not using correct codec file suffix for non PCM/G.723.1 codecs.
189 * Revision 1.32 2003/04/08 05:09:14 craigs
190 * Added ability to use commands as an audio source
192 * Revision 1.31 2003/03/17 08:03:07 robertj
193 * Combined to the separate incoming and outgoing substream classes into
194 * a single class to make it easier to produce codec aware descendents.
195 * Added G.729 substream class.
197 * Revision 1.30 2002/12/03 22:39:14 robertj
198 * Removed get document that just returns a content length as the chunked
199 * transfer encoding makes this very dangerous.
201 * Revision 1.29 2002/11/19 10:36:30 robertj
202 * Added functions to set anf get "file:" URL. as PFilePath and do the right
203 * things with platform dependent directory components.
205 * Revision 1.28 2002/11/08 03:39:27 craigs
206 * Fixed problem with G.723.1 files
208 * Revision 1.27 2002/09/24 13:47:41 robertj
209 * Added support for more vxml commands, thanks Alexander Kovatch
211 * Revision 1.26 2002/09/18 06:37:40 robertj
212 * Added functions to load vxml directly, via file or URL. Old function
213 * intelligently picks which one to use.
215 * Revision 1.25 2002/09/03 04:38:14 craigs
216 * Added VXML 2.0 time attribute to <break>
218 * Revision 1.24 2002/09/03 04:11:37 craigs
219 * More changes from Alexander Kovatch
221 * Revision 1.23 2002/08/30 07:33:16 craigs
222 * Added extra initialisations
224 * Revision 1.22 2002/08/30 05:05:54 craigs
225 * Added changes for PVXMLGrammar from Alexander Kovatch
227 * Revision 1.21 2002/08/29 00:16:12 craigs
228 * Fixed typo, thanks to Peter Robinson
230 * Revision 1.20 2002/08/28 08:05:16 craigs
231 * Reorganised VXMLSession class as per code from Alexander Kovatch
233 * Revision 1.19 2002/08/28 05:10:57 craigs
234 * Added ability to load resources via URI
235 * Added cache
237 * Revision 1.18 2002/08/27 02:46:56 craigs
238 * Removed need for application to call AllowClearCall
240 * Revision 1.17 2002/08/27 02:20:09 craigs
241 * Added <break> command in prompt blocks
242 * Fixed potential deadlock
243 * Added <prompt> command in top level fields, thanks to Alexander Kovatch
245 * Revision 1.16 2002/08/15 04:11:16 robertj
246 * Fixed shutdown problems with closing vxml session, leaks a thread.
247 * Fixed potential problems with indirect channel Close() function.
249 * Revision 1.15 2002/08/15 02:13:10 craigs
250 * Fixed problem with handle leak (maybe) and change tts files back to autodelete
252 * Revision 1.14 2002/08/14 15:18:07 craigs
253 * Improved random filename generation
255 * Revision 1.13 2002/08/08 01:03:06 craigs
256 * Added function to re-enable automatic call clearing on script end
258 * Revision 1.12 2002/08/07 13:38:14 craigs
259 * Fixed bug in calculating lengths of G.723.1 packets
261 * Revision 1.11 2002/08/06 07:45:28 craigs
262 * Added lots of stuff from OpalVXML
264 * Revision 1.10 2002/07/29 15:08:50 craigs
265 * Added autodelete option to PlayFile
267 * Revision 1.9 2002/07/29 15:03:36 craigs
268 * Added access to queue functions
269 * Added autodelete option to AddFile
271 * Revision 1.8 2002/07/29 14:16:05 craigs
272 * Added asynchronous VXML execution
274 * Revision 1.7 2002/07/17 08:34:25 craigs
275 * Fixed deadlock problems
277 * Revision 1.6 2002/07/17 06:08:23 craigs
278 * Added additional "sayas" classes
280 * Revision 1.5 2002/07/10 13:15:20 craigs
281 * Moved some VXML classes from Opal back into PTCLib
282 * Fixed various race conditions
284 * Revision 1.4 2002/07/05 06:28:07 craigs
285 * Added OnEmptyAction callback
287 * Revision 1.3 2002/07/02 06:24:53 craigs
288 * Added recording functions
290 * Revision 1.2 2002/06/28 01:30:29 robertj
291 * Fixed ability to compile if do not have expat library.
293 * Revision 1.1 2002/06/27 05:27:49 craigs
294 * Initial version
299 #ifdef __GNUC__
300 #pragma implementation "vxml.h"
301 #endif
303 #include <ptlib.h>
305 #define P_DISABLE_FACTORY_INSTANCES
307 #if P_EXPAT
309 #include <ptlib/pfactory.h>
310 #include <ptclib/vxml.h>
311 #include <ptclib/memfile.h>
312 #include <ptclib/random.h>
313 #include <ptclib/http.h>
316 class PVXMLChannelPCM : public PVXMLChannel
318 PCLASSINFO(PVXMLChannelPCM, PVXMLChannel);
320 public:
321 PVXMLChannelPCM();
323 protected:
324 // overrides from PVXMLChannel
325 virtual BOOL WriteFrame(const void * buf, PINDEX len);
326 virtual BOOL ReadFrame(void * buffer, PINDEX amount);
327 virtual PINDEX CreateSilenceFrame(void * buffer, PINDEX amount);
328 virtual BOOL IsSilenceFrame(const void * buf, PINDEX len) const;
329 virtual void GetBeepData(PBYTEArray & data, unsigned ms);
333 class PVXMLChannelG7231 : public PVXMLChannel
335 PCLASSINFO(PVXMLChannelG7231, PVXMLChannel);
336 public:
337 PVXMLChannelG7231();
339 // overrides from PVXMLChannel
340 virtual BOOL WriteFrame(const void * buf, PINDEX len);
341 virtual BOOL ReadFrame(void * buffer, PINDEX amount);
342 virtual PINDEX CreateSilenceFrame(void * buffer, PINDEX amount);
343 virtual BOOL IsSilenceFrame(const void * buf, PINDEX len) const;
347 class PVXMLChannelG729 : public PVXMLChannel
349 PCLASSINFO(PVXMLChannelG729, PVXMLChannel);
350 public:
351 PVXMLChannelG729();
353 // overrides from PVXMLChannel
354 virtual BOOL WriteFrame(const void * buf, PINDEX len);
355 virtual BOOL ReadFrame(void * buffer, PINDEX amount);
356 virtual PINDEX CreateSilenceFrame(void * buffer, PINDEX amount);
357 virtual BOOL IsSilenceFrame(const void * buf, PINDEX len) const;
361 #define new PNEW
364 #define SMALL_BREAK_MSECS 1000
365 #define MEDIUM_BREAK_MSECS 2500
366 #define LARGE_BREAK_MSECS 5000
368 // LATER: Lookup what this value should be
369 #define DEFAULT_TIMEOUT 10000
371 //////////////////////////////////////////////////////////
373 static PString GetContentType(const PFilePath & fn)
375 PString type = fn.GetType();
377 if (type *= ".vxml")
378 return "text/vxml";
380 if (type *= ".wav")
381 return "audio/x-wav";
383 return PString::Empty();
387 ///////////////////////////////////////////////////////////////
389 BOOL PVXMLPlayable::ReadFrame(PVXMLChannel & channel, void * _buf, PINDEX origLen)
391 BYTE * buf = (BYTE *)_buf;
392 PINDEX len = origLen;
394 while (len > 0) {
395 BOOL stat = channel.ReadFrame(buf, len);
396 if (stat)
397 return TRUE;
398 if ((repeat == 0) || !Rewind(channel.GetBaseReadChannel()))
399 return FALSE;
400 PINDEX readLen = channel.GetLastReadCount();
401 if(readLen == 0){
402 return TRUE;
404 len -= readLen;
405 buf += readLen;
408 return TRUE;
411 ///////////////////////////////////////////////////////////////
413 BOOL PVXMLPlayableFilename::Open(PVXMLChannel & chan, const PString & _fn, PINDEX _delay, PINDEX _repeat, BOOL _autoDelete)
415 fn = _fn;
416 arg = _fn;
417 if (!PFile::Exists(chan.AdjustWavFilename(fn)))
418 return FALSE;
420 return PVXMLPlayable::Open(chan, _delay, _repeat, _autoDelete);
423 void PVXMLPlayableFilename::Play(PVXMLChannel & outgoingChannel)
425 PChannel * chan = NULL;
427 // check the file extension and open a .wav or a raw (.sw or .g723) file
428 if ((fn.Right(4)).ToLower() == ".wav")
429 chan = outgoingChannel.CreateWAVFile(fn);
430 else {
431 PFile * fileChan = new PFile(fn);
432 if (fileChan->Open(PFile::ReadOnly))
433 chan = fileChan;
434 else {
435 delete fileChan;
439 if (chan == NULL)
440 PTRACE(2, "PVXML\tCannot open file \"" << fn << "\"");
441 else {
442 PTRACE(3, "PVXML\tPlaying file \"" << fn << "\"");
443 outgoingChannel.SetReadChannel(chan, TRUE);
447 void PVXMLPlayableFilename::OnStop()
449 if (autoDelete) {
450 PTRACE(3, "PVXML\tDeleting file \"" << fn << "\"");
451 PFile::Remove(fn);
455 BOOL PVXMLPlayableFilename::Rewind(PChannel * chan)
457 PFile * file = dynamic_cast<PFile *>(chan);
458 if (file == NULL)
459 return FALSE;
461 return file->SetPosition(0);
464 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableFilename> vxmlPlayableFilenameFactory("File");
466 ///////////////////////////////////////////////////////////////
468 BOOL PVXMLPlayableFilenameList::Open(PVXMLChannel & chan, const PStringArray & _list, PINDEX _delay, PINDEX _repeat, BOOL _autoDelete)
470 for (PINDEX i = 0; i < _list.GetSize(); ++i) {
471 PString fn = chan.AdjustWavFilename(_list[i]);
472 if (PFile::Exists(fn))
473 filenames.AppendString(fn);
476 if (filenames.GetSize() == 0)
477 return FALSE;
479 currentIndex = 0;
481 return PVXMLPlayable::Open(chan, _delay, ((_repeat >= 0) ? _repeat : 1) * filenames.GetSize(), _autoDelete);
484 void PVXMLPlayableFilenameList::OnRepeat(PVXMLChannel & outgoingChannel)
486 PFilePath fn = filenames[currentIndex++ % filenames.GetSize()];
488 PChannel * chan = NULL;
490 // check the file extension and open a .wav or a raw (.sw or .g723) file
491 if ((fn.Right(4)).ToLower() == ".wav")
492 chan = outgoingChannel.CreateWAVFile(fn);
493 else {
494 PFile * fileChan = new PFile(fn);
495 if (fileChan->Open(PFile::ReadOnly))
496 chan = fileChan;
497 else {
498 delete fileChan;
502 if (chan == NULL)
503 PTRACE(2, "PVXML\tCannot open file \"" << fn << "\"");
504 else {
505 PTRACE(3, "PVXML\tPlaying file \"" << fn << "\"");
506 outgoingChannel.SetReadChannel(chan, TRUE);
510 void PVXMLPlayableFilenameList::OnStop()
512 if (autoDelete) {
513 for (PINDEX i = 0; i < filenames.GetSize(); ++i) {
514 PTRACE(3, "PVXML\tDeleting file \"" << filenames[i] << "\"");
515 PFile::Remove(filenames[i]);
520 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableFilenameList> vxmlPlayableFilenameListFactory("FileList");
522 ///////////////////////////////////////////////////////////////
524 #if P_PIPECHAN
526 PVXMLPlayableCommand::PVXMLPlayableCommand()
528 pipeCmd = NULL;
531 void PVXMLPlayableCommand::Play(PVXMLChannel & outgoingChannel)
533 arg.Replace("%s", PString(PString::Unsigned, sampleFrequency));
534 arg.Replace("%f", format);
536 // execute a command and send the output through the stream
537 pipeCmd = new PPipeChannel;
538 if (!pipeCmd->Open(arg, PPipeChannel::ReadOnly)) {
539 PTRACE(2, "PVXML\tCannot open command " << arg);
540 delete pipeCmd;
541 return;
544 if (pipeCmd == NULL)
545 PTRACE(2, "PVXML\tCannot open command \"" << arg << "\"");
546 else {
547 pipeCmd->Execute();
548 PTRACE(3, "PVXML\tPlaying command \"" << arg << "\"");
549 outgoingChannel.SetReadChannel(pipeCmd, TRUE);
553 void PVXMLPlayableCommand::OnStop()
555 if (pipeCmd != NULL) {
556 pipeCmd->WaitForTermination();
557 delete pipeCmd;
561 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableCommand> vxmlPlayableCommandFactory("Command");
563 #endif
565 ///////////////////////////////////////////////////////////////
567 BOOL PVXMLPlayableData::Open(PVXMLChannel & chan, const PString & /*_fn*/, PINDEX _delay, PINDEX _repeat, BOOL v)
569 return PVXMLPlayable::Open(chan, _delay, _repeat, v);
572 void PVXMLPlayableData::SetData(const PBYTEArray & _data)
574 data = _data;
577 void PVXMLPlayableData::Play(PVXMLChannel & outgoingChannel)
579 PMemoryFile * chan = new PMemoryFile(data);
580 PTRACE(3, "PVXML\tPlaying " << data.GetSize() << " bytes");
581 outgoingChannel.SetReadChannel(chan, TRUE);
584 BOOL PVXMLPlayableData::Rewind(PChannel * chan)
586 PMemoryFile * memfile = dynamic_cast<PMemoryFile *>(chan);
587 if (memfile == NULL)
588 return FALSE;
589 return memfile->SetPosition(0);
592 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableData> vxmlPlayableDataFactory("PCM Data");
594 ///////////////////////////////////////////////////////////////
596 BOOL PVXMLPlayableTone::Open(PVXMLChannel & chan, const PString & toneSpec, PINDEX _delay, PINDEX _repeat, BOOL v)
598 // populate the tone buffer
599 PTones tones;
601 if (!tones.Generate(toneSpec))
602 return FALSE;
604 PINDEX len = tones.GetSize() * sizeof(short);
605 memcpy(data.GetPointer(len), tones.GetPointer(), len);
607 return PVXMLPlayable::Open(chan, _delay, _repeat, v);
610 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableTone> vxmlPlayableToneFactory("Tone");
612 ///////////////////////////////////////////////////////////////
614 BOOL PVXMLPlayableURL::Open(PVXMLChannel & chan, const PString & _url, PINDEX _delay, PINDEX _repeat, BOOL autoDelete)
616 url = arg = _url;
617 return PVXMLPlayable::Open(chan, _delay, _repeat, autoDelete);
620 void PVXMLPlayableURL::Play(PVXMLChannel & outgoingChannel)
622 // open the resource
623 PHTTPClient * client = new PHTTPClient;
624 PMIMEInfo outMIME, replyMIME;
625 int code = client->GetDocument(url, outMIME, replyMIME, FALSE);
626 if ((code != 200) || (replyMIME(PHTTP::TransferEncodingTag()) *= PHTTP::ChunkedTag()))
627 delete client;
628 else {
629 outgoingChannel.SetReadChannel(client, TRUE);
633 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableURL> vxmlPlayableURLFactory("URL");
635 ///////////////////////////////////////////////////////////////
637 BOOL PVXMLRecordableFilename::Open(const PString & _arg)
639 fn = _arg;
640 consecutiveSilence = 0;
641 return TRUE;
644 void PVXMLRecordableFilename::Record(PVXMLChannel & outgoingChannel)
646 PChannel * chan = NULL;
648 // check the file extension and open a .wav or a raw (.sw or .g723) file
649 if ((fn.Right(4)).ToLower() == ".wav")
650 chan = outgoingChannel.CreateWAVFile(fn, TRUE);
651 else {
652 PFile * fileChan = new PFile(fn);
653 if (fileChan->Open(PFile::WriteOnly))
654 chan = fileChan;
655 else {
656 delete fileChan;
660 if (chan == NULL)
661 PTRACE(2, "PVXML\tCannot open file \"" << fn << "\"");
662 else {
663 PTRACE(3, "PVXML\tRecording to file \"" << fn << "\"");
664 outgoingChannel.SetWriteChannel(chan, TRUE);
667 recordStart = PTime();
668 silenceStart = PTime();
669 consecutiveSilence = 0;
672 BOOL PVXMLRecordableFilename::OnFrame(BOOL isSilence)
674 if (!isSilence) {
675 silenceStart = PTime();
676 consecutiveSilence = 0;
677 } else {
678 consecutiveSilence++;
679 if ( ((consecutiveSilence % 20) == 0) &&
681 ((finalSilence > 0) && ((PTime() - silenceStart).GetMilliSeconds() >= finalSilence)) ||
682 ((maxDuration > 0) && ((PTime() - recordStart).GetMilliSeconds() >= maxDuration))
685 return TRUE;
688 return FALSE;
691 ///////////////////////////////////////////////////////////////
693 PVXMLCache::PVXMLCache(const PDirectory & _directory)
694 : directory(_directory)
696 if (!directory.Exists())
697 directory.Create();
700 static PString MD5AsHex(const PString & str)
702 PMessageDigest::Result digest;
703 PMessageDigest5::Encode(str, digest);
705 PString hexStr;
706 const BYTE * data = digest.GetPointer();
707 for (PINDEX i = 0; i < digest.GetSize(); ++i)
708 hexStr.sprintf("%02x", (unsigned)data[i]);
709 return hexStr;
713 PFilePath PVXMLCache::CreateFilename(const PString & prefix, const PString & key, const PString & fileType)
715 PString md5 = MD5AsHex(key);
716 PString md5_2 = MD5AsHex(key);
718 return directory + ((prefix + "_") + md5 + fileType);
721 BOOL PVXMLCache::Get(const PString & prefix,
722 const PString & key,
723 const PString & fileType,
724 PString & contentType,
725 PFilePath & dataFn)
727 PWaitAndSignal m(*this);
729 dataFn = CreateFilename(prefix, key, "." + fileType);
730 PFilePath typeFn = CreateFilename(prefix, key, "_type.txt");
731 if (!PFile::Exists(dataFn) || !PFile::Exists(typeFn)) {
732 PTRACE(4, "PVXML\tKey \"" << key << "\" not found in cache");
733 return FALSE;
736 PTextFile typeFile(typeFn, PFile::ReadOnly);
737 if (!typeFile.IsOpen()) {
738 PTRACE(4, "PVXML\tCannot find type for cached key " << key << " in cache");
739 PFile::Remove(dataFn);
740 return FALSE;
743 typeFile.ReadLine(contentType);
744 contentType.Trim();
745 if (contentType.IsEmpty())
746 contentType = GetContentType(dataFn);
748 return TRUE;
751 void PVXMLCache::Put(const PString & prefix,
752 const PString & key,
753 const PString & fileType,
754 const PString & contentType,
755 const PFilePath & fn,
756 PFilePath & dataFn)
758 PWaitAndSignal m(*this);
760 // create the filename for the cache files
761 dataFn = CreateFilename(prefix, key, "." + fileType);
762 PFilePath typeFn = CreateFilename(prefix, key, "_type.txt");
764 // write the content type file
765 PTextFile typeFile(typeFn, PFile::WriteOnly);
766 if (contentType.IsEmpty())
767 typeFile.WriteLine(GetContentType(fn));
768 else
769 typeFile.WriteLine(contentType);
771 // rename the file to the correct name
772 PFile::Rename(fn, dataFn.GetFileName(), TRUE);
775 PVXMLCache & PVXMLCache::GetResourceCache()
777 static PVXMLCache cache(PDirectory() + "cache");
778 return cache;
782 PFilePath PVXMLCache::GetRandomFilename(const PString & prefix, const PString & fileType)
784 PFilePath fn;
786 // create a random temporary filename
787 PRandom r;
788 for (;;) {
789 fn = directory + psprintf("%s_%i.%s", (const char *)prefix, r.Generate() % 1000000, (const char *)fileType);
790 if (!PFile::Exists(fn))
791 break;
794 return fn;
797 //////////////////////////////////////////////////////////
799 PVXMLSession::PVXMLSession(PTextToSpeech * _tts, BOOL autoDelete)
801 vxmlThread = NULL;
802 vxmlChannel = NULL;
803 finishWhenEmpty = TRUE;
804 textToSpeech = NULL;
805 autoDeleteTextToSpeech = FALSE;
807 autoDeleteTextToSpeech = FALSE;
808 SetTextToSpeech(_tts, autoDelete);
810 Initialise();
813 void PVXMLSession::Initialise()
815 recording = FALSE;
816 allowFinish = FALSE;
817 listening = FALSE;
818 activeGrammar = NULL;
819 listening = FALSE;
820 forceEnd = FALSE;
821 currentForm = NULL;
822 currentNode = NULL;
823 autoDeleteTextToSpeech = FALSE;
826 PVXMLSession::~PVXMLSession()
828 Close();
830 if ((textToSpeech != NULL) && autoDeleteTextToSpeech)
831 delete textToSpeech;
834 PTextToSpeech * PVXMLSession::SetTextToSpeech(PTextToSpeech * _tts, BOOL autoDelete)
836 PWaitAndSignal m(sessionMutex);
838 if (autoDeleteTextToSpeech && (textToSpeech != NULL))
839 delete textToSpeech;
841 autoDeleteTextToSpeech = autoDelete;
842 textToSpeech = _tts;
843 return textToSpeech;
846 PTextToSpeech * PVXMLSession::SetTextToSpeech(const PString & ttsName)
848 PWaitAndSignal m(sessionMutex);
850 if (autoDeleteTextToSpeech && (textToSpeech != NULL))
851 delete textToSpeech;
853 autoDeleteTextToSpeech = TRUE;
854 textToSpeech = PFactory<PTextToSpeech>::CreateInstance(ttsName);
855 return textToSpeech;
858 BOOL PVXMLSession::Load(const PString & source)
860 // Lets try and guess what was passed, if file exists then is file
861 PFilePath file = source;
862 if (PFile::Exists(file))
863 return LoadFile(file);
865 // see if looks like URL
866 PINDEX pos = source.Find(':');
867 if (pos != P_MAX_INDEX) {
868 PString scheme = source.Left(pos);
869 if ((scheme *= "http") || (scheme *= "https") || (scheme *= "file"))
870 return LoadURL(source);
873 // See if is actual VXML
874 if (PCaselessString(source).Find("<vxml") != P_MAX_INDEX)
875 return LoadVXML(source);
877 return FALSE;
881 BOOL PVXMLSession::LoadFile(const PFilePath & filename)
883 // create a file URL from the filename
884 return LoadURL(filename);
888 BOOL PVXMLSession::LoadURL(const PURL & url)
890 // retreive the document (may be a HTTP get)
891 PFilePath fn;
892 PString contentType;
893 if (!RetreiveResource(url, contentType, fn, FALSE)) {
894 PTRACE(1, "PVXML\tCannot load document " << url);
895 return FALSE;
898 PTextFile file(fn, PFile::ReadOnly);
899 if (!file.IsOpen()) {
900 PTRACE(1, "PVXML\tCannot read data from " << fn);
901 return FALSE;
904 off_t len = file.GetLength();
905 PString text;
906 file.Read(text.GetPointer(len+1), len);
907 len = file.GetLastReadCount();
908 text.SetSize(len+1);
909 text[(PINDEX)len] = '\0';
911 if (!LoadVXML(text)) {
912 PTRACE(1, "PVXML\tCannot load VXML in " << url);
913 return FALSE;
916 rootURL = url;
917 return TRUE;
920 BOOL PVXMLSession::LoadVXML(const PString & xmlText)
922 PWaitAndSignal m(sessionMutex);
924 allowFinish = loaded = FALSE;
925 rootURL = PString::Empty();
927 // parse the XML
928 xmlFile.RemoveAll();
929 if (!xmlFile.Load(xmlText)) {
930 PTRACE(1, "PVXML\tCannot parse root document: " << GetXMLError());
931 return FALSE;
934 PXMLElement * root = xmlFile.GetRootElement();
935 if (root == NULL)
936 return FALSE;
938 // reset interpeter state
939 Initialise();
941 // find the first form
942 if ((currentForm = FindForm(PString::Empty())) == NULL)
943 return FALSE;
945 // start processing with this <form> element
946 currentNode = currentForm;
948 loaded = TRUE;
949 return TRUE;
952 PURL PVXMLSession::NormaliseResourceName(const PString & src)
954 // if resource name has a scheme, then use as is
955 PINDEX pos = src.Find(':');
956 if ((pos != P_MAX_INDEX) && (pos < 5))
957 return src;
959 if (rootURL.IsEmpty())
960 return "file:" + src;
962 // else use scheme and path from root document
963 PURL url = rootURL;
964 PStringArray path = url.GetPath();
965 PString pathStr;
966 if (path.GetSize() > 0) {
967 pathStr += path[0];
968 PINDEX i;
969 for (i = 1; i < path.GetSize()-1; i++)
970 pathStr += "/" + path[i];
971 pathStr += "/" + src;
972 url.SetPathStr(pathStr);
975 return url;
979 BOOL PVXMLSession::RetreiveResource(const PURL & url,
980 PString & contentType,
981 PFilePath & dataFn,
982 BOOL useCache)
984 BOOL stat = FALSE;
986 // files on the local file system get loaded locally
987 if (url.GetScheme() *= "file") {
988 dataFn = url.AsFilePath();
989 if (contentType.IsEmpty())
990 contentType = GetContentType(dataFn);
991 stat = TRUE;
994 // do a HTTP get when appropriate
995 else if ((url.GetScheme() *= "http") || (url.GetScheme() *= "https")) {
997 PFilePath fn;
998 PString fileType = url.AsFilePath().GetType();
1000 BOOL inCache = FALSE;
1001 if (useCache)
1002 inCache = PVXMLCache::GetResourceCache().Get("url", url.AsString(), fileType, contentType, dataFn);
1004 if (!inCache) {
1006 // get a random filename
1007 fn = PVXMLCache::GetResourceCache().GetRandomFilename("url", fileType);
1009 // get the resource header information
1010 PHTTPClient client;
1011 PMIMEInfo outMIME, replyMIME;
1012 if (!client.GetDocument(url, outMIME, replyMIME)) {
1013 PTRACE(2, "PVXML\tCannot load resource " << url);
1014 stat =FALSE;
1017 else {
1019 // Get the body of the response in a PBYTEArray (might be binary data)
1020 PBYTEArray incomingData;
1021 client.ReadContentBody(replyMIME, incomingData);
1022 contentType = replyMIME(PHTTPClient::ContentTypeTag());
1024 // write the data in the file
1025 PFile cacheFile(fn, PFile::WriteOnly);
1026 cacheFile.Write(incomingData.GetPointer(), incomingData.GetSize() );
1028 // if we have a cache and we are using it, then save the data
1029 if (useCache)
1030 PVXMLCache::GetResourceCache().Put("url", url.AsString(), fileType, contentType, fn, dataFn);
1032 // data is loaded
1033 stat = TRUE;
1038 // files on the local file system get loaded locally
1039 else if (url.GetScheme() *= "file") {
1040 dataFn = url.AsFilePath();
1041 stat = TRUE;
1044 // unknown schemes give an error
1045 else
1046 stat = FALSE;
1048 return stat;
1052 PXMLElement * PVXMLSession::FindForm(const PString & id)
1054 // NOTE: should have some flag to know if it is loaded
1055 PXMLElement * root = xmlFile.GetRootElement();
1056 if (root == NULL)
1057 return NULL;
1059 // Only handle search of top level nodes for <form> element
1060 PINDEX i;
1061 for (i = 0; i < root->GetSize(); i++) {
1062 PXMLObject * xmlObject = root->GetElement(i);
1063 if (xmlObject->IsElement()) {
1064 PXMLElement * xmlElement = (PXMLElement*)xmlObject;
1065 if (
1066 (xmlElement->GetName() *= "form") &&
1067 (id.IsEmpty() || (xmlElement->GetAttribute("id") *= id))
1069 return xmlElement;
1072 return NULL;
1076 BOOL PVXMLSession::Open(BOOL isPCM)
1078 if (isPCM)
1079 return Open(VXML_PCM16);
1080 else
1081 return Open(VXML_G7231);
1084 BOOL PVXMLSession::Open(const PString & _mediaFormat)
1086 mediaFormat = _mediaFormat;
1088 PVXMLChannel * chan = PFactory<PVXMLChannel>::CreateInstance(mediaFormat);
1089 if (chan == NULL) {
1090 PTRACE(1, "VXML\tCannot create VXML channel with format " << mediaFormat);
1091 return FALSE;
1094 // set the underlying channel
1095 if (!PIndirectChannel::Open(chan, chan))
1096 return FALSE;
1098 // start the VXML session in another thread
1100 PWaitAndSignal m(sessionMutex);
1101 if (!chan->Open(this))
1102 return FALSE;
1103 vxmlChannel = chan;
1106 return Execute();
1109 BOOL PVXMLSession::Execute()
1111 PWaitAndSignal m(sessionMutex);
1113 // cannot open if no data is loaded
1114 if (loaded && vxmlThread == NULL) {
1115 threadRunning = TRUE;
1116 vxmlThread = PThread::Create(PCREATE_NOTIFIER(VXMLExecute), 0, PThread::NoAutoDeleteThread);
1119 return TRUE;
1123 BOOL PVXMLSession::Close()
1126 PWaitAndSignal m(sessionMutex);
1127 if (vxmlThread != NULL) {
1129 // Stop condition for thread
1130 threadRunning = FALSE;
1131 forceEnd = TRUE;
1132 waitForEvent.Signal();
1134 // Signal all syncpoints that could be waiting for things
1135 answerSync.Signal();
1136 vxmlChannel->Close();
1138 vxmlThread->WaitForTermination();
1139 delete vxmlThread;
1140 vxmlThread = NULL;
1143 vxmlChannel = NULL;
1146 return PIndirectChannel::Close();
1150 void PVXMLSession::VXMLExecute(PThread &, INT)
1152 while (!forceEnd && threadRunning) {
1154 // process current node in the VXML script
1155 ExecuteDialog();
1157 // wait for something to happen
1158 if (currentNode == NULL || IsPlaying())
1159 waitForEvent.Wait();
1162 // Make sure the script has been run to the end so
1163 // submit actions etc. can be performed
1164 // record and audio and other user interaction commands should be skipped
1165 if (forceEnd) {
1166 PTRACE(2, "PVXML\tFast forwarding through script because of forceEnd" );
1167 while (currentNode != NULL)
1168 ExecuteDialog();
1171 OnEndSession();
1173 //PWaitAndSignal m(sessionMutex);
1174 if (vxmlChannel != NULL)
1175 vxmlChannel->Close();
1177 return;
1180 void PVXMLSession::ProcessUserInput()
1182 // without this initialisation, gcc 4.1 gives a warning
1183 char ch = 0;
1185 PWaitAndSignal m(userInputMutex);
1186 if (userInputQueue.size() == 0)
1187 return;
1188 ch = userInputQueue.front();
1189 userInputQueue.pop();
1190 PTRACE(3, "VXML\tHandling user input " << ch);
1194 // recording
1195 if (recording) {
1196 if (recordDTMFTerm)
1197 RecordEnd();
1200 // playback
1201 else {
1202 if (activeGrammar != NULL)
1203 activeGrammar->OnUserInput(ch);
1207 void PVXMLSession::ExecuteDialog()
1209 // check for user input
1210 ProcessUserInput();
1212 // process any active grammars
1213 ProcessGrammar();
1215 // process current node in the VXML script
1216 ProcessNode();
1218 // Wait for the buffer to complete before continuing to the next node
1219 if (currentNode == NULL) {
1220 if (IsPlaying())
1221 return;
1224 // if the current node has children, then process the first child
1225 else if (currentNode->IsElement() && (((PXMLElement *)currentNode)->GetElement(0) != NULL))
1226 currentNode = ((PXMLElement *)currentNode)->GetElement(0);
1228 // else process the next sibling
1229 else {
1230 // Keep moving up the parents until we find a next sibling
1231 while ((currentNode != NULL) && currentNode->GetNextObject() == NULL) {
1232 currentNode = currentNode->GetParent();
1233 // if we are on the backwards traversal through a <field> then wait
1234 // for a grammar recognition and throw events if necessary
1235 if (currentNode != NULL && (currentNode->IsElement() == TRUE) && (((PXMLElement*)currentNode)->GetName() *= "field")) {
1236 listening = TRUE;
1237 PlaySilence(timeout);
1241 if (currentNode != NULL)
1242 currentNode = currentNode->GetNextObject();
1245 // Determine if we should quit
1246 if ((currentNode == NULL) && (activeGrammar == NULL) && !IsPlaying() && !IsRecording() && allowFinish && finishWhenEmpty) {
1247 threadRunning = FALSE;
1248 waitForEvent.Signal();
1253 void PVXMLSession::ProcessGrammar()
1255 if (activeGrammar == NULL)
1256 return;
1258 BOOL processGrammar(FALSE);
1260 // Stop if we've matched a grammar or have a failed recognition
1261 if (activeGrammar->GetState() == PVXMLGrammar::FILLED || activeGrammar->GetState() == PVXMLGrammar::NOMATCH)
1262 processGrammar = TRUE;
1264 // Stop the grammar if we've timed out
1265 else if (listening && !IsPlaying()) {
1266 activeGrammar->Stop();
1267 processGrammar = TRUE;
1270 // Let the loop run again if we're still waiting to time out and haven't resolved the grammar one way or the other
1271 if (!processGrammar && listening)
1272 return;
1274 if (processGrammar)
1276 PVXMLGrammar::GrammarState state = activeGrammar->GetState();
1277 grammarResult = activeGrammar->GetValue();
1278 LoadGrammar(NULL);
1279 listening = FALSE;
1281 // Stop any playback
1282 if (vxmlChannel != NULL) {
1283 vxmlChannel->FlushQueue();
1284 vxmlChannel->EndRecording();
1287 // Check we're not in a menu
1288 if (eventName.IsEmpty()) {
1290 // Figure out what happened
1291 switch (state)
1293 case PVXMLGrammar::FILLED:
1294 eventName = "filled";
1295 break;
1296 case PVXMLGrammar::NOINPUT:
1297 eventName = "noinput";
1298 break;
1299 case PVXMLGrammar::NOMATCH:
1300 eventName = "nomatch";
1301 break;
1302 default:
1303 ; //ERROR - unexpected grammar state
1306 // Find the handler and move there
1307 PXMLElement * handler = FindHandler(eventName);
1308 if (handler != NULL)
1309 currentNode = handler;
1315 void PVXMLSession::ProcessNode()
1317 if (currentNode == NULL)
1318 return;
1320 if (!currentNode->IsElement()) {
1321 if (!forceEnd)
1322 TraverseAudio();
1323 else
1324 currentNode = NULL;
1327 else {
1328 PXMLElement * element = (PXMLElement*)currentNode;
1329 PCaselessString nodeType = element->GetName();
1330 PTRACE(3, "PVXML\t**** Processing VoiceXML element: <" << nodeType << "> ***");
1332 if (nodeType *= "audio") {
1333 if (!forceEnd)
1334 TraverseAudio();
1337 else if (nodeType *= "block") {
1338 // check 'cond' attribute to see if this element's children are to be skipped
1339 // go on and process the children
1342 else if (nodeType *= "break")
1343 TraverseAudio();
1345 else if (nodeType *= "disconnect")
1346 currentNode = NULL;
1348 else if (nodeType *= "field") {
1349 currentField = (PXMLElement*)currentNode;
1350 timeout = DEFAULT_TIMEOUT;
1351 TraverseGrammar(); // this will set activeGrammar
1354 else if (nodeType *= "form") {
1355 // this is now the current element - go on
1356 currentForm = element;
1357 currentField = NULL; // no active field in a new form
1360 else if (nodeType *= "goto")
1361 TraverseGoto();
1363 else if (nodeType *= "grammar")
1364 TraverseGrammar(); // this will set activeGrammar
1366 else if (nodeType *= "record") {
1367 if (!forceEnd)
1368 TraverseRecord();
1371 else if (nodeType *= "prompt") {
1372 if (!forceEnd) {
1373 // LATER:
1374 // check 'cond' attribute to see if the children of this node should be processed
1375 // check 'count' attribute to see if this node should be processed
1376 // flush all prompts if 'bargein' attribute is set to false
1378 // Update timeout of current recognition (if 'timeout' attribute is set)
1379 if (element->HasAttribute("timeout")) {
1380 PTimeInterval timeout = StringToTime(element->GetAttribute("timeout"));
1385 else if (nodeType *= "say-as") {
1386 if (!forceEnd) {
1390 else if (nodeType *= "value") {
1391 if (!forceEnd)
1392 TraverseAudio();
1395 else if (nodeType *= "var")
1396 TraverseVar();
1398 else if (nodeType *= "if")
1399 TraverseIf();
1401 else if (nodeType *= "exit")
1402 TraverseExit();
1404 else if (nodeType *= "menu") {
1405 if (!forceEnd) {
1406 TraverseMenu();
1407 eventName = "menu";
1411 else if (nodeType *= "choice") {
1412 if (!TraverseChoice(grammarResult))
1413 defaultDTMF++;
1414 else {
1415 // If the correct choice has been found,
1416 /// make sure everything is reset correctly
1417 eventName.MakeEmpty();
1418 grammarResult.MakeEmpty();
1419 defaultDTMF = 1;
1423 else if (nodeType *= "submit")
1424 TraverseSubmit();
1426 else if (nodeType *= "property")
1427 TraverseProperty();
1431 BOOL PVXMLSession::OnUserInput(const PString & str)
1434 PWaitAndSignal m(userInputMutex);
1435 for (PINDEX i = 0; i < str.GetLength(); i++)
1436 userInputQueue.push(str[i]);
1438 waitForEvent.Signal();
1439 return TRUE;
1442 BOOL PVXMLSession::TraverseRecord()
1444 if (currentNode->IsElement()) {
1446 PString strName;
1447 PXMLElement * element = (PXMLElement *)currentNode;
1449 // Get the name (name)
1450 if (element->HasAttribute("name"))
1451 strName = element->GetAttribute("name");
1452 else if (element->HasAttribute("id"))
1453 strName = element->GetAttribute("id");
1455 // Get the destination filename (dest)
1456 PString strDest;
1457 if (element->HasAttribute("dest"))
1458 strDest = element->GetAttribute("dest");
1460 // see if we need a beep
1461 if (element->GetAttribute("beep").ToLower() *= "true") {
1462 PBYTEArray beepData;
1463 GetBeepData(beepData, 1000);
1464 if (beepData.GetSize() != 0)
1465 PlayData(beepData);
1468 if (strDest.IsEmpty()) {
1469 PTime now;
1470 strDest = GetVar("session.telephone.dnis" ) + "_" + GetVar( "session.telephone.ani" ) + "_" + now.AsString( "yyyyMMdd_hhmmss") + ".wav";
1473 // For some reason, if the file is there the create
1474 // seems to fail.
1475 PFile::Remove(strDest);
1476 PFilePath file(strDest);
1478 // Get max record time (maxtime)
1479 PTimeInterval maxTime = PMaxTimeInterval;
1480 if (element->HasAttribute("maxtime"))
1481 maxTime = StringToTime(element->GetAttribute("maxtime"));
1483 // Get terminating silence duration (finalsilence)
1484 PTimeInterval termTime(3000);
1485 if (element->HasAttribute("finalsilence"))
1486 termTime = StringToTime(element->GetAttribute("finalsilence"));
1488 // Get dtmf term (dtmfterm)
1489 BOOL dtmfTerm = TRUE;
1490 if (element->HasAttribute("dtmfterm"))
1491 dtmfTerm = !(element->GetAttribute("dtmfterm").ToLower() *= "false");
1493 // create a semaphore, and then wait for the recording to terminate
1494 StartRecording(file, dtmfTerm, maxTime, termTime);
1495 recordSync.Wait(maxTime);
1497 if (!recordSync.Wait(maxTime)) {
1498 // The Wait() has timed out, to signal that the record timed out.
1499 // This is VXML version 2 property, but nice.
1500 // So it's possible to detect if the record timed out from within the
1501 // VXML script
1502 SetVar(strName + "$.maxtime", "true");
1504 else {
1505 // Normal hangup before timeout
1506 SetVar( strName + "$.maxtime", "false");
1509 // when this returns, we are done
1510 EndRecording();
1513 return TRUE;
1516 PString PVXMLSession::GetXMLError() const
1518 return psprintf("(%i:%i) ", xmlFile.GetErrorLine(), xmlFile.GetErrorColumn()) + xmlFile.GetErrorString();
1521 PString PVXMLSession::EvaluateExpr(const PString & oexpr)
1523 PString expr = oexpr.Trim();
1525 // see if all digits
1526 PINDEX i;
1527 BOOL allDigits = TRUE;
1528 for (i = 0; i < expr.GetLength(); i++) {
1529 allDigits = allDigits && isdigit(expr[i]);
1532 if (allDigits)
1533 return expr;
1535 return GetVar(expr);
1538 PString PVXMLSession::GetVar(const PString & ostr) const
1540 PString str = ostr;
1541 PString scope;
1543 // get scope
1544 PINDEX pos = str.Find('.');
1545 if (pos != P_MAX_INDEX) {
1546 scope = str.Left(pos);
1547 str = str.Mid(pos+1);
1550 // process session scope
1551 if (scope.IsEmpty() || (scope *= "session")) {
1552 if (sessionVars.Contains(str))
1553 return sessionVars(str);
1556 // assume any other scope is actually document or application
1557 return documentVars(str);
1560 void PVXMLSession::SetVar(const PString & ostr, const PString & val)
1562 PString str = ostr;
1563 PString scope;
1565 // get scope
1566 PINDEX pos = str.Find('.');
1567 if (pos != P_MAX_INDEX) {
1568 scope = str.Left(pos);
1569 str = str.Mid(pos+1);
1572 // do session scope
1573 if (scope.IsEmpty() || (scope *= "session")) {
1574 sessionVars.SetAt(str, val);
1575 return;
1578 PTRACE(3, "PVXML\tDocument: " << str << " = \"" << val << "\"");
1580 // assume any other scope is actually document or application
1581 documentVars.SetAt(str, val);
1584 BOOL PVXMLSession::PlayFile(const PString & fn, PINDEX repeat, PINDEX delay, BOOL autoDelete)
1586 if (vxmlChannel == NULL || !vxmlChannel->QueueFile(fn, repeat, delay, autoDelete))
1587 return FALSE;
1589 AllowClearCall();
1591 return TRUE;
1594 BOOL PVXMLSession::PlayCommand(const PString & cmd, PINDEX repeat, PINDEX delay)
1596 if (vxmlChannel == NULL || !vxmlChannel->QueueCommand(cmd, repeat, delay))
1597 return FALSE;
1599 AllowClearCall();
1601 return TRUE;
1604 BOOL PVXMLSession::PlayData(const PBYTEArray & data, PINDEX repeat, PINDEX delay)
1606 if (vxmlChannel == NULL || !vxmlChannel->QueueData(data, repeat, delay))
1607 return FALSE;
1609 AllowClearCall();
1611 return TRUE;
1614 BOOL PVXMLSession::PlayTone(const PString & toneSpec, PINDEX repeat, PINDEX delay)
1616 if (vxmlChannel == NULL || !vxmlChannel->QueuePlayable("Tone", toneSpec, repeat, delay, true))
1617 return FALSE;
1619 AllowClearCall();
1621 return TRUE;
1624 void PVXMLSession::GetBeepData(PBYTEArray & data, unsigned ms)
1626 if (vxmlChannel != NULL)
1627 vxmlChannel->GetBeepData(data, ms);
1630 BOOL PVXMLSession::PlaySilence(const PTimeInterval & timeout)
1632 return PlaySilence((PINDEX)timeout.GetMilliSeconds());
1635 BOOL PVXMLSession::PlaySilence(PINDEX msecs)
1637 PBYTEArray nothing;
1638 if (vxmlChannel == NULL || !vxmlChannel->QueueData(nothing, 1, msecs))
1639 return FALSE;
1641 AllowClearCall();
1643 return TRUE;
1646 BOOL PVXMLSession::PlayResource(const PURL & url, PINDEX repeat, PINDEX delay)
1648 if (vxmlChannel == NULL || !vxmlChannel->QueueResource(url, repeat, delay))
1649 return FALSE;
1651 AllowClearCall();
1653 return TRUE;
1656 BOOL PVXMLSession::LoadGrammar(PVXMLGrammar * grammar)
1658 if (activeGrammar != NULL) {
1659 delete activeGrammar;
1660 activeGrammar = FALSE;
1663 activeGrammar = grammar;
1665 return TRUE;
1668 BOOL PVXMLSession::PlayText(const PString & _text,
1669 PTextToSpeech::TextType type,
1670 PINDEX repeat,
1671 PINDEX delay)
1673 PStringArray list;
1674 BOOL useCache = !(GetVar("caching") *= "safe");
1675 if (!ConvertTextToFilenameList(_text, type, list, useCache) || (list.GetSize() == 0)) {
1676 PTRACE(1, "PVXML\tCannot convert text to speech");
1677 return FALSE;
1680 PVXMLPlayableFilenameList * playable = new PVXMLPlayableFilenameList;
1681 if (!playable->Open(*vxmlChannel, list, delay, repeat, !useCache)) {
1682 delete playable;
1683 PTRACE(1, "PVXML\tCannot create playable for filename list");
1684 return FALSE;
1687 return vxmlChannel->QueuePlayable(playable);
1690 BOOL PVXMLSession::ConvertTextToFilenameList(const PString & _text, PTextToSpeech::TextType type, PStringArray & filenameList, BOOL useCache)
1692 PString prefix = psprintf("tts%i", type);
1694 PStringArray lines = _text.Trim().Lines();
1695 for (PINDEX i = 0; i < lines.GetSize(); i++) {
1697 PString text = lines[i].Trim();
1698 if (text.IsEmpty())
1699 continue;
1701 BOOL spoken = FALSE;
1702 PFilePath dataFn;
1704 // see if we have converted this text before
1705 PString contentType;
1706 if (useCache)
1707 spoken = PVXMLCache::GetResourceCache().Get(prefix, text, "wav", contentType, dataFn);
1709 // if not cached, then use the text to speech converter
1710 if (!spoken) {
1711 PFilePath tmpfname;
1712 if (textToSpeech != NULL) {
1713 tmpfname = PVXMLCache::GetResourceCache().GetRandomFilename("tts", "wav");
1714 if (!textToSpeech->OpenFile(tmpfname)) {
1715 PTRACE(2, "PVXML\tcannot open file " << tmpfname);
1716 } else {
1717 spoken = textToSpeech->Speak(text, type);
1718 if (!textToSpeech->Close()) {
1719 PTRACE(2, "PVXML\tcannot close TTS engine");
1722 textToSpeech->Close();
1723 if (useCache)
1724 PVXMLCache::GetResourceCache().Put(prefix, text, "wav", contentType, tmpfname, dataFn);
1725 else
1726 dataFn = tmpfname;
1730 if (!spoken) {
1731 PTRACE(2, "PVXML\tcannot speak text using TTS engine");
1732 } else
1733 filenameList.AppendString(dataFn);
1736 return filenameList.GetSize() > 0;
1739 void PVXMLSession::SetPause(BOOL _pause)
1741 if (vxmlChannel != NULL)
1742 vxmlChannel->SetPause(_pause);
1746 BOOL PVXMLSession::IsPlaying() const
1748 return (vxmlChannel != NULL) && vxmlChannel->IsPlaying();
1751 BOOL PVXMLSession::StartRecording(const PFilePath & /*_recordFn*/,
1752 BOOL /*_recordDTMFTerm*/,
1753 const PTimeInterval & /*_recordMaxTime*/,
1754 const PTimeInterval & /*_recordFinalSilence*/)
1757 recording = TRUE;
1758 recordFn = _recordFn;
1759 recordDTMFTerm = _recordDTMFTerm;
1760 recordMaxTime = _recordMaxTime;
1761 recordFinalSilence = _recordFinalSilence;
1763 if (incomingChannel != NULL) {
1764 PXMLElement* element = (PXMLElement*) currentNode;
1765 if ( element->HasAttribute("name")) {
1766 PString chanName = element->GetAttribute("name");
1767 incomingChannel->SetName(chanName);
1769 return incomingChannel->StartRecording(recordFn, (unsigned )recordFinalSilence.GetMilliSeconds());
1774 return FALSE;
1777 void PVXMLSession::RecordEnd()
1779 if (recording)
1780 recordSync.Signal();
1783 BOOL PVXMLSession::EndRecording()
1785 if (recording) {
1786 recording = FALSE;
1787 if (vxmlChannel != NULL)
1788 return vxmlChannel->EndRecording();
1791 return FALSE;
1795 BOOL PVXMLSession::IsRecording() const
1797 return (vxmlChannel != NULL) && vxmlChannel->IsRecording();
1800 PWAVFile * PVXMLSession::CreateWAVFile(const PFilePath & fn, PFile::OpenMode mode, int opts, unsigned fmt)
1802 if (!fn.IsEmpty())
1803 return new PWAVFile(fn, mode, opts, fmt);
1805 return new PWAVFile(mode, opts, fmt);
1808 void PVXMLSession::AllowClearCall()
1810 allowFinish = TRUE;
1813 BOOL PVXMLSession::TraverseAudio()
1815 if (!currentNode->IsElement()) {
1816 PlayText(((PXMLData *)currentNode)->GetString());
1819 else {
1820 PXMLElement * element = (PXMLElement *)currentNode;
1822 if (element->GetName() *= "value") {
1823 PString className = element->GetAttribute("class");
1824 PString value = EvaluateExpr(element->GetAttribute("expr"));
1825 PString voice = element->GetAttribute("voice");
1826 if (voice.IsEmpty())
1827 GetVar("voice");
1828 SayAs(className, value, voice);
1831 else if (element->GetName() *= "sayas") {
1832 PString className = element->GetAttribute("class");
1833 PXMLObject * object = element->GetElement();
1834 if (!object->IsElement()) {
1835 PString text = ((PXMLData *)object)->GetString();
1836 SayAs(className, text);
1840 else if (element->GetName() *= "break") {
1842 // msecs is VXML 1.0
1843 if (element->HasAttribute("msecs"))
1844 PlaySilence(element->GetAttribute("msecs").AsInteger());
1846 // time is VXML 2.0
1847 else if (element->HasAttribute("time")) {
1848 PTimeInterval time = StringToTime(element->GetAttribute("time"));
1849 PlaySilence(time);
1852 else if (element->HasAttribute("size")) {
1853 PString size = element->GetAttribute("size");
1854 if (size *= "none")
1856 else if (size *= "small")
1857 PlaySilence(SMALL_BREAK_MSECS);
1858 else if (size *= "large")
1859 PlaySilence(LARGE_BREAK_MSECS);
1860 else
1861 PlaySilence(MEDIUM_BREAK_MSECS);
1864 // default to medium pause
1865 else {
1866 PlaySilence(MEDIUM_BREAK_MSECS);
1870 else if (element->GetName() *= "audio") {
1871 BOOL loaded = FALSE;
1873 if (element->HasAttribute("src")) {
1875 PString str = element->GetAttribute("src").Trim();
1876 if (!str.IsEmpty() && (str[0] == '|')) {
1877 loaded = TRUE;
1878 PlayCommand(str.Mid(1));
1881 else {
1882 // get a normalised name for the resource
1883 PFilePath fn;
1884 PURL url = NormaliseResourceName(str);
1886 // load the resource from the cache
1887 PString contentType;
1888 BOOL useCache = !(GetVar("caching") *= "safe") && !(element->GetAttribute("caching") *= "safe");
1889 if (RetreiveResource(url, contentType, fn, useCache)) {
1890 PWAVFile * wavFile = vxmlChannel->CreateWAVFile(fn);
1891 if (wavFile == NULL)
1892 PTRACE(2, "PVXML\tCannot create audio file " + fn);
1893 else if (!wavFile->IsOpen())
1894 delete wavFile;
1895 else {
1896 loaded = TRUE;
1897 PlayFile(fn, 0, 0, !useCache); // make sure we delete the file if not cacheing
1902 if (loaded) {
1903 // skip to the next node
1904 if (element->HasSubObjects())
1905 currentNode = element->GetElement(element->GetSize() - 1);
1910 else
1911 PTRACE(2, "PVXML\tUnknown audio tag " << element->GetName() << " encountered");
1914 return TRUE;
1918 BOOL PVXMLSession::TraverseGoto() // <goto>
1920 PAssert(currentNode != NULL, "ProcessGotoElement(): Expected valid node");
1921 if (currentNode == NULL)
1922 return FALSE;
1924 // LATER: handle expr, expritem, fetchaudio, fetchhint, fetchtimeout, maxage, maxstale
1926 PAssert(currentNode->IsElement(), "ProcessGotoElement(): Expected element");
1928 // nextitem
1929 PString nextitem = ((PXMLElement*)currentNode)->GetAttribute("nextitem");
1930 if (!nextitem.IsEmpty()) {
1931 // LATER: Take out the optional #
1932 currentForm = FindForm(nextitem);
1933 currentNode = currentForm;
1934 if (currentForm == NULL) {
1935 // LATER: throw "error.semantic" or "error.badfetch" -- lookup which
1936 return FALSE;
1938 return TRUE;
1941 // next
1942 PString next = ((PXMLElement*)currentNode)->GetAttribute("next");
1943 // LATER: fixup filename to prepend path
1944 if (!next.IsEmpty()) {
1945 if (next[0] == '#') {
1946 next = next.Right( next.GetLength() -1 );
1947 currentForm = FindForm(next);
1948 currentNode = currentForm;
1949 // LATER: throw "error.semantic" or "error.badfetch" -- lookup which
1950 return currentForm != NULL;
1952 else {
1953 PURL url = NormaliseResourceName(next);
1954 return LoadURL(url) && (currentForm != NULL);
1957 return FALSE;
1960 BOOL PVXMLSession::TraverseGrammar() // <grammar>
1962 // LATER: A bunch of work to do here!
1964 // For now we only support the builtin digits type and do not parse any grammars.
1966 // NOTE: For now we will process both <grammar> and <field> here.
1967 // NOTE: Later there needs to be a check for <grammar> which will pull
1968 // out the text and process a grammar like '1 | 2'
1970 // Right now we only support one active grammar.
1971 if (activeGrammar != NULL) {
1972 PTRACE(2, "PVXML\tWarning: can only process one grammar at a time, ignoring previous grammar");
1973 delete activeGrammar;
1974 activeGrammar = NULL;
1977 PVXMLGrammar * newGrammar = NULL;
1979 // Is this a built-in type?
1980 PString type = ((PXMLElement*)currentNode)->GetAttribute("type");
1981 if (!type.IsEmpty()) {
1982 PStringArray tokens = type.Tokenise("?;", TRUE);
1983 PString builtintype;
1984 if (tokens.GetSize() > 0)
1985 builtintype = tokens[0];
1987 if (builtintype *= "digits") {
1988 PINDEX minDigits(1);
1989 PINDEX maxDigits(100);
1991 // look at each parameter
1992 for (PINDEX i(1); i < tokens.GetSize(); i++) {
1993 PStringArray params = tokens[i].Tokenise("=", TRUE);
1994 if (params.GetSize() == 2) {
1995 if (params[0] *= "minlength") {
1996 minDigits = params[1].AsInteger();
1998 else if (params[0] *= "maxlength") {
1999 maxDigits = params[1].AsInteger();
2001 else if (params[0] *= "length") {
2002 minDigits = maxDigits = params[1].AsInteger();
2005 else {
2006 // Invalid parameter skipped
2007 // LATER: throw 'error.semantic'
2010 newGrammar = new PVXMLDigitsGrammar((PXMLElement*)currentNode, minDigits, maxDigits, "");
2012 else {
2013 // LATER: throw 'error.unsupported'
2014 return FALSE;
2018 if (newGrammar != NULL)
2019 return LoadGrammar(newGrammar);
2021 return TRUE;
2024 // Finds the proper event hander for 'noinput', 'filled', 'nomatch' and 'error'
2025 // by searching the scope hiearchy from the current from
2026 PXMLElement * PVXMLSession::FindHandler(const PString & event)
2028 PAssert(currentNode->IsElement(), "Expected 'PXMLElement' in PVXMLSession::FindHandler");
2029 PXMLElement * tmp = (PXMLElement *)currentNode;
2030 PXMLElement * handler = NULL;
2032 // Look in all the way up the tree for a handler either explicitly or in a catch
2033 while (tmp != NULL) {
2034 // Check for an explicit hander - i.e. <error>, <filled>, <noinput>, <nomatch>, <help>
2035 if ((handler = tmp->GetElement(event)) != NULL)
2036 return handler;
2038 // Check for a <catch>
2039 if ((handler = tmp->GetElement("catch")) != NULL) {
2040 PString strCond = handler->GetAttribute("cond");
2041 if (strCond.Find(event))
2042 return handler;
2045 tmp = tmp->GetParent();
2048 return NULL;
2051 void PVXMLSession::SayAs(const PString & className, const PString & _text)
2053 SayAs(className, _text, GetVar("voice"));
2057 void PVXMLSession::SayAs(const PString & className, const PString & _text, const PString & voice)
2059 if (textToSpeech != NULL)
2060 textToSpeech->SetVoice(voice);
2062 PString text = _text.Trim();
2063 if (!text.IsEmpty()) {
2064 PTextToSpeech::TextType type = PTextToSpeech::Literal;
2066 if (className *= "digits")
2067 type = PTextToSpeech::Digits;
2069 else if (className *= "literal")
2070 type = PTextToSpeech::Literal;
2072 else if (className *= "number")
2073 type = PTextToSpeech::Number;
2075 else if (className *= "currency")
2076 type = PTextToSpeech::Currency;
2078 else if (className *= "time")
2079 type = PTextToSpeech::Time;
2081 else if (className *= "date")
2082 type = PTextToSpeech::Date;
2084 else if (className *= "phone")
2085 type = PTextToSpeech::Phone;
2087 else if (className *= "ipaddress")
2088 type = PTextToSpeech::IPAddress;
2090 else if (className *= "duration")
2091 type = PTextToSpeech::Duration;
2093 PlayText(text, type);
2097 PTimeInterval PVXMLSession::StringToTime(const PString & str)
2099 PTimeInterval timeout;
2101 long msecs = str.AsInteger();
2102 if (str.Find("ms") != P_MAX_INDEX)
2104 else if (str.Find("s") != P_MAX_INDEX)
2105 msecs = msecs * 1000;
2107 return PTimeInterval(msecs);
2110 BOOL PVXMLSession::TraverseIf()
2112 // If 'cond' parameter evaluates to true, enter child entities, else
2113 // go to next element.
2115 PString condition = ((PXMLElement*)currentNode)->GetAttribute("cond");
2117 // Find comparison type
2118 PINDEX location = condition.Find("==");
2119 BOOL isEqual = (location < condition.GetSize());
2121 if (isEqual) {
2122 // Find var name
2123 PString varname = condition.Left(location);
2125 // Find value, skip '=' signs
2126 PString cond_value = condition.Right(condition.GetSize() - (location + 3));
2128 // check if var value equals value from condition and if not skip child elements
2129 PString value = GetVar(varname);
2130 if (cond_value == value) {
2131 PTRACE( 3, "VXMLSess\t\tCondition matched \"" << condition << "\"" );
2132 } else {
2133 PTRACE( 3, "VXMLSess\t\tCondition \"" << condition << "\"did not match, " << varname << " == " << value );
2134 if (currentNode->IsElement()) {
2135 PXMLElement* element = (PXMLElement*) currentNode;
2136 if (element->HasSubObjects()) {
2137 // Step to last child element (really last element is NULL?)
2138 currentNode = element->GetElement(element->GetSize() - 1);
2144 else {
2145 PTRACE( 1, "\tPVXMLSession, <if> element contains condition with operator other than ==, not implemented" );
2146 return FALSE;
2149 return TRUE;
2152 BOOL PVXMLSession::TraverseExit()
2154 currentNode = NULL;
2155 forceEnd = TRUE;
2156 waitForEvent.Signal();
2157 return TRUE;
2161 BOOL PVXMLSession::TraverseSubmit()
2163 BOOL result = FALSE;
2165 // Do HTTP client stuff here
2167 // Find out what to submit, for now, only support a WAV file
2168 PXMLElement * element = (PXMLElement *)currentNode;
2170 if (!element->HasAttribute("namelist")){
2171 PTRACE(1, "VXMLSess\t<submit> does not contain \"namelist\" parameter");
2172 return FALSE;
2175 PString name = element->GetAttribute("namelist");
2177 if (name.Find(" ") < name.GetSize()) {
2178 PTRACE(1, "VXMLSess\t<submit> does not support more than one value in \"namelist\" parameter");
2179 return FALSE;
2182 if (!element->HasAttribute("next")) {
2183 PTRACE(1, "VXMLSess\t<submit> does not contain \"next\" parameter");
2184 return FALSE;
2187 PString url = element->GetAttribute("next");
2189 if (url.Find( "http://" ) > url.GetSize()) {
2190 PTRACE(1, "VXMLSess\t<submit> needs a full url as the \"next\" parameter");
2191 return FALSE;
2194 if (!(GetVar(name + ".type") == "audio/x-wav" )) {
2195 PTRACE(1, "VXMLSess\t<submit> does not (yet) support submissions of types other than \"audio/x-wav\"");
2196 return FALSE;
2199 PString fileName = GetVar(name + ".filename");
2201 if (!(element->HasAttribute("method"))) {
2202 PTRACE(1, "VXMLSess\t<submit> does not (yet) support default method type \"get\"");
2203 return FALSE;
2206 if ( !PFile::Exists(fileName )) {
2207 PTRACE(1, "VXMLSess\t<submit> cannot find file " << fileName);
2208 return FALSE;
2211 PString fileNameOnly;
2212 int pos = fileName.FindLast( "/" );
2213 if (pos < fileName.GetLength()) {
2214 fileNameOnly = fileName.Right( ( fileName.GetLength() - pos ) - 1 );
2216 else {
2217 pos = fileName.FindLast("\\");
2218 if (pos < fileName.GetSize()) {
2219 fileNameOnly = fileName.Right((fileName.GetLength() - pos) - 1);
2221 else {
2222 fileNameOnly = fileName;
2226 PHTTPClient client;
2227 PMIMEInfo sendMIME, replyMIME;
2229 if (element->GetAttribute("method") *= "post") {
2231 // 1 2 3 4123
2232 PString boundary = "--------012345678901234567890123458VXML";
2234 sendMIME.SetAt( PHTTP::ContentTypeTag(), "multipart/form-data; boundary=" + boundary);
2235 sendMIME.SetAt( PHTTP::UserAgentTag(), "PVXML TraverseSubmit" );
2236 sendMIME.SetAt( "Accept", "text/html" );
2238 // After this all boundaries have a "--" prepended
2239 boundary = "--" + boundary;
2241 // Create the mime header
2242 // First set the primary boundary
2243 PString mimeHeader = boundary + "\r\n";
2245 // Add content disposition
2246 mimeHeader += "Content-Disposition: form-data; name=\"voicemail\"; filename=\"" + fileNameOnly + "\"\r\n";
2248 // Add content type
2249 mimeHeader += "Content-Type: audio/wav\r\n\r\n";
2251 // Create the footer and add the closing of the content with a CR/LF
2252 PString mimeFooter = "\r\n";
2254 // Copy the header, buffer and footer together in one PString
2256 // Load the WAV file into memory
2257 PFile file( fileName, PFile::ReadOnly );
2258 int size = file.GetLength();
2259 PString mimeThing;
2261 // Make PHP happy?
2262 // Anyway, this shows how to add more variables, for when namelist containes more elements
2263 PString mimeMaxFileSize = boundary + "\r\nContent-Disposition: form-data; name=\"MAX_FILE_SIZE\"\r\n\r\n3000000\r\n";
2265 // Finally close the body with the boundary again, but also add "--"
2266 // to show this is the final boundary
2267 boundary = boundary + "--";
2268 mimeFooter += boundary + "\r\n";
2269 mimeHeader = mimeMaxFileSize + mimeHeader;
2270 mimeThing.SetSize( mimeHeader.GetSize() + size + mimeFooter.GetSize() );
2272 // Copy the header to the result
2273 memcpy( mimeThing.GetPointer(), mimeHeader.GetPointer(), mimeHeader.GetLength());
2275 // Copy the contents of the file to the mime result
2276 file.Read( mimeThing.GetPointer() + mimeHeader.GetLength(), size );
2278 // Copy the footer to the result
2279 memcpy( mimeThing.GetPointer() + mimeHeader.GetLength() + size, mimeFooter.GetPointer(), mimeFooter.GetLength());
2281 // Send the POST request to the server
2282 result = client.PostData( url, sendMIME, mimeThing, replyMIME );
2284 // TODO, Later:
2285 // Remove file?
2286 // Load reply from server as new VXML docuemnt ala <goto>
2289 else {
2290 if (element->GetAttribute("method") != "get") {
2291 PTRACE(1, "VXMLSess\t<submit> does not (yet) support method type \"" << element->GetAttribute( "method" ) << "\"");
2292 return FALSE;
2295 PString getURL = url + "?" + name + "=" + GetVar( name );
2297 client.GetDocument( url, sendMIME, replyMIME );
2298 // TODO, Later:
2299 // Load reply from server as new VXML document ala <goto>
2302 if (!result) {
2303 PTRACE( 1, "VXMLSess\t<submit> to server failed with "
2304 << client.GetLastResponseCode() << " "
2305 << client.GetLastResponseInfo() );
2308 return result;
2311 BOOL PVXMLSession::TraverseProperty()
2313 PXMLElement* element = (PXMLElement *) currentNode;
2314 if (element->HasAttribute("name"))
2315 SetVar(element->GetAttribute("name"), element->GetAttribute("value"));
2317 return TRUE;
2321 BOOL PVXMLSession::TraverseMenu()
2323 BOOL result = FALSE;
2324 PVXMLGrammar * newGrammar = new PVXMLDigitsGrammar((PXMLElement*) currentNode, 1, 1, "" );
2325 LoadGrammar(newGrammar);
2326 result = TRUE;
2327 return result;
2330 BOOL PVXMLSession::TraverseChoice(const PString & grammarResult)
2332 // Iterate over all choice elements starting at currentnode
2333 BOOL result = FALSE;
2335 PXMLElement* element = (PXMLElement *) currentNode;
2336 // Current node is a choice element
2338 PString dtmf = element->GetAttribute( "dtmf" );
2340 if (dtmf.IsEmpty())
2341 dtmf = PString(PString::Unsigned, defaultDTMF);
2343 // Check if DTMF value for grammarResult matches the DTMF value for the choice
2344 if (dtmf == grammarResult) {
2346 // Find the form at next parameter
2347 PString formID = element->GetAttribute( "next" );
2349 PTRACE(3, "VXMLsess\tFound form id " << formID );
2351 if (!formID.IsEmpty()) {
2352 formID = formID.Right( formID.GetLength() - 1 );
2353 currentNode = FindForm( formID );
2354 if (currentNode != NULL)
2355 result = TRUE;
2358 return result;
2361 BOOL PVXMLSession::TraverseVar()
2363 BOOL result = FALSE;
2365 PXMLElement* element = (PXMLElement *) currentNode;
2367 PString name = element->GetAttribute( "name" );
2368 PString expr = element->GetAttribute( "expr" );
2370 if (name.IsEmpty() || expr.IsEmpty()) {
2371 PTRACE( 1, "VXMLSess\t<var> has a problem with its parameters, name=\"" << name << "\", expr=\"" << expr << "\"" );
2373 else {
2374 SetVar(name, expr);
2375 result = TRUE;
2378 return result;
2382 void PVXMLSession::OnEndRecording(const PString & /*channelName*/)
2384 //SetVar(channelName + ".size", PString(incomingChannel->GetWAVFile()->GetDataLength() ) );
2385 //SetVar(channelName + ".type", "audio/x-wav" );
2386 //SetVar(channelName + ".filename", incomingChannel->GetWAVFile()->GetName() );
2390 void PVXMLSession::Trigger()
2392 waitForEvent.Signal();
2397 /////////////////////////////////////////////////////////////////////////////////////////
2399 PVXMLGrammar::PVXMLGrammar(PXMLElement * _field)
2400 : field(_field), state(PVXMLGrammar::NOINPUT)
2404 //////////////////////////////////////////////////////////////////
2406 PVXMLMenuGrammar::PVXMLMenuGrammar(PXMLElement * _field)
2407 : PVXMLGrammar(_field)
2411 //////////////////////////////////////////////////////////////////
2413 PVXMLDigitsGrammar::PVXMLDigitsGrammar(PXMLElement * _field, PINDEX _minDigits, PINDEX _maxDigits, PString _terminators)
2414 : PVXMLGrammar(_field),
2415 minDigits(_minDigits),
2416 maxDigits(_maxDigits),
2417 terminators(_terminators)
2419 PAssert(_minDigits <= _maxDigits, "Error - invalid grammar parameter");
2422 BOOL PVXMLDigitsGrammar::OnUserInput(const char ch)
2424 // Ignore any other keys if we've already filled the grammar
2425 if (state == PVXMLGrammar::FILLED || state == PVXMLGrammar::NOMATCH)
2426 return TRUE;
2428 // is this char the terminator?
2429 if (terminators.Find(ch) != P_MAX_INDEX) {
2430 state = (value.GetLength() >= minDigits && value.GetLength() <= maxDigits) ?
2431 PVXMLGrammar::FILLED :
2432 PVXMLGrammar::NOMATCH;
2433 return TRUE;
2436 // Otherwise add to the grammar and check to see if we're done
2437 value += ch;
2438 if (value.GetLength() == maxDigits) {
2439 state = PVXMLGrammar::FILLED; // the grammar is filled!
2440 return TRUE;
2443 return FALSE;
2447 void PVXMLDigitsGrammar::Stop()
2449 // Stopping recognition here may change the state if something was
2450 // recognized but it didn't fill the number of digits requested
2451 if (!value.IsEmpty())
2452 state = PVXMLGrammar::NOMATCH;
2453 // otherwise the state will stay as NOINPUT
2456 //////////////////////////////////////////////////////////////////
2458 PVXMLChannel::PVXMLChannel(unsigned _frameDelay, PINDEX frameSize)
2459 : PDelayChannel(DelayReadsAndWrites, _frameDelay, frameSize)
2461 vxmlInterface = NULL;
2463 sampleFrequency = 8000;
2464 closed = FALSE;
2466 recording = FALSE;
2467 recordable = NULL;
2469 playing = FALSE;
2470 silentCount = 20; // wait 20 frames before playing the OGM
2471 paused = FALSE;
2473 currentPlayItem = NULL;
2476 BOOL PVXMLChannel::Open(PVXMLChannelInterface * _vxmlInterface)
2478 currentPlayItem = NULL;
2479 vxmlInterface = _vxmlInterface;
2480 return TRUE;
2483 PVXMLChannel::~PVXMLChannel()
2485 Close();
2488 BOOL PVXMLChannel::IsOpen() const
2490 return !closed;
2493 BOOL PVXMLChannel::Close()
2495 if (!closed) {
2496 EndRecording();
2497 FlushQueue();
2499 closed = TRUE;
2501 PDelayChannel::Close();
2504 return TRUE;
2507 PString PVXMLChannel::AdjustWavFilename(const PString & ofn)
2509 if (wavFilePrefix.IsEmpty())
2510 return ofn;
2512 PString fn = ofn;
2514 // add in suffix required for channel format, if any
2515 PINDEX pos = ofn.FindLast('.');
2516 if (pos == P_MAX_INDEX) {
2517 if (fn.Right(wavFilePrefix.GetLength()) != wavFilePrefix)
2518 fn += wavFilePrefix;
2520 else {
2521 PString basename = ofn.Left(pos);
2522 PString ext = ofn.Mid(pos+1);
2523 if (basename.Right(wavFilePrefix.GetLength()) != wavFilePrefix)
2524 basename += wavFilePrefix;
2525 fn = basename + "." + ext;
2527 return fn;
2530 PWAVFile * PVXMLChannel::CreateWAVFile(const PFilePath & fn, BOOL recording)
2532 PWAVFile * wav = PWAVFile::format(mediaFormat);
2533 if (wav == NULL) {
2534 PTRACE(1, "VXML\tWAV file format " << mediaFormat << " not known");
2535 return NULL;
2538 wav->SetAutoconvert();
2539 if (!wav->Open(AdjustWavFilename(fn),
2540 recording ? PFile::WriteOnly : PFile::ReadOnly,
2541 PFile::ModeDefault))
2542 PTRACE(2, "VXML\tCould not open WAV file " << wav->GetName());
2544 else if (recording) {
2545 wav->SetChannels(1);
2546 wav->SetSampleRate(8000);
2547 wav->SetSampleSize(16);
2548 return wav;
2551 else if (!wav->IsValid())
2552 PTRACE(2, "VXML\tWAV file header invalid for " << wav->GetName());
2554 else if (wav->GetSampleRate() != sampleFrequency)
2555 PTRACE(2, "VXML\tWAV file has unsupported sample frequency " << wav->GetSampleRate());
2557 else if (wav->GetChannels() != 1)
2558 PTRACE(2, "VXML\tWAV file has unsupported channel count " << wav->GetChannels());
2560 else {
2561 wav->SetAutoconvert(); /// enable autoconvert
2562 PTRACE(3, "VXML\tOpened WAV file " << wav->GetName());
2563 return wav;
2566 delete wav;
2567 return NULL;
2571 BOOL PVXMLChannel::Write(const void * buf, PINDEX len)
2573 if (closed)
2574 return FALSE;
2576 channelWriteMutex.Wait();
2578 // let the recordable do silence detection
2579 if (recordable != NULL && recordable->OnFrame(IsSilenceFrame(buf, len))) {
2580 PTRACE(3, "VXML\tRecording finished due to silence");
2581 EndRecording();
2584 // if nothing is capturing incoming data, then fake the timing and return
2585 if ((recordable == NULL) && (GetBaseWriteChannel() == NULL)) {
2586 lastWriteCount = len;
2587 channelWriteMutex.Signal();
2588 PDelayChannel::Wait(len, nextWriteTick);
2589 return TRUE;
2592 // write the data and do the correct delay
2593 if (!WriteFrame(buf, len))
2594 EndRecording();
2595 else
2596 totalData += lastWriteCount;
2598 channelWriteMutex.Signal();
2600 return TRUE;
2603 BOOL PVXMLChannel::StartRecording(const PFilePath & fn, unsigned _finalSilence, unsigned _maxDuration)
2605 PVXMLRecordableFilename * recordable = new PVXMLRecordableFilename();
2606 if (!recordable->Open(fn)) {
2607 delete recordable;
2608 return FALSE;
2611 recordable->SetFinalSilence(_finalSilence);
2612 recordable->SetMaxDuration(_maxDuration);
2613 return QueueRecordable(recordable);
2616 BOOL PVXMLChannel::QueueRecordable(PVXMLRecordable * newItem)
2618 totalData = 0;
2620 // shutdown any existing recording
2621 EndRecording();
2623 // insert the new recordable
2624 PWaitAndSignal mutex(channelWriteMutex);
2625 recordable = newItem;
2626 recording = TRUE;
2627 totalData = 0;
2628 newItem->OnStart();
2629 newItem->Record(*this);
2630 SetReadTimeout(frameDelay);
2631 return TRUE;
2635 BOOL PVXMLChannel::EndRecording()
2637 PWaitAndSignal mutex(channelWriteMutex);
2639 if (recordable != NULL) {
2640 PTRACE(3, "PVXML\tFinished recording " << totalData << " bytes");
2642 PDelayChannel::Close();
2643 recordable->OnStop();
2644 delete recordable;
2645 recordable = NULL;
2646 PTRACE(4, "PVXML\tRecording finished");
2649 return TRUE;
2652 BOOL PVXMLChannel::Read(void * buffer, PINDEX amount)
2654 // assume we are returning silence
2655 BOOL done = FALSE;
2656 BOOL silenceStuff = FALSE;
2657 BOOL delayDone = FALSE;
2659 while (!done && !silenceStuff) {
2661 if (closed)
2662 return FALSE;
2665 PWaitAndSignal m(channelReadMutex);
2667 // if we are paused or in a delay, then do return silence
2668 if (paused || delayTimer.IsRunning()) {
2669 silenceStuff = TRUE;
2670 break;
2673 // if we are returning silence frames, then decrement the frame count
2674 // and continue returning silence
2675 if (silentCount > 0) {
2676 silentCount--;
2677 silenceStuff = TRUE;
2678 break;
2681 // try and read data from the underlying channel
2682 if (GetBaseReadChannel() != NULL) {
2684 PWaitAndSignal m(queueMutex);
2686 // see if the item needs to repeat
2687 PAssert(currentPlayItem != NULL, "current VXML play item disappeared");
2689 // if the read succeeds, we are done
2690 if (currentPlayItem->ReadFrame(*this, buffer, amount)) {
2691 totalData += amount;
2692 delayDone = TRUE;
2693 done = TRUE;
2694 break;
2697 // if a timeout, send silence
2698 if (GetErrorCode(LastReadError) == Timeout) {
2699 silenceStuff = TRUE;
2700 break;
2703 PTRACE(3, "PVXML\tFinished playing " << totalData << " bytes");
2704 PDelayChannel::Close();
2706 // repeat the item if needed
2707 if (currentPlayItem->GetRepeat() > 1) {
2708 currentPlayItem->SetRepeat(currentPlayItem->GetRepeat()-1);
2709 currentPlayItem->OnRepeat(*this);
2710 continue;
2713 // see if end of queue delay specified
2714 PINDEX delay = 0;
2715 if (currentPlayItem->delayDone) {
2716 delay = currentPlayItem->GetDelay();
2717 if (delay != 0) {
2718 PTRACE(3, "PVXML\tDelaying for " << delay);
2719 delayTimer = delay;
2720 currentPlayItem->delayDone = TRUE;
2721 continue;
2725 // stop the current item
2726 currentPlayItem->OnStop();
2727 delete currentPlayItem;
2728 currentPlayItem = NULL;
2731 // check the queue for the next action
2733 PWaitAndSignal m(queueMutex);
2735 // if nothing in the queue (which is weird as something just stopped playing)
2736 // then trigger the VXML and send silence
2737 currentPlayItem = playQueue.Dequeue();
2738 if (currentPlayItem == NULL) {
2739 vxmlInterface->Trigger();
2740 silenceStuff = TRUE;
2741 break;
2744 // start the new item
2745 currentPlayItem->OnStart();
2746 currentPlayItem->Play(*this);
2747 SetReadTimeout(frameDelay);
2748 totalData = 0;
2753 // start silence frame if required
2754 // note that this always requires a delay
2755 if (silenceStuff) {
2756 lastReadCount = CreateSilenceFrame(buffer, amount);
2759 // make sure we always do the correct delay
2760 if (!delayDone)
2761 Wait(amount, nextReadTick);
2763 return TRUE;
2766 BOOL PVXMLChannel::QueuePlayable(const PString & type,
2767 const PString & arg,
2768 PINDEX repeat,
2769 PINDEX delay,
2770 BOOL autoDelete)
2772 PTRACE(3, "PVXML\tEnqueueing playable " << type << " with arg " << arg << " for playing");
2773 PVXMLPlayable * item = PFactory<PVXMLPlayable>::CreateInstance(type);
2774 if (item == NULL) {
2775 PTRACE(2, "VXML\tCannot find playable of type " << type);
2776 delete item;
2777 return FALSE;
2780 if (!item->Open(*this, arg, delay, repeat, autoDelete)) {
2781 PTRACE(2, "VXML\tCannot open playable of type " << type << " with arg " << arg);
2782 delete item;
2783 return FALSE;
2786 if (QueuePlayable(item))
2787 return TRUE;
2789 delete item;
2790 return FALSE;
2793 BOOL PVXMLChannel::QueuePlayable(PVXMLPlayable * newItem)
2795 newItem->SetSampleFrequency(sampleFrequency);
2796 PWaitAndSignal mutex(queueMutex);
2797 playQueue.Enqueue(newItem);
2798 return TRUE;
2801 BOOL PVXMLChannel::QueueResource(const PURL & url, PINDEX repeat, PINDEX delay)
2803 if (url.GetScheme() *= "file")
2804 return QueuePlayable("File", url.AsFilePath(), repeat, delay, FALSE);
2805 else
2806 return QueuePlayable("URL", url.AsString(), repeat, delay);
2809 BOOL PVXMLChannel::QueueData(const PBYTEArray & data, PINDEX repeat, PINDEX delay)
2811 PTRACE(3, "PVXML\tEnqueueing " << data.GetSize() << " bytes for playing");
2812 PVXMLPlayableData * item = dynamic_cast<PVXMLPlayableData *>(PFactory<PVXMLPlayable>::CreateInstance("PCM Data"));
2813 if (item == NULL) {
2814 PTRACE(2, "VXML\tCannot find playable of type 'PCM Data'");
2815 delete item;
2816 return FALSE;
2819 if (!item->Open(*this, "", delay, repeat, TRUE)) {
2820 PTRACE(2, "VXML\tCannot open playable of type 'PCM Data'");
2821 delete item;
2822 return FALSE;
2825 if (QueuePlayable(item))
2826 return TRUE;
2828 delete item;
2829 return FALSE;
2832 void PVXMLChannel::FlushQueue()
2834 PWaitAndSignal mutex(channelReadMutex);
2836 if (GetBaseReadChannel() != NULL)
2837 PDelayChannel::Close();
2839 PWaitAndSignal m(queueMutex);
2841 PVXMLPlayable * qItem;
2842 while ((qItem = playQueue.Dequeue()) != NULL) {
2843 qItem->OnStop();
2844 delete qItem;
2847 if (currentPlayItem != NULL) {
2848 currentPlayItem->OnStop();
2849 delete currentPlayItem;
2850 currentPlayItem = NULL;
2854 ///////////////////////////////////////////////////////////////
2856 PFactory<PVXMLChannel>::Worker<PVXMLChannelPCM> pcmVXMLChannelFactory(VXML_PCM16);
2858 PVXMLChannelPCM::PVXMLChannelPCM()
2859 : PVXMLChannel(30, 480)
2861 mediaFormat = VXML_PCM16;
2862 wavFilePrefix = PString::Empty();
2865 BOOL PVXMLChannelPCM::WriteFrame(const void * buf, PINDEX len)
2867 return PDelayChannel::Write(buf, len);
2870 BOOL PVXMLChannelPCM::ReadFrame(void * buffer, PINDEX amount)
2872 PINDEX len = 0;
2873 while (len < amount) {
2874 if (!PDelayChannel::Read(len + (char *)buffer, amount-len))
2875 return FALSE;
2876 len += GetLastReadCount();
2879 return TRUE;
2882 PINDEX PVXMLChannelPCM::CreateSilenceFrame(void * buffer, PINDEX amount)
2884 memset(buffer, 0, amount);
2885 return amount;
2888 BOOL PVXMLChannelPCM::IsSilenceFrame(const void * buf, PINDEX len) const
2890 // Calculate the average signal level of this frame
2891 int sum = 0;
2893 const short * pcm = (const short *)buf;
2894 const short * end = pcm + len/2;
2895 while (pcm != end) {
2896 if (*pcm < 0)
2897 sum -= *pcm++;
2898 else
2899 sum += *pcm++;
2902 // calc average
2903 unsigned level = sum / (len / 2);
2905 return level < 500; // arbitrary level
2908 static short beepData[] = { 0, 18784, 30432, 30400, 18784, 0, -18784, -30432, -30400, -18784 };
2911 void PVXMLChannelPCM::GetBeepData(PBYTEArray & data, unsigned ms)
2913 data.SetSize(0);
2914 while (data.GetSize() < (PINDEX)((ms * 8) / 2)) {
2915 PINDEX len = data.GetSize();
2916 data.SetSize(len + sizeof(beepData));
2917 memcpy(len + data.GetPointer(), beepData, sizeof(beepData));
2921 ///////////////////////////////////////////////////////////////
2923 PFactory<PVXMLChannel>::Worker<PVXMLChannelG7231> g7231VXMLChannelFactory(VXML_G7231);
2925 PVXMLChannelG7231::PVXMLChannelG7231()
2926 : PVXMLChannel(30, 0)
2928 mediaFormat = VXML_G7231;
2929 wavFilePrefix = "_g7231";
2932 static const PINDEX g7231Lens[] = { 24, 20, 4, 1 };
2934 BOOL PVXMLChannelG7231::WriteFrame(const void * buffer, PINDEX actualLen)
2936 PINDEX len = g7231Lens[(*(BYTE *)buffer)&3];
2937 if (len > actualLen)
2938 return FALSE;
2940 return PDelayChannel::Write(buffer, len);
2943 BOOL PVXMLChannelG7231::ReadFrame(void * buffer, PINDEX /*amount*/)
2945 if (!PDelayChannel::Read(buffer, 1))
2946 return FALSE;
2948 PINDEX len = g7231Lens[(*(BYTE *)buffer)&3];
2949 if (len != 1) {
2950 if (!PIndirectChannel::Read(1+(BYTE *)buffer, len-1))
2951 return FALSE;
2952 lastReadCount++;
2955 return TRUE;
2958 PINDEX PVXMLChannelG7231::CreateSilenceFrame(void * buffer, PINDEX /* len */)
2962 ((BYTE *)buffer)[0] = 2;
2963 memset(((BYTE *)buffer)+1, 0, 3);
2964 return 4;
2967 BOOL PVXMLChannelG7231::IsSilenceFrame(const void * buf, PINDEX len) const
2969 if (len == 4)
2970 return TRUE;
2971 if (buf == NULL)
2972 return FALSE;
2973 return ((*(const BYTE *)buf)&3) == 2;
2976 ///////////////////////////////////////////////////////////////
2978 PFactory<PVXMLChannel>::Worker<PVXMLChannelG729> g729VXMLChannelFactory(VXML_G729);
2980 PVXMLChannelG729::PVXMLChannelG729()
2981 : PVXMLChannel(10, 0)
2983 mediaFormat = VXML_G729;
2984 wavFilePrefix = "_g729";
2987 BOOL PVXMLChannelG729::WriteFrame(const void * buf, PINDEX /*len*/)
2989 return PDelayChannel::Write(buf, 10);
2992 BOOL PVXMLChannelG729::ReadFrame(void * buffer, PINDEX /*amount*/)
2994 return PDelayChannel::Read(buffer, 10);
2997 PINDEX PVXMLChannelG729::CreateSilenceFrame(void * buffer, PINDEX /* len */)
2999 memset(buffer, 0, 10);
3000 return 10;
3003 BOOL PVXMLChannelG729::IsSilenceFrame(const void * /*buf*/, PINDEX /*len*/) const
3005 return FALSE;
3008 //////////////////////////////////////////////////////////////////////////////////
3010 class TextToSpeech_Sample : public PTextToSpeech
3012 public:
3013 TextToSpeech_Sample();
3014 PStringArray GetVoiceList();
3015 BOOL SetVoice(const PString & voice);
3016 BOOL SetRate(unsigned rate);
3017 unsigned GetRate();
3018 BOOL SetVolume(unsigned volume);
3019 unsigned GetVolume();
3020 BOOL OpenFile (const PFilePath & fn);
3021 BOOL OpenChannel(PChannel * chanel);
3022 BOOL IsOpen() { return opened; }
3023 BOOL Close();
3024 BOOL Speak(const PString & text, TextType hint = Default);
3025 BOOL SpeakFile(const PString & text);
3027 protected:
3028 //PTextToSpeech * defaultEngine;
3030 PMutex mutex;
3031 BOOL opened;
3032 BOOL usingFile;
3033 PString text;
3034 PFilePath path;
3035 unsigned volume, rate;
3036 PString voice;
3038 std::vector<PFilePath> filenames;
3041 TextToSpeech_Sample::TextToSpeech_Sample()
3043 PWaitAndSignal m(mutex);
3044 usingFile = opened = FALSE;
3045 rate = 8000;
3046 volume = 100;
3049 PStringArray TextToSpeech_Sample::GetVoiceList()
3051 PStringArray r;
3052 return r;
3055 BOOL TextToSpeech_Sample::SetVoice(const PString & v)
3057 voice = v;
3058 return TRUE;
3061 BOOL TextToSpeech_Sample::SetRate(unsigned v)
3063 rate = v;
3064 return TRUE;
3067 unsigned TextToSpeech_Sample::GetRate()
3069 return rate;
3072 BOOL TextToSpeech_Sample::SetVolume(unsigned v)
3074 volume = v;
3075 return TRUE;
3078 unsigned TextToSpeech_Sample::GetVolume()
3080 return volume;
3083 BOOL TextToSpeech_Sample::OpenFile(const PFilePath & fn)
3085 PWaitAndSignal m(mutex);
3087 Close();
3088 usingFile = TRUE;
3089 path = fn;
3090 opened = TRUE;
3092 PTRACE(3, "TTS\tWriting speech to " << fn);
3094 return TRUE;
3097 BOOL TextToSpeech_Sample::OpenChannel(PChannel * /*chanel*/)
3099 PWaitAndSignal m(mutex);
3101 Close();
3102 usingFile = FALSE;
3103 opened = FALSE;
3105 return TRUE;
3108 BOOL TextToSpeech_Sample::Close()
3110 PWaitAndSignal m(mutex);
3112 if (!opened)
3113 return TRUE;
3115 BOOL stat = TRUE;
3117 if (usingFile) {
3118 PWAVFile outputFile("PCM-16", path, PFile::WriteOnly);
3119 if (!outputFile.IsOpen()) {
3120 PTRACE(1, "TTS\tCannot create output file " << path);
3121 stat = FALSE;
3123 else {
3124 std::vector<PFilePath>::const_iterator r;
3125 for (r = filenames.begin(); r != filenames.end(); ++r) {
3126 PFilePath f = *r;
3127 PWAVFile file;
3128 file.SetAutoconvert();
3129 if (!file.Open(f, PFile::ReadOnly)) {
3130 PTRACE(1, "TTS\tCannot open input file " << f);
3131 stat = FALSE;
3132 } else {
3133 PTRACE(1, "TTS\tReading from " << f);
3134 BYTE buffer[1024];
3135 for (;;) {
3136 if (!file.Read(buffer, 1024))
3137 break;
3138 outputFile.Write(buffer, file.GetLastReadCount());
3143 filenames.erase(filenames.begin(), filenames.end());
3146 opened = FALSE;
3147 return stat;
3150 BOOL TextToSpeech_Sample::Speak(const PString & text, TextType hint)
3152 PStringArray tokens = text.Tokenise("\t\n\r ", FALSE);
3153 for (PINDEX i = 0; i < tokens.GetSize(); ++i) {
3154 PString word = tokens[i].Trim();
3155 switch (hint) {
3156 case Default:
3157 break;
3158 case Literal:
3159 break;
3160 case Phone:
3161 case Digits:
3162 for (PINDEX i = 0; i < text.GetLength(); ++i) {
3163 if (isdigit(text[i]))
3164 SpeakFile(PString(text[i]));
3166 break;
3167 case Number:
3169 int number = atoi(word);
3170 if (number < 0) {
3171 SpeakFile("negative");
3172 number = -number;
3174 else if (number == 0) {
3175 SpeakFile("0");
3177 else {
3178 if (number >= 1000000) {
3179 int millions = number / 1000000;
3180 number = number % 1000000;
3181 Speak(PString(PString::Signed, millions), Number);
3182 SpeakFile("million");
3184 if (number >= 1000) {
3185 int thousands = number / 1000;
3186 number = number % 1000;
3187 Speak(PString(PString::Signed, thousands), Number);
3188 SpeakFile("thousand");
3190 if (number >= 100) {
3191 int hundreds = number / 100;
3192 number = number % 100;
3193 Speak(PString(PString::Signed, hundreds), Number);
3194 SpeakFile("hundred");
3196 if (!SpeakFile(PString(PString::Signed, number))) {
3197 int tens = number / 10;
3198 number = number % 10;
3199 if (tens > 0)
3200 SpeakFile(PString(PString::Signed, tens*10));
3201 if (number > 0)
3202 SpeakFile(PString(PString::Signed, number));
3206 break;
3207 case Currency:
3208 break;
3209 case Time:
3210 break;
3211 case Date:
3212 break;
3213 case IPAddress:
3215 PIPSocket::Address addr(text);
3216 for (PINDEX i = 0; i < 4; ++i) {
3217 int octet = addr[i];
3218 if (octet < 100)
3219 Speak(octet, Number);
3220 else
3221 Speak(octet, Digits);
3222 if (i != 3)
3223 SpeakFile("dot");
3226 break;
3227 case Duration:
3228 break;
3231 return TRUE;
3234 BOOL TextToSpeech_Sample::SpeakFile(const PString & text)
3236 PFilePath f = PDirectory(voice) + (text + ".wav");
3237 if (!PFile::Exists(f))
3238 return FALSE;
3239 filenames.push_back(f);
3240 return TRUE;
3243 PFactory<PTextToSpeech>::Worker<TextToSpeech_Sample> sampleTTSFactory("sampler", false);
3245 #endif // P_EXPAT
3247 ///////////////////////////////////////////////////////////////