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
18 * The Original Code is Portable Windows Library.
20 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
22 * Contributor(s): ______________________________________.
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
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
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
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
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
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
300 #pragma implementation "vxml.h"
305 #define P_DISABLE_FACTORY_INSTANCES
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
);
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
);
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
);
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;
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();
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
;
395 BOOL stat
= channel
.ReadFrame(buf
, len
);
398 if ((repeat
== 0) || !Rewind(channel
.GetBaseReadChannel()))
400 PINDEX readLen
= channel
.GetLastReadCount();
411 ///////////////////////////////////////////////////////////////
413 BOOL
PVXMLPlayableFilename::Open(PVXMLChannel
& chan
, const PString
& _fn
, PINDEX _delay
, PINDEX _repeat
, BOOL _autoDelete
)
417 if (!PFile::Exists(chan
.AdjustWavFilename(fn
)))
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
);
431 PFile
* fileChan
= new PFile(fn
);
432 if (fileChan
->Open(PFile::ReadOnly
))
440 PTRACE(2, "PVXML\tCannot open file \"" << fn
<< "\"");
442 PTRACE(3, "PVXML\tPlaying file \"" << fn
<< "\"");
443 outgoingChannel
.SetReadChannel(chan
, TRUE
);
447 void PVXMLPlayableFilename::OnStop()
450 PTRACE(3, "PVXML\tDeleting file \"" << fn
<< "\"");
455 BOOL
PVXMLPlayableFilename::Rewind(PChannel
* chan
)
457 PFile
* file
= dynamic_cast<PFile
*>(chan
);
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)
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
);
494 PFile
* fileChan
= new PFile(fn
);
495 if (fileChan
->Open(PFile::ReadOnly
))
503 PTRACE(2, "PVXML\tCannot open file \"" << fn
<< "\"");
505 PTRACE(3, "PVXML\tPlaying file \"" << fn
<< "\"");
506 outgoingChannel
.SetReadChannel(chan
, TRUE
);
510 void PVXMLPlayableFilenameList::OnStop()
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 ///////////////////////////////////////////////////////////////
526 PVXMLPlayableCommand::PVXMLPlayableCommand()
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
);
545 PTRACE(2, "PVXML\tCannot open command \"" << arg
<< "\"");
548 PTRACE(3, "PVXML\tPlaying command \"" << arg
<< "\"");
549 outgoingChannel
.SetReadChannel(pipeCmd
, TRUE
);
553 void PVXMLPlayableCommand::OnStop()
555 if (pipeCmd
!= NULL
) {
556 pipeCmd
->WaitForTermination();
561 PFactory
<PVXMLPlayable
>::Worker
<PVXMLPlayableCommand
> vxmlPlayableCommandFactory("Command");
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
)
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
);
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
601 if (!tones
.Generate(toneSpec
))
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
)
617 return PVXMLPlayable::Open(chan
, _delay
, _repeat
, autoDelete
);
620 void PVXMLPlayableURL::Play(PVXMLChannel
& outgoingChannel
)
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()))
629 outgoingChannel
.SetReadChannel(client
, TRUE
);
633 PFactory
<PVXMLPlayable
>::Worker
<PVXMLPlayableURL
> vxmlPlayableURLFactory("URL");
635 ///////////////////////////////////////////////////////////////
637 BOOL
PVXMLRecordableFilename::Open(const PString
& _arg
)
640 consecutiveSilence
= 0;
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
);
652 PFile
* fileChan
= new PFile(fn
);
653 if (fileChan
->Open(PFile::WriteOnly
))
661 PTRACE(2, "PVXML\tCannot open file \"" << fn
<< "\"");
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
)
675 silenceStart
= PTime();
676 consecutiveSilence
= 0;
678 consecutiveSilence
++;
679 if ( ((consecutiveSilence
% 20) == 0) &&
681 ((finalSilence
> 0) && ((PTime() - silenceStart
).GetMilliSeconds() >= finalSilence
)) ||
682 ((maxDuration
> 0) && ((PTime() - recordStart
).GetMilliSeconds() >= maxDuration
))
691 ///////////////////////////////////////////////////////////////
693 PVXMLCache::PVXMLCache(const PDirectory
& _directory
)
694 : directory(_directory
)
696 if (!directory
.Exists())
700 static PString
MD5AsHex(const PString
& str
)
702 PMessageDigest::Result digest
;
703 PMessageDigest5::Encode(str
, digest
);
706 const BYTE
* data
= digest
.GetPointer();
707 for (PINDEX i
= 0; i
< digest
.GetSize(); ++i
)
708 hexStr
.sprintf("%02x", (unsigned)data
[i
]);
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
,
723 const PString
& fileType
,
724 PString
& contentType
,
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");
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
);
743 typeFile
.ReadLine(contentType
);
745 if (contentType
.IsEmpty())
746 contentType
= GetContentType(dataFn
);
751 void PVXMLCache::Put(const PString
& prefix
,
753 const PString
& fileType
,
754 const PString
& contentType
,
755 const PFilePath
& fn
,
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
));
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");
782 PFilePath
PVXMLCache::GetRandomFilename(const PString
& prefix
, const PString
& fileType
)
786 // create a random temporary filename
789 fn
= directory
+ psprintf("%s_%i.%s", (const char *)prefix
, r
.Generate() % 1000000, (const char *)fileType
);
790 if (!PFile::Exists(fn
))
797 //////////////////////////////////////////////////////////
799 PVXMLSession::PVXMLSession(PTextToSpeech
* _tts
, BOOL autoDelete
)
803 finishWhenEmpty
= TRUE
;
805 autoDeleteTextToSpeech
= FALSE
;
807 autoDeleteTextToSpeech
= FALSE
;
808 SetTextToSpeech(_tts
, autoDelete
);
813 void PVXMLSession::Initialise()
818 activeGrammar
= NULL
;
823 autoDeleteTextToSpeech
= FALSE
;
826 PVXMLSession::~PVXMLSession()
830 if ((textToSpeech
!= NULL
) && autoDeleteTextToSpeech
)
834 PTextToSpeech
* PVXMLSession::SetTextToSpeech(PTextToSpeech
* _tts
, BOOL autoDelete
)
836 PWaitAndSignal
m(sessionMutex
);
838 if (autoDeleteTextToSpeech
&& (textToSpeech
!= NULL
))
841 autoDeleteTextToSpeech
= autoDelete
;
846 PTextToSpeech
* PVXMLSession::SetTextToSpeech(const PString
& ttsName
)
848 PWaitAndSignal
m(sessionMutex
);
850 if (autoDeleteTextToSpeech
&& (textToSpeech
!= NULL
))
853 autoDeleteTextToSpeech
= TRUE
;
854 textToSpeech
= PFactory
<PTextToSpeech
>::CreateInstance(ttsName
);
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
);
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)
893 if (!RetreiveResource(url
, contentType
, fn
, FALSE
)) {
894 PTRACE(1, "PVXML\tCannot load document " << url
);
898 PTextFile
file(fn
, PFile::ReadOnly
);
899 if (!file
.IsOpen()) {
900 PTRACE(1, "PVXML\tCannot read data from " << fn
);
904 off_t len
= file
.GetLength();
906 file
.Read(text
.GetPointer(len
+1), len
);
907 len
= file
.GetLastReadCount();
909 text
[(PINDEX
)len
] = '\0';
911 if (!LoadVXML(text
)) {
912 PTRACE(1, "PVXML\tCannot load VXML in " << url
);
920 BOOL
PVXMLSession::LoadVXML(const PString
& xmlText
)
922 PWaitAndSignal
m(sessionMutex
);
924 allowFinish
= loaded
= FALSE
;
925 rootURL
= PString::Empty();
929 if (!xmlFile
.Load(xmlText
)) {
930 PTRACE(1, "PVXML\tCannot parse root document: " << GetXMLError());
934 PXMLElement
* root
= xmlFile
.GetRootElement();
938 // reset interpeter state
941 // find the first form
942 if ((currentForm
= FindForm(PString::Empty())) == NULL
)
945 // start processing with this <form> element
946 currentNode
= currentForm
;
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))
959 if (rootURL
.IsEmpty())
960 return "file:" + src
;
962 // else use scheme and path from root document
964 PStringArray path
= url
.GetPath();
966 if (path
.GetSize() > 0) {
969 for (i
= 1; i
< path
.GetSize()-1; i
++)
970 pathStr
+= "/" + path
[i
];
971 pathStr
+= "/" + src
;
972 url
.SetPathStr(pathStr
);
979 BOOL
PVXMLSession::RetreiveResource(const PURL
& url
,
980 PString
& contentType
,
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
);
994 // do a HTTP get when appropriate
995 else if ((url
.GetScheme() *= "http") || (url
.GetScheme() *= "https")) {
998 PString fileType
= url
.AsFilePath().GetType();
1000 BOOL inCache
= FALSE
;
1002 inCache
= PVXMLCache::GetResourceCache().Get("url", url
.AsString(), fileType
, contentType
, dataFn
);
1006 // get a random filename
1007 fn
= PVXMLCache::GetResourceCache().GetRandomFilename("url", fileType
);
1009 // get the resource header information
1011 PMIMEInfo outMIME
, replyMIME
;
1012 if (!client
.GetDocument(url
, outMIME
, replyMIME
)) {
1013 PTRACE(2, "PVXML\tCannot load resource " << url
);
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
1030 PVXMLCache::GetResourceCache().Put("url", url
.AsString(), fileType
, contentType
, fn
, dataFn
);
1038 // files on the local file system get loaded locally
1039 else if (url
.GetScheme() *= "file") {
1040 dataFn
= url
.AsFilePath();
1044 // unknown schemes give an error
1052 PXMLElement
* PVXMLSession::FindForm(const PString
& id
)
1054 // NOTE: should have some flag to know if it is loaded
1055 PXMLElement
* root
= xmlFile
.GetRootElement();
1059 // Only handle search of top level nodes for <form> element
1061 for (i
= 0; i
< root
->GetSize(); i
++) {
1062 PXMLObject
* xmlObject
= root
->GetElement(i
);
1063 if (xmlObject
->IsElement()) {
1064 PXMLElement
* xmlElement
= (PXMLElement
*)xmlObject
;
1066 (xmlElement
->GetName() *= "form") &&
1067 (id
.IsEmpty() || (xmlElement
->GetAttribute("id") *= id
))
1076 BOOL
PVXMLSession::Open(BOOL isPCM
)
1079 return Open(VXML_PCM16
);
1081 return Open(VXML_G7231
);
1084 BOOL
PVXMLSession::Open(const PString
& _mediaFormat
)
1086 mediaFormat
= _mediaFormat
;
1088 PVXMLChannel
* chan
= PFactory
<PVXMLChannel
>::CreateInstance(mediaFormat
);
1090 PTRACE(1, "VXML\tCannot create VXML channel with format " << mediaFormat
);
1094 // set the underlying channel
1095 if (!PIndirectChannel::Open(chan
, chan
))
1098 // start the VXML session in another thread
1100 PWaitAndSignal
m(sessionMutex
);
1101 if (!chan
->Open(this))
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
);
1123 BOOL
PVXMLSession::Close()
1126 PWaitAndSignal
m(sessionMutex
);
1127 if (vxmlThread
!= NULL
) {
1129 // Stop condition for thread
1130 threadRunning
= FALSE
;
1132 waitForEvent
.Signal();
1134 // Signal all syncpoints that could be waiting for things
1135 answerSync
.Signal();
1136 vxmlChannel
->Close();
1138 vxmlThread
->WaitForTermination();
1146 return PIndirectChannel::Close();
1150 void PVXMLSession::VXMLExecute(PThread
&, INT
)
1152 while (!forceEnd
&& threadRunning
) {
1154 // process current node in the VXML script
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
1166 PTRACE(2, "PVXML\tFast forwarding through script because of forceEnd" );
1167 while (currentNode
!= NULL
)
1173 //PWaitAndSignal m(sessionMutex);
1174 if (vxmlChannel
!= NULL
)
1175 vxmlChannel
->Close();
1180 void PVXMLSession::ProcessUserInput()
1182 // without this initialisation, gcc 4.1 gives a warning
1185 PWaitAndSignal
m(userInputMutex
);
1186 if (userInputQueue
.size() == 0)
1188 ch
= userInputQueue
.front();
1189 userInputQueue
.pop();
1190 PTRACE(3, "VXML\tHandling user input " << ch
);
1202 if (activeGrammar
!= NULL
)
1203 activeGrammar
->OnUserInput(ch
);
1207 void PVXMLSession::ExecuteDialog()
1209 // check for user input
1212 // process any active grammars
1215 // process current node in the VXML script
1218 // Wait for the buffer to complete before continuing to the next node
1219 if (currentNode
== NULL
) {
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
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")) {
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
)
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
)
1276 PVXMLGrammar::GrammarState state
= activeGrammar
->GetState();
1277 grammarResult
= activeGrammar
->GetValue();
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
1293 case PVXMLGrammar::FILLED
:
1294 eventName
= "filled";
1296 case PVXMLGrammar::NOINPUT
:
1297 eventName
= "noinput";
1299 case PVXMLGrammar::NOMATCH
:
1300 eventName
= "nomatch";
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
)
1320 if (!currentNode
->IsElement()) {
1328 PXMLElement
* element
= (PXMLElement
*)currentNode
;
1329 PCaselessString nodeType
= element
->GetName();
1330 PTRACE(3, "PVXML\t**** Processing VoiceXML element: <" << nodeType
<< "> ***");
1332 if (nodeType
*= "audio") {
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")
1345 else if (nodeType
*= "disconnect")
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")
1363 else if (nodeType
*= "grammar")
1364 TraverseGrammar(); // this will set activeGrammar
1366 else if (nodeType
*= "record") {
1371 else if (nodeType
*= "prompt") {
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") {
1390 else if (nodeType
*= "value") {
1395 else if (nodeType
*= "var")
1398 else if (nodeType
*= "if")
1401 else if (nodeType
*= "exit")
1404 else if (nodeType
*= "menu") {
1411 else if (nodeType
*= "choice") {
1412 if (!TraverseChoice(grammarResult
))
1415 // If the correct choice has been found,
1416 /// make sure everything is reset correctly
1417 eventName
.MakeEmpty();
1418 grammarResult
.MakeEmpty();
1423 else if (nodeType
*= "submit")
1426 else if (nodeType
*= "property")
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();
1442 BOOL
PVXMLSession::TraverseRecord()
1444 if (currentNode
->IsElement()) {
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)
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)
1468 if (strDest
.IsEmpty()) {
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
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
1502 SetVar(strName
+ "$.maxtime", "true");
1505 // Normal hangup before timeout
1506 SetVar( strName
+ "$.maxtime", "false");
1509 // when this returns, we are done
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
1527 BOOL allDigits
= TRUE
;
1528 for (i
= 0; i
< expr
.GetLength(); i
++) {
1529 allDigits
= allDigits
&& isdigit(expr
[i
]);
1535 return GetVar(expr
);
1538 PString
PVXMLSession::GetVar(const PString
& ostr
) const
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
)
1566 PINDEX pos
= str
.Find('.');
1567 if (pos
!= P_MAX_INDEX
) {
1568 scope
= str
.Left(pos
);
1569 str
= str
.Mid(pos
+1);
1573 if (scope
.IsEmpty() || (scope
*= "session")) {
1574 sessionVars
.SetAt(str
, val
);
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
))
1594 BOOL
PVXMLSession::PlayCommand(const PString
& cmd
, PINDEX repeat
, PINDEX delay
)
1596 if (vxmlChannel
== NULL
|| !vxmlChannel
->QueueCommand(cmd
, repeat
, delay
))
1604 BOOL
PVXMLSession::PlayData(const PBYTEArray
& data
, PINDEX repeat
, PINDEX delay
)
1606 if (vxmlChannel
== NULL
|| !vxmlChannel
->QueueData(data
, repeat
, delay
))
1614 BOOL
PVXMLSession::PlayTone(const PString
& toneSpec
, PINDEX repeat
, PINDEX delay
)
1616 if (vxmlChannel
== NULL
|| !vxmlChannel
->QueuePlayable("Tone", toneSpec
, repeat
, delay
, 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
)
1638 if (vxmlChannel
== NULL
|| !vxmlChannel
->QueueData(nothing
, 1, msecs
))
1646 BOOL
PVXMLSession::PlayResource(const PURL
& url
, PINDEX repeat
, PINDEX delay
)
1648 if (vxmlChannel
== NULL
|| !vxmlChannel
->QueueResource(url
, repeat
, delay
))
1656 BOOL
PVXMLSession::LoadGrammar(PVXMLGrammar
* grammar
)
1658 if (activeGrammar
!= NULL
) {
1659 delete activeGrammar
;
1660 activeGrammar
= FALSE
;
1663 activeGrammar
= grammar
;
1668 BOOL
PVXMLSession::PlayText(const PString
& _text
,
1669 PTextToSpeech::TextType type
,
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");
1680 PVXMLPlayableFilenameList
* playable
= new PVXMLPlayableFilenameList
;
1681 if (!playable
->Open(*vxmlChannel
, list
, delay
, repeat
, !useCache
)) {
1683 PTRACE(1, "PVXML\tCannot create playable for filename list");
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();
1701 BOOL spoken
= FALSE
;
1704 // see if we have converted this text before
1705 PString contentType
;
1707 spoken
= PVXMLCache::GetResourceCache().Get(prefix
, text
, "wav", contentType
, dataFn
);
1709 // if not cached, then use the text to speech converter
1712 if (textToSpeech
!= NULL
) {
1713 tmpfname
= PVXMLCache::GetResourceCache().GetRandomFilename("tts", "wav");
1714 if (!textToSpeech
->OpenFile(tmpfname
)) {
1715 PTRACE(2, "PVXML\tcannot open file " << tmpfname
);
1717 spoken
= textToSpeech
->Speak(text
, type
);
1718 if (!textToSpeech
->Close()) {
1719 PTRACE(2, "PVXML\tcannot close TTS engine");
1722 textToSpeech
->Close();
1724 PVXMLCache::GetResourceCache().Put(prefix
, text
, "wav", contentType
, tmpfname
, dataFn
);
1731 PTRACE(2, "PVXML\tcannot speak text using TTS engine");
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*/)
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());
1777 void PVXMLSession::RecordEnd()
1780 recordSync
.Signal();
1783 BOOL
PVXMLSession::EndRecording()
1787 if (vxmlChannel
!= NULL
)
1788 return vxmlChannel
->EndRecording();
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
)
1803 return new PWAVFile(fn
, mode
, opts
, fmt
);
1805 return new PWAVFile(mode
, opts
, fmt
);
1808 void PVXMLSession::AllowClearCall()
1813 BOOL
PVXMLSession::TraverseAudio()
1815 if (!currentNode
->IsElement()) {
1816 PlayText(((PXMLData
*)currentNode
)->GetString());
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())
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());
1847 else if (element
->HasAttribute("time")) {
1848 PTimeInterval time
= StringToTime(element
->GetAttribute("time"));
1852 else if (element
->HasAttribute("size")) {
1853 PString size
= element
->GetAttribute("size");
1856 else if (size
*= "small")
1857 PlaySilence(SMALL_BREAK_MSECS
);
1858 else if (size
*= "large")
1859 PlaySilence(LARGE_BREAK_MSECS
);
1861 PlaySilence(MEDIUM_BREAK_MSECS
);
1864 // default to medium pause
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] == '|')) {
1878 PlayCommand(str
.Mid(1));
1882 // get a normalised name for the resource
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())
1897 PlayFile(fn
, 0, 0, !useCache
); // make sure we delete the file if not cacheing
1903 // skip to the next node
1904 if (element
->HasSubObjects())
1905 currentNode
= element
->GetElement(element
->GetSize() - 1);
1911 PTRACE(2, "PVXML\tUnknown audio tag " << element
->GetName() << " encountered");
1918 BOOL
PVXMLSession::TraverseGoto() // <goto>
1920 PAssert(currentNode
!= NULL
, "ProcessGotoElement(): Expected valid node");
1921 if (currentNode
== NULL
)
1924 // LATER: handle expr, expritem, fetchaudio, fetchhint, fetchtimeout, maxage, maxstale
1926 PAssert(currentNode
->IsElement(), "ProcessGotoElement(): Expected element");
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
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
;
1953 PURL url
= NormaliseResourceName(next
);
1954 return LoadURL(url
) && (currentForm
!= NULL
);
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();
2006 // Invalid parameter skipped
2007 // LATER: throw 'error.semantic'
2010 newGrammar
= new PVXMLDigitsGrammar((PXMLElement
*)currentNode
, minDigits
, maxDigits
, "");
2013 // LATER: throw 'error.unsupported'
2018 if (newGrammar
!= NULL
)
2019 return LoadGrammar(newGrammar
);
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
)
2038 // Check for a <catch>
2039 if ((handler
= tmp
->GetElement("catch")) != NULL
) {
2040 PString strCond
= handler
->GetAttribute("cond");
2041 if (strCond
.Find(event
))
2045 tmp
= tmp
->GetParent();
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());
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
<< "\"" );
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);
2145 PTRACE( 1, "\tPVXMLSession, <if> element contains condition with operator other than ==, not implemented" );
2152 BOOL
PVXMLSession::TraverseExit()
2156 waitForEvent
.Signal();
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");
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");
2182 if (!element
->HasAttribute("next")) {
2183 PTRACE(1, "VXMLSess\t<submit> does not contain \"next\" parameter");
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");
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\"");
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\"");
2206 if ( !PFile::Exists(fileName
)) {
2207 PTRACE(1, "VXMLSess\t<submit> cannot find file " << fileName
);
2211 PString fileNameOnly
;
2212 int pos
= fileName
.FindLast( "/" );
2213 if (pos
< fileName
.GetLength()) {
2214 fileNameOnly
= fileName
.Right( ( fileName
.GetLength() - pos
) - 1 );
2217 pos
= fileName
.FindLast("\\");
2218 if (pos
< fileName
.GetSize()) {
2219 fileNameOnly
= fileName
.Right((fileName
.GetLength() - pos
) - 1);
2222 fileNameOnly
= fileName
;
2227 PMIMEInfo sendMIME
, replyMIME
;
2229 if (element
->GetAttribute("method") *= "post") {
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";
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();
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
);
2286 // Load reply from server as new VXML docuemnt ala <goto>
2290 if (element
->GetAttribute("method") != "get") {
2291 PTRACE(1, "VXMLSess\t<submit> does not (yet) support method type \"" << element
->GetAttribute( "method" ) << "\"");
2295 PString getURL
= url
+ "?" + name
+ "=" + GetVar( name
);
2297 client
.GetDocument( url
, sendMIME
, replyMIME
);
2299 // Load reply from server as new VXML document ala <goto>
2303 PTRACE( 1, "VXMLSess\t<submit> to server failed with "
2304 << client
.GetLastResponseCode() << " "
2305 << client
.GetLastResponseInfo() );
2311 BOOL
PVXMLSession::TraverseProperty()
2313 PXMLElement
* element
= (PXMLElement
*) currentNode
;
2314 if (element
->HasAttribute("name"))
2315 SetVar(element
->GetAttribute("name"), element
->GetAttribute("value"));
2321 BOOL
PVXMLSession::TraverseMenu()
2323 BOOL result
= FALSE
;
2324 PVXMLGrammar
* newGrammar
= new PVXMLDigitsGrammar((PXMLElement
*) currentNode
, 1, 1, "" );
2325 LoadGrammar(newGrammar
);
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" );
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
)
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
<< "\"" );
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
)
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
;
2436 // Otherwise add to the grammar and check to see if we're done
2438 if (value
.GetLength() == maxDigits
) {
2439 state
= PVXMLGrammar::FILLED
; // the grammar is filled!
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;
2470 silentCount
= 20; // wait 20 frames before playing the OGM
2473 currentPlayItem
= NULL
;
2476 BOOL
PVXMLChannel::Open(PVXMLChannelInterface
* _vxmlInterface
)
2478 currentPlayItem
= NULL
;
2479 vxmlInterface
= _vxmlInterface
;
2483 PVXMLChannel::~PVXMLChannel()
2488 BOOL
PVXMLChannel::IsOpen() const
2493 BOOL
PVXMLChannel::Close()
2501 PDelayChannel::Close();
2507 PString
PVXMLChannel::AdjustWavFilename(const PString
& ofn
)
2509 if (wavFilePrefix
.IsEmpty())
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
;
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
;
2530 PWAVFile
* PVXMLChannel::CreateWAVFile(const PFilePath
& fn
, BOOL recording
)
2532 PWAVFile
* wav
= PWAVFile::format(mediaFormat
);
2534 PTRACE(1, "VXML\tWAV file format " << mediaFormat
<< " not known");
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);
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());
2561 wav
->SetAutoconvert(); /// enable autoconvert
2562 PTRACE(3, "VXML\tOpened WAV file " << wav
->GetName());
2571 BOOL
PVXMLChannel::Write(const void * buf
, PINDEX len
)
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");
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
);
2592 // write the data and do the correct delay
2593 if (!WriteFrame(buf
, len
))
2596 totalData
+= lastWriteCount
;
2598 channelWriteMutex
.Signal();
2603 BOOL
PVXMLChannel::StartRecording(const PFilePath
& fn
, unsigned _finalSilence
, unsigned _maxDuration
)
2605 PVXMLRecordableFilename
* recordable
= new PVXMLRecordableFilename();
2606 if (!recordable
->Open(fn
)) {
2611 recordable
->SetFinalSilence(_finalSilence
);
2612 recordable
->SetMaxDuration(_maxDuration
);
2613 return QueueRecordable(recordable
);
2616 BOOL
PVXMLChannel::QueueRecordable(PVXMLRecordable
* newItem
)
2620 // shutdown any existing recording
2623 // insert the new recordable
2624 PWaitAndSignal
mutex(channelWriteMutex
);
2625 recordable
= newItem
;
2629 newItem
->Record(*this);
2630 SetReadTimeout(frameDelay
);
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();
2646 PTRACE(4, "PVXML\tRecording finished");
2652 BOOL
PVXMLChannel::Read(void * buffer
, PINDEX amount
)
2654 // assume we are returning silence
2656 BOOL silenceStuff
= FALSE
;
2657 BOOL delayDone
= FALSE
;
2659 while (!done
&& !silenceStuff
) {
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
;
2673 // if we are returning silence frames, then decrement the frame count
2674 // and continue returning silence
2675 if (silentCount
> 0) {
2677 silenceStuff
= TRUE
;
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
;
2697 // if a timeout, send silence
2698 if (GetErrorCode(LastReadError
) == Timeout
) {
2699 silenceStuff
= TRUE
;
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);
2713 // see if end of queue delay specified
2715 if (currentPlayItem
->delayDone
) {
2716 delay
= currentPlayItem
->GetDelay();
2718 PTRACE(3, "PVXML\tDelaying for " << delay
);
2720 currentPlayItem
->delayDone
= TRUE
;
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
;
2744 // start the new item
2745 currentPlayItem
->OnStart();
2746 currentPlayItem
->Play(*this);
2747 SetReadTimeout(frameDelay
);
2753 // start silence frame if required
2754 // note that this always requires a delay
2756 lastReadCount
= CreateSilenceFrame(buffer
, amount
);
2759 // make sure we always do the correct delay
2761 Wait(amount
, nextReadTick
);
2766 BOOL
PVXMLChannel::QueuePlayable(const PString
& type
,
2767 const PString
& arg
,
2772 PTRACE(3, "PVXML\tEnqueueing playable " << type
<< " with arg " << arg
<< " for playing");
2773 PVXMLPlayable
* item
= PFactory
<PVXMLPlayable
>::CreateInstance(type
);
2775 PTRACE(2, "VXML\tCannot find playable of type " << type
);
2780 if (!item
->Open(*this, arg
, delay
, repeat
, autoDelete
)) {
2781 PTRACE(2, "VXML\tCannot open playable of type " << type
<< " with arg " << arg
);
2786 if (QueuePlayable(item
))
2793 BOOL
PVXMLChannel::QueuePlayable(PVXMLPlayable
* newItem
)
2795 newItem
->SetSampleFrequency(sampleFrequency
);
2796 PWaitAndSignal
mutex(queueMutex
);
2797 playQueue
.Enqueue(newItem
);
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
);
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"));
2814 PTRACE(2, "VXML\tCannot find playable of type 'PCM Data'");
2819 if (!item
->Open(*this, "", delay
, repeat
, TRUE
)) {
2820 PTRACE(2, "VXML\tCannot open playable of type 'PCM Data'");
2825 if (QueuePlayable(item
))
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
) {
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
)
2873 while (len
< amount
) {
2874 if (!PDelayChannel::Read(len
+ (char *)buffer
, amount
-len
))
2876 len
+= GetLastReadCount();
2882 PINDEX
PVXMLChannelPCM::CreateSilenceFrame(void * buffer
, PINDEX amount
)
2884 memset(buffer
, 0, amount
);
2888 BOOL
PVXMLChannelPCM::IsSilenceFrame(const void * buf
, PINDEX len
) const
2890 // Calculate the average signal level of this frame
2893 const short * pcm
= (const short *)buf
;
2894 const short * end
= pcm
+ len
/2;
2895 while (pcm
!= end
) {
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
)
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
)
2940 return PDelayChannel::Write(buffer
, len
);
2943 BOOL
PVXMLChannelG7231::ReadFrame(void * buffer
, PINDEX
/*amount*/)
2945 if (!PDelayChannel::Read(buffer
, 1))
2948 PINDEX len
= g7231Lens
[(*(BYTE
*)buffer
)&3];
2950 if (!PIndirectChannel::Read(1+(BYTE
*)buffer
, len
-1))
2958 PINDEX
PVXMLChannelG7231::CreateSilenceFrame(void * buffer
, PINDEX
/* len */)
2962 ((BYTE
*)buffer
)[0] = 2;
2963 memset(((BYTE
*)buffer
)+1, 0, 3);
2967 BOOL
PVXMLChannelG7231::IsSilenceFrame(const void * buf
, PINDEX len
) const
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);
3003 BOOL
PVXMLChannelG729::IsSilenceFrame(const void * /*buf*/, PINDEX
/*len*/) const
3008 //////////////////////////////////////////////////////////////////////////////////
3010 class TextToSpeech_Sample
: public PTextToSpeech
3013 TextToSpeech_Sample();
3014 PStringArray
GetVoiceList();
3015 BOOL
SetVoice(const PString
& voice
);
3016 BOOL
SetRate(unsigned rate
);
3018 BOOL
SetVolume(unsigned volume
);
3019 unsigned GetVolume();
3020 BOOL
OpenFile (const PFilePath
& fn
);
3021 BOOL
OpenChannel(PChannel
* chanel
);
3022 BOOL
IsOpen() { return opened
; }
3024 BOOL
Speak(const PString
& text
, TextType hint
= Default
);
3025 BOOL
SpeakFile(const PString
& text
);
3028 //PTextToSpeech * defaultEngine;
3035 unsigned volume
, rate
;
3038 std::vector
<PFilePath
> filenames
;
3041 TextToSpeech_Sample::TextToSpeech_Sample()
3043 PWaitAndSignal
m(mutex
);
3044 usingFile
= opened
= FALSE
;
3049 PStringArray
TextToSpeech_Sample::GetVoiceList()
3055 BOOL
TextToSpeech_Sample::SetVoice(const PString
& v
)
3061 BOOL
TextToSpeech_Sample::SetRate(unsigned v
)
3067 unsigned TextToSpeech_Sample::GetRate()
3072 BOOL
TextToSpeech_Sample::SetVolume(unsigned v
)
3078 unsigned TextToSpeech_Sample::GetVolume()
3083 BOOL
TextToSpeech_Sample::OpenFile(const PFilePath
& fn
)
3085 PWaitAndSignal
m(mutex
);
3092 PTRACE(3, "TTS\tWriting speech to " << fn
);
3097 BOOL
TextToSpeech_Sample::OpenChannel(PChannel
* /*chanel*/)
3099 PWaitAndSignal
m(mutex
);
3108 BOOL
TextToSpeech_Sample::Close()
3110 PWaitAndSignal
m(mutex
);
3118 PWAVFile
outputFile("PCM-16", path
, PFile::WriteOnly
);
3119 if (!outputFile
.IsOpen()) {
3120 PTRACE(1, "TTS\tCannot create output file " << path
);
3124 std::vector
<PFilePath
>::const_iterator r
;
3125 for (r
= filenames
.begin(); r
!= filenames
.end(); ++r
) {
3128 file
.SetAutoconvert();
3129 if (!file
.Open(f
, PFile::ReadOnly
)) {
3130 PTRACE(1, "TTS\tCannot open input file " << f
);
3133 PTRACE(1, "TTS\tReading from " << f
);
3136 if (!file
.Read(buffer
, 1024))
3138 outputFile
.Write(buffer
, file
.GetLastReadCount());
3143 filenames
.erase(filenames
.begin(), filenames
.end());
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();
3162 for (PINDEX i
= 0; i
< text
.GetLength(); ++i
) {
3163 if (isdigit(text
[i
]))
3164 SpeakFile(PString(text
[i
]));
3169 int number
= atoi(word
);
3171 SpeakFile("negative");
3174 else if (number
== 0) {
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;
3200 SpeakFile(PString(PString::Signed
, tens
*10));
3202 SpeakFile(PString(PString::Signed
, number
));
3215 PIPSocket::Address
addr(text
);
3216 for (PINDEX i
= 0; i
< 4; ++i
) {
3217 int octet
= addr
[i
];
3219 Speak(octet
, Number
);
3221 Speak(octet
, Digits
);
3234 BOOL
TextToSpeech_Sample::SpeakFile(const PString
& text
)
3236 PFilePath f
= PDirectory(voice
) + (text
+ ".wav");
3237 if (!PFile::Exists(f
))
3239 filenames
.push_back(f
);
3243 PFactory
<PTextToSpeech
>::Worker
<TextToSpeech_Sample
> sampleTTSFactory("sampler", false);
3247 ///////////////////////////////////////////////////////////////