Added IsSupportingRTP function to simplify detecting when STUN supports RTP
[pwlib.git] / src / ptclib / vxml.cxx
blobcfc0e2f7ad33659eae3acbf51970c44f41364f89
1 /*
2 * vxml.cxx
4 * VXML engine for pwlib library
6 * Copyright (C) 2002 Equivalence Pty. Ltd.
8 * The contents of this file are subject to the Mozilla Public License
9 * Version 1.0 (the "License"); you may not use this file except in
10 * compliance with the License. You may obtain a copy of the License at
11 * http://www.mozilla.org/MPL/
13 * Software distributed under the License is distributed on an "AS IS"
14 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
15 * the License for the specific language governing rights and limitations
16 * under the License.
18 * The Original Code is Portable Windows Library.
20 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
22 * Contributor(s): ______________________________________.
24 * $Log$
25 * Revision 1.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
32 * Fixed recording
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
160 * Added cache
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
219 * Initial version
224 #ifdef __GNUC__
225 #pragma implementation "vxml.h"
226 #endif
228 #include <ptlib.h>
230 #define P_DISABLE_FACTORY_INSTANCES
232 #if P_EXPAT
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();
253 if (type *= ".vxml")
254 return "text/vxml";
256 if (type *= ".wav")
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)
266 fn = _fn;
267 arg = _fn;
268 if (!PFile::Exists(chan.AdjustWavFilename(fn)))
269 return FALSE;
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);
281 else {
282 PFile * fileChan = new PFile(fn);
283 if (fileChan->Open(PFile::ReadOnly))
284 chan = fileChan;
285 else {
286 delete fileChan;
290 if (chan == NULL)
291 PTRACE(3, "PVXML\tCannot open file \"" << fn << "\"");
292 else {
293 PTRACE(3, "PVXML\tPlaying file \"" << fn << "\"");
294 outgoingChannel.SetReadChannel(chan, TRUE);
298 void PVXMLPlayableFilename::OnStop()
300 if (autoDelete) {
301 PTRACE(3, "PVXML\tDeleting file \"" << fn << "\"");
302 PFile::Remove(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)
319 return FALSE;
321 currentIndex = 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);
335 else {
336 PFile * fileChan = new PFile(fn);
337 if (fileChan->Open(PFile::ReadOnly))
338 chan = fileChan;
339 else {
340 delete fileChan;
344 if (chan == NULL)
345 PTRACE(3, "PVXML\tCannot open file \"" << fn << "\"");
346 else {
347 PTRACE(3, "PVXML\tPlaying file \"" << fn << "\"");
348 outgoingChannel.SetReadChannel(chan, TRUE);
352 void PVXMLPlayableFilenameList::OnStop()
354 if (autoDelete) {
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()
368 pipeCmd = NULL;
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);
380 delete pipeCmd;
381 return;
384 if (pipeCmd == NULL)
385 PTRACE(3, "PVXML\tCannot open command \"" << arg << "\"");
386 else {
387 pipeCmd->Execute();
388 PTRACE(3, "PVXML\tPlaying command \"" << arg << "\"");
389 outgoingChannel.SetReadChannel(pipeCmd, TRUE);
393 void PVXMLPlayableCommand::OnStop()
395 if (pipeCmd != NULL) {
396 pipeCmd->WaitForTermination();
397 delete pipeCmd;
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)
412 data = _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)
428 url = arg = _url;
429 return PVXMLPlayable::Open(chan, _delay, _repeat, autoDelete);
432 void PVXMLPlayableURL::Play(PVXMLChannel & outgoingChannel)
434 // open the resource
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))
439 delete client;
440 else {
441 outgoingChannel.SetReadChannel(client, TRUE);
445 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableURL> vxmlPlayableURLFactory("URL");
447 ///////////////////////////////////////////////////////////////
449 BOOL PVXMLRecordableFilename::Open(const PString & _arg)
451 fn = _arg;
452 consecutiveSilence = 0;
453 return TRUE;
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);
463 else {
464 PFile * fileChan = new PFile(fn);
465 if (fileChan->Open(PFile::WriteOnly))
466 chan = fileChan;
467 else {
468 delete fileChan;
472 if (chan == NULL)
473 PTRACE(3, "PVXML\tCannot open file \"" << fn << "\"");
474 else {
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)
486 if (!isSilence) {
487 silenceStart = PTime();
488 consecutiveSilence = 0;
489 } else {
490 consecutiveSilence++;
491 if ( ((consecutiveSilence % 20) == 0) &&
493 ((finalSilence > 0) && ((PTime() - silenceStart).GetMilliSeconds() >= finalSilence)) ||
494 ((maxDuration > 0) && ((PTime() - recordStart).GetMilliSeconds() >= maxDuration))
497 return TRUE;
500 return FALSE;
503 ///////////////////////////////////////////////////////////////
505 PVXMLCache::PVXMLCache(const PDirectory & _directory)
506 : directory(_directory)
508 if (!directory.Exists())
509 directory.Create();
512 static PString MD5AsHex(const PString & str)
514 PMessageDigest::Result digest;
515 PMessageDigest5::Encode(str, digest);
517 PString hexStr;
518 const BYTE * data = digest.GetPointer();
519 for (PINDEX i = 0; i < digest.GetSize(); ++i)
520 hexStr.sprintf("%02x", (unsigned)data[i]);
521 return hexStr;
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,
534 const PString & key,
535 const PString & fileType,
536 PString & contentType,
537 PFilePath & dataFn)
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");
545 return FALSE;
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);
552 return FALSE;
555 typeFile.ReadLine(contentType);
556 contentType.Trim();
557 if (contentType.IsEmpty())
558 contentType = GetContentType(dataFn);
560 return TRUE;
563 void PVXMLCache::Put(const PString & prefix,
564 const PString & key,
565 const PString & fileType,
566 const PString & contentType,
567 const PFilePath & fn,
568 PFilePath & dataFn)
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));
580 else
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");
590 return cache;
594 PFilePath PVXMLCache::GetRandomFilename(const PString & prefix, const PString & fileType)
596 PFilePath fn;
598 // create a random temporary filename
599 PRandom r;
600 for (;;) {
601 fn = directory + psprintf("%s_%i.%s", (const char *)prefix, r.Generate() % 1000000, (const char *)fileType);
602 if (!PFile::Exists(fn))
603 break;
606 return fn;
609 //////////////////////////////////////////////////////////
611 PVXMLSession::PVXMLSession(PTextToSpeech * _tts, BOOL autoDelete)
613 vxmlThread = NULL;
614 vxmlChannel = NULL;
615 finishWhenEmpty = TRUE;
616 textToSpeech = NULL;
618 SetTextToSpeech(_tts, autoDelete);
620 Initialise();
623 void PVXMLSession::Initialise()
625 recording = FALSE;
626 allowFinish = FALSE;
627 listening = FALSE;
628 activeGrammar = NULL;
629 listening = FALSE;
630 forceEnd = FALSE;
631 currentForm = NULL;
632 currentNode = NULL;
635 PVXMLSession::~PVXMLSession()
637 Close();
639 if ((textToSpeech != NULL) && autoDeleteTextToSpeech)
640 delete textToSpeech;
643 PTextToSpeech * PVXMLSession::SetTextToSpeech(PTextToSpeech * _tts, BOOL autoDelete)
645 PWaitAndSignal m(sessionMutex);
647 if (autoDeleteTextToSpeech && (textToSpeech != NULL))
648 delete textToSpeech;
650 autoDeleteTextToSpeech = autoDelete;
651 textToSpeech = _tts;
652 return textToSpeech;
655 PTextToSpeech * PVXMLSession::SetTextToSpeech(const PString & ttsName)
657 PWaitAndSignal m(sessionMutex);
659 if (autoDeleteTextToSpeech && (textToSpeech != NULL))
660 delete textToSpeech;
662 autoDeleteTextToSpeech = TRUE;
663 textToSpeech = PFactory<PTextToSpeech>::CreateInstance(ttsName);
664 return textToSpeech;
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);
686 return FALSE;
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)
700 PFilePath fn;
701 PString contentType;
702 if (!RetreiveResource(url, contentType, fn, FALSE)) {
703 PTRACE(1, "PVXML\tCannot load document " << url);
704 return FALSE;
707 PTextFile file(fn, PFile::ReadOnly);
708 if (!file.IsOpen()) {
709 PTRACE(1, "PVXML\tCannot read data from " << fn);
710 return FALSE;
713 off_t len = file.GetLength();
714 PString text;
715 file.Read(text.GetPointer(len+1), len);
716 len = file.GetLastReadCount();
717 text.SetSize(len+1);
718 text[(PINDEX)len] = '\0';
720 if (!LoadVXML(text)) {
721 PTRACE(1, "PVXML\tCannot load VXML in " << url);
722 return FALSE;
725 rootURL = url;
726 return TRUE;
729 BOOL PVXMLSession::LoadVXML(const PString & xmlText)
731 PWaitAndSignal m(sessionMutex);
733 allowFinish = loaded = FALSE;
734 rootURL = PString::Empty();
736 // parse the XML
737 xmlFile.RemoveAll();
738 if (!xmlFile.Load(xmlText)) {
739 PTRACE(1, "PVXML\tCannot parse root document: " << GetXMLError());
740 return FALSE;
743 PXMLElement * root = xmlFile.GetRootElement();
744 if (root == NULL)
745 return FALSE;
747 // reset interpeter state
748 Initialise();
750 // find the first form
751 if ((currentForm = FindForm(PString::Empty())) == NULL)
752 return FALSE;
754 // start processing with this <form> element
755 currentNode = currentForm;
757 loaded = TRUE;
758 return TRUE;
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))
766 return src;
768 if (rootURL.IsEmpty())
769 return "file:" + src;
771 // else use scheme and path from root document
772 PURL url = rootURL;
773 PStringArray path = url.GetPath();
774 PString pathStr;
775 if (path.GetSize() > 0) {
776 pathStr += path[0];
777 PINDEX i;
778 for (i = 1; i < path.GetSize()-1; i++)
779 pathStr += "/" + path[i];
780 pathStr += "/" + src;
781 url.SetPathStr(pathStr);
784 return url;
788 BOOL PVXMLSession::RetreiveResource(const PURL & url,
789 PString & contentType,
790 PFilePath & dataFn,
791 BOOL useCache)
793 BOOL stat = FALSE;
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);
800 stat = TRUE;
803 // do a HTTP get when appropriate
804 else if ((url.GetScheme() *= "http") || (url.GetScheme() *= "https")) {
806 PFilePath fn;
807 PString fileType = url.AsFilePath().GetType();
809 BOOL inCache = FALSE;
810 if (useCache)
811 inCache = PVXMLCache::GetResourceCache().Get("url", url.AsString(), fileType, contentType, dataFn);
813 if (!inCache) {
815 // get a random filename
816 fn = PVXMLCache::GetResourceCache().GetRandomFilename("url", fileType);
818 // get the resource header information
819 PHTTPClient client;
820 PMIMEInfo outMIME, replyMIME;
821 if (!client.GetDocument(url, outMIME, replyMIME)) {
822 PTRACE(2, "PVXML\tCannot load resource " << url);
823 stat =FALSE;
826 else {
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
838 if (useCache)
839 PVXMLCache::GetResourceCache().Put("url", url.AsString(), fileType, contentType, fn, dataFn);
841 // data is loaded
842 stat = TRUE;
847 // files on the local file system get loaded locally
848 else if (url.GetScheme() *= "file") {
849 dataFn = url.AsFilePath();
850 stat = TRUE;
853 // unknown schemes give an error
854 else
855 stat = FALSE;
857 return stat;
861 PXMLElement * PVXMLSession::FindForm(const PString & id)
863 // NOTE: should have some flag to know if it is loaded
864 PXMLElement * root = xmlFile.GetRootElement();
865 if (root == NULL)
866 return NULL;
868 // Only handle search of top level nodes for <form> element
869 PINDEX i;
870 for (i = 0; i < root->GetSize(); i++) {
871 PXMLObject * xmlObject = root->GetElement(i);
872 if (xmlObject->IsElement()) {
873 PXMLElement * xmlElement = (PXMLElement*)xmlObject;
874 if (
875 (xmlElement->GetName() *= "form") &&
876 (id.IsEmpty() || (xmlElement->GetAttribute("id") *= id))
878 return xmlElement;
881 return NULL;
885 BOOL PVXMLSession::Open(BOOL isPCM)
887 if (isPCM)
888 return Open(VXML_PCM16);
889 else
890 return Open(VXML_G7231);
893 BOOL PVXMLSession::Open(const PString & _mediaFormat)
895 mediaFormat = _mediaFormat;
897 PVXMLChannel * chan = PFactory<PVXMLChannel>::CreateInstance(mediaFormat);
898 if (chan == NULL) {
899 PTRACE(1, "VXML\tCannot create VXML channel with format " << mediaFormat);
900 return FALSE;
903 Close();
905 // set the underlying channel
906 if (!PIndirectChannel::Open(chan, chan))
907 return FALSE;
909 // start the VXML session in another thread
911 PWaitAndSignal m(sessionMutex);
912 if (!chan->Open(this))
913 return FALSE;
914 vxmlChannel = chan;
917 return Execute();
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);
930 return TRUE;
934 BOOL PVXMLSession::Close()
937 PWaitAndSignal m(sessionMutex);
938 if (vxmlThread != NULL) {
940 // Stop condition for thread
941 threadRunning = FALSE;
942 forceEnd = TRUE;
943 waitForEvent.Signal();
945 // Signal all syncpoints that could be waiting for things
946 transferSync.Signal();
947 answerSync.Signal();
948 vxmlChannel->Close();
950 vxmlThread->WaitForTermination();
951 delete vxmlThread;
952 vxmlThread = NULL;
955 vxmlChannel = NULL;
958 return PIndirectChannel::Close();
962 void PVXMLSession::VXMLExecute(PThread &, INT)
964 while (!forceEnd && threadRunning) {
966 // process current node in the VXML script
967 ExecuteDialog();
969 // wait for something to happen
970 if (currentNode == NULL || IsPlaying())
971 waitForEvent.Wait();
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
977 if (forceEnd) {
978 PTRACE(1, "Fast forwarding through script because of forceEnd" );
979 while (currentNode != NULL)
980 ExecuteDialog();
983 OnEndSession();
985 //PWaitAndSignal m(sessionMutex);
986 if (vxmlChannel != NULL)
987 vxmlChannel->Close();
989 return;
992 void PVXMLSession::ProcessUserInput()
994 char ch;
996 PWaitAndSignal m(userInputMutex);
997 if (userInputQueue.size() == 0)
998 return;
999 ch = userInputQueue.front();
1000 userInputQueue.pop();
1003 PTRACE(3, "VXML\tHandling user input " << ch);
1005 // recording
1006 if (recording) {
1007 if (recordDTMFTerm)
1008 RecordEnd();
1011 // playback
1012 else {
1013 if (activeGrammar != NULL)
1014 activeGrammar->OnUserInput(ch);
1018 void PVXMLSession::ExecuteDialog()
1020 // check for user input
1021 ProcessUserInput();
1023 // process any active grammars
1024 ProcessGrammar();
1026 // process current node in the VXML script
1027 ProcessNode();
1029 // Wait for the buffer to complete before continuing to the next node
1030 if (currentNode == NULL) {
1031 if (IsPlaying())
1032 return;
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
1040 else {
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")) {
1047 listening = TRUE;
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)
1067 return;
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)
1083 return;
1085 if (processGrammar)
1087 PVXMLGrammar::GrammarState state = activeGrammar->GetState();
1088 grammarResult = activeGrammar->GetValue();
1089 LoadGrammar(NULL);
1090 listening = FALSE;
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
1102 switch (state)
1104 case PVXMLGrammar::FILLED:
1105 eventName = "filled";
1106 break;
1107 case PVXMLGrammar::NOINPUT:
1108 eventName = "noinput";
1109 break;
1110 case PVXMLGrammar::NOMATCH:
1111 eventName = "nomatch";
1112 break;
1113 default:
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)
1129 return;
1131 if (!currentNode->IsElement()) {
1132 if (!forceEnd)
1133 TraverseAudio();
1134 else
1135 currentNode = NULL;
1138 else {
1139 PXMLElement * element = (PXMLElement*)currentNode;
1140 PCaselessString nodeType = element->GetName();
1141 PTRACE(3, "PVXML\t**** Processing VoiceXML element: <" << nodeType << "> ***");
1143 if (nodeType *= "audio") {
1144 if (!forceEnd)
1145 TraverseAudio();
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")
1154 TraverseAudio();
1156 else if (nodeType *= "disconnect")
1157 currentNode = NULL;
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")
1172 TraverseGoto();
1174 else if (nodeType *= "grammar")
1175 TraverseGrammar(); // this will set activeGrammar
1177 else if (nodeType *= "record") {
1178 if (!forceEnd)
1179 TraverseRecord();
1182 else if (nodeType *= "prompt") {
1183 if (!forceEnd) {
1184 // LATER:
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") {
1197 if (!forceEnd) {
1201 else if (nodeType *= "value") {
1202 if (!forceEnd)
1203 TraverseAudio();
1206 else if (nodeType *= "var")
1207 TraverseVar();
1209 else if (nodeType *= "if")
1210 TraverseIf();
1212 else if (nodeType *= "exit")
1213 TraverseExit();
1215 else if (nodeType *= "menu") {
1216 if (!forceEnd) {
1217 TraverseMenu();
1218 eventName = "menu";
1222 else if (nodeType *= "choice") {
1223 if (!TraverseChoice(grammarResult))
1224 defaultDTMF++;
1225 else {
1226 // If the correct choice has been found,
1227 /// make sure everything is reset correctly
1228 eventName.MakeEmpty();
1229 grammarResult.MakeEmpty();
1230 defaultDTMF = 1;
1234 else if (nodeType *= "transfer") {
1235 if (!forceEnd)
1236 TraverseTransfer();
1239 else if (nodeType *= "submit")
1240 TraverseSubmit();
1242 else if (nodeType *= "property")
1243 TraverseProperty();
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();
1255 return TRUE;
1258 BOOL PVXMLSession::TraverseRecord()
1260 if (currentNode->IsElement()) {
1262 PString strName;
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)
1272 PString strDest;
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)
1281 PlayData(beepData);
1284 if (strDest.IsEmpty()) {
1285 PTime now;
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
1290 // seems to fail.
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
1317 // VXML script
1318 SetVar(strName + "$.maxtime", "true");
1320 else {
1321 // Normal hangup before timeout
1322 SetVar( strName + "$.maxtime", "false");
1325 // when this returns, we are done
1326 EndRecording();
1329 return TRUE;
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
1342 PINDEX i;
1343 BOOL allDigits = TRUE;
1344 for (i = 0; i < expr.GetLength(); i++) {
1345 allDigits = allDigits && isdigit(expr[i]);
1348 if (allDigits)
1349 return expr;
1351 return GetVar(expr);
1354 PString PVXMLSession::GetVar(const PString & ostr) const
1356 PString str = ostr;
1357 PString scope;
1359 // get scope
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)
1378 PString str = ostr;
1379 PString scope;
1381 // get scope
1382 PINDEX pos = str.Find('.');
1383 if (pos != P_MAX_INDEX) {
1384 scope = str.Left(pos);
1385 str = str.Mid(pos+1);
1388 // do session scope
1389 if (scope.IsEmpty() || (scope *= "session")) {
1390 sessionVars.SetAt(str, val);
1391 return;
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))
1403 return FALSE;
1405 AllowClearCall();
1407 return TRUE;
1410 BOOL PVXMLSession::PlayCommand(const PString & cmd, PINDEX repeat, PINDEX delay)
1412 if (vxmlChannel == NULL || !vxmlChannel->QueueCommand(cmd, repeat, delay))
1413 return FALSE;
1415 AllowClearCall();
1417 return TRUE;
1420 BOOL PVXMLSession::PlayData(const PBYTEArray & data, PINDEX repeat, PINDEX delay)
1422 if (vxmlChannel == NULL || !vxmlChannel->QueueData(data, repeat, delay))
1423 return FALSE;
1425 AllowClearCall();
1427 return TRUE;
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)
1444 PBYTEArray nothing;
1445 if (vxmlChannel == NULL || !vxmlChannel->QueueData(nothing, 1, msecs))
1446 return FALSE;
1448 AllowClearCall();
1450 return TRUE;
1453 BOOL PVXMLSession::PlayResource(const PURL & url, PINDEX repeat, PINDEX delay)
1455 if (vxmlChannel == NULL || !vxmlChannel->QueueResource(url, repeat, delay))
1456 return FALSE;
1458 AllowClearCall();
1460 return TRUE;
1463 BOOL PVXMLSession::LoadGrammar(PVXMLGrammar * grammar)
1465 if (activeGrammar != NULL) {
1466 delete activeGrammar;
1467 activeGrammar = FALSE;
1470 activeGrammar = grammar;
1472 return TRUE;
1475 BOOL PVXMLSession::PlayText(const PString & _text,
1476 PTextToSpeech::TextType type,
1477 PINDEX repeat,
1478 PINDEX delay)
1480 PStringArray list;
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");
1484 return FALSE;
1487 PVXMLPlayableFilenameList * playable = new PVXMLPlayableFilenameList;
1488 if (!playable->Open(*vxmlChannel, list, delay, repeat, !useCache)) {
1489 delete playable;
1490 PTRACE(1, "PVXML\tCannot create playable for filename list");
1491 return FALSE;
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();
1505 if (text.IsEmpty())
1506 continue;
1508 BOOL spoken = FALSE;
1509 PFilePath dataFn;
1511 // see if we have converted this text before
1512 PString contentType;
1513 if (useCache)
1514 spoken = PVXMLCache::GetResourceCache().Get(prefix, text, "wav", contentType, dataFn);
1516 // if not cached, then use the text to speech converter
1517 if (!spoken) {
1518 PFilePath tmpfname;
1519 if (textToSpeech != NULL) {
1520 tmpfname = PVXMLCache::GetResourceCache().GetRandomFilename("tts", "wav");
1521 if (!textToSpeech->OpenFile(tmpfname)) {
1522 PTRACE(2, "PVXML\tcannot open file " << tmpfname);
1523 } else {
1524 spoken = textToSpeech->Speak(text, type);
1525 if (!textToSpeech->Close()) {
1526 PTRACE(2, "PVXML\tcannot close TTS engine");
1529 textToSpeech->Close();
1530 if (useCache)
1531 PVXMLCache::GetResourceCache().Put(prefix, text, "wav", contentType, tmpfname, dataFn);
1532 else
1533 dataFn = tmpfname;
1537 if (!spoken) {
1538 PTRACE(2, "PVXML\tcannot speak text using TTS engine");
1539 } else
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*/)
1564 recording = TRUE;
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());
1581 return FALSE;
1584 void PVXMLSession::RecordEnd()
1586 if (recording)
1587 recordSync.Signal();
1590 BOOL PVXMLSession::EndRecording()
1592 if (recording) {
1593 recording = FALSE;
1594 if (vxmlChannel != NULL)
1595 return vxmlChannel->EndRecording();
1598 return FALSE;
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)
1609 if (!fn.IsEmpty())
1610 return new PWAVFile(fn, mode, opts, fmt);
1612 return new PWAVFile(mode, opts, fmt);
1615 void PVXMLSession::AllowClearCall()
1617 allowFinish = TRUE;
1620 BOOL PVXMLSession::TraverseAudio()
1622 if (!currentNode->IsElement()) {
1623 PlayText(((PXMLData *)currentNode)->GetString());
1626 else {
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());
1650 // time is VXML 2.0
1651 else if (element->HasAttribute("time")) {
1652 PTimeInterval time = StringToTime(element->GetAttribute("time"));
1653 PlaySilence(time);
1656 else if (element->HasAttribute("size")) {
1657 PString size = element->GetAttribute("size");
1658 if (size *= "none")
1660 else if (size *= "small")
1661 PlaySilence(SMALL_BREAK_MSECS);
1662 else if (size *= "large")
1663 PlaySilence(LARGE_BREAK_MSECS);
1664 else
1665 PlaySilence(MEDIUM_BREAK_MSECS);
1668 // default to medium pause
1669 else {
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] == '|')) {
1681 loaded = TRUE;
1682 PlayCommand(str.Mid(1));
1685 else {
1686 // get a normalised name for the resource
1687 PFilePath fn;
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())
1698 delete wavFile;
1699 else {
1700 loaded = TRUE;
1701 PlayFile(fn, 0, 0, !useCache); // make sure we delete the file if not cacheing
1706 if (loaded) {
1707 // skip to the next node
1708 if (element->HasSubObjects())
1709 currentNode = element->GetElement(element->GetSize() - 1);
1714 else
1715 PTRACE(3, "PVXML\tUnknown audio tag " << element->GetName() << " encountered");
1718 return TRUE;
1722 BOOL PVXMLSession::TraverseGoto() // <goto>
1724 PAssert(currentNode != NULL, "ProcessGotoElement(): Expected valid node");
1725 if (currentNode == NULL)
1726 return FALSE;
1728 // LATER: handle expr, expritem, fetchaudio, fetchhint, fetchtimeout, maxage, maxstale
1730 PAssert(currentNode->IsElement(), "ProcessGotoElement(): Expected element");
1732 // nextitem
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
1740 return FALSE;
1742 return TRUE;
1745 // next
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;
1756 else {
1757 PURL url = NormaliseResourceName(next);
1758 return LoadURL(url) && (currentForm != NULL);
1761 return FALSE;
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();
1809 else {
1810 // Invalid parameter skipped
1811 // LATER: throw 'error.semantic'
1814 newGrammar = new PVXMLDigitsGrammar((PXMLElement*)currentNode, minDigits, maxDigits, "");
1816 else {
1817 // LATER: throw 'error.unsupported'
1818 return FALSE;
1822 if (newGrammar != NULL)
1823 return LoadGrammar(newGrammar);
1825 return TRUE;
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)
1840 return handler;
1842 // Check for a <catch>
1843 if ((handler = tmp->GetElement("catch")) != NULL) {
1844 PString strCond = handler->GetAttribute("cond");
1845 if (strCond.Find(event))
1846 return handler;
1849 tmp = tmp->GetParent();
1852 return NULL;
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;
1888 else
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)
1912 return FALSE;
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)
1929 return FALSE;
1930 dest.Delete(0, 8);
1932 if (source.Find("phone://") == P_MAX_INDEX)
1933 return FALSE;
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);
1942 DoTransfer(opts);
1944 // Wait for the transfer result signal
1945 transferSync.Wait();
1947 return TRUE;
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
1956 // continue
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());
1971 if (isEqual) {
1972 // Find var name
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 << "\"" );
1982 } else {
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);
1994 else {
1995 PTRACE( 1, "\tPVXMLSession, <if> element contains condition with operator other than ==, not implemented" );
1996 return FALSE;
1999 return TRUE;
2002 BOOL PVXMLSession::TraverseExit()
2004 currentNode = NULL;
2005 forceEnd = TRUE;
2006 waitForEvent.Signal();
2007 return TRUE;
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");
2022 return FALSE;
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");
2029 return FALSE;
2032 if (!element->HasAttribute("next")) {
2033 PTRACE(1, "VXMLSess\t<submit> does not contain \"next\" parameter");
2034 return FALSE;
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");
2041 return FALSE;
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\"");
2046 return FALSE;
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\"");
2053 return FALSE;
2056 if ( !PFile::Exists(fileName )) {
2057 PTRACE(1, "VXMLSess\t<submit> cannot find file " << fileName);
2058 return FALSE;
2061 PString fileNameOnly;
2062 int pos = fileName.FindLast( "/" );
2063 if (pos < fileName.GetLength()) {
2064 fileNameOnly = fileName.Right( ( fileName.GetLength() - pos ) - 1 );
2066 else {
2067 pos = fileName.FindLast("\\");
2068 if (pos < fileName.GetSize()) {
2069 fileNameOnly = fileName.Right((fileName.GetLength() - pos) - 1);
2071 else {
2072 fileNameOnly = fileName;
2076 PHTTPClient client;
2077 PMIMEInfo sendMIME, replyMIME;
2079 if (element->GetAttribute("method") *= "post") {
2081 // 1 2 3 4123
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";
2098 // Add content type
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();
2109 PString mimeThing;
2111 // Make PHP happy?
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 );
2134 // TODO, Later:
2135 // Remove file?
2136 // Load reply from server as new VXML docuemnt ala <goto>
2139 else {
2140 if (element->GetAttribute("method") != "get") {
2141 PTRACE(1, "VXMLSess\t<submit> does not (yet) support method type \"" << element->GetAttribute( "method" ) << "\"");
2142 return FALSE;
2145 PString getURL = url + "?" + name + "=" + GetVar( name );
2147 client.GetDocument( url, sendMIME, replyMIME );
2148 // TODO, Later:
2149 // Load reply from server as new VXML document ala <goto>
2152 if (!result) {
2153 PTRACE( 1, "VXMLSess\t<submit> to server failed with "
2154 << client.GetLastResponseCode() << " "
2155 << client.GetLastResponseInfo() );
2158 return result;
2161 BOOL PVXMLSession::TraverseProperty()
2163 PXMLElement* element = (PXMLElement *) currentNode;
2164 if (element->HasAttribute("name"))
2165 SetVar(element->GetAttribute("name"), element->GetAttribute("value"));
2167 return TRUE;
2171 BOOL PVXMLSession::TraverseMenu()
2173 BOOL result = FALSE;
2174 PVXMLGrammar * newGrammar = new PVXMLDigitsGrammar((PXMLElement*) currentNode, 1, 1, "" );
2175 LoadGrammar(newGrammar);
2176 result = TRUE;
2177 return result;
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" );
2190 if (dtmf.IsEmpty())
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)
2205 result = TRUE;
2208 return result;
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 << "\"" );
2223 else {
2224 SetVar(name, expr);
2225 result = TRUE;
2228 return result;
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)
2276 return TRUE;
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;
2283 return TRUE;
2286 // Otherwise add to the grammar and check to see if we're done
2287 value += ch;
2288 if (value.GetLength() == maxDigits) {
2289 state = PVXMLGrammar::FILLED; // the grammar is filled!
2290 return TRUE;
2293 return FALSE;
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;
2314 closed = FALSE;
2316 recording = FALSE;
2317 recordable = NULL;
2319 playing = FALSE;
2320 silentCount = 20; // wait 20 frames before playing the OGM
2321 paused = FALSE;
2324 BOOL PVXMLChannel::Open(PVXMLChannelInterface * _vxmlInterface)
2326 vxmlInterface = _vxmlInterface;
2327 return TRUE;
2330 PVXMLChannel::~PVXMLChannel()
2332 EndRecording();
2335 BOOL PVXMLChannel::IsOpen() const
2337 return !closed;
2340 BOOL PVXMLChannel::Close()
2342 closed = TRUE;
2344 PDelayChannel::Close();
2345 return TRUE;
2348 PString PVXMLChannel::AdjustWavFilename(const PString & ofn)
2350 if (wavFilePrefix.IsEmpty())
2351 return ofn;
2353 PString fn = ofn;
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;
2361 else {
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;
2368 return fn;
2371 PWAVFile * PVXMLChannel::CreateWAVFile(const PFilePath & fn, BOOL recording)
2373 PWAVFile * wav = PWAVFile::format(mediaFormat);
2374 if (wav == NULL) {
2375 PTRACE(1, "VXML\tWAV file format " << mediaFormat << " not known");
2376 return NULL;
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);
2389 return wav;
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());
2401 else {
2402 wav->SetAutoconvert(); /// enable autoconvert
2403 PTRACE(4, "VXML\tOpened WAV file " << wav->GetName());
2404 return wav;
2407 delete wav;
2408 return NULL;
2412 BOOL PVXMLChannel::Write(const void * buf, PINDEX len)
2414 if (closed)
2415 return FALSE;
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");
2422 EndRecording();
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);
2430 return TRUE;
2433 // write the data and do the correct delay
2434 if (!WriteFrame(buf, len))
2435 EndRecording();
2436 else
2437 totalData += lastWriteCount;
2439 channelWriteMutex.Signal();
2441 return TRUE;
2444 BOOL PVXMLChannel::StartRecording(const PFilePath & fn, unsigned _finalSilence, unsigned _maxDuration)
2446 PVXMLRecordableFilename * recordable = new PVXMLRecordableFilename();
2447 if (!recordable->Open(fn)) {
2448 delete recordable;
2449 return FALSE;
2452 recordable->SetFinalSilence(_finalSilence);
2453 recordable->SetMaxDuration(_maxDuration);
2454 return QueueRecordable(recordable);
2457 BOOL PVXMLChannel::QueueRecordable(PVXMLRecordable * newItem)
2459 totalData = 0;
2461 // shutdown any existing recording
2462 EndRecording();
2464 // insert the new recordable
2465 PWaitAndSignal mutex(channelWriteMutex);
2466 recordable = newItem;
2467 recording = TRUE;
2468 totalData = 0;
2469 newItem->OnStart();
2470 newItem->Record(*this);
2471 SetReadTimeout(frameDelay);
2472 return TRUE;
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();
2485 delete recordable;
2486 recordable = NULL;
2487 PTRACE(3, "PVXML\tRecording finished");
2490 return TRUE;
2493 BOOL PVXMLChannel::Read(void * buffer, PINDEX amount)
2495 // assume we are returning silence
2496 BOOL silenceStuff = FALSE;
2497 BOOL delayDone = FALSE;
2500 if (closed)
2501 return 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;
2513 silentCount--;
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
2521 else {
2522 PINDEX qSize;
2524 PWaitAndSignal m(queueMutex);
2525 qSize = playQueue.GetSize();
2528 // if nothing in queue, then return silence
2529 if (qSize == 0)
2530 silenceStuff = TRUE;
2532 // otherwise queue the next data item
2533 else {
2535 PWaitAndSignal m(queueMutex);
2536 PVXMLPlayable * qItem = (PVXMLPlayable *)playQueue.GetAt(0);
2537 qItem->OnStart();
2538 qItem->Play(*this);
2539 SetWriteTimeout(frameDelay);
2541 silenceStuff = FALSE;
2542 totalData = 0;
2543 playing = TRUE;
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;
2554 delayDone = TRUE;
2557 else if (GetErrorCode(LastReadError) == Timeout)
2558 silenceStuff = TRUE;
2560 else {
2562 PTRACE(3, "PVXML\tFinished playing " << totalData << " bytes");
2563 PDelayChannel::Close();
2565 playing = FALSE;
2566 silenceStuff = TRUE;
2568 // get the item that was just playing
2569 PINDEX delay;
2571 PWaitAndSignal m(queueMutex);
2572 PVXMLPlayable * qItem = (PVXMLPlayable *)playQueue.GetAt(0);
2573 if (qItem == NULL)
2574 delay = 0;
2575 else
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);
2582 } else {
2583 qItem->OnStop();
2584 delete playQueue.Dequeue();
2588 // if delay required, then setup the delay
2589 if (delay != 0) {
2590 PTRACE(3, "PVXML\tDelaying for " << delay);
2591 delayTimer = delay;
2594 // if no delay, then check the queue size
2595 else {
2596 PINDEX qSize;
2598 PWaitAndSignal m(queueMutex);
2599 qSize = playQueue.GetSize();
2601 // if nothing in the queue, trigger the main loop to perhaps send more data
2602 if (qSize == 0)
2603 vxmlInterface->Trigger();
2609 // start silence frame if required
2610 // note that this always requires a delay
2611 if (silenceStuff) {
2612 lastReadCount = CreateSilenceFrame(buffer, amount);
2615 // make sure we always do the correct delay
2616 if (!delayDone)
2617 Wait(amount, nextReadTick);
2619 return TRUE;
2622 BOOL PVXMLChannel::QueuePlayable(const PString & type,
2623 const PString & arg,
2624 PINDEX repeat,
2625 PINDEX delay,
2626 BOOL autoDelete)
2628 PTRACE(3, "PVXML\tEnqueueing playable " << type << " with arg " << arg << " for playing");
2629 PVXMLPlayable * item = PFactory<PVXMLPlayable>::CreateInstance(type);
2630 if (item == NULL) {
2631 PTRACE(1, "VXML\tCannot find playable of type " << type);
2632 delete item;
2633 return FALSE;
2636 if (!item->Open(*this, arg, delay, repeat, autoDelete)) {
2637 PTRACE(1, "VXML\tCannot open playable of type " << type << " with arg " << arg);
2638 delete item;
2639 return FALSE;
2642 if (QueuePlayable(item))
2643 return TRUE;
2645 delete item;
2646 return FALSE;
2649 BOOL PVXMLChannel::QueuePlayable(PVXMLPlayable * newItem)
2651 newItem->SetSampleFrequency(sampleFrequency);
2652 PWaitAndSignal mutex(queueMutex);
2653 playQueue.Enqueue(newItem);
2654 return TRUE;
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);
2661 else
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"));
2669 if (item != NULL) {
2670 PTRACE(1, "VXML\tCannot find playable of type 'PCM Data'");
2671 delete item;
2672 return FALSE;
2675 if (!item->Open(*this, "", delay, repeat, TRUE)) {
2676 PTRACE(1, "VXML\tCannot open playable of type 'PCM Data'");
2677 delete item;
2678 return FALSE;
2681 if (QueuePlayable(item))
2682 return TRUE;
2684 delete item;
2685 return FALSE;
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)
2698 delete qItem;
2701 ///////////////////////////////////////////////////////////////
2703 class PVXMLChannelPCM : public PVXMLChannel
2705 PCLASSINFO(PVXMLChannelPCM, PVXMLChannel);
2707 public:
2708 PVXMLChannelPCM();
2710 protected:
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)
2735 PINDEX len = 0;
2736 while (len < amount) {
2737 if (!PDelayChannel::Read(len + (char *)buffer, amount-len))
2738 return FALSE;
2740 len += GetLastReadCount();
2743 return TRUE;
2746 PINDEX PVXMLChannelPCM::CreateSilenceFrame(void * buffer, PINDEX amount)
2748 memset(buffer, 0, amount);
2749 return amount;
2752 BOOL PVXMLChannelPCM::IsSilenceFrame(const void * buf, PINDEX len) const
2754 // Calculate the average signal level of this frame
2755 int sum = 0;
2757 const short * pcm = (const short *)buf;
2758 const short * end = pcm + len/2;
2759 while (pcm != end) {
2760 if (*pcm < 0)
2761 sum -= *pcm++;
2762 else
2763 sum += *pcm++;
2766 // calc average
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)
2777 data.SetSize(0);
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);
2790 public:
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)
2815 return FALSE;
2817 return PDelayChannel::Write(buffer, len);
2820 BOOL PVXMLChannelG7231::ReadFrame(void * buffer, PINDEX /*amount*/)
2822 if (!PDelayChannel::Read(buffer, 1))
2823 return FALSE;
2825 PINDEX len = g7231Lens[(*(BYTE *)buffer)&3];
2826 if (len != 1) {
2827 if (!PIndirectChannel::Read(1+(BYTE *)buffer, len-1))
2828 return FALSE;
2829 lastReadCount++;
2832 return TRUE;
2835 PINDEX PVXMLChannelG7231::CreateSilenceFrame(void * buffer, PINDEX /* len */)
2839 ((BYTE *)buffer)[0] = 2;
2840 memset(((BYTE *)buffer)+1, 0, 3);
2841 return 4;
2844 BOOL PVXMLChannelG7231::IsSilenceFrame(const void * buf, PINDEX len) const
2846 if (len == 4)
2847 return TRUE;
2848 if (buf == NULL)
2849 return FALSE;
2850 return ((*(const BYTE *)buf)&3) == 2;
2853 ///////////////////////////////////////////////////////////////
2855 class PVXMLChannelG729 : public PVXMLChannel
2857 PCLASSINFO(PVXMLChannelG729, PVXMLChannel);
2858 public:
2859 PVXMLChannelG729();
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);
2890 return 10;
2893 BOOL PVXMLChannelG729::IsSilenceFrame(const void * /*buf*/, PINDEX /*len*/) const
2895 return FALSE;
2899 #endif // P_EXPAT
2901 ///////////////////////////////////////////////////////////////