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.53 2004/08/09 11:10:34 csoutheren
26 * Changed SetTextToSpeech to return ptr to new engine
28 * Revision 1.52 2004/07/28 02:01:51 csoutheren
29 * Removed deadlock in some call shutdown scenarios
31 * Revision 1.51 2004/07/27 05:26:46 csoutheren
34 * Revision 1.50 2004/07/27 00:00:41 csoutheren
35 * Allowed Close to set closed flag before attepting lock of channels
37 * Revision 1.49 2004/07/26 07:25:02 csoutheren
38 * Fixed another problem with thread starvation due to delaying inside a mutex lock
40 * Revision 1.48 2004/07/26 00:40:41 csoutheren
41 * Fixed thread starvation problem under Linux by splitting channelMutex
42 * into seperate read and write mutexes
44 * Revision 1.47 2004/07/23 00:59:26 csoutheren
45 * Check in latest changes
47 * Revision 1.46 2004/07/17 09:44:12 rjongbloed
48 * Fixed missing set of last write count if not actually writing frames.
50 * Revision 1.45 2004/07/15 03:12:42 csoutheren
51 * Migrated changes from crs_vxnml_devel branch into main trunk
53 * Revision 1.42.2.7 2004/07/13 08:13:05 csoutheren
54 * Lots of implementation of factory-based PWAVFile
56 * Revision 1.42.2.6 2004/07/12 08:30:17 csoutheren
57 * More fixes for abstract factory implementation of PWAVFile
59 * Revision 1.42.2.5 2004/07/08 04:58:11 csoutheren
60 * Exposed VXML playable classes to allow descendants
62 * Revision 1.42.2.4 2004/07/07 07:07:43 csoutheren
63 * Changed PWAVFile to use abstract factories (extensively)
64 * Removed redundant blocking/unblocking when using G.723.1
65 * More support for call transfer
67 * Revision 1.42.2.3 2004/07/06 01:38:57 csoutheren
68 * Changed PVXMLChannel to use PDelayChannel
69 * Fixed bug where played files were deleted after playing
71 * Revision 1.42.2.2 2004/07/02 07:22:40 csoutheren
72 * Updated for latest factory changes
74 * Revision 1.42.2.1 2004/06/20 11:18:03 csoutheren
75 * Rewrite of resource cacheing to cache text-to-speech output
77 * Revision 1.42 2004/06/19 07:21:08 csoutheren
78 * Change TTS engine registration to use abstract factory code
79 * Started disentanglement of PVXMLChannel from PVXMLSession
80 * Fixed problem with VXML session closing if played data file is not exact frame size multiple
81 * Allowed PVXMLSession to be used without a VXML script
82 * Changed PVXMLChannel to handle "file:" URLs
83 * Numerous other small improvements and optimisations
85 * Revision 1.41 2004/06/02 08:29:28 csoutheren
86 * Added new code from Andreas Sikkema to implement various VXML features
88 * Revision 1.40 2004/06/02 06:16:48 csoutheren
89 * Removed unnecessary buffer copying and removed potential busy loop
91 * Revision 1.39 2004/05/02 05:14:43 rjongbloed
92 * Fixed possible deadlock in shutdown of VXML channel/session.
94 * Revision 1.38 2004/04/24 06:27:56 rjongbloed
95 * Fixed GCC 3.4.0 warnings about PAssertNULL and improved recoverability on
96 * NULL pointer usage in various bits of code.
98 * Revision 1.37 2004/03/23 04:48:42 csoutheren
99 * Improved ability to start VXML scripts as needed
101 * Revision 1.36 2003/11/12 20:38:16 csoutheren
102 * Fixed problem with incorrect sense of ContentLength header detection thanks to Andreas Sikkema
104 * Revision 1.35 2003/05/14 01:12:53 rjongbloed
105 * Fixed test for SID frames in record silence detection on G.723.1A
107 * Revision 1.34 2003/04/23 11:54:53 craigs
108 * Added ability to record audio
110 * Revision 1.33 2003/04/10 04:19:43 robertj
111 * Fixed incorrect timing on G.723.1 (framed codec)
112 * Fixed not using correct codec file suffix for non PCM/G.723.1 codecs.
114 * Revision 1.32 2003/04/08 05:09:14 craigs
115 * Added ability to use commands as an audio source
117 * Revision 1.31 2003/03/17 08:03:07 robertj
118 * Combined to the separate incoming and outgoing substream classes into
119 * a single class to make it easier to produce codec aware descendents.
120 * Added G.729 substream class.
122 * Revision 1.30 2002/12/03 22:39:14 robertj
123 * Removed get document that just returns a content length as the chunked
124 * transfer encoding makes this very dangerous.
126 * Revision 1.29 2002/11/19 10:36:30 robertj
127 * Added functions to set anf get "file:" URL. as PFilePath and do the right
128 * things with platform dependent directory components.
130 * Revision 1.28 2002/11/08 03:39:27 craigs
131 * Fixed problem with G.723.1 files
133 * Revision 1.27 2002/09/24 13:47:41 robertj
134 * Added support for more vxml commands, thanks Alexander Kovatch
136 * Revision 1.26 2002/09/18 06:37:40 robertj
137 * Added functions to load vxml directly, via file or URL. Old function
138 * intelligently picks which one to use.
140 * Revision 1.25 2002/09/03 04:38:14 craigs
141 * Added VXML 2.0 time attribute to <break>
143 * Revision 1.24 2002/09/03 04:11:37 craigs
144 * More changes from Alexander Kovatch
146 * Revision 1.23 2002/08/30 07:33:16 craigs
147 * Added extra initialisations
149 * Revision 1.22 2002/08/30 05:05:54 craigs
150 * Added changes for PVXMLGrammar from Alexander Kovatch
152 * Revision 1.21 2002/08/29 00:16:12 craigs
153 * Fixed typo, thanks to Peter Robinson
155 * Revision 1.20 2002/08/28 08:05:16 craigs
156 * Reorganised VXMLSession class as per code from Alexander Kovatch
158 * Revision 1.19 2002/08/28 05:10:57 craigs
159 * Added ability to load resources via URI
162 * Revision 1.18 2002/08/27 02:46:56 craigs
163 * Removed need for application to call AllowClearCall
165 * Revision 1.17 2002/08/27 02:20:09 craigs
166 * Added <break> command in prompt blocks
167 * Fixed potential deadlock
168 * Added <prompt> command in top level fields, thanks to Alexander Kovatch
170 * Revision 1.16 2002/08/15 04:11:16 robertj
171 * Fixed shutdown problems with closing vxml session, leaks a thread.
172 * Fixed potential problems with indirect channel Close() function.
174 * Revision 1.15 2002/08/15 02:13:10 craigs
175 * Fixed problem with handle leak (maybe) and change tts files back to autodelete
177 * Revision 1.14 2002/08/14 15:18:07 craigs
178 * Improved random filename generation
180 * Revision 1.13 2002/08/08 01:03:06 craigs
181 * Added function to re-enable automatic call clearing on script end
183 * Revision 1.12 2002/08/07 13:38:14 craigs
184 * Fixed bug in calculating lengths of G.723.1 packets
186 * Revision 1.11 2002/08/06 07:45:28 craigs
187 * Added lots of stuff from OpalVXML
189 * Revision 1.10 2002/07/29 15:08:50 craigs
190 * Added autodelete option to PlayFile
192 * Revision 1.9 2002/07/29 15:03:36 craigs
193 * Added access to queue functions
194 * Added autodelete option to AddFile
196 * Revision 1.8 2002/07/29 14:16:05 craigs
197 * Added asynchronous VXML execution
199 * Revision 1.7 2002/07/17 08:34:25 craigs
200 * Fixed deadlock problems
202 * Revision 1.6 2002/07/17 06:08:23 craigs
203 * Added additional "sayas" classes
205 * Revision 1.5 2002/07/10 13:15:20 craigs
206 * Moved some VXML classes from Opal back into PTCLib
207 * Fixed various race conditions
209 * Revision 1.4 2002/07/05 06:28:07 craigs
210 * Added OnEmptyAction callback
212 * Revision 1.3 2002/07/02 06:24:53 craigs
213 * Added recording functions
215 * Revision 1.2 2002/06/28 01:30:29 robertj
216 * Fixed ability to compile if do not have expat library.
218 * Revision 1.1 2002/06/27 05:27:49 craigs
225 #pragma implementation "vxml.h"
230 #define P_DISABLE_FACTORY_INSTANCES
234 #include <ptlib/pfactory.h>
235 #include <ptclib/vxml.h>
236 #include <ptclib/memfile.h>
237 #include <ptclib/random.h>
238 #include <ptclib/http.h>
240 #define SMALL_BREAK_MSECS 1000
241 #define MEDIUM_BREAK_MSECS 2500
242 #define LARGE_BREAK_MSECS 5000
244 // LATER: Lookup what this value should be
245 #define DEFAULT_TIMEOUT 10000
247 //////////////////////////////////////////////////////////
249 static PString
GetContentType(const PFilePath
& fn
)
251 PString type
= fn
.GetType();
257 return "audio/x-wav";
259 return PString::Empty();
262 ///////////////////////////////////////////////////////////////
264 BOOL
PVXMLPlayableFilename::Open(PVXMLChannel
& chan
, const PString
& _fn
, PINDEX _delay
, PINDEX _repeat
, BOOL _autoDelete
)
268 if (!PFile::Exists(chan
.AdjustWavFilename(fn
)))
271 return PVXMLPlayable::Open(chan
, _delay
, _repeat
, _autoDelete
);
274 void PVXMLPlayableFilename::Play(PVXMLChannel
& outgoingChannel
)
276 PChannel
* chan
= NULL
;
278 // check the file extension and open a .wav or a raw (.sw or .g723) file
279 if ((fn
.Right(4)).ToLower() == ".wav")
280 chan
= outgoingChannel
.CreateWAVFile(fn
);
282 PFile
* fileChan
= new PFile(fn
);
283 if (fileChan
->Open(PFile::ReadOnly
))
291 PTRACE(3, "PVXML\tCannot open file \"" << fn
<< "\"");
293 PTRACE(3, "PVXML\tPlaying file \"" << fn
<< "\"");
294 outgoingChannel
.SetReadChannel(chan
, TRUE
);
298 void PVXMLPlayableFilename::OnStop()
301 PTRACE(3, "PVXML\tDeleting file \"" << fn
<< "\"");
306 PFactory
<PVXMLPlayable
>::Worker
<PVXMLPlayableFilename
> vxmlPlayableFilenameFactory("File");
308 ///////////////////////////////////////////////////////////////
310 BOOL
PVXMLPlayableFilenameList::Open(PVXMLChannel
& chan
, const PStringArray
& _list
, PINDEX _delay
, PINDEX _repeat
, BOOL _autoDelete
)
312 for (PINDEX i
= 0; i
< _list
.GetSize(); ++i
) {
313 PString fn
= chan
.AdjustWavFilename(_list
[i
]);
314 if (PFile::Exists(fn
))
315 filenames
.AppendString(fn
);
318 if (filenames
.GetSize() == 0)
323 return PVXMLPlayable::Open(chan
, _delay
, ((_repeat
>= 0) ? _repeat
: 1) * filenames
.GetSize(), _autoDelete
);
326 void PVXMLPlayableFilenameList::OnRepeat(PVXMLChannel
& outgoingChannel
)
328 PFilePath fn
= filenames
[currentIndex
++ % filenames
.GetSize()];
330 PChannel
* chan
= NULL
;
332 // check the file extension and open a .wav or a raw (.sw or .g723) file
333 if ((fn
.Right(4)).ToLower() == ".wav")
334 chan
= outgoingChannel
.CreateWAVFile(fn
);
336 PFile
* fileChan
= new PFile(fn
);
337 if (fileChan
->Open(PFile::ReadOnly
))
345 PTRACE(3, "PVXML\tCannot open file \"" << fn
<< "\"");
347 PTRACE(3, "PVXML\tPlaying file \"" << fn
<< "\"");
348 outgoingChannel
.SetReadChannel(chan
, TRUE
);
352 void PVXMLPlayableFilenameList::OnStop()
355 for (PINDEX i
= 0; i
< filenames
.GetSize(); ++i
) {
356 PTRACE(3, "PVXML\tDeleting file \"" << filenames
[i
] << "\"");
357 PFile::Remove(filenames
[i
]);
362 PFactory
<PVXMLPlayable
>::Worker
<PVXMLPlayableFilenameList
> vxmlPlayableFilenameListFactory("FileList");
364 ///////////////////////////////////////////////////////////////
366 PVXMLPlayableCommand::PVXMLPlayableCommand()
371 void PVXMLPlayableCommand::Play(PVXMLChannel
& outgoingChannel
)
373 arg
.Replace("%s", PString(PString::Unsigned
, sampleFrequency
));
374 arg
.Replace("%f", format
);
376 // execute a command and send the output through the stream
377 pipeCmd
= new PPipeChannel
;
378 if (!pipeCmd
->Open(arg
, PPipeChannel::ReadOnly
)) {
379 PTRACE(3, "PVXML\tCannot open command " << arg
);
385 PTRACE(3, "PVXML\tCannot open command \"" << arg
<< "\"");
388 PTRACE(3, "PVXML\tPlaying command \"" << arg
<< "\"");
389 outgoingChannel
.SetReadChannel(pipeCmd
, TRUE
);
393 void PVXMLPlayableCommand::OnStop()
395 if (pipeCmd
!= NULL
) {
396 pipeCmd
->WaitForTermination();
401 PFactory
<PVXMLPlayable
>::Worker
<PVXMLPlayableCommand
> vxmlPlayableCommandFactory("Command");
403 ///////////////////////////////////////////////////////////////
405 BOOL
PVXMLPlayableData::Open(PVXMLChannel
& chan
, const PString
& /*_fn*/, PINDEX _delay
, PINDEX _repeat
, BOOL v
)
407 return PVXMLPlayable::Open(chan
, _delay
, _repeat
, v
);
410 void PVXMLPlayableData::SetData(const PBYTEArray
& _data
)
415 void PVXMLPlayableData::Play(PVXMLChannel
& outgoingChannel
)
417 PMemoryFile
* chan
= new PMemoryFile(data
);
418 PTRACE(3, "PVXML\tPlaying " << data
.GetSize() << " bytes");
419 outgoingChannel
.SetReadChannel(chan
, TRUE
);
422 PFactory
<PVXMLPlayable
>::Worker
<PVXMLPlayableData
> vxmlPlayableDataFactory("PCM Data");
424 ///////////////////////////////////////////////////////////////
426 BOOL
PVXMLPlayableURL::Open(PVXMLChannel
& chan
, const PString
& _url
, PINDEX _delay
, PINDEX _repeat
, BOOL autoDelete
)
429 return PVXMLPlayable::Open(chan
, _delay
, _repeat
, autoDelete
);
432 void PVXMLPlayableURL::Play(PVXMLChannel
& outgoingChannel
)
435 PHTTPClient
* client
= new PHTTPClient
;
436 PMIMEInfo outMIME
, replyMIME
;
437 int code
= client
->GetDocument(url
, outMIME
, replyMIME
, FALSE
);
438 if ((code
!= 200) || (replyMIME(PHTTP::TransferEncodingTag
) *= PHTTP::ChunkedTag
))
441 outgoingChannel
.SetReadChannel(client
, TRUE
);
445 PFactory
<PVXMLPlayable
>::Worker
<PVXMLPlayableURL
> vxmlPlayableURLFactory("URL");
447 ///////////////////////////////////////////////////////////////
449 BOOL
PVXMLRecordableFilename::Open(const PString
& _arg
)
452 consecutiveSilence
= 0;
456 void PVXMLRecordableFilename::Record(PVXMLChannel
& outgoingChannel
)
458 PChannel
* chan
= NULL
;
460 // check the file extension and open a .wav or a raw (.sw or .g723) file
461 if ((fn
.Right(4)).ToLower() == ".wav")
462 chan
= outgoingChannel
.CreateWAVFile(fn
, TRUE
);
464 PFile
* fileChan
= new PFile(fn
);
465 if (fileChan
->Open(PFile::WriteOnly
))
473 PTRACE(3, "PVXML\tCannot open file \"" << fn
<< "\"");
475 PTRACE(3, "PVXML\tRecording to file \"" << fn
<< "\"");
476 outgoingChannel
.SetWriteChannel(chan
, TRUE
);
479 recordStart
= PTime();
480 silenceStart
= PTime();
481 consecutiveSilence
= 0;
484 BOOL
PVXMLRecordableFilename::OnFrame(BOOL isSilence
)
487 silenceStart
= PTime();
488 consecutiveSilence
= 0;
490 consecutiveSilence
++;
491 if ( ((consecutiveSilence
% 20) == 0) &&
493 ((finalSilence
> 0) && ((PTime() - silenceStart
).GetMilliSeconds() >= finalSilence
)) ||
494 ((maxDuration
> 0) && ((PTime() - recordStart
).GetMilliSeconds() >= maxDuration
))
503 ///////////////////////////////////////////////////////////////
505 PVXMLCache::PVXMLCache(const PDirectory
& _directory
)
506 : directory(_directory
)
508 if (!directory
.Exists())
512 static PString
MD5AsHex(const PString
& str
)
514 PMessageDigest::Result digest
;
515 PMessageDigest5::Encode(str
, digest
);
518 const BYTE
* data
= digest
.GetPointer();
519 for (PINDEX i
= 0; i
< digest
.GetSize(); ++i
)
520 hexStr
.sprintf("%02x", (unsigned)data
[i
]);
525 PFilePath
PVXMLCache::CreateFilename(const PString
& prefix
, const PString
& key
, const PString
& fileType
)
527 PString md5
= MD5AsHex(key
);
528 PString md5_2
= MD5AsHex(key
);
530 return directory
+ ((prefix
+ "_") + md5
+ fileType
);
533 BOOL
PVXMLCache::Get(const PString
& prefix
,
535 const PString
& fileType
,
536 PString
& contentType
,
539 PWaitAndSignal
m(*this);
541 dataFn
= CreateFilename(prefix
, key
, "." + fileType
);
542 PFilePath typeFn
= CreateFilename(prefix
, key
, "_type.txt");
543 if (!PFile::Exists(dataFn
) || !PFile::Exists(typeFn
)) {
544 PTRACE(4, "Key \"" << key
<< "\" not found in cache");
548 PTextFile
typeFile(typeFn
, PFile::ReadOnly
);
549 if (!typeFile
.IsOpen()) {
550 PTRACE(4, "Cannot find type for cached key " << key
<< " in cache");
551 PFile::Remove(dataFn
);
555 typeFile
.ReadLine(contentType
);
557 if (contentType
.IsEmpty())
558 contentType
= GetContentType(dataFn
);
563 void PVXMLCache::Put(const PString
& prefix
,
565 const PString
& fileType
,
566 const PString
& contentType
,
567 const PFilePath
& fn
,
570 PWaitAndSignal
m(*this);
572 // create the filename for the cache files
573 dataFn
= CreateFilename(prefix
, key
, "." + fileType
);
574 PFilePath typeFn
= CreateFilename(prefix
, key
, "_type.txt");
576 // write the content type file
577 PTextFile
typeFile(typeFn
, PFile::WriteOnly
);
578 if (contentType
.IsEmpty())
579 typeFile
.WriteLine(GetContentType(fn
));
581 typeFile
.WriteLine(contentType
);
583 // rename the file to the correct name
584 PFile::Rename(fn
, dataFn
.GetFileName(), TRUE
);
587 PVXMLCache
& PVXMLCache::GetResourceCache()
589 static PVXMLCache
cache(PDirectory() + "cache");
594 PFilePath
PVXMLCache::GetRandomFilename(const PString
& prefix
, const PString
& fileType
)
598 // create a random temporary filename
601 fn
= directory
+ psprintf("%s_%i.%s", (const char *)prefix
, r
.Generate() % 1000000, (const char *)fileType
);
602 if (!PFile::Exists(fn
))
609 //////////////////////////////////////////////////////////
611 PVXMLSession::PVXMLSession(PTextToSpeech
* _tts
, BOOL autoDelete
)
615 finishWhenEmpty
= TRUE
;
618 SetTextToSpeech(_tts
, autoDelete
);
623 void PVXMLSession::Initialise()
628 activeGrammar
= NULL
;
635 PVXMLSession::~PVXMLSession()
639 if ((textToSpeech
!= NULL
) && autoDeleteTextToSpeech
)
643 PTextToSpeech
* PVXMLSession::SetTextToSpeech(PTextToSpeech
* _tts
, BOOL autoDelete
)
645 PWaitAndSignal
m(sessionMutex
);
647 if (autoDeleteTextToSpeech
&& (textToSpeech
!= NULL
))
650 autoDeleteTextToSpeech
= autoDelete
;
655 PTextToSpeech
* PVXMLSession::SetTextToSpeech(const PString
& ttsName
)
657 PWaitAndSignal
m(sessionMutex
);
659 if (autoDeleteTextToSpeech
&& (textToSpeech
!= NULL
))
662 autoDeleteTextToSpeech
= TRUE
;
663 textToSpeech
= PFactory
<PTextToSpeech
>::CreateInstance(ttsName
);
667 BOOL
PVXMLSession::Load(const PString
& source
)
669 // Lets try and guess what was passed, if file exists then is file
670 PFilePath file
= source
;
671 if (PFile::Exists(file
))
672 return LoadFile(file
);
674 // see if looks like URL
675 PINDEX pos
= source
.Find(':');
676 if (pos
!= P_MAX_INDEX
) {
677 PString scheme
= source
.Left(pos
);
678 if ((scheme
*= "http") || (scheme
*= "https") || (scheme
*= "file"))
679 return LoadURL(source
);
682 // See if is actual VXML
683 if (PCaselessString(source
).Find("<vxml") != P_MAX_INDEX
)
684 return LoadVXML(source
);
690 BOOL
PVXMLSession::LoadFile(const PFilePath
& filename
)
692 // create a file URL from the filename
693 return LoadURL(filename
);
697 BOOL
PVXMLSession::LoadURL(const PURL
& url
)
699 // retreive the document (may be a HTTP get)
702 if (!RetreiveResource(url
, contentType
, fn
, FALSE
)) {
703 PTRACE(1, "PVXML\tCannot load document " << url
);
707 PTextFile
file(fn
, PFile::ReadOnly
);
708 if (!file
.IsOpen()) {
709 PTRACE(1, "PVXML\tCannot read data from " << fn
);
713 off_t len
= file
.GetLength();
715 file
.Read(text
.GetPointer(len
+1), len
);
716 len
= file
.GetLastReadCount();
718 text
[(PINDEX
)len
] = '\0';
720 if (!LoadVXML(text
)) {
721 PTRACE(1, "PVXML\tCannot load VXML in " << url
);
729 BOOL
PVXMLSession::LoadVXML(const PString
& xmlText
)
731 PWaitAndSignal
m(sessionMutex
);
733 allowFinish
= loaded
= FALSE
;
734 rootURL
= PString::Empty();
738 if (!xmlFile
.Load(xmlText
)) {
739 PTRACE(1, "PVXML\tCannot parse root document: " << GetXMLError());
743 PXMLElement
* root
= xmlFile
.GetRootElement();
747 // reset interpeter state
750 // find the first form
751 if ((currentForm
= FindForm(PString::Empty())) == NULL
)
754 // start processing with this <form> element
755 currentNode
= currentForm
;
761 PURL
PVXMLSession::NormaliseResourceName(const PString
& src
)
763 // if resource name has a scheme, then use as is
764 PINDEX pos
= src
.Find(':');
765 if ((pos
!= P_MAX_INDEX
) && (pos
< 5))
768 if (rootURL
.IsEmpty())
769 return "file:" + src
;
771 // else use scheme and path from root document
773 PStringArray path
= url
.GetPath();
775 if (path
.GetSize() > 0) {
778 for (i
= 1; i
< path
.GetSize()-1; i
++)
779 pathStr
+= "/" + path
[i
];
780 pathStr
+= "/" + src
;
781 url
.SetPathStr(pathStr
);
788 BOOL
PVXMLSession::RetreiveResource(const PURL
& url
,
789 PString
& contentType
,
795 // files on the local file system get loaded locally
796 if (url
.GetScheme() *= "file") {
797 dataFn
= url
.AsFilePath();
798 if (contentType
.IsEmpty())
799 contentType
= GetContentType(dataFn
);
803 // do a HTTP get when appropriate
804 else if ((url
.GetScheme() *= "http") || (url
.GetScheme() *= "https")) {
807 PString fileType
= url
.AsFilePath().GetType();
809 BOOL inCache
= FALSE
;
811 inCache
= PVXMLCache::GetResourceCache().Get("url", url
.AsString(), fileType
, contentType
, dataFn
);
815 // get a random filename
816 fn
= PVXMLCache::GetResourceCache().GetRandomFilename("url", fileType
);
818 // get the resource header information
820 PMIMEInfo outMIME
, replyMIME
;
821 if (!client
.GetDocument(url
, outMIME
, replyMIME
)) {
822 PTRACE(2, "PVXML\tCannot load resource " << url
);
828 // Get the body of the response in a PBYTEArray (might be binary data)
829 PBYTEArray incomingData
;
830 client
.ReadContentBody(replyMIME
, incomingData
);
831 contentType
= replyMIME(PHTTPClient::ContentTypeTag
);
833 // write the data in the file
834 PFile
cacheFile(fn
, PFile::WriteOnly
);
835 cacheFile
.Write(incomingData
.GetPointer(), incomingData
.GetSize() );
837 // if we have a cache and we are using it, then save the data
839 PVXMLCache::GetResourceCache().Put("url", url
.AsString(), fileType
, contentType
, fn
, dataFn
);
847 // files on the local file system get loaded locally
848 else if (url
.GetScheme() *= "file") {
849 dataFn
= url
.AsFilePath();
853 // unknown schemes give an error
861 PXMLElement
* PVXMLSession::FindForm(const PString
& id
)
863 // NOTE: should have some flag to know if it is loaded
864 PXMLElement
* root
= xmlFile
.GetRootElement();
868 // Only handle search of top level nodes for <form> element
870 for (i
= 0; i
< root
->GetSize(); i
++) {
871 PXMLObject
* xmlObject
= root
->GetElement(i
);
872 if (xmlObject
->IsElement()) {
873 PXMLElement
* xmlElement
= (PXMLElement
*)xmlObject
;
875 (xmlElement
->GetName() *= "form") &&
876 (id
.IsEmpty() || (xmlElement
->GetAttribute("id") *= id
))
885 BOOL
PVXMLSession::Open(BOOL isPCM
)
888 return Open(VXML_PCM16
);
890 return Open(VXML_G7231
);
893 BOOL
PVXMLSession::Open(const PString
& _mediaFormat
)
895 mediaFormat
= _mediaFormat
;
897 PVXMLChannel
* chan
= PFactory
<PVXMLChannel
>::CreateInstance(mediaFormat
);
899 PTRACE(1, "VXML\tCannot create VXML channel with format " << mediaFormat
);
905 // set the underlying channel
906 if (!PIndirectChannel::Open(chan
, chan
))
909 // start the VXML session in another thread
911 PWaitAndSignal
m(sessionMutex
);
912 if (!chan
->Open(this))
920 BOOL
PVXMLSession::Execute()
922 PWaitAndSignal
m(sessionMutex
);
924 // cannot open if no data is loaded
925 if (loaded
&& vxmlThread
== NULL
) {
926 threadRunning
= TRUE
;
927 vxmlThread
= PThread::Create(PCREATE_NOTIFIER(VXMLExecute
), 0, PThread::NoAutoDeleteThread
);
934 BOOL
PVXMLSession::Close()
937 PWaitAndSignal
m(sessionMutex
);
938 if (vxmlThread
!= NULL
) {
940 // Stop condition for thread
941 threadRunning
= FALSE
;
943 waitForEvent
.Signal();
945 // Signal all syncpoints that could be waiting for things
946 transferSync
.Signal();
948 vxmlChannel
->Close();
950 vxmlThread
->WaitForTermination();
958 return PIndirectChannel::Close();
962 void PVXMLSession::VXMLExecute(PThread
&, INT
)
964 while (!forceEnd
&& threadRunning
) {
966 // process current node in the VXML script
969 // wait for something to happen
970 if (currentNode
== NULL
|| IsPlaying())
974 // Make sure the script has been run to the end so
975 // submit actions etc. can be performed
976 // record and audio and other user interaction commands should be skipped
978 PTRACE(1, "Fast forwarding through script because of forceEnd" );
979 while (currentNode
!= NULL
)
985 //PWaitAndSignal m(sessionMutex);
986 if (vxmlChannel
!= NULL
)
987 vxmlChannel
->Close();
992 void PVXMLSession::ProcessUserInput()
996 PWaitAndSignal
m(userInputMutex
);
997 if (userInputQueue
.size() == 0)
999 ch
= userInputQueue
.front();
1000 userInputQueue
.pop();
1003 PTRACE(3, "VXML\tHandling user input " << ch
);
1013 if (activeGrammar
!= NULL
)
1014 activeGrammar
->OnUserInput(ch
);
1018 void PVXMLSession::ExecuteDialog()
1020 // check for user input
1023 // process any active grammars
1026 // process current node in the VXML script
1029 // Wait for the buffer to complete before continuing to the next node
1030 if (currentNode
== NULL
) {
1035 // if the current node has children, then process the first child
1036 else if (currentNode
->IsElement() && (((PXMLElement
*)currentNode
)->GetElement(0) != NULL
))
1037 currentNode
= ((PXMLElement
*)currentNode
)->GetElement(0);
1039 // else process the next sibling
1041 // Keep moving up the parents until we find a next sibling
1042 while ((currentNode
!= NULL
) && currentNode
->GetNextObject() == NULL
) {
1043 currentNode
= currentNode
->GetParent();
1044 // if we are on the backwards traversal through a <field> then wait
1045 // for a grammar recognition and throw events if necessary
1046 if (currentNode
!= NULL
&& (currentNode
->IsElement() == TRUE
) && (((PXMLElement
*)currentNode
)->GetName() *= "field")) {
1048 PlaySilence(timeout
);
1052 if (currentNode
!= NULL
)
1053 currentNode
= currentNode
->GetNextObject();
1056 // Determine if we should quit
1057 if ((currentNode
== NULL
) && (activeGrammar
== NULL
) && !IsPlaying() && !IsRecording() && allowFinish
&& finishWhenEmpty
) {
1058 threadRunning
= FALSE
;
1059 waitForEvent
.Signal();
1064 void PVXMLSession::ProcessGrammar()
1066 if (activeGrammar
== NULL
)
1069 BOOL
processGrammar(FALSE
);
1071 // Stop if we've matched a grammar or have a failed recognition
1072 if (activeGrammar
->GetState() == PVXMLGrammar::FILLED
|| activeGrammar
->GetState() == PVXMLGrammar::NOMATCH
)
1073 processGrammar
= TRUE
;
1075 // Stop the grammar if we've timed out
1076 else if (listening
&& !IsPlaying()) {
1077 activeGrammar
->Stop();
1078 processGrammar
= TRUE
;
1081 // Let the loop run again if we're still waiting to time out and haven't resolved the grammar one way or the other
1082 if (!processGrammar
&& listening
)
1087 PVXMLGrammar::GrammarState state
= activeGrammar
->GetState();
1088 grammarResult
= activeGrammar
->GetValue();
1092 // Stop any playback
1093 if (vxmlChannel
!= NULL
) {
1094 vxmlChannel
->FlushQueue();
1095 vxmlChannel
->EndRecording();
1098 // Check we're not in a menu
1099 if (eventName
.IsEmpty()) {
1101 // Figure out what happened
1104 case PVXMLGrammar::FILLED
:
1105 eventName
= "filled";
1107 case PVXMLGrammar::NOINPUT
:
1108 eventName
= "noinput";
1110 case PVXMLGrammar::NOMATCH
:
1111 eventName
= "nomatch";
1114 ; //ERROR - unexpected grammar state
1117 // Find the handler and move there
1118 PXMLElement
* handler
= FindHandler(eventName
);
1119 if (handler
!= NULL
)
1120 currentNode
= handler
;
1126 void PVXMLSession::ProcessNode()
1128 if (currentNode
== NULL
)
1131 if (!currentNode
->IsElement()) {
1139 PXMLElement
* element
= (PXMLElement
*)currentNode
;
1140 PCaselessString nodeType
= element
->GetName();
1141 PTRACE(3, "PVXML\t**** Processing VoiceXML element: <" << nodeType
<< "> ***");
1143 if (nodeType
*= "audio") {
1148 else if (nodeType
*= "block") {
1149 // check 'cond' attribute to see if this element's children are to be skipped
1150 // go on and process the children
1153 else if (nodeType
*= "break")
1156 else if (nodeType
*= "disconnect")
1159 else if (nodeType
*= "field") {
1160 currentField
= (PXMLElement
*)currentNode
;
1161 timeout
= DEFAULT_TIMEOUT
;
1162 TraverseGrammar(); // this will set activeGrammar
1165 else if (nodeType
*= "form") {
1166 // this is now the current element - go on
1167 currentForm
= element
;
1168 currentField
= NULL
; // no active field in a new form
1171 else if (nodeType
*= "goto")
1174 else if (nodeType
*= "grammar")
1175 TraverseGrammar(); // this will set activeGrammar
1177 else if (nodeType
*= "record") {
1182 else if (nodeType
*= "prompt") {
1185 // check 'cond' attribute to see if the children of this node should be processed
1186 // check 'count' attribute to see if this node should be processed
1187 // flush all prompts if 'bargein' attribute is set to false
1189 // Update timeout of current recognition (if 'timeout' attribute is set)
1190 if (element
->HasAttribute("timeout")) {
1191 PTimeInterval timeout
= StringToTime(element
->GetAttribute("timeout"));
1196 else if (nodeType
*= "say-as") {
1201 else if (nodeType
*= "value") {
1206 else if (nodeType
*= "var")
1209 else if (nodeType
*= "if")
1212 else if (nodeType
*= "exit")
1215 else if (nodeType
*= "menu") {
1222 else if (nodeType
*= "choice") {
1223 if (!TraverseChoice(grammarResult
))
1226 // If the correct choice has been found,
1227 /// make sure everything is reset correctly
1228 eventName
.MakeEmpty();
1229 grammarResult
.MakeEmpty();
1234 else if (nodeType
*= "transfer") {
1239 else if (nodeType
*= "submit")
1242 else if (nodeType
*= "property")
1247 BOOL
PVXMLSession::OnUserInput(const PString
& str
)
1250 PWaitAndSignal
m(userInputMutex
);
1251 for (PINDEX i
= 0; i
< str
.GetLength(); i
++)
1252 userInputQueue
.push(str
[i
]);
1254 waitForEvent
.Signal();
1258 BOOL
PVXMLSession::TraverseRecord()
1260 if (currentNode
->IsElement()) {
1263 PXMLElement
* element
= (PXMLElement
*)currentNode
;
1265 // Get the name (name)
1266 if (element
->HasAttribute("name"))
1267 strName
= element
->GetAttribute("name");
1268 else if (element
->HasAttribute("id"))
1269 strName
= element
->GetAttribute("id");
1271 // Get the destination filename (dest)
1273 if (element
->HasAttribute("dest"))
1274 strDest
= element
->GetAttribute("dest");
1276 // see if we need a beep
1277 if (element
->GetAttribute("beep").ToLower() *= "true") {
1278 PBYTEArray beepData
;
1279 GetBeepData(beepData
, 1000);
1280 if (beepData
.GetSize() != 0)
1284 if (strDest
.IsEmpty()) {
1286 strDest
= GetVar("session.telephone.dnis" ) + "_" + GetVar( "session.telephone.ani" ) + "_" + now
.AsString( "yyyyMMdd_hhmmss") + ".wav";
1289 // For some reason, if the file is there the create
1291 PFile::Remove(strDest
);
1292 PFilePath
file(strDest
);
1294 // Get max record time (maxtime)
1295 PTimeInterval maxTime
= PMaxTimeInterval
;
1296 if (element
->HasAttribute("maxtime"))
1297 maxTime
= StringToTime(element
->GetAttribute("maxtime"));
1299 // Get terminating silence duration (finalsilence)
1300 PTimeInterval
termTime(3000);
1301 if (element
->HasAttribute("finalsilence"))
1302 termTime
= StringToTime(element
->GetAttribute("finalsilence"));
1304 // Get dtmf term (dtmfterm)
1305 BOOL dtmfTerm
= TRUE
;
1306 if (element
->HasAttribute("dtmfterm"))
1307 dtmfTerm
= !(element
->GetAttribute("dtmfterm").ToLower() *= "false");
1309 // create a semaphore, and then wait for the recording to terminate
1310 StartRecording(file
, dtmfTerm
, maxTime
, termTime
);
1311 recordSync
.Wait(maxTime
);
1313 if (!recordSync
.Wait(maxTime
)) {
1314 // The Wait() has timed out, to signal that the record timed out.
1315 // This is VXML version 2 property, but nice.
1316 // So it's possible to detect if the record timed out from within the
1318 SetVar(strName
+ "$.maxtime", "true");
1321 // Normal hangup before timeout
1322 SetVar( strName
+ "$.maxtime", "false");
1325 // when this returns, we are done
1332 PString
PVXMLSession::GetXMLError() const
1334 return psprintf("(%i:%i) ", xmlFile
.GetErrorLine(), xmlFile
.GetErrorColumn()) + xmlFile
.GetErrorString();
1337 PString
PVXMLSession::EvaluateExpr(const PString
& oexpr
)
1339 PString expr
= oexpr
.Trim();
1341 // see if all digits
1343 BOOL allDigits
= TRUE
;
1344 for (i
= 0; i
< expr
.GetLength(); i
++) {
1345 allDigits
= allDigits
&& isdigit(expr
[i
]);
1351 return GetVar(expr
);
1354 PString
PVXMLSession::GetVar(const PString
& ostr
) const
1360 PINDEX pos
= str
.Find('.');
1361 if (pos
!= P_MAX_INDEX
) {
1362 scope
= str
.Left(pos
);
1363 str
= str
.Mid(pos
+1);
1366 // process session scope
1367 if (scope
.IsEmpty() || (scope
*= "session")) {
1368 if (sessionVars
.Contains(str
))
1369 return sessionVars(str
);
1372 // assume any other scope is actually document or application
1373 return documentVars(str
);
1376 void PVXMLSession::SetVar(const PString
& ostr
, const PString
& val
)
1382 PINDEX pos
= str
.Find('.');
1383 if (pos
!= P_MAX_INDEX
) {
1384 scope
= str
.Left(pos
);
1385 str
= str
.Mid(pos
+1);
1389 if (scope
.IsEmpty() || (scope
*= "session")) {
1390 sessionVars
.SetAt(str
, val
);
1394 PTRACE(3, "PVXML\tDocument: " << str
<< " = \"" << val
<< "\"");
1396 // assume any other scope is actually document or application
1397 documentVars
.SetAt(str
, val
);
1400 BOOL
PVXMLSession::PlayFile(const PString
& fn
, PINDEX repeat
, PINDEX delay
, BOOL autoDelete
)
1402 if (vxmlChannel
== NULL
|| !vxmlChannel
->QueueFile(fn
, repeat
, delay
, autoDelete
))
1410 BOOL
PVXMLSession::PlayCommand(const PString
& cmd
, PINDEX repeat
, PINDEX delay
)
1412 if (vxmlChannel
== NULL
|| !vxmlChannel
->QueueCommand(cmd
, repeat
, delay
))
1420 BOOL
PVXMLSession::PlayData(const PBYTEArray
& data
, PINDEX repeat
, PINDEX delay
)
1422 if (vxmlChannel
== NULL
|| !vxmlChannel
->QueueData(data
, repeat
, delay
))
1431 void PVXMLSession::GetBeepData(PBYTEArray
& data
, unsigned ms
)
1433 if (vxmlChannel
!= NULL
)
1434 vxmlChannel
->GetBeepData(data
, ms
);
1437 BOOL
PVXMLSession::PlaySilence(const PTimeInterval
& timeout
)
1439 return PlaySilence((PINDEX
)timeout
.GetMilliSeconds());
1442 BOOL
PVXMLSession::PlaySilence(PINDEX msecs
)
1445 if (vxmlChannel
== NULL
|| !vxmlChannel
->QueueData(nothing
, 1, msecs
))
1453 BOOL
PVXMLSession::PlayResource(const PURL
& url
, PINDEX repeat
, PINDEX delay
)
1455 if (vxmlChannel
== NULL
|| !vxmlChannel
->QueueResource(url
, repeat
, delay
))
1463 BOOL
PVXMLSession::LoadGrammar(PVXMLGrammar
* grammar
)
1465 if (activeGrammar
!= NULL
) {
1466 delete activeGrammar
;
1467 activeGrammar
= FALSE
;
1470 activeGrammar
= grammar
;
1475 BOOL
PVXMLSession::PlayText(const PString
& _text
,
1476 PTextToSpeech::TextType type
,
1481 BOOL useCache
= !(GetVar("caching") *= "safe");
1482 if (!ConvertTextToFilenameList(_text
, type
, list
, useCache
) || (list
.GetSize() == 0)) {
1483 PTRACE(1, "PVXML\tCannot convert text to speech");
1487 PVXMLPlayableFilenameList
* playable
= new PVXMLPlayableFilenameList
;
1488 if (!playable
->Open(*vxmlChannel
, list
, delay
, repeat
, !useCache
)) {
1490 PTRACE(1, "PVXML\tCannot create playable for filename list");
1494 return vxmlChannel
->QueuePlayable(playable
);
1497 BOOL
PVXMLSession::ConvertTextToFilenameList(const PString
& _text
, PTextToSpeech::TextType type
, PStringArray
& filenameList
, BOOL useCache
)
1499 PString prefix
= psprintf("tts%i", type
);
1501 PStringArray lines
= _text
.Lines();
1502 for (PINDEX i
= 0; i
< lines
.GetSize(); i
++) {
1504 PString text
= lines
[i
].Trim();
1508 BOOL spoken
= FALSE
;
1511 // see if we have converted this text before
1512 PString contentType
;
1514 spoken
= PVXMLCache::GetResourceCache().Get(prefix
, text
, "wav", contentType
, dataFn
);
1516 // if not cached, then use the text to speech converter
1519 if (textToSpeech
!= NULL
) {
1520 tmpfname
= PVXMLCache::GetResourceCache().GetRandomFilename("tts", "wav");
1521 if (!textToSpeech
->OpenFile(tmpfname
)) {
1522 PTRACE(2, "PVXML\tcannot open file " << tmpfname
);
1524 spoken
= textToSpeech
->Speak(text
, type
);
1525 if (!textToSpeech
->Close()) {
1526 PTRACE(2, "PVXML\tcannot close TTS engine");
1529 textToSpeech
->Close();
1531 PVXMLCache::GetResourceCache().Put(prefix
, text
, "wav", contentType
, tmpfname
, dataFn
);
1538 PTRACE(2, "PVXML\tcannot speak text using TTS engine");
1540 filenameList
.AppendString(dataFn
);
1543 return filenameList
.GetSize() > 0;
1546 void PVXMLSession::SetPause(BOOL _pause
)
1548 if (vxmlChannel
!= NULL
)
1549 vxmlChannel
->SetPause(_pause
);
1553 BOOL
PVXMLSession::IsPlaying() const
1555 return (vxmlChannel
!= NULL
) && vxmlChannel
->IsPlaying();
1558 BOOL
PVXMLSession::StartRecording(const PFilePath
& /*_recordFn*/,
1559 BOOL
/*_recordDTMFTerm*/,
1560 const PTimeInterval
& /*_recordMaxTime*/,
1561 const PTimeInterval
& /*_recordFinalSilence*/)
1565 recordFn = _recordFn;
1566 recordDTMFTerm = _recordDTMFTerm;
1567 recordMaxTime = _recordMaxTime;
1568 recordFinalSilence = _recordFinalSilence;
1570 if (incomingChannel != NULL) {
1571 PXMLElement* element = (PXMLElement*) currentNode;
1572 if ( element->HasAttribute("name")) {
1573 PString chanName = element->GetAttribute("name");
1574 incomingChannel->SetName(chanName);
1576 return incomingChannel->StartRecording(recordFn, (unsigned )recordFinalSilence.GetMilliSeconds());
1584 void PVXMLSession::RecordEnd()
1587 recordSync
.Signal();
1590 BOOL
PVXMLSession::EndRecording()
1594 if (vxmlChannel
!= NULL
)
1595 return vxmlChannel
->EndRecording();
1602 BOOL
PVXMLSession::IsRecording() const
1604 return (vxmlChannel
!= NULL
) && vxmlChannel
->IsRecording();
1607 PWAVFile
* PVXMLSession::CreateWAVFile(const PFilePath
& fn
, PFile::OpenMode mode
, int opts
, unsigned fmt
)
1610 return new PWAVFile(fn
, mode
, opts
, fmt
);
1612 return new PWAVFile(mode
, opts
, fmt
);
1615 void PVXMLSession::AllowClearCall()
1620 BOOL
PVXMLSession::TraverseAudio()
1622 if (!currentNode
->IsElement()) {
1623 PlayText(((PXMLData
*)currentNode
)->GetString());
1627 PXMLElement
* element
= (PXMLElement
*)currentNode
;
1629 if (element
->GetName() *= "value") {
1630 PString className
= element
->GetAttribute("class");
1631 PString value
= EvaluateExpr(element
->GetAttribute("expr"));
1632 SayAs(className
, value
);
1635 else if (element
->GetName() *= "sayas") {
1636 PString className
= element
->GetAttribute("class");
1637 PXMLObject
* object
= element
->GetElement();
1638 if (!object
->IsElement()) {
1639 PString text
= ((PXMLData
*)object
)->GetString();
1640 SayAs(className
, text
);
1644 else if (element
->GetName() *= "break") {
1646 // msecs is VXML 1.0
1647 if (element
->HasAttribute("msecs"))
1648 PlaySilence(element
->GetAttribute("msecs").AsInteger());
1651 else if (element
->HasAttribute("time")) {
1652 PTimeInterval time
= StringToTime(element
->GetAttribute("time"));
1656 else if (element
->HasAttribute("size")) {
1657 PString size
= element
->GetAttribute("size");
1660 else if (size
*= "small")
1661 PlaySilence(SMALL_BREAK_MSECS
);
1662 else if (size
*= "large")
1663 PlaySilence(LARGE_BREAK_MSECS
);
1665 PlaySilence(MEDIUM_BREAK_MSECS
);
1668 // default to medium pause
1670 PlaySilence(MEDIUM_BREAK_MSECS
);
1674 else if (element
->GetName() *= "audio") {
1675 BOOL loaded
= FALSE
;
1677 if (element
->HasAttribute("src")) {
1679 PString str
= element
->GetAttribute("src").Trim();
1680 if (!str
.IsEmpty() && (str
[0] == '|')) {
1682 PlayCommand(str
.Mid(1));
1686 // get a normalised name for the resource
1688 PURL url
= NormaliseResourceName(str
);
1690 // load the resource from the cache
1691 PString contentType
;
1692 BOOL useCache
= !(GetVar("caching") *= "safe") && !(element
->GetAttribute("caching") *= "safe");
1693 if (RetreiveResource(url
, contentType
, fn
, useCache
)) {
1694 PWAVFile
* wavFile
= vxmlChannel
->CreateWAVFile(fn
);
1695 if (wavFile
== NULL
)
1696 PTRACE(3, "PVXML\tCannot create audio file " + fn
);
1697 else if (!wavFile
->IsOpen())
1701 PlayFile(fn
, 0, 0, !useCache
); // make sure we delete the file if not cacheing
1707 // skip to the next node
1708 if (element
->HasSubObjects())
1709 currentNode
= element
->GetElement(element
->GetSize() - 1);
1715 PTRACE(3, "PVXML\tUnknown audio tag " << element
->GetName() << " encountered");
1722 BOOL
PVXMLSession::TraverseGoto() // <goto>
1724 PAssert(currentNode
!= NULL
, "ProcessGotoElement(): Expected valid node");
1725 if (currentNode
== NULL
)
1728 // LATER: handle expr, expritem, fetchaudio, fetchhint, fetchtimeout, maxage, maxstale
1730 PAssert(currentNode
->IsElement(), "ProcessGotoElement(): Expected element");
1733 PString nextitem
= ((PXMLElement
*)currentNode
)->GetAttribute("nextitem");
1734 if (!nextitem
.IsEmpty()) {
1735 // LATER: Take out the optional #
1736 currentForm
= FindForm(nextitem
);
1737 currentNode
= currentForm
;
1738 if (currentForm
== NULL
) {
1739 // LATER: throw "error.semantic" or "error.badfetch" -- lookup which
1746 PString next
= ((PXMLElement
*)currentNode
)->GetAttribute("next");
1747 // LATER: fixup filename to prepend path
1748 if (!next
.IsEmpty()) {
1749 if (next
[0] == '#') {
1750 next
= next
.Right( next
.GetLength() -1 );
1751 currentForm
= FindForm(next
);
1752 currentNode
= currentForm
;
1753 // LATER: throw "error.semantic" or "error.badfetch" -- lookup which
1754 return currentForm
!= NULL
;
1757 PURL url
= NormaliseResourceName(next
);
1758 return LoadURL(url
) && (currentForm
!= NULL
);
1764 BOOL
PVXMLSession::TraverseGrammar() // <grammar>
1766 // LATER: A bunch of work to do here!
1768 // For now we only support the builtin digits type and do not parse any grammars.
1770 // NOTE: For now we will process both <grammar> and <field> here.
1771 // NOTE: Later there needs to be a check for <grammar> which will pull
1772 // out the text and process a grammar like '1 | 2'
1774 // Right now we only support one active grammar.
1775 if (activeGrammar
!= NULL
) {
1776 PTRACE(2, "PVXML\tWarning: can only process one grammar at a time, ignoring previous grammar");
1777 delete activeGrammar
;
1778 activeGrammar
= NULL
;
1781 PVXMLGrammar
* newGrammar
= NULL
;
1783 // Is this a built-in type?
1784 PString type
= ((PXMLElement
*)currentNode
)->GetAttribute("type");
1785 if (!type
.IsEmpty()) {
1786 PStringArray tokens
= type
.Tokenise("?;", TRUE
);
1787 PString builtintype
;
1788 if (tokens
.GetSize() > 0)
1789 builtintype
= tokens
[0];
1791 if (builtintype
*= "digits") {
1792 PINDEX
minDigits(1);
1793 PINDEX
maxDigits(100);
1795 // look at each parameter
1796 for (PINDEX
i(1); i
< tokens
.GetSize(); i
++) {
1797 PStringArray params
= tokens
[i
].Tokenise("=", TRUE
);
1798 if (params
.GetSize() == 2) {
1799 if (params
[0] *= "minlength") {
1800 minDigits
= params
[1].AsInteger();
1802 else if (params
[0] *= "maxlength") {
1803 maxDigits
= params
[1].AsInteger();
1805 else if (params
[0] *= "length") {
1806 minDigits
= maxDigits
= params
[1].AsInteger();
1810 // Invalid parameter skipped
1811 // LATER: throw 'error.semantic'
1814 newGrammar
= new PVXMLDigitsGrammar((PXMLElement
*)currentNode
, minDigits
, maxDigits
, "");
1817 // LATER: throw 'error.unsupported'
1822 if (newGrammar
!= NULL
)
1823 return LoadGrammar(newGrammar
);
1828 // Finds the proper event hander for 'noinput', 'filled', 'nomatch' and 'error'
1829 // by searching the scope hiearchy from the current from
1830 PXMLElement
* PVXMLSession::FindHandler(const PString
& event
)
1832 PAssert(currentNode
->IsElement(), "Expected 'PXMLElement' in PVXMLSession::FindHandler");
1833 PXMLElement
* tmp
= (PXMLElement
*)currentNode
;
1834 PXMLElement
* handler
= NULL
;
1836 // Look in all the way up the tree for a handler either explicitly or in a catch
1837 while (tmp
!= NULL
) {
1838 // Check for an explicit hander - i.e. <error>, <filled>, <noinput>, <nomatch>, <help>
1839 if ((handler
= tmp
->GetElement(event
)) != NULL
)
1842 // Check for a <catch>
1843 if ((handler
= tmp
->GetElement("catch")) != NULL
) {
1844 PString strCond
= handler
->GetAttribute("cond");
1845 if (strCond
.Find(event
))
1849 tmp
= tmp
->GetParent();
1855 void PVXMLSession::SayAs(const PString
& className
, const PString
& _text
)
1857 PString text
= _text
.Trim();
1858 if (!text
.IsEmpty()) {
1859 PTextToSpeech::TextType type
= PTextToSpeech::Literal
;
1861 if (className
*= "digits")
1862 type
= PTextToSpeech::Digits
;
1864 else if (className
*= "literal")
1865 type
= PTextToSpeech::Literal
;
1867 else if (className
*= "number")
1868 type
= PTextToSpeech::Number
;
1870 else if (className
*= "currency")
1871 type
= PTextToSpeech::Currency
;
1873 else if (className
*= "time")
1874 type
= PTextToSpeech::Time
;
1876 else if (className
*= "date")
1877 type
= PTextToSpeech::Date
;
1879 else if (className
*= "phone")
1880 type
= PTextToSpeech::Phone
;
1882 else if (className
*= "ipaddress")
1883 type
= PTextToSpeech::IPAddress
;
1885 else if (className
*= "duration")
1886 type
= PTextToSpeech::Duration
;
1889 PlayText(text
, type
);
1893 PTimeInterval
PVXMLSession::StringToTime(const PString
& str
)
1895 PTimeInterval timeout
;
1897 long msecs
= str
.AsInteger();
1898 if (str
.Find("ms") != P_MAX_INDEX
)
1900 else if (str
.Find("s") != P_MAX_INDEX
)
1901 msecs
= msecs
* 1000;
1903 return PTimeInterval(msecs
);
1906 BOOL
PVXMLSession::TraverseTransfer()
1908 PVXMLTransferOptions opts
;
1910 PAssert(currentNode
!= NULL
, "TraverseTransfer(): Expected valid node");
1911 if (currentNode
== NULL
)
1914 PAssert(currentNode
->IsElement(), "TraverseTransfer(): Expected element");
1916 // Retreive parameters
1917 PString dest
= ((PXMLElement
*)currentNode
)->GetAttribute("dest");
1918 PString source
= ((PXMLElement
*)currentNode
)->GetAttribute("source");
1919 PString connectTimeoutStr
= ((PXMLElement
*)currentNode
)->GetAttribute("connecttimeout");
1920 PString bridgeStr
= ((PXMLElement
*)currentNode
)->GetAttribute("dest");
1922 BOOL bridge
= bridgeStr
*= "true";
1923 PINDEX connectTimeout
= connectTimeoutStr
.AsInteger();
1925 if ((connectTimeout
< 2) && (connectTimeout
> 30))
1926 connectTimeout
= 30;
1928 if (dest
.Find("phone://") == P_MAX_INDEX
)
1932 if (source
.Find("phone://") == P_MAX_INDEX
)
1934 source
.Delete(0, 8);
1936 opts
.SetCallingToken(callingCallToken
);
1937 opts
.SetDestinationDNR(dest
);
1938 opts
.SetSourceDNR(source
);
1939 opts
.SetTimeout(connectTimeout
);
1940 opts
.SetBridge(bridge
);
1944 // Wait for the transfer result signal
1945 transferSync
.Wait();
1950 void PVXMLSession::OnTransfer(const PVXMLTransferResult
& args
)
1952 // transfer has ended, save result
1953 SetVar(args
.GetName(), args
);
1955 // Signal transfer initiator that the transfer has ended and the VXML can
1957 transferSync
.Signal();
1960 BOOL
PVXMLSession::TraverseIf()
1962 // If 'cond' parameter evaluates to true, enter child entities, else
1963 // go to next element.
1965 PString condition
= ((PXMLElement
*)currentNode
)->GetAttribute("cond");
1967 // Find comparison type
1968 PINDEX location
= condition
.Find("==");
1969 BOOL isEqual
= (location
< condition
.GetSize());
1973 PString varname
= condition
.Left(location
);
1975 // Find value, skip '=' signs
1976 PString cond_value
= condition
.Right(condition
.GetSize() - (location
+ 3));
1978 // check if var value equals value from condition and if not skip child elements
1979 PString value
= GetVar(varname
);
1980 if (cond_value
== value
) {
1981 PTRACE( 3, "VXMLSess\t\tCondition matched \"" << condition
<< "\"" );
1983 PTRACE( 3, "VXMLSess\t\tCondition \"" << condition
<< "\"did not match, " << varname
<< " == " << value
);
1984 if (currentNode
->IsElement()) {
1985 PXMLElement
* element
= (PXMLElement
*) currentNode
;
1986 if (element
->HasSubObjects()) {
1987 // Step to last child element (really last element is NULL?)
1988 currentNode
= element
->GetElement(element
->GetSize() - 1);
1995 PTRACE( 1, "\tPVXMLSession, <if> element contains condition with operator other than ==, not implemented" );
2002 BOOL
PVXMLSession::TraverseExit()
2006 waitForEvent
.Signal();
2011 BOOL
PVXMLSession::TraverseSubmit()
2013 BOOL result
= FALSE
;
2015 // Do HTTP client stuff here
2017 // Find out what to submit, for now, only support a WAV file
2018 PXMLElement
* element
= (PXMLElement
*)currentNode
;
2020 if (!element
->HasAttribute("namelist")){
2021 PTRACE(1, "VXMLSess\t<submit> does not contain \"namelist\" parameter");
2025 PString name
= element
->GetAttribute("namelist");
2027 if (name
.Find(" ") < name
.GetSize()) {
2028 PTRACE(1, "VXMLSess\t<submit> does not support more than one value in \"namelist\" parameter");
2032 if (!element
->HasAttribute("next")) {
2033 PTRACE(1, "VXMLSess\t<submit> does not contain \"next\" parameter");
2037 PString url
= element
->GetAttribute("next");
2039 if (url
.Find( "http://" ) > url
.GetSize()) {
2040 PTRACE(1, "VXMLSess\t<submit> needs a full url as the \"next\" parameter");
2044 if (!(GetVar(name
+ ".type") == "audio/x-wav" )) {
2045 PTRACE(1, "VXMLSess\t<submit> does not (yet) support submissions of types other than \"audio/x-wav\"");
2049 PString fileName
= GetVar(name
+ ".filename");
2051 if (!(element
->HasAttribute("method"))) {
2052 PTRACE(1, "VXMLSess\t<submit> does not (yet) support default method type \"get\"");
2056 if ( !PFile::Exists(fileName
)) {
2057 PTRACE(1, "VXMLSess\t<submit> cannot find file " << fileName
);
2061 PString fileNameOnly
;
2062 int pos
= fileName
.FindLast( "/" );
2063 if (pos
< fileName
.GetLength()) {
2064 fileNameOnly
= fileName
.Right( ( fileName
.GetLength() - pos
) - 1 );
2067 pos
= fileName
.FindLast("\\");
2068 if (pos
< fileName
.GetSize()) {
2069 fileNameOnly
= fileName
.Right((fileName
.GetLength() - pos
) - 1);
2072 fileNameOnly
= fileName
;
2077 PMIMEInfo sendMIME
, replyMIME
;
2079 if (element
->GetAttribute("method") *= "post") {
2082 PString boundary
= "--------012345678901234567890123458VXML";
2084 sendMIME
.SetAt( PHTTP::ContentTypeTag
, "multipart/form-data; boundary=" + boundary
);
2085 sendMIME
.SetAt( PHTTP::UserAgentTag
, "PVXML TraverseSubmit" );
2086 sendMIME
.SetAt( "Accept", "text/html" );
2088 // After this all boundaries have a "--" prepended
2089 boundary
= "--" + boundary
;
2091 // Create the mime header
2092 // First set the primary boundary
2093 PString mimeHeader
= boundary
+ "\r\n";
2095 // Add content disposition
2096 mimeHeader
+= "Content-Disposition: form-data; name=\"voicemail\"; filename=\"" + fileNameOnly
+ "\"\r\n";
2099 mimeHeader
+= "Content-Type: audio/wav\r\n\r\n";
2101 // Create the footer and add the closing of the content with a CR/LF
2102 PString mimeFooter
= "\r\n";
2104 // Copy the header, buffer and footer together in one PString
2106 // Load the WAV file into memory
2107 PFile
file( fileName
, PFile::ReadOnly
);
2108 int size
= file
.GetLength();
2112 // Anyway, this shows how to add more variables, for when namelist containes more elements
2113 PString mimeMaxFileSize
= boundary
+ "\r\nContent-Disposition: form-data; name=\"MAX_FILE_SIZE\"\r\n\r\n3000000\r\n";
2115 // Finally close the body with the boundary again, but also add "--"
2116 // to show this is the final boundary
2117 boundary
= boundary
+ "--";
2118 mimeFooter
+= boundary
+ "\r\n";
2119 mimeHeader
= mimeMaxFileSize
+ mimeHeader
;
2120 mimeThing
.SetSize( mimeHeader
.GetSize() + size
+ mimeFooter
.GetSize() );
2122 // Copy the header to the result
2123 memcpy( mimeThing
.GetPointer(), mimeHeader
.GetPointer(), mimeHeader
.GetLength());
2125 // Copy the contents of the file to the mime result
2126 file
.Read( mimeThing
.GetPointer() + mimeHeader
.GetLength(), size
);
2128 // Copy the footer to the result
2129 memcpy( mimeThing
.GetPointer() + mimeHeader
.GetLength() + size
, mimeFooter
.GetPointer(), mimeFooter
.GetLength());
2131 // Send the POST request to the server
2132 result
= client
.PostData( url
, sendMIME
, mimeThing
, replyMIME
);
2136 // Load reply from server as new VXML docuemnt ala <goto>
2140 if (element
->GetAttribute("method") != "get") {
2141 PTRACE(1, "VXMLSess\t<submit> does not (yet) support method type \"" << element
->GetAttribute( "method" ) << "\"");
2145 PString getURL
= url
+ "?" + name
+ "=" + GetVar( name
);
2147 client
.GetDocument( url
, sendMIME
, replyMIME
);
2149 // Load reply from server as new VXML document ala <goto>
2153 PTRACE( 1, "VXMLSess\t<submit> to server failed with "
2154 << client
.GetLastResponseCode() << " "
2155 << client
.GetLastResponseInfo() );
2161 BOOL
PVXMLSession::TraverseProperty()
2163 PXMLElement
* element
= (PXMLElement
*) currentNode
;
2164 if (element
->HasAttribute("name"))
2165 SetVar(element
->GetAttribute("name"), element
->GetAttribute("value"));
2171 BOOL
PVXMLSession::TraverseMenu()
2173 BOOL result
= FALSE
;
2174 PVXMLGrammar
* newGrammar
= new PVXMLDigitsGrammar((PXMLElement
*) currentNode
, 1, 1, "" );
2175 LoadGrammar(newGrammar
);
2180 BOOL
PVXMLSession::TraverseChoice(const PString
& grammarResult
)
2182 // Iterate over all choice elements starting at currentnode
2183 BOOL result
= FALSE
;
2185 PXMLElement
* element
= (PXMLElement
*) currentNode
;
2186 // Current node is a choice element
2188 PString dtmf
= element
->GetAttribute( "dtmf" );
2191 dtmf
= PString(PString::Unsigned
, defaultDTMF
);
2193 // Check if DTMF value for grammarResult matches the DTMF value for the choice
2194 if (dtmf
== grammarResult
) {
2196 // Find the form at next parameter
2197 PString formID
= element
->GetAttribute( "next" );
2199 PTRACE(2, "VXMLsess\tFound form id " << formID
);
2201 if (!formID
.IsEmpty()) {
2202 formID
= formID
.Right( formID
.GetLength() - 1 );
2203 currentNode
= FindForm( formID
);
2204 if (currentNode
!= NULL
)
2211 BOOL
PVXMLSession::TraverseVar()
2213 BOOL result
= FALSE
;
2215 PXMLElement
* element
= (PXMLElement
*) currentNode
;
2217 PString name
= element
->GetAttribute( "name" );
2218 PString expr
= element
->GetAttribute( "expr" );
2220 if (name
.IsEmpty() || expr
.IsEmpty()) {
2221 PTRACE( 1, "VXMLSess\t<var> has a problem with its parameters, name=\"" << name
<< "\", expr=\"" << expr
<< "\"" );
2232 void PVXMLSession::OnEndRecording(const PString
& /*channelName*/)
2234 //SetVar(channelName + ".size", PString(incomingChannel->GetWAVFile()->GetDataLength() ) );
2235 //SetVar(channelName + ".type", "audio/x-wav" );
2236 //SetVar(channelName + ".filename", incomingChannel->GetWAVFile()->GetName() );
2240 void PVXMLSession::Trigger()
2242 waitForEvent
.Signal();
2247 /////////////////////////////////////////////////////////////////////////////////////////
2249 PVXMLGrammar::PVXMLGrammar(PXMLElement
* _field
)
2250 : field(_field
), state(PVXMLGrammar::NOINPUT
)
2254 //////////////////////////////////////////////////////////////////
2256 PVXMLMenuGrammar::PVXMLMenuGrammar(PXMLElement
* _field
)
2257 : PVXMLGrammar(_field
)
2261 //////////////////////////////////////////////////////////////////
2263 PVXMLDigitsGrammar::PVXMLDigitsGrammar(PXMLElement
* _field
, PINDEX _minDigits
, PINDEX _maxDigits
, PString _terminators
)
2264 : PVXMLGrammar(_field
),
2265 minDigits(_minDigits
),
2266 maxDigits(_maxDigits
),
2267 terminators(_terminators
)
2269 PAssert(_minDigits
<= _maxDigits
, "Error - invalid grammar parameter");
2272 BOOL
PVXMLDigitsGrammar::OnUserInput(const char ch
)
2274 // Ignore any other keys if we've already filled the grammar
2275 if (state
== PVXMLGrammar::FILLED
|| state
== PVXMLGrammar::NOMATCH
)
2278 // is this char the terminator?
2279 if (terminators
.Find(ch
) != P_MAX_INDEX
) {
2280 state
= (value
.GetLength() >= minDigits
&& value
.GetLength() <= maxDigits
) ?
2281 PVXMLGrammar::FILLED
:
2282 PVXMLGrammar::NOMATCH
;
2286 // Otherwise add to the grammar and check to see if we're done
2288 if (value
.GetLength() == maxDigits
) {
2289 state
= PVXMLGrammar::FILLED
; // the grammar is filled!
2297 void PVXMLDigitsGrammar::Stop()
2299 // Stopping recognition here may change the state if something was
2300 // recognized but it didn't fill the number of digits requested
2301 if (!value
.IsEmpty())
2302 state
= PVXMLGrammar::NOMATCH
;
2303 // otherwise the state will stay as NOINPUT
2306 //////////////////////////////////////////////////////////////////
2308 PVXMLChannel::PVXMLChannel(unsigned _frameDelay
, PINDEX frameSize
)
2309 : PDelayChannel(DelayReadsAndWrites
, _frameDelay
, frameSize
)
2311 vxmlInterface
= NULL
;
2313 sampleFrequency
= 8000;
2320 silentCount
= 20; // wait 20 frames before playing the OGM
2324 BOOL
PVXMLChannel::Open(PVXMLChannelInterface
* _vxmlInterface
)
2326 vxmlInterface
= _vxmlInterface
;
2330 PVXMLChannel::~PVXMLChannel()
2335 BOOL
PVXMLChannel::IsOpen() const
2340 BOOL
PVXMLChannel::Close()
2344 PDelayChannel::Close();
2348 PString
PVXMLChannel::AdjustWavFilename(const PString
& ofn
)
2350 if (wavFilePrefix
.IsEmpty())
2355 // add in suffix required for channel format, if any
2356 PINDEX pos
= ofn
.FindLast('.');
2357 if (pos
== P_MAX_INDEX
) {
2358 if (fn
.Right(wavFilePrefix
.GetLength()) != wavFilePrefix
)
2359 fn
+= wavFilePrefix
;
2362 PString basename
= ofn
.Left(pos
);
2363 PString ext
= ofn
.Mid(pos
+1);
2364 if (basename
.Right(wavFilePrefix
.GetLength()) != wavFilePrefix
)
2365 basename
+= wavFilePrefix
;
2366 fn
= basename
+ "." + ext
;
2371 PWAVFile
* PVXMLChannel::CreateWAVFile(const PFilePath
& fn
, BOOL recording
)
2373 PWAVFile
* wav
= PWAVFile::format(mediaFormat
);
2375 PTRACE(1, "VXML\tWAV file format " << mediaFormat
<< " not known");
2379 wav
->SetAutoconvert();
2380 if (!wav
->Open(AdjustWavFilename(fn
),
2381 recording
? PFile::WriteOnly
: PFile::ReadOnly
,
2382 PFile::ModeDefault
))
2383 PTRACE(1, "VXML\tCould not open WAV file " << wav
->GetName());
2385 else if (recording
) {
2386 wav
->SetChannels(1);
2387 wav
->SetSampleRate(8000);
2388 wav
->SetSampleSize(16);
2392 else if (!wav
->IsValid())
2393 PTRACE(1, "VXML\tWAV file header invalid for " << wav
->GetName());
2395 else if (wav
->GetSampleRate() != sampleFrequency
)
2396 PTRACE(1, "VXML\tWAV file has unsupported sample frequency " << wav
->GetSampleRate());
2398 else if (wav
->GetChannels() != 1)
2399 PTRACE(1, "VXML\tWAV file has unsupported channel count " << wav
->GetChannels());
2402 wav
->SetAutoconvert(); /// enable autoconvert
2403 PTRACE(4, "VXML\tOpened WAV file " << wav
->GetName());
2412 BOOL
PVXMLChannel::Write(const void * buf
, PINDEX len
)
2417 channelWriteMutex
.Wait();
2419 // let the recordable do silence detection
2420 if (recordable
!= NULL
&& recordable
->OnFrame(IsSilenceFrame(buf
, len
))) {
2421 PTRACE(1, "VXML\tRecording finished due to silence");
2425 // if nothing is capturing incoming data, then fake the timing and return
2426 if ((recordable
== NULL
) && (GetBaseWriteChannel() == NULL
)) {
2427 lastWriteCount
= len
;
2428 channelWriteMutex
.Signal();
2429 PDelayChannel::Wait(len
, nextWriteTick
);
2433 // write the data and do the correct delay
2434 if (!WriteFrame(buf
, len
))
2437 totalData
+= lastWriteCount
;
2439 channelWriteMutex
.Signal();
2444 BOOL
PVXMLChannel::StartRecording(const PFilePath
& fn
, unsigned _finalSilence
, unsigned _maxDuration
)
2446 PVXMLRecordableFilename
* recordable
= new PVXMLRecordableFilename();
2447 if (!recordable
->Open(fn
)) {
2452 recordable
->SetFinalSilence(_finalSilence
);
2453 recordable
->SetMaxDuration(_maxDuration
);
2454 return QueueRecordable(recordable
);
2457 BOOL
PVXMLChannel::QueueRecordable(PVXMLRecordable
* newItem
)
2461 // shutdown any existing recording
2464 // insert the new recordable
2465 PWaitAndSignal
mutex(channelWriteMutex
);
2466 recordable
= newItem
;
2470 newItem
->Record(*this);
2471 SetReadTimeout(frameDelay
);
2476 BOOL
PVXMLChannel::EndRecording()
2478 PWaitAndSignal
mutex(channelWriteMutex
);
2480 if (recordable
!= NULL
) {
2481 PTRACE(3, "PVXML\tFinished recording " << totalData
<< " bytes");
2483 PDelayChannel::Close();
2484 recordable
->OnStop();
2487 PTRACE(3, "PVXML\tRecording finished");
2493 BOOL
PVXMLChannel::Read(void * buffer
, PINDEX amount
)
2495 // assume we are returning silence
2496 BOOL silenceStuff
= FALSE
;
2497 BOOL delayDone
= FALSE
;
2503 PWaitAndSignal
m(channelReadMutex
);
2505 // if we are paused or in a delay, then do return silence
2506 if (paused
|| delayTimer
.IsRunning())
2507 silenceStuff
= TRUE
;
2509 // if we are returning silence frames, then decrement the frame count
2510 // and contine returning silence
2511 else if (silentCount
> 0) {
2512 silenceStuff
= TRUE
;
2516 // if underlying channel is open, then read from the channel
2517 else if (GetBaseReadChannel() != NULL
)
2518 silenceStuff
= FALSE
;
2520 // if underlying channel is not open, then see if there is anything in the queue
2524 PWaitAndSignal
m(queueMutex
);
2525 qSize
= playQueue
.GetSize();
2528 // if nothing in queue, then return silence
2530 silenceStuff
= TRUE
;
2532 // otherwise queue the next data item
2535 PWaitAndSignal
m(queueMutex
);
2536 PVXMLPlayable
* qItem
= (PVXMLPlayable
*)playQueue
.GetAt(0);
2539 SetWriteTimeout(frameDelay
);
2541 silenceStuff
= FALSE
;
2547 // if not doing silence, try and read more data
2548 // from the underlying channel
2550 if (!silenceStuff
) {
2552 if (ReadFrame(buffer
, amount
)) {
2553 totalData
+= amount
;
2557 else if (GetErrorCode(LastReadError
) == Timeout
)
2558 silenceStuff
= TRUE
;
2562 PTRACE(3, "PVXML\tFinished playing " << totalData
<< " bytes");
2563 PDelayChannel::Close();
2566 silenceStuff
= TRUE
;
2568 // get the item that was just playing
2571 PWaitAndSignal
m(queueMutex
);
2572 PVXMLPlayable
* qItem
= (PVXMLPlayable
*)playQueue
.GetAt(0);
2576 delay
= qItem
->GetDelay();
2578 // if the repeat count is non-zero, then repeat entry
2579 if (qItem
->GetRepeat() > 1) {
2580 qItem
->SetRepeat(qItem
->GetRepeat()-1);
2581 qItem
->OnRepeat(*this);
2584 delete playQueue
.Dequeue();
2588 // if delay required, then setup the delay
2590 PTRACE(3, "PVXML\tDelaying for " << delay
);
2594 // if no delay, then check the queue size
2598 PWaitAndSignal
m(queueMutex
);
2599 qSize
= playQueue
.GetSize();
2601 // if nothing in the queue, trigger the main loop to perhaps send more data
2603 vxmlInterface
->Trigger();
2609 // start silence frame if required
2610 // note that this always requires a delay
2612 lastReadCount
= CreateSilenceFrame(buffer
, amount
);
2615 // make sure we always do the correct delay
2617 Wait(amount
, nextReadTick
);
2622 BOOL
PVXMLChannel::QueuePlayable(const PString
& type
,
2623 const PString
& arg
,
2628 PTRACE(3, "PVXML\tEnqueueing playable " << type
<< " with arg " << arg
<< " for playing");
2629 PVXMLPlayable
* item
= PFactory
<PVXMLPlayable
>::CreateInstance(type
);
2631 PTRACE(1, "VXML\tCannot find playable of type " << type
);
2636 if (!item
->Open(*this, arg
, delay
, repeat
, autoDelete
)) {
2637 PTRACE(1, "VXML\tCannot open playable of type " << type
<< " with arg " << arg
);
2642 if (QueuePlayable(item
))
2649 BOOL
PVXMLChannel::QueuePlayable(PVXMLPlayable
* newItem
)
2651 newItem
->SetSampleFrequency(sampleFrequency
);
2652 PWaitAndSignal
mutex(queueMutex
);
2653 playQueue
.Enqueue(newItem
);
2657 BOOL
PVXMLChannel::QueueResource(const PURL
& url
, PINDEX repeat
, PINDEX delay
)
2659 if (url
.GetScheme() *= "file")
2660 return QueuePlayable("File", url
.AsFilePath(), repeat
, delay
, FALSE
);
2662 return QueuePlayable("URL", url
.AsString(), repeat
, delay
);
2665 BOOL
PVXMLChannel::QueueData(const PBYTEArray
& data
, PINDEX repeat
, PINDEX delay
)
2667 PTRACE(3, "PVXML\tEnqueueing " << data
.GetSize() << " bytes for playing");
2668 PVXMLPlayableData
* item
= dynamic_cast<PVXMLPlayableData
*>(PFactory
<PVXMLPlayable
>::CreateInstance("PCM Data"));
2670 PTRACE(1, "VXML\tCannot find playable of type 'PCM Data'");
2675 if (!item
->Open(*this, "", delay
, repeat
, TRUE
)) {
2676 PTRACE(1, "VXML\tCannot open playable of type 'PCM Data'");
2681 if (QueuePlayable(item
))
2688 void PVXMLChannel::FlushQueue()
2690 PWaitAndSignal
mutex(channelReadMutex
);
2692 if (GetBaseReadChannel() != NULL
)
2693 PDelayChannel::Close();
2695 PWaitAndSignal
m(queueMutex
);
2696 PVXMLPlayable
* qItem
;
2697 while ((qItem
= playQueue
.Dequeue()) != NULL
)
2701 ///////////////////////////////////////////////////////////////
2703 class PVXMLChannelPCM
: public PVXMLChannel
2705 PCLASSINFO(PVXMLChannelPCM
, PVXMLChannel
);
2711 // overrides from PVXMLChannel
2712 virtual BOOL
WriteFrame(const void * buf
, PINDEX len
);
2713 virtual BOOL
ReadFrame(void * buffer
, PINDEX amount
);
2714 virtual PINDEX
CreateSilenceFrame(void * buffer
, PINDEX amount
);
2715 virtual BOOL
IsSilenceFrame(const void * buf
, PINDEX len
) const;
2716 virtual void GetBeepData(PBYTEArray
& data
, unsigned ms
);
2719 PFactory
<PVXMLChannel
>::Worker
<PVXMLChannelPCM
> pcmVXMLChannelFactory(VXML_PCM16
);
2721 PVXMLChannelPCM::PVXMLChannelPCM()
2722 : PVXMLChannel(30, 480)
2724 mediaFormat
= VXML_PCM16
;
2725 wavFilePrefix
= PString::Empty();
2728 BOOL
PVXMLChannelPCM::WriteFrame(const void * buf
, PINDEX len
)
2730 return PDelayChannel::Write(buf
, len
);
2733 BOOL
PVXMLChannelPCM::ReadFrame(void * buffer
, PINDEX amount
)
2736 while (len
< amount
) {
2737 if (!PDelayChannel::Read(len
+ (char *)buffer
, amount
-len
))
2740 len
+= GetLastReadCount();
2746 PINDEX
PVXMLChannelPCM::CreateSilenceFrame(void * buffer
, PINDEX amount
)
2748 memset(buffer
, 0, amount
);
2752 BOOL
PVXMLChannelPCM::IsSilenceFrame(const void * buf
, PINDEX len
) const
2754 // Calculate the average signal level of this frame
2757 const short * pcm
= (const short *)buf
;
2758 const short * end
= pcm
+ len
/2;
2759 while (pcm
!= end
) {
2767 unsigned level
= sum
/ (len
/ 2);
2769 return level
< 500; // arbitrary level
2772 static short beepData
[] = { 0, 18784, 30432, 30400, 18784, 0, -18784, -30432, -30400, -18784 };
2775 void PVXMLChannelPCM::GetBeepData(PBYTEArray
& data
, unsigned ms
)
2778 while (data
.GetSize() < (PINDEX
)((ms
* 8) / 2)) {
2779 PINDEX len
= data
.GetSize();
2780 data
.SetSize(len
+ sizeof(beepData
));
2781 memcpy(len
+ data
.GetPointer(), beepData
, sizeof(beepData
));
2785 ///////////////////////////////////////////////////////////////
2787 class PVXMLChannelG7231
: public PVXMLChannel
2789 PCLASSINFO(PVXMLChannelG7231
, PVXMLChannel
);
2791 PVXMLChannelG7231();
2793 // overrides from PVXMLChannel
2794 virtual BOOL
WriteFrame(const void * buf
, PINDEX len
);
2795 virtual BOOL
ReadFrame(void * buffer
, PINDEX amount
);
2796 virtual PINDEX
CreateSilenceFrame(void * buffer
, PINDEX amount
);
2797 virtual BOOL
IsSilenceFrame(const void * buf
, PINDEX len
) const;
2800 PFactory
<PVXMLChannel
>::Worker
<PVXMLChannelG7231
> g7231VXMLChannelFactory(VXML_G7231
);
2802 PVXMLChannelG7231::PVXMLChannelG7231()
2803 : PVXMLChannel(30, 0)
2805 mediaFormat
= VXML_G7231
;
2806 wavFilePrefix
= "_g7231";
2809 static const PINDEX g7231Lens
[] = { 24, 20, 4, 1 };
2811 BOOL
PVXMLChannelG7231::WriteFrame(const void * buffer
, PINDEX actualLen
)
2813 PINDEX len
= g7231Lens
[(*(BYTE
*)buffer
)&3];
2814 if (len
> actualLen
)
2817 return PDelayChannel::Write(buffer
, len
);
2820 BOOL
PVXMLChannelG7231::ReadFrame(void * buffer
, PINDEX
/*amount*/)
2822 if (!PDelayChannel::Read(buffer
, 1))
2825 PINDEX len
= g7231Lens
[(*(BYTE
*)buffer
)&3];
2827 if (!PIndirectChannel::Read(1+(BYTE
*)buffer
, len
-1))
2835 PINDEX
PVXMLChannelG7231::CreateSilenceFrame(void * buffer
, PINDEX
/* len */)
2839 ((BYTE
*)buffer
)[0] = 2;
2840 memset(((BYTE
*)buffer
)+1, 0, 3);
2844 BOOL
PVXMLChannelG7231::IsSilenceFrame(const void * buf
, PINDEX len
) const
2850 return ((*(const BYTE
*)buf
)&3) == 2;
2853 ///////////////////////////////////////////////////////////////
2855 class PVXMLChannelG729
: public PVXMLChannel
2857 PCLASSINFO(PVXMLChannelG729
, PVXMLChannel
);
2861 // overrides from PVXMLChannel
2862 virtual BOOL
WriteFrame(const void * buf
, PINDEX len
);
2863 virtual BOOL
ReadFrame(void * buffer
, PINDEX amount
);
2864 virtual PINDEX
CreateSilenceFrame(void * buffer
, PINDEX amount
);
2865 virtual BOOL
IsSilenceFrame(const void * buf
, PINDEX len
) const;
2868 PFactory
<PVXMLChannel
>::Worker
<PVXMLChannelG729
> g729VXMLChannelFactory(VXML_G729
);
2870 PVXMLChannelG729::PVXMLChannelG729()
2871 : PVXMLChannel(10, 0)
2873 mediaFormat
= VXML_G729
;
2874 wavFilePrefix
= "_g729";
2877 BOOL
PVXMLChannelG729::WriteFrame(const void * buf
, PINDEX
/*len*/)
2879 return PDelayChannel::Write(buf
, 10);
2882 BOOL
PVXMLChannelG729::ReadFrame(void * buffer
, PINDEX
/*amount*/)
2884 return PDelayChannel::Read(buffer
, 10);
2887 PINDEX
PVXMLChannelG729::CreateSilenceFrame(void * buffer
, PINDEX
/* len */)
2889 memset(buffer
, 0, 10);
2893 BOOL
PVXMLChannelG729::IsSilenceFrame(const void * /*buf*/, PINDEX
/*len*/) const
2901 ///////////////////////////////////////////////////////////////