Uncommented beaudio code
[pwlib.git] / src / ptclib / vxml.cxx
blob1806aebd0f1c2f68b2495cc6d52b6a7eec7a9b7d
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.37 2004/03/23 04:48:42 csoutheren
26 * Improved ability to start VXML scripts as needed
28 * Revision 1.36 2003/11/12 20:38:16 csoutheren
29 * Fixed problem with incorrect sense of ContentLength header detection thanks to Andreas Sikkema
31 * Revision 1.35 2003/05/14 01:12:53 rjongbloed
32 * Fixed test for SID frames in record silence detection on G.723.1A
34 * Revision 1.34 2003/04/23 11:54:53 craigs
35 * Added ability to record audio
37 * Revision 1.33 2003/04/10 04:19:43 robertj
38 * Fixed incorrect timing on G.723.1 (framed codec)
39 * Fixed not using correct codec file suffix for non PCM/G.723.1 codecs.
41 * Revision 1.32 2003/04/08 05:09:14 craigs
42 * Added ability to use commands as an audio source
44 * Revision 1.31 2003/03/17 08:03:07 robertj
45 * Combined to the separate incoming and outgoing substream classes into
46 * a single class to make it easier to produce codec aware descendents.
47 * Added G.729 substream class.
49 * Revision 1.30 2002/12/03 22:39:14 robertj
50 * Removed get document that just returns a content length as the chunked
51 * transfer encoding makes this very dangerous.
53 * Revision 1.29 2002/11/19 10:36:30 robertj
54 * Added functions to set anf get "file:" URL. as PFilePath and do the right
55 * things with platform dependent directory components.
57 * Revision 1.28 2002/11/08 03:39:27 craigs
58 * Fixed problem with G.723.1 files
60 * Revision 1.27 2002/09/24 13:47:41 robertj
61 * Added support for more vxml commands, thanks Alexander Kovatch
63 * Revision 1.26 2002/09/18 06:37:40 robertj
64 * Added functions to load vxml directly, via file or URL. Old function
65 * intelligently picks which one to use.
67 * Revision 1.25 2002/09/03 04:38:14 craigs
68 * Added VXML 2.0 time attribute to <break>
70 * Revision 1.24 2002/09/03 04:11:37 craigs
71 * More changes from Alexander Kovatch
73 * Revision 1.23 2002/08/30 07:33:16 craigs
74 * Added extra initialisations
76 * Revision 1.22 2002/08/30 05:05:54 craigs
77 * Added changes for PVXMLGrammar from Alexander Kovatch
79 * Revision 1.21 2002/08/29 00:16:12 craigs
80 * Fixed typo, thanks to Peter Robinson
82 * Revision 1.20 2002/08/28 08:05:16 craigs
83 * Reorganised VXMLSession class as per code from Alexander Kovatch
85 * Revision 1.19 2002/08/28 05:10:57 craigs
86 * Added ability to load resources via URI
87 * Added cache
89 * Revision 1.18 2002/08/27 02:46:56 craigs
90 * Removed need for application to call AllowClearCall
92 * Revision 1.17 2002/08/27 02:20:09 craigs
93 * Added <break> command in prompt blocks
94 * Fixed potential deadlock
95 * Added <prompt> command in top level fields, thanks to Alexander Kovatch
97 * Revision 1.16 2002/08/15 04:11:16 robertj
98 * Fixed shutdown problems with closing vxml session, leaks a thread.
99 * Fixed potential problems with indirect channel Close() function.
101 * Revision 1.15 2002/08/15 02:13:10 craigs
102 * Fixed problem with handle leak (maybe) and change tts files back to autodelete
104 * Revision 1.14 2002/08/14 15:18:07 craigs
105 * Improved random filename generation
107 * Revision 1.13 2002/08/08 01:03:06 craigs
108 * Added function to re-enable automatic call clearing on script end
110 * Revision 1.12 2002/08/07 13:38:14 craigs
111 * Fixed bug in calculating lengths of G.723.1 packets
113 * Revision 1.11 2002/08/06 07:45:28 craigs
114 * Added lots of stuff from OpalVXML
116 * Revision 1.10 2002/07/29 15:08:50 craigs
117 * Added autodelete option to PlayFile
119 * Revision 1.9 2002/07/29 15:03:36 craigs
120 * Added access to queue functions
121 * Added autodelete option to AddFile
123 * Revision 1.8 2002/07/29 14:16:05 craigs
124 * Added asynchronous VXML execution
126 * Revision 1.7 2002/07/17 08:34:25 craigs
127 * Fixed deadlock problems
129 * Revision 1.6 2002/07/17 06:08:23 craigs
130 * Added additional "sayas" classes
132 * Revision 1.5 2002/07/10 13:15:20 craigs
133 * Moved some VXML classes from Opal back into PTCLib
134 * Fixed various race conditions
136 * Revision 1.4 2002/07/05 06:28:07 craigs
137 * Added OnEmptyAction callback
139 * Revision 1.3 2002/07/02 06:24:53 craigs
140 * Added recording functions
142 * Revision 1.2 2002/06/28 01:30:29 robertj
143 * Fixed ability to compile if do not have expat library.
145 * Revision 1.1 2002/06/27 05:27:49 craigs
146 * Initial version
151 #ifdef __GNUC__
152 #pragma implementation "vxml.h"
153 #endif
155 #include <ptlib.h>
157 #if P_EXPAT
159 #include <ptclib/vxml.h>
160 #include <ptclib/memfile.h>
161 #include <ptclib/random.h>
162 #include <ptclib/http.h>
165 #define SMALL_BREAK_MSECS 1000
166 #define MEDIUM_BREAK_MSECS 2500
167 #define LARGE_BREAK_MSECS 5000
169 // LATER: Lookup what this value should be
170 #define DEFAULT_TIMEOUT 10000
172 #define CACHE_BUFFER_SIZE 1024
175 PMutex PVXMLSession::cacheMutex;
176 PDirectory PVXMLSession::cacheDir;
177 PVXMLCache * PVXMLSession::resourceCache = NULL;
178 PINDEX PVXMLSession::cacheCount = 0;
181 //////////////////////////////////////////////////////////
183 PVXMLSession::PVXMLSession(PTextToSpeech * _tts, BOOL autoDelete)
185 //activeGrammar = NULL;
186 recording = FALSE;
187 vxmlThread = NULL;
188 incomingChannel = NULL;
189 outgoingChannel = NULL;
190 loaded = FALSE;
191 forceEnd = FALSE;
192 textToSpeech = NULL;
193 listening = FALSE;
194 activeGrammar = NULL;
195 currentNode = NULL;
196 emptyAction = TRUE;
198 SetTextToSpeech(_tts, autoDelete);
200 PWaitAndSignal m(cacheMutex);
201 cacheCount++;
202 if (resourceCache == NULL) {
203 resourceCache = new PVXMLCache;
204 cacheDir = PDirectory() + "cache";
206 // load the cache information, if already present
207 PFilePath cacheInfo = cacheDir + "cache.txt";
208 if (PFile::Exists(cacheInfo)) {
209 PTextFile cacheFile;
210 if (cacheFile.Open(cacheInfo, PFile::ReadOnly)) {
211 PString line;
212 while (cacheFile.ReadLine(line)) {
213 PStringArray info = line.Tokenise("|", TRUE);
214 if (info.GetSize() > 3) {
215 PVXMLCacheItem * item = new PVXMLCacheItem(info[0]);
216 item->fn = cacheDir + info[1];
217 item->contentType = info[2];
218 item->loadTime = PTime();
219 item->ok = info[3] *= "y";
220 resourceCache->Append(item);
228 PVXMLSession::~PVXMLSession()
230 Close();
232 if ((textToSpeech != NULL) && autoDeleteTextToSpeech) {
233 delete textToSpeech;
236 PWaitAndSignal m(cacheMutex);
237 cacheCount--;
239 // write out the cache information
240 if (cacheCount == 0) {
241 PFilePath cacheInfo = cacheDir + "cache.txt";
242 PTextFile cacheFile;
243 if (cacheFile.Open(cacheInfo, PFile::WriteOnly)) {
244 PINDEX i;
245 for (i = 0; i < resourceCache->GetSize(); i++) {
246 PVXMLCacheItem & item = (*resourceCache)[i];
247 cacheFile << item.AsString() << "|"
248 << item.fn.GetFileName() << "|"
249 << item.contentType << "|"
250 << (item.ok ? "Y" : "N")
251 << endl;
254 delete resourceCache;
255 resourceCache = NULL;
259 void PVXMLSession::SetTextToSpeech(PTextToSpeech * _tts, BOOL autoDelete)
261 PWaitAndSignal m(sessionMutex);
263 if (autoDeleteTextToSpeech && (textToSpeech != NULL))
264 delete textToSpeech;
266 autoDeleteTextToSpeech = autoDelete;
267 textToSpeech = _tts;
270 BOOL PVXMLSession::Load(const PString & source)
272 // Lets try and guess what was passed, if file exists then is file
273 PFilePath file = source;
274 if (PFile::Exists(file))
275 return LoadFile(file);
277 // see if looks like URL
278 PINDEX pos = source.Find(':');
279 if (pos != P_MAX_INDEX) {
280 PString scheme = source.Left(pos);
281 if ((scheme *= "http") || (scheme *= "https") || (scheme *= "file"))
282 return LoadURL(source);
285 // See if is actual VXML
286 if (PCaselessString(source).Find("<vxml") != P_MAX_INDEX)
287 return LoadVXML(source);
289 return FALSE;
293 BOOL PVXMLSession::LoadFile(const PFilePath & filename)
295 // create a file URL from the filename
296 return LoadURL(filename);
300 BOOL PVXMLSession::LoadURL(const PURL & url)
302 // retreive the document (may be a HTTP get)
303 PBYTEArray data;
304 PString contentType;
305 if (!RetrieveResource(url, data, contentType)) {
306 PTRACE(1, "PVXML\tCannot load document " << url);
307 return FALSE;
310 if (!LoadVXML(PString((const char *)(const BYTE *)data, data.GetSize()))) {
311 PTRACE(1, "PVXML\tCannot load VXML in " << url);
312 return FALSE;
315 rootURL = url;
316 return TRUE;
319 BOOL PVXMLSession::LoadVXML(const PString & xmlText)
321 PWaitAndSignal m(sessionMutex);
323 loaded = FALSE;
324 rootURL = PString::Empty();
326 // parse the XML
327 xmlFile.RemoveAll();
328 if (!xmlFile.Load(xmlText)) {
329 PTRACE(1, "PVXML\tCannot parse root document: " << GetXMLError());
330 return FALSE;
333 PXMLElement * root = xmlFile.GetRootElement();
334 if (root == NULL)
335 return FALSE;
337 // find the first form
338 if ((currentForm = FindForm("")) == NULL)
339 return FALSE;
341 // start processing with this <form> element
342 currentNode = currentForm;
344 loaded = TRUE;
345 return TRUE;
348 PURL PVXMLSession::NormaliseResourceName(const PString & src)
350 // if resource name has a scheme, then use as is
351 PINDEX pos = src.Find(':');
352 if ((pos != P_MAX_INDEX) && (pos < 5))
353 return src;
355 if (rootURL.IsEmpty())
356 return "file:" + src;
358 // else use scheme and path from root document
359 PURL url = rootURL;
360 PStringArray path = url.GetPath();
361 PString pathStr;
362 if (path.GetSize() > 0) {
363 pathStr += path[0];
364 PINDEX i;
365 for (i = 1; i < path.GetSize()-1; i++)
366 pathStr += "/" + path[i];
367 pathStr += "/" + src;
368 url.SetPathStr(pathStr);
371 return url;
375 BOOL PVXMLSession::RetrieveResource(const PURL & url, PBYTEArray & text, PString & contentType, PFilePath & fn)
377 BOOL loadFile = FALSE;
378 text.SetSize(0);
380 // do a HTTP get when appropriate
381 if ((url.GetScheme() *= "http") || (url.GetScheme() *= "https")) {
383 PWaitAndSignal m(cacheMutex);
385 // see if the URL is in the cache
386 PINDEX index = resourceCache->GetValuesIndex(url);
387 if (index != P_MAX_INDEX) {
389 // if too long since last examined, then expire the cache
390 PTimeInterval interval = PTime() - (*resourceCache)[index].loadTime;
391 if (interval.GetMilliSeconds() > 1000*60)
392 resourceCache->RemoveAt(index);
393 else {
394 PVXMLCacheItem & item = (*resourceCache)[index];
396 // if the cache indicates the resource was invalid
397 if (!item.ok)
398 return FALSE;
400 // check the content type, maybe
401 if (!contentType.IsEmpty() && (contentType != item.contentType))
402 return FALSE;
404 // set the file load information
405 fn = item.fn;
406 contentType = item.contentType;
407 loadFile = TRUE;
411 // resource was not in the cache, so add it
412 if (!loadFile) {
413 PHTTPClient client;
414 PINDEX contentLength;
415 PMIMEInfo outMIME, replyMIME;
417 // create a cache item indicating a failed load
418 PVXMLCacheItem * item = new PVXMLCacheItem(url);
419 item->ok = FALSE;
420 resourceCache->Append(item);
422 // get the resource header information
423 if (!client.GetDocument(url, outMIME, replyMIME)) {
424 PTRACE(2, "PVXML\tCannot load resource " << url);
425 return FALSE;
428 // get the length of the data
429 if (replyMIME.Contains(PHTTPClient::ContentLengthTag))
430 contentLength = (PINDEX)replyMIME[PHTTPClient::ContentLengthTag].AsUnsigned();
431 else
432 contentLength = P_MAX_INDEX;
434 // create the cache directory, if not already in existence
435 if (!cacheDir.Exists())
436 cacheDir.Create();
438 // create a filename for the cache item
439 PRandom r;
440 for (;;) {
441 fn = cacheDir + psprintf("url_%i.wav", r.Generate() % 1000000);
442 if (!PFile::Exists(fn))
443 break;
446 // open the cache file
447 PFile cacheFile;
448 if (!cacheFile.Open(fn, PFile::WriteOnly)) {
449 PTRACE(2, "PVXML\tCannot create temporary cache file " << fn);
450 return FALSE;
453 // download the resource into the cache file
454 PINDEX offs = 0;
455 for (;;) {
456 PINDEX len;
457 if (contentLength == P_MAX_INDEX)
458 len = CACHE_BUFFER_SIZE;
459 else if (offs == contentLength)
460 break;
461 else
462 len = PMIN(contentLength = offs, CACHE_BUFFER_SIZE);
464 if (!client.Read(offs + text.GetPointer(offs + len), len))
465 break;
467 len = client.GetLastReadCount();
468 if (!cacheFile.Write(offs + (const BYTE *)text, len))
469 break;
471 offs += len;
474 // set the cache information
475 item->ok = TRUE;
476 item->fn = fn;
477 item->loadTime = PTime();
478 item->contentType = replyMIME(PHTTPClient::ContentTypeTag);
480 // check the content type, maybe
481 if (!contentType.IsEmpty() && (contentType != item->contentType))
482 return FALSE;
484 // data is loaded
485 return TRUE;
489 // files on the local file system get loaded locally
490 else if (url.GetScheme() *= "file") {
492 fn = url.AsFilePath();
493 loadFile = TRUE;
496 // unknown schemes give an error
497 else
498 return FALSE;
500 return loadFile;
503 BOOL PVXMLSession::RetrieveResource(const PURL & url, PBYTEArray & text, PString & contentType)
505 PFilePath fn;
507 // get name of file
508 if (!RetrieveResource(url, text, contentType, fn))
509 return FALSE;
511 // if data was already loaded, do nothing
512 if (text.GetSize() != 0)
513 return TRUE;
516 // load the data
517 PFile file;
518 if (!file.Open(fn, PFile::ReadOnly))
519 return FALSE;
521 // read the data
522 off_t len = file.GetLength();
523 if (!file.Read(text.GetPointer(len), len))
524 return FALSE;
526 // set content type from extension, if required
527 if (!contentType.IsEmpty()) {
528 if (fn.GetType() *= ".vxml")
529 contentType = "text/vxml";
530 else if (fn.GetType() *= ".wav")
531 contentType = "audio/x-wav";
534 return TRUE;
537 PXMLElement * PVXMLSession::FindForm(const PString & id)
539 // NOTE: should have some flag to know if it is loaded
540 PXMLElement * root = xmlFile.GetRootElement();
541 if (root == NULL)
542 return NULL;
544 // Only handle search of top level nodes for <form> element
545 PINDEX i;
546 for (i = 0; i < root->GetSize(); i++) {
547 PXMLObject * xmlObject = root->GetElement(i);
548 if (xmlObject->IsElement()) {
549 PXMLElement * xmlElement = (PXMLElement*)xmlObject;
550 if (
551 (xmlElement->GetName() *= "form") &&
552 (id.IsEmpty() || (xmlElement->GetAttribute("id") *= id))
554 return xmlElement;
557 return NULL;
561 BOOL PVXMLSession::Open(BOOL isPCM)
563 if (isPCM)
564 return Open(new PVXMLChannelPCM(*this, TRUE), new PVXMLChannelPCM(*this, FALSE));
565 else
566 return Open(new PVXMLChannelG7231(*this, TRUE), new PVXMLChannelG7231(*this, FALSE));
570 BOOL PVXMLSession::Open(PVXMLChannel * in, PVXMLChannel * out)
572 if (!PIndirectChannel::Open(out, in))
573 return FALSE;
575 PWaitAndSignal m(sessionMutex);
576 outgoingChannel = out;
577 incomingChannel = in;
578 return TRUE;
582 BOOL PVXMLSession::Close()
584 PWaitAndSignal m(sessionMutex);
586 if (vxmlThread != NULL) {
587 vxmlThread->WaitForTermination();
588 delete vxmlThread;
589 vxmlThread = NULL;
592 outgoingChannel = NULL;
593 incomingChannel = NULL;
594 return PIndirectChannel::Close();
598 BOOL PVXMLSession::Execute()
600 PWaitAndSignal m(sessionMutex);
602 return ExecuteWithoutLock();
605 BOOL PVXMLSession::ExecuteWithoutLock()
607 // check to see if a vxml thread has stopped since last we looked
608 if ((vxmlThread != NULL) && (vxmlThread->IsTerminated())) {
609 vxmlThread->WaitForTermination();
610 delete vxmlThread;
611 vxmlThread = NULL;
614 // check to see if we are ending a call
615 if (forceEnd) {
616 OnEndSession();
617 return FALSE;
620 // if:
621 // no script has been loaded or
622 // there is already a thread running or
623 // a grammar defined or
624 // recording is in progress
625 // no outgoing channel
626 // then just return silence
628 if (!loaded || (vxmlThread != NULL) || recording || (outgoingChannel == NULL))
629 return TRUE;
631 // throw a thread to execute the VXML, because this can take some time
632 vxmlThread = PThread::Create(PCREATE_NOTIFIER(DialogExecute), 0, PThread::NoAutoDeleteThread);
633 return TRUE;
636 void PVXMLSession::DialogExecute(PThread &, INT)
638 // --- Process any recognition ---
640 // If we have an active grammar then check to see if there has been some recognition
641 if (activeGrammar) {
642 BOOL processGrammar(FALSE);
644 // Stop if we've matched a grammar or have a failed recognition
645 if (activeGrammar->GetState() == PVXMLGrammar::FILLED || activeGrammar->GetState() == PVXMLGrammar::NOMATCH)
646 processGrammar = TRUE;
647 // Stop the grammar if we've timed out
648 else if (listening && !IsPlaying()) {
649 activeGrammar->Stop();
650 processGrammar = TRUE;
653 // Let the loop run again if we're still waiting to time out and haven't resolved the grammar one way or the other
654 if (!processGrammar && listening)
655 return;
657 if (processGrammar) {
658 PVXMLGrammar::GrammarState state = activeGrammar->GetState();
659 PString value = activeGrammar->GetValue();
660 LoadGrammar(NULL);
661 listening = FALSE;
663 // Stop any playback
664 if (outgoingChannel != NULL) {
665 outgoingChannel->FlushQueue();
668 // Figure out what happened
669 PString eventname;
670 switch (state)
672 case PVXMLGrammar::FILLED:
673 eventname = "filled";
674 break;
675 case PVXMLGrammar::NOINPUT:
676 eventname = "noinput";
677 break;
678 case PVXMLGrammar::NOMATCH:
679 eventname = "nomatch";
680 break;
681 default:
682 ; //ERROR - unexpected grammar state
685 // Find the handler and move there
686 PXMLElement * handler = FindHandler(eventname);
687 if (handler != NULL)
688 currentNode = handler;
689 // otherwise move on to the next node (already pointed there)
691 } // (end) there is an active grammar
694 // --- Process the current element ---
696 // Process the current node
697 if (currentNode != NULL) {
699 if (!currentNode->IsElement())
700 TraverseAudio();
701 else {
702 PXMLElement * element = (PXMLElement*)currentNode;
703 PCaselessString nodeType = element->GetName();
704 PTRACE(3, "PVXML\t**** Processing VoiceXML element: <" << nodeType << "> ***");
706 if (nodeType *= "audio") {
707 TraverseAudio();
710 else if (nodeType *= "block") {
711 // check 'cond' attribute to see if this element's children are to be skipped
712 // go on and process the children
715 else if (nodeType *= "break") {
716 TraverseAudio();
719 else if (nodeType *= "disconnect") {
720 currentNode = NULL;
723 else if (nodeType *= "field") {
724 currentField = (PXMLElement*)currentNode;
725 timeout = DEFAULT_TIMEOUT;
726 TraverseGrammar(); // this will set activeGrammar
729 else if (nodeType *= "form") {
730 // this is now the current element - go on
731 currentForm = element;
732 currentField = NULL; // no active field in a new form
735 else if (nodeType *= "goto") {
736 TraverseGoto();
739 else if (nodeType *= "grammar") {
740 TraverseGrammar(); // this will set activeGrammar
743 else if (nodeType *= "record") {
744 TraverseRecord();
747 else if (nodeType *= "prompt") {
748 // LATER:
749 // check 'cond' attribute to see if the children of this node should be processed
750 // check 'count' attribute to see if this node should be processed
751 // flush all prompts if 'bargein' attribute is set to false
753 // Update timeout of current recognition (if 'timeout' attribute is set)
754 if (element->HasAttribute("timeout")) {
755 PTimeInterval timeout = StringToTime(element->GetAttribute("timeout"));
758 // go on to process the children
761 else if (nodeType *= "say-as") {
764 else if (nodeType *= "value") {
765 TraverseAudio();
768 else if (nodeType *= "var") {
770 } // (end) else the current node is a PXMLElement
771 } // (end) there is a current node
774 // --- Move along the XML tree ---
776 // Wait for the buffer to complete before quiting
777 if (currentNode == NULL) {
778 if (IsPlaying())
779 return;
782 // if the current node has children, then process the first child
783 else if (currentNode->IsElement() && (((PXMLElement *)currentNode)->GetElement(0) != NULL))
784 currentNode = ((PXMLElement *)currentNode)->GetElement(0);
786 // else process the next sibling
787 else {
788 // Keep moving up the parents until we find a next sibling
789 while ((currentNode != NULL) && currentNode->GetNextObject() == NULL) {
790 currentNode = currentNode->GetParent();
791 // if we are on the backwards traversal through a <field> then wait
792 // for a grammar recognition and throw events if necessary
793 if (currentNode != NULL && (currentNode->IsElement() == TRUE) && (((PXMLElement*)currentNode)->GetName() *= "field")) {
794 listening = TRUE;
795 PlaySilence(timeout);
799 if (currentNode != NULL)
800 currentNode = currentNode->GetNextObject();
803 // Determine if we should quit
804 if ((currentNode == NULL) && (activeGrammar == NULL) && !IsPlaying() && !IsRecording()) {
805 if (OnEmptyAction())
806 forceEnd = TRUE;
810 BOOL PVXMLSession::OnUserInput(const PString & str)
812 PWaitAndSignal m(sessionMutex);
814 // record
815 if (recording) {
816 if (recordDTMFTerm)
817 RecordEnd();
818 return TRUE;
821 // playback
822 else if (activeGrammar != NULL && activeGrammar->OnUserInput(str)) {
823 ExecuteWithoutLock();
824 return TRUE;
827 return FALSE;
830 BOOL PVXMLSession::TraverseRecord()
832 if (currentNode->IsElement()) {
834 PString strName;
835 PXMLElement * element = (PXMLElement *)currentNode;
837 // Get the name (name)
838 if (element->HasAttribute("name"))
839 strName = element->GetAttribute("name");
840 else if (element->HasAttribute("id"))
841 strName = element->GetAttribute("id");
843 // Get the destination filename (dest)
844 PString strDest("c:\\temp.wav");
845 if (element->HasAttribute("dest"))
846 strDest = element->GetAttribute("dest");
848 // see if we need a beep
849 if (element->GetAttribute("beep").ToLower() *= "true") {
850 PBYTEArray beepData;
851 GetBeepData(beepData, 1000);
852 if (beepData.GetSize() != 0)
853 PlayData(beepData);
856 // For some reason, if the file is there the create
857 // seems to fail.
858 PFile::Remove(strDest);
859 PFilePath file(strDest);
861 // Get max record time (maxtime)
862 PTimeInterval maxTime = PMaxTimeInterval;
863 if (element->HasAttribute("maxtime"))
864 maxTime = StringToTime(element->GetAttribute("maxtime"));
866 // Get terminating silence duration (finalsilence)
867 PTimeInterval termTime(3000);
868 if (element->HasAttribute("finalsilence"))
869 termTime = StringToTime(element->GetAttribute("finalsilence"));
871 // Get dtmf term (dtmfterm)
872 BOOL dtmfTerm = TRUE;
873 if (element->HasAttribute("dtmfterm"))
874 dtmfTerm = !(element->GetAttribute("dtmfterm").ToLower() *= "false");
876 // create a semaphore, and then wait for the recording to terminate
877 StartRecording(file, dtmfTerm, maxTime, termTime);
878 recordSync.Wait(maxTime);
880 // when this returns, we are done
881 EndRecording();
884 return TRUE;
887 PString PVXMLSession::GetXMLError() const
889 return psprintf("(%i:%i) ", xmlFile.GetErrorLine(), xmlFile.GetErrorColumn()) + xmlFile.GetErrorString();
892 PString PVXMLSession::EvaluateExpr(const PString & oexpr)
894 PString expr = oexpr.Trim();
896 // see if all digits
897 PINDEX i;
898 BOOL allDigits = TRUE;
899 for (i = 0; i < expr.GetLength(); i++) {
900 allDigits = allDigits && isdigit(expr[i]);
903 if (allDigits)
904 return expr;
906 return GetVar(expr);
909 PString PVXMLSession::GetVar(const PString & ostr) const
911 PString str = ostr;
912 PString scope;
914 // get scope
915 PINDEX pos = str.Find('.');
916 if (pos != P_MAX_INDEX) {
917 scope = str.Left(pos);
918 str = str.Mid(pos+1);
921 // process session scope
922 if (scope.IsEmpty() || (scope *= "session")) {
923 if (sessionVars.Contains(str))
924 return sessionVars(str);
927 // assume any other scope is actually document or application
928 return documentVars(str);
931 void PVXMLSession::SetVar(const PString & ostr, const PString & val)
933 PString str = ostr;
934 PString scope;
936 // get scope
937 PINDEX pos = str.Find('.');
938 if (pos != P_MAX_INDEX) {
939 scope = str.Left(pos);
940 str = str.Mid(pos+1);
943 // do session scope
944 if (scope.IsEmpty() || (scope *= "session")) {
945 sessionVars.SetAt(str, val);
946 return;
949 PTRACE(3, "PVXML\tDocument: " << str << " = \"" << val << "\"");
951 // assume any other scope is actually document or application
952 documentVars.SetAt(str, val);
955 BOOL PVXMLSession::PlayFile(const PString & fn, PINDEX repeat, PINDEX delay, BOOL autoDelete)
957 if (outgoingChannel != NULL) {
958 outgoingChannel->QueueFile(fn, repeat, delay, autoDelete);
959 AllowClearCall();
962 return TRUE;
965 BOOL PVXMLSession::PlayCommand(const PString & cmd, PINDEX repeat, PINDEX delay)
967 if (outgoingChannel != NULL) {
968 outgoingChannel->QueueCommand(cmd, repeat, delay);
969 AllowClearCall();
972 return TRUE;
975 BOOL PVXMLSession::PlayData(const PBYTEArray & data, PINDEX repeat, PINDEX delay)
977 if (outgoingChannel != NULL) {
978 outgoingChannel->QueueData(data, repeat, delay);
979 AllowClearCall();
982 return TRUE;
986 void PVXMLSession::GetBeepData(PBYTEArray & data, unsigned ms)
988 if (outgoingChannel != NULL)
989 outgoingChannel->GetBeepData(data, ms);
993 BOOL PVXMLSession::PlaySilence(const PTimeInterval & timeout)
995 return PlaySilence((PINDEX)timeout.GetMilliSeconds());
998 BOOL PVXMLSession::PlaySilence(PINDEX msecs)
1000 if (outgoingChannel != NULL) {
1001 PBYTEArray nothing;
1002 outgoingChannel->QueueData(nothing, 1, msecs);
1003 AllowClearCall();
1006 return TRUE;
1009 BOOL PVXMLSession::PlayResource(const PURL & url, PINDEX repeat, PINDEX delay)
1011 if (outgoingChannel != NULL) {
1012 outgoingChannel->QueueResource(url, repeat, delay);
1013 AllowClearCall();
1016 return TRUE;
1019 BOOL PVXMLSession::LoadGrammar(PVXMLGrammar * grammar)
1021 if (activeGrammar != NULL) {
1022 delete activeGrammar;
1023 activeGrammar = FALSE;
1026 activeGrammar = grammar;
1028 return TRUE;
1031 BOOL PVXMLSession::PlayText(const PString & text, PTextToSpeech::TextType type, PINDEX repeat, PINDEX delay)
1033 if (textToSpeech != NULL) {
1034 PFilePath tmpfname("tts", NULL);
1035 PRandom r;
1036 PFilePath fname(tmpfname.GetDirectory() + (psprintf("tts_%i.wav", r.Generate() % 1000000)));
1037 if (!textToSpeech->OpenFile(fname)) {
1038 PTRACE(2, "PVXML\tcannot open file " << fname);
1039 } else {
1040 BOOL spoken = textToSpeech->Speak(text, type);
1041 if (!textToSpeech->Close()) {
1042 PTRACE(2, "PVXML\tcannot close TTS engine");
1044 if (!spoken) {
1045 PTRACE(2, "PVXML\tcannot speak text using TTS engine");
1046 } else if (!PlayFile(fname, repeat, delay, TRUE)) {
1047 PTRACE(2, "PVXML\tCannot play " << fname);
1048 } else {
1049 PTRACE(2, "PVXML\tText queued");
1054 return TRUE;
1057 void PVXMLSession::SetPause(BOOL _pause)
1059 incomingChannel->SetPause(_pause);
1060 outgoingChannel->SetPause(_pause);
1065 BOOL PVXMLSession::IsPlaying() const
1067 return (outgoingChannel != NULL) && outgoingChannel->IsPlaying();
1070 BOOL PVXMLSession::StartRecording(const PFilePath & _recordFn,
1071 BOOL _recordDTMFTerm,
1072 const PTimeInterval & _recordMaxTime,
1073 const PTimeInterval & _recordFinalSilence)
1075 recording = TRUE;
1076 recordFn = _recordFn;
1077 recordDTMFTerm = _recordDTMFTerm;
1078 recordMaxTime = _recordMaxTime;
1079 recordFinalSilence = _recordFinalSilence;
1081 if (incomingChannel != NULL)
1082 return incomingChannel->StartRecording(recordFn, (unsigned )recordFinalSilence.GetMilliSeconds());
1084 return FALSE;
1087 void PVXMLSession::RecordEnd()
1089 if (recording)
1090 recordSync.Signal();
1093 BOOL PVXMLSession::EndRecording()
1095 if (recording) {
1096 recording = FALSE;
1097 if (incomingChannel != NULL)
1098 return incomingChannel->EndRecording();
1101 return FALSE;
1105 BOOL PVXMLSession::IsRecording() const
1107 return (incomingChannel != NULL) && incomingChannel->IsRecording();
1110 PWAVFile * PVXMLSession::CreateWAVFile(const PFilePath & fn, PFile::OpenMode mode, int opts, unsigned fmt)
1112 return new PWAVFile(fn, mode, opts, fmt);
1115 void PVXMLSession::AllowClearCall()
1117 loaded = TRUE;
1120 BOOL PVXMLSession::TraverseAudio()
1122 if (!currentNode->IsElement()) {
1123 PlayText(((PXMLData *)currentNode)->GetString());
1126 else {
1127 PXMLElement * element = (PXMLElement *)currentNode;
1129 if (element->GetName() *= "value") {
1130 PString className = element->GetAttribute("class");
1131 PString value = EvaluateExpr(element->GetAttribute("expr"));
1132 SayAs(className, value);
1135 else if (element->GetName() *= "sayas") {
1136 PString className = element->GetAttribute("class");
1137 PXMLObject * object = element->GetElement();
1138 if (!object->IsElement()) {
1139 PString text = ((PXMLData *)object)->GetString();
1140 SayAs(className, text);
1144 else if (element->GetName() *= "break") {
1146 // msecs is VXML 1.0
1147 if (element->HasAttribute("msecs"))
1148 PlaySilence(element->GetAttribute("msecs").AsInteger());
1150 // time is VXML 2.0
1151 else if (element->HasAttribute("time")) {
1152 PTimeInterval time = StringToTime(element->GetAttribute("time"));
1153 PlaySilence(time);
1156 else if (element->HasAttribute("size")) {
1157 PString size = element->GetAttribute("size");
1158 if (size *= "none")
1160 else if (size *= "small")
1161 PlaySilence(SMALL_BREAK_MSECS);
1162 else if (size *= "large")
1163 PlaySilence(LARGE_BREAK_MSECS);
1164 else
1165 PlaySilence(MEDIUM_BREAK_MSECS);
1168 // default to medium pause
1169 else {
1170 PlaySilence(MEDIUM_BREAK_MSECS);
1174 else if (element->GetName() *= "audio") {
1175 BOOL loaded = FALSE;
1177 if (element->HasAttribute("src")) {
1179 PString str = element->GetAttribute("src").Trim();
1180 if (!str.IsEmpty() && (str[0] == '|')) {
1181 loaded = TRUE;
1182 PlayCommand(str.Mid(1));
1185 else {
1186 PFilePath fn;
1187 BOOL haveFn = FALSE;
1189 // get a normalised name for the resource
1190 PURL url = NormaliseResourceName(str);
1192 if ((url.GetScheme() *= "http") || (url.GetScheme() *= "https")) {
1194 PString contentType;
1195 PBYTEArray data;
1196 if (RetrieveResource(url, data, contentType, fn))
1197 haveFn = TRUE;
1200 // attempt to load the resource if a file
1201 else if (url.GetScheme() *= "file") {
1202 fn = url.AsFilePath();
1203 haveFn = TRUE;
1206 // check the file type
1207 if (haveFn) {
1208 PWAVFile * wavFile = outgoingChannel->CreateWAVFile(fn);
1209 if (wavFile == NULL)
1210 PTRACE(3, "PVXML\tCannot create audio file " + fn);
1211 else if (!wavFile->IsOpen())
1212 delete wavFile;
1213 else {
1214 loaded = TRUE;
1215 PlayFile(fn);
1220 if (loaded) {
1221 // skip to the next node
1222 if (element->HasSubObjects())
1223 currentNode = element->GetElement(element->GetSize() - 1);
1228 else
1229 PTRACE(3, "PVXML\tUnknown audio tag " << element->GetName() << " encountered");
1232 return TRUE;
1236 BOOL PVXMLSession::TraverseGoto() // <goto>
1238 PAssert(currentNode != NULL, "ProcessGotoElement(): Expected valid node");
1239 if (currentNode == NULL)
1240 return FALSE;
1242 // LATER: handle expr, expritem, fetchaudio, fetchhint, fetchtimeout, maxage, maxstale
1244 PAssert(currentNode->IsElement(), "ProcessGotoElement(): Expected element");
1246 // nextitem
1247 PString nextitem = ((PXMLElement*)currentNode)->GetAttribute("nextitem");
1248 if (!nextitem.IsEmpty()) {
1249 // LATER: Take out the optional #
1250 currentForm = FindForm(nextitem);
1251 currentNode = currentForm;
1252 if (currentForm == NULL) {
1253 // LATER: throw "error.semantic" or "error.badfetch" -- lookup which
1254 return FALSE;
1256 return TRUE;
1259 // next
1260 PString next = ((PXMLElement*)currentNode)->GetAttribute("next");
1261 // LATER: fixup filename to prepend path
1262 if (!next.IsEmpty())
1264 PURL url = NormaliseResourceName(next);
1265 if (!LoadURL(url)) {
1266 // LATER: throw 'error.badfetch'
1267 return FALSE;
1270 // LATER: handle '#' for picking the correct form (inside of the document)
1271 if (currentForm == NULL) {
1272 // LATER: throw 'error.badfetch'
1273 return FALSE;
1275 return TRUE;
1278 return FALSE;
1281 BOOL PVXMLSession::TraverseGrammar() // <grammar>
1283 // LATER: A bunch of work to do here!
1285 // For now we only support the builtin digits type and do not parse any grammars.
1287 // NOTE: For now we will process both <grammar> and <field> here.
1288 // NOTE: Later there needs to be a check for <grammar> which will pull
1289 // out the text and process a grammar like '1 | 2'
1291 // Right now we only support one active grammar.
1292 if (activeGrammar != NULL) {
1293 PTRACE(2, "PVXML\tWarning: can only process one grammar at a time, ignoring previous grammar");
1294 delete activeGrammar;
1295 activeGrammar = NULL;
1298 PVXMLGrammar * newGrammar = NULL;
1300 // Is this a built-in type?
1301 PString type = ((PXMLElement*)currentNode)->GetAttribute("type");
1302 if (!type.IsEmpty()) {
1303 PStringArray tokens = type.Tokenise("?;", TRUE);
1304 PString builtintype;
1305 if (tokens.GetSize() > 0)
1306 builtintype = tokens[0];
1308 if (builtintype *= "digits") {
1309 PINDEX minDigits(1);
1310 PINDEX maxDigits(100);
1312 // look at each parameter
1313 for (PINDEX i(1); i < tokens.GetSize(); i++) {
1314 PStringArray params = tokens[i].Tokenise("=", TRUE);
1315 if (params.GetSize() == 2) {
1316 if (params[0] *= "minlength") {
1317 minDigits = params[1].AsInteger();
1319 else if (params[0] *= "maxlength") {
1320 maxDigits = params[1].AsInteger();
1322 else if (params[0] *= "length") {
1323 minDigits = maxDigits = params[1].AsInteger();
1326 else {
1327 // Invalid parameter skipped
1328 // LATER: throw 'error.semantic'
1331 newGrammar = new PVXMLDigitsGrammar((PXMLElement*)currentNode, minDigits, maxDigits, "");
1333 else {
1334 // LATER: throw 'error.unsupported'
1335 return FALSE;
1339 if (newGrammar != NULL)
1340 return LoadGrammar(newGrammar);
1342 return TRUE;
1345 // Finds the proper event hander for 'noinput', 'filled', 'nomatch' and 'error'
1346 // by searching the scope hiearchy from the current from
1347 PXMLElement * PVXMLSession::FindHandler(const PString & event)
1349 PAssert(currentNode->IsElement(), "Expected 'PXMLElement' in PVXMLSession::FindHandler");
1350 PXMLElement * tmp = (PXMLElement *)currentNode;
1351 PXMLElement * handler = NULL;
1353 // Look in all the way up the tree for a handler either explicitly or in a catch
1354 while (tmp != NULL) {
1355 // Check for an explicit hander - i.e. <error>, <filled>, <noinput>, <nomatch>, <help>
1356 if ((handler = tmp->GetElement(event)) != NULL)
1357 return handler;
1359 // Check for a <catch>
1360 if ((handler = tmp->GetElement("catch")) != NULL) {
1361 PString strCond = handler->GetAttribute("cond");
1362 if (strCond.Find(event))
1363 return handler;
1366 tmp = tmp->GetParent();
1369 return NULL;
1372 void PVXMLSession::SayAs(const PString & className, const PString & text)
1374 if (!text.IsEmpty()) {
1375 PTextToSpeech::TextType type = PTextToSpeech::Literal;
1377 if (className *= "digits")
1378 type = PTextToSpeech::Digits;
1380 else if (className *= "literal")
1381 type = PTextToSpeech::Literal;
1383 else if (className *= "number")
1384 type = PTextToSpeech::Number;
1386 else if (className *= "currency")
1387 type = PTextToSpeech::Currency;
1389 else if (className *= "time")
1390 type = PTextToSpeech::Time;
1392 else if (className *= "date")
1393 type = PTextToSpeech::Date;
1395 else if (className *= "phone")
1396 type = PTextToSpeech::Phone;
1398 else if (className *= "ipaddress")
1399 type = PTextToSpeech::IPAddress;
1401 else if (className *= "duration")
1402 type = PTextToSpeech::Duration;
1404 PlayText(text, type);
1408 PTimeInterval PVXMLSession::StringToTime(const PString & str)
1410 PTimeInterval timeout;
1412 long msecs = str.AsInteger();
1413 if (str.Find("ms") != P_MAX_INDEX)
1415 else if (str.Find("s") != P_MAX_INDEX)
1416 msecs = msecs * 1000;
1418 return PTimeInterval(msecs);
1423 ///////////////////////////////////////////////////////////////
1425 PVXMLChannel::PVXMLChannel(PVXMLSession & _vxml,
1426 BOOL incoming,
1427 const PString & fmtName,
1428 PINDEX frBytes,
1429 unsigned frTime,
1430 unsigned wavType,
1431 const PString & prefix)
1432 : vxml(_vxml),
1433 isIncoming(incoming),
1434 frameBytes(frBytes),
1435 frameTime(frTime),
1436 wavFileType(wavType),
1437 wavFilePrefix(prefix)
1439 PINDEX pos = fmtName.Find('/');
1440 if (pos == P_MAX_INDEX) {
1441 formatName = fmtName;
1442 sampleFrequency = 8000;
1443 } else {
1444 formatName = fmtName.Left(pos);
1445 sampleFrequency = fmtName.Mid(pos+1).AsUnsigned();
1447 closed = FALSE;
1448 wavFile = NULL;
1449 playing = FALSE;
1450 recording = FALSE;
1451 frameLen = frameOffs = 0;
1452 silentCount = 20; // wait 20 frames before playing the OGM
1453 paused = FALSE;
1457 PVXMLChannel::~PVXMLChannel()
1459 EndRecording();
1462 BOOL PVXMLChannel::IsOpen() const
1464 return !closed;
1467 BOOL PVXMLChannel::Close()
1469 PWaitAndSignal m(channelMutex);
1471 PIndirectChannel::Close();
1472 closed = TRUE;
1473 return TRUE;
1476 PString PVXMLChannel::AdjustWavFilename(const PString & ofn)
1478 if (wavFilePrefix.IsEmpty())
1479 return ofn;
1481 PString fn = ofn;
1483 // add in _g7231 prefix, if not there already
1484 PINDEX pos = ofn.FindLast('.');
1485 if (pos == P_MAX_INDEX) {
1486 if (fn.Right(wavFilePrefix.GetLength()) != wavFilePrefix)
1487 fn += wavFilePrefix;
1489 else {
1490 PString basename = ofn.Left(pos);
1491 PString ext = ofn.Mid(pos+1);
1492 if (basename.Right(wavFilePrefix.GetLength()) != wavFilePrefix)
1493 basename += wavFilePrefix;
1494 fn = basename + "." + ext;
1496 return fn;
1499 PWAVFile * PVXMLChannel::CreateWAVFile(const PFilePath & fn)
1501 PWAVFile * wav = vxml.CreateWAVFile(AdjustWavFilename(fn),
1502 isIncoming ? PFile::WriteOnly : PFile::ReadOnly,
1503 PFile::ModeDefault,
1504 wavFileType);
1506 if (!wav->IsOpen())
1507 PTRACE(1, "VXML\tCould not open WAV file " << wav->GetName());
1508 else {
1509 // Check the wave file header
1510 if (!isIncoming && !wav->IsValid())
1511 PTRACE(1, "VXML\tWAV file header invalid for " << wav->GetName());
1512 else {
1513 if (wav->GetFormat() != wavFileType)
1514 PTRACE(1, "VXML\tIncorrect codec (is " << wav->GetFormat()
1515 << " should be " << GetWavFileType()
1516 << ") in WAV file " << wav->GetName());
1517 else {
1518 if (wav->GetFormat() == PWAVFile::fmt_PCM &&
1519 (wav->GetSampleRate() != 8000 ||
1520 wav->GetChannels() != 1 ||
1521 wav->GetSampleSize() != 16))
1522 PTRACE(1, "VXML\tIncorrect format for PCM WAV file" << wav->GetName() << "\n"
1523 "Is " << wav->GetSampleSize() << " bits, "
1524 << (wav->GetChannels()==1 ? "mono " : "stereo ")
1525 << wav->GetSampleRate() << " Hz"
1526 " and should be a 16 Bit, Mono, 8000 Hz (8Khz)");
1527 else {
1528 PTRACE(4, "VXML\tOpened WAV file " << wav->GetName());
1529 return wav;
1535 delete wav;
1536 return NULL;
1540 BOOL PVXMLChannel::Write(const void * buf, PINDEX len)
1542 PWaitAndSignal mutex(channelMutex);
1544 if (closed)
1545 return FALSE;
1547 unsigned msecs = (len+frameBytes-1)/frameBytes * frameTime;
1548 delay.Delay(msecs);
1550 lastWriteCount = len;
1552 if (wavFile == NULL || !wavFile->IsOpen())
1553 return TRUE;
1555 // only look for silence if nothing is playing
1556 if (recording && !playing) {
1557 if (!IsSilenceFrame(buf, len))
1558 silenceRun = 0;
1559 else {
1560 silenceRun += msecs;
1561 if (silenceRun > finalSilence) {
1562 PTRACE(3, "PVXML\tTriggering end of record due to silence timeout");
1563 vxml.RecordEnd();
1568 return WriteFrame(buf, len);
1571 BOOL PVXMLChannel::StartRecording(const PFilePath & fn, unsigned _finalSilence)
1573 // if there is already a file open, close it
1574 EndRecording();
1576 // open the output file
1577 PWaitAndSignal mutex(channelMutex);
1578 wavFile = CreateWAVFile(fn);
1579 if (wavFile == NULL || !wavFile->IsOpen()) {
1580 PTRACE(2, "PVXML\tCannot create record file " << fn);
1581 delete wavFile;
1582 return FALSE;
1585 PTRACE(3, "PVXML\tStarting recording to " << fn);
1587 // save the number of seconds of silence that will terminate recording
1588 finalSilence = _finalSilence;
1589 silenceRun = 0;
1590 recording = TRUE;
1592 return TRUE;
1595 BOOL PVXMLChannel::EndRecording()
1597 PWaitAndSignal mutex(channelMutex);
1599 recording = FALSE;
1601 PTRACE(3, "PVXML\tRecording finished");
1603 if (wavFile == NULL)
1604 return TRUE;
1606 wavFile->Close();
1608 delete wavFile;
1609 wavFile = NULL;
1611 return TRUE;
1615 BOOL PVXMLChannel::Read(void * buffer, PINDEX amount)
1617 PWaitAndSignal m(channelMutex);
1619 if (closed)
1620 return FALSE;
1622 // Create the frame buffer using the amount of bytes the codec wants to
1623 // read. Different codecs use different read sizes.
1624 frameBuffer.SetMinSize(1024); //amount);
1626 // assume we are returning silence
1627 BOOL doSilence = TRUE;
1628 BOOL frameBoundary = FALSE;
1630 // if still outputting a frame from last time, then keep doing it
1631 if (frameOffs < frameLen) {
1633 frameBoundary = AdjustFrame(buffer, amount);
1634 doSilence = FALSE;
1636 } else {
1638 // if we are paused or in a delay, then do nothing
1639 if (paused || delayTimer.IsRunning())
1642 // if we are returning silence frames, then decrement the frame count
1643 else if (silentCount > 0)
1644 silentCount--;
1646 // if a channel is already open, don't do silence
1647 else if (GetBaseReadChannel() != NULL)
1648 doSilence = FALSE;
1650 // check play queue
1651 else {
1652 PINDEX qSize;
1654 PWaitAndSignal m(queueMutex);
1655 qSize = playQueue.GetSize();
1658 // if nothing in queue, then re-execute VXML
1659 if (qSize == 0) {
1660 if (!vxml.Execute())
1661 return FALSE;
1664 // otherwise queue the next data item
1665 else {
1667 PWaitAndSignal m(queueMutex);
1668 PVXMLQueueItem * qItem = (PVXMLQueueItem *)playQueue.GetAt(0);
1669 qItem->OnStart();
1670 qItem->Play(*this);
1672 doSilence = FALSE;
1673 totalData = 0;
1674 playing = TRUE;
1678 // if not doing silence, try and read more data
1679 if (!doSilence) {
1681 if (ReadFrame(amount)) {
1682 frameBoundary = AdjustFrame(buffer, amount);
1683 totalData += amount;
1685 } else {
1687 playing = FALSE;
1688 doSilence = TRUE;
1690 PTRACE(3, "PVXML\tFinished playing " << totalData << " bytes");
1691 PIndirectChannel::Close();
1693 // get the item that was just playing
1694 PINDEX delay;
1696 PWaitAndSignal m(queueMutex);
1697 PVXMLQueueItem * qItem = (PVXMLQueueItem *)playQueue.GetAt(0);
1698 PAssertNULL(qItem);
1700 // get the delay time BEFORE deleting the info
1701 delay = qItem->delay;
1703 // if the repeat count is zero, then dequeue entry
1704 if (--qItem->repeat == 0) {
1705 qItem->OnStop();
1706 delete playQueue.Dequeue();
1710 // if delay required, then setup the delay
1711 if (delay != 0) {
1712 PTRACE(3, "PVXML\tDelaying for " << delay);
1713 delayTimer = delay;
1716 // if no delay, re-evaluate VXML script
1717 else {
1718 PINDEX qSize;
1720 PWaitAndSignal m(queueMutex);
1721 qSize = playQueue.GetSize();
1723 if (qSize == 0) {
1724 if (!vxml.Execute())
1725 return FALSE;
1732 // start silence frame if required
1733 if (doSilence) {
1734 CreateSilenceFrame(amount);
1735 frameBoundary = AdjustFrame(buffer, amount);
1738 // delay to synchronise to frame boundary
1739 if (frameBoundary)
1740 delay.Delay((amount+frameBytes-1)/frameBytes * frameTime);
1742 return TRUE;
1746 BOOL PVXMLChannel::AdjustFrame(void * buffer, PINDEX amount)
1748 if ((frameOffs + amount) > frameLen) {
1749 PTRACE(5, "Reading past end of frame:offs=" << frameOffs << ",amt=" << amount << ",len=" << frameLen);
1750 amount = frameLen - frameOffs;
1753 memcpy(buffer, frameBuffer.GetPointer()+frameOffs, amount);
1754 frameOffs += amount;
1756 lastReadCount = amount;
1758 return frameOffs == frameLen;
1761 void PVXMLChannel::QueueResource(const PURL & url, PINDEX repeat, PINDEX delay)
1763 PTRACE(3, "PVXML\tEnqueueing resource " << url << " for playing");
1764 QueueItem(new PVXMLQueueURLItem(url, repeat, delay));
1767 void PVXMLChannel::QueueFile(const PString & fn, PINDEX repeat, PINDEX delay, BOOL autoDelete)
1769 PTRACE(3, "PVXML\tEnqueueing file " << fn << " for playing");
1770 QueueItem(new PVXMLQueueFilenameItem(fn, repeat, delay, autoDelete));
1773 void PVXMLChannel::QueueCommand(const PString & cmd, PINDEX repeat, PINDEX delay)
1775 PTRACE(3, "PVXML\tEnqueueing command " << cmd << " for playing");
1776 QueueItem(new PVXMLQueueCommandItem(cmd, formatName, sampleFrequency, repeat, delay));
1779 void PVXMLChannel::QueueData(const PBYTEArray & data, PINDEX repeat, PINDEX delay)
1781 PTRACE(3, "PVXML\tEnqueueing " << data.GetSize() << " bytes for playing");
1782 QueueItem(new PVXMLQueueDataItem(data, repeat, delay));
1785 void PVXMLChannel::QueueItem(PVXMLQueueItem * newItem)
1787 PWaitAndSignal mutex(queueMutex);
1788 playQueue.Enqueue(newItem);
1791 void PVXMLChannel::FlushQueue()
1793 PWaitAndSignal mutex(channelMutex);
1795 if (GetBaseReadChannel() != NULL)
1796 PIndirectChannel::Close();
1798 PWaitAndSignal m(queueMutex);
1799 PVXMLQueueItem * qItem;
1800 while ((qItem = playQueue.Dequeue()) != NULL)
1801 delete qItem;
1804 ///////////////////////////////////////////////////////////////
1806 PVXMLChannelPCM::PVXMLChannelPCM(PVXMLSession & vxml, BOOL incoming)
1807 : PVXMLChannel(vxml, incoming, "PCM-16", 16, 1, PWAVFile::PCM_WavFile, PString::Empty())
1811 BOOL PVXMLChannelPCM::WriteFrame(const void * buf, PINDEX len)
1813 return wavFile->Write(buf, len);
1816 BOOL PVXMLChannelPCM::ReadFrame(PINDEX amount)
1818 frameOffs = 0;
1819 frameLen = amount;
1821 BOOL result = PIndirectChannel::Read(frameBuffer.GetPointer(), frameLen);
1823 // if we did not read a full frame of audio, fill the end of the
1824 // frame with zeros.
1825 PINDEX count = GetLastReadCount();
1826 if (count < frameLen)
1827 memset(frameBuffer.GetPointer()+count, 0, frameLen-count);
1829 return result;
1832 void PVXMLChannelPCM::CreateSilenceFrame(PINDEX amount)
1834 frameOffs = 0;
1835 frameLen = amount;
1836 memset(frameBuffer.GetPointer(), 0, frameLen);
1839 BOOL PVXMLChannelPCM::IsSilenceFrame(const void * buf, PINDEX len) const
1841 // Calculate the average signal level of this frame
1842 int sum = 0;
1844 const short * pcm = (const short *)buf;
1845 const short * end = pcm + len/2;
1846 while (pcm != end) {
1847 if (*pcm < 0)
1848 sum -= *pcm++;
1849 else
1850 sum += *pcm++;
1853 // calc average
1854 unsigned level = sum / (len / 2);
1856 return level < 500; // arbitrary level
1859 static short beepData[] = { 0, 18784, 30432, 30400, 18784, 0, -18784, -30432, -30400, -18784 };
1862 void PVXMLChannelPCM::GetBeepData(PBYTEArray & data, unsigned ms)
1864 data.SetSize(0);
1865 while (data.GetSize() < (PINDEX)((ms * 8) / 2)) {
1866 PINDEX len = data.GetSize();
1867 data.SetSize(len + sizeof(beepData));
1868 memcpy(len + data.GetPointer(), beepData, sizeof(beepData));
1872 ///////////////////////////////////////////////////////////////
1874 PVXMLChannelG7231::PVXMLChannelG7231(PVXMLSession & vxml, BOOL incoming)
1875 : PVXMLChannel(vxml, incoming, "G.723.1", 24, 30, PWAVFile::fmt_VivoG7231, "_g7231")
1879 BOOL PVXMLChannelG7231::WriteFrame(const void * buf, PINDEX /*len*/)
1881 return wavFile->Write(buf, 24);
1884 BOOL PVXMLChannelG7231::ReadFrame(PINDEX /*amount*/)
1886 if (!PIndirectChannel::Read(frameBuffer.GetPointer(), 1))
1887 return FALSE;
1889 frameOffs = 0;
1890 static const PINDEX g7231Lens[] = { 24, 20, 4, 1 };
1891 frameLen = g7231Lens[frameBuffer[0]&3];
1893 return PIndirectChannel::Read(frameBuffer.GetPointer()+1, frameLen-1);
1896 void PVXMLChannelG7231::CreateSilenceFrame(PINDEX /*amount*/)
1898 frameOffs = 0;
1899 frameLen = 4;
1901 frameBuffer[0] = 2;
1902 memset(frameBuffer.GetPointer()+1, 0, 3);
1905 BOOL PVXMLChannelG7231::IsSilenceFrame(const void * buf, PINDEX len) const
1907 if (len == 4)
1908 return TRUE;
1909 if (buf == NULL)
1910 return FALSE;
1911 return ((*(const BYTE *)buf)&3) == 2;
1914 ///////////////////////////////////////////////////////////////
1916 PVXMLChannelG729::PVXMLChannelG729(PVXMLSession & vxml, BOOL incoming)
1917 : PVXMLChannel(vxml, incoming, "G.729", 10, 10, PWAVFile::fmt_G729, "_g729")
1921 BOOL PVXMLChannelG729::WriteFrame(const void * buf, PINDEX /*len*/)
1923 return wavFile->Write(buf, 10);
1926 BOOL PVXMLChannelG729::ReadFrame(PINDEX /*amount*/)
1928 return PIndirectChannel::Read(frameBuffer.GetPointer(), 10);
1931 void PVXMLChannelG729::CreateSilenceFrame(PINDEX /*amount*/)
1933 frameOffs = 0;
1934 frameLen = 10;
1936 memset(frameBuffer.GetPointer(), 0, 10);
1939 BOOL PVXMLChannelG729::IsSilenceFrame(const void * /*buf*/, PINDEX /*len*/) const
1941 return FALSE;
1944 ///////////////////////////////////////////////////////////////
1946 void PVXMLQueueFilenameItem::Play(PVXMLChannel & outgoingChannel)
1948 PChannel * chan = NULL;
1950 // check the file extension and open a .wav or a raw (.sw or .g723) file
1951 if ((fn.Right(4)).ToLower() == ".wav")
1952 chan = outgoingChannel.CreateWAVFile(fn);
1953 else {
1954 PFile * fileChan = new PFile(fn);
1955 if (fileChan->Open(PFile::ReadOnly))
1956 chan = fileChan;
1957 else
1958 delete fileChan;
1961 if (chan == NULL)
1962 PTRACE(3, "PVXML\tCannot open file \"" << fn << "\"");
1963 else {
1964 PTRACE(3, "PVXML\tPlaying file \"" << fn << "\"");
1965 outgoingChannel.SetReadChannel(chan, TRUE);
1969 void PVXMLQueueFilenameItem::OnStop()
1971 if (autoDelete)
1972 PFile::Remove(fn);
1975 ///////////////////////////////////////////////////////////////
1977 void PVXMLQueueCommandItem::Play(PVXMLChannel & outgoingChannel)
1979 cmd.Replace("%s", PString(PString::Unsigned, sampleFrequency));
1980 cmd.Replace("%f", format);
1982 // execute a command and send the output through the stream
1983 pipeCmd = new PPipeChannel;
1984 if (!pipeCmd->Open(cmd, PPipeChannel::ReadOnly)) {
1985 PTRACE(3, "PVXML\tCannot open command " << cmd);
1986 delete pipeCmd;
1987 return;
1990 if (pipeCmd == NULL)
1991 PTRACE(3, "PVXML\tCannot open command \"" << cmd << "\"");
1992 else {
1993 pipeCmd->Execute();
1994 PTRACE(3, "PVXML\tPlaying command \"" << cmd << "\"");
1995 outgoingChannel.SetReadChannel(pipeCmd, TRUE);
1999 void PVXMLQueueCommandItem::OnStop()
2001 if (pipeCmd != NULL) {
2002 pipeCmd->WaitForTermination();
2003 delete pipeCmd;
2007 ///////////////////////////////////////////////////////////////
2009 void PVXMLQueueURLItem::Play(PVXMLChannel & outgoingChannel)
2011 // open the resource
2012 PHTTPClient * client = new PHTTPClient;
2013 PMIMEInfo outMIME, replyMIME;
2014 int code = client->GetDocument(url, outMIME, replyMIME, FALSE);
2015 if ((code != 200) || (replyMIME(PHTTP::TransferEncodingTag) *= PHTTP::ChunkedTag))
2016 delete client;
2017 else {
2018 outgoingChannel.SetReadChannel(client, TRUE);
2022 ///////////////////////////////////////////////////////////////
2024 void PVXMLQueueDataItem::Play(PVXMLChannel & outgoingChannel)
2026 PMemoryFile * chan = new PMemoryFile(data);
2027 PTRACE(3, "PVXML\tPlaying " << data.GetSize() << " bytes");
2028 outgoingChannel.SetReadChannel(chan, TRUE);
2031 /////////////////////////////////////////////////////////////////////////////////////////
2033 PVXMLGrammar::PVXMLGrammar(PXMLElement * _field)
2034 : field(_field), state(PVXMLGrammar::NOINPUT)
2038 //////////////////////////////////////////////////////////////////
2040 PVXMLMenuGrammar::PVXMLMenuGrammar(PXMLElement * _field)
2041 : PVXMLGrammar(_field)
2045 //////////////////////////////////////////////////////////////////
2047 PVXMLDigitsGrammar::PVXMLDigitsGrammar(PXMLElement * _field, PINDEX _minDigits, PINDEX _maxDigits, PString _terminators)
2048 : PVXMLGrammar(_field),
2049 minDigits(_minDigits),
2050 maxDigits(_maxDigits),
2051 terminators(_terminators)
2053 PAssert(_minDigits <= _maxDigits, "Error - invalid grammar parameter");
2056 BOOL PVXMLDigitsGrammar::OnUserInput(const PString & str)
2058 // Ignore any other keys if we've already filled the grammar
2059 if (state == PVXMLGrammar::FILLED || state == PVXMLGrammar::NOMATCH)
2060 return TRUE;
2062 // Does this string contain a terminator?
2063 PINDEX idx = str.FindOneOf(terminators);
2064 if (idx != P_MAX_INDEX) {
2065 // ??? should we return the string with the terminator
2066 value += str.Left(idx);
2067 state = (value.GetLength() >= minDigits && value.GetLength() <= maxDigits) ?
2068 PVXMLGrammar::FILLED :
2069 PVXMLGrammar::NOMATCH;
2070 return TRUE;
2073 // Otherwise add to the grammar and check to see if we're done
2074 value += str;
2075 if (value.GetLength() == maxDigits) {
2076 state = PVXMLGrammar::FILLED; // the grammar is filled!
2077 return TRUE;
2080 return FALSE;
2084 void PVXMLDigitsGrammar::Stop()
2086 // Stopping recognition here may change the state if something was
2087 // recognized but it didn't fill the number of digits requested
2088 if (!value.IsEmpty())
2089 state = PVXMLGrammar::NOMATCH;
2090 // otherwise the state will stay as NOINPUT
2093 //////////////////////////////////////////////////////////////////
2095 #endif