4 * VXML engine for pwlib library
6 * Copyright (C) 2002 Equivalence Pty. Ltd.
8 * The contents of this file are subject to the Mozilla Public License
9 * Version 1.0 (the "License"); you may not use this file except in
10 * compliance with the License. You may obtain a copy of the License at
11 * http://www.mozilla.org/MPL/
13 * Software distributed under the License is distributed on an "AS IS"
14 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
15 * the License for the specific language governing rights and limitations
18 * The Original Code is Portable Windows Library.
20 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
22 * Contributor(s): ______________________________________.
25 * Revision 1.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
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
152 #pragma implementation "vxml.h"
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;
188 incomingChannel
= NULL
;
189 outgoingChannel
= NULL
;
194 activeGrammar
= NULL
;
198 SetTextToSpeech(_tts
, autoDelete
);
200 PWaitAndSignal
m(cacheMutex
);
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
)) {
210 if (cacheFile
.Open(cacheInfo
, PFile::ReadOnly
)) {
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()
232 if ((textToSpeech
!= NULL
) && autoDeleteTextToSpeech
) {
236 PWaitAndSignal
m(cacheMutex
);
239 // write out the cache information
240 if (cacheCount
== 0) {
241 PFilePath cacheInfo
= cacheDir
+ "cache.txt";
243 if (cacheFile
.Open(cacheInfo
, PFile::WriteOnly
)) {
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")
254 delete resourceCache
;
255 resourceCache
= NULL
;
259 void PVXMLSession::SetTextToSpeech(PTextToSpeech
* _tts
, BOOL autoDelete
)
261 PWaitAndSignal
m(sessionMutex
);
263 if (autoDeleteTextToSpeech
&& (textToSpeech
!= NULL
))
266 autoDeleteTextToSpeech
= autoDelete
;
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
);
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)
305 if (!RetrieveResource(url
, data
, contentType
)) {
306 PTRACE(1, "PVXML\tCannot load document " << url
);
310 if (!LoadVXML(PString((const char *)(const BYTE
*)data
, data
.GetSize()))) {
311 PTRACE(1, "PVXML\tCannot load VXML in " << url
);
319 BOOL
PVXMLSession::LoadVXML(const PString
& xmlText
)
321 PWaitAndSignal
m(sessionMutex
);
324 rootURL
= PString::Empty();
328 if (!xmlFile
.Load(xmlText
)) {
329 PTRACE(1, "PVXML\tCannot parse root document: " << GetXMLError());
333 PXMLElement
* root
= xmlFile
.GetRootElement();
337 // find the first form
338 if ((currentForm
= FindForm("")) == NULL
)
341 // start processing with this <form> element
342 currentNode
= currentForm
;
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))
355 if (rootURL
.IsEmpty())
356 return "file:" + src
;
358 // else use scheme and path from root document
360 PStringArray path
= url
.GetPath();
362 if (path
.GetSize() > 0) {
365 for (i
= 1; i
< path
.GetSize()-1; i
++)
366 pathStr
+= "/" + path
[i
];
367 pathStr
+= "/" + src
;
368 url
.SetPathStr(pathStr
);
375 BOOL
PVXMLSession::RetrieveResource(const PURL
& url
, PBYTEArray
& text
, PString
& contentType
, PFilePath
& fn
)
377 BOOL loadFile
= FALSE
;
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
);
394 PVXMLCacheItem
& item
= (*resourceCache
)[index
];
396 // if the cache indicates the resource was invalid
400 // check the content type, maybe
401 if (!contentType
.IsEmpty() && (contentType
!= item
.contentType
))
404 // set the file load information
406 contentType
= item
.contentType
;
411 // resource was not in the cache, so add it
414 PINDEX contentLength
;
415 PMIMEInfo outMIME
, replyMIME
;
417 // create a cache item indicating a failed load
418 PVXMLCacheItem
* item
= new PVXMLCacheItem(url
);
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
);
428 // get the length of the data
429 if (replyMIME
.Contains(PHTTPClient::ContentLengthTag
))
430 contentLength
= (PINDEX
)replyMIME
[PHTTPClient::ContentLengthTag
].AsUnsigned();
432 contentLength
= P_MAX_INDEX
;
434 // create the cache directory, if not already in existence
435 if (!cacheDir
.Exists())
438 // create a filename for the cache item
441 fn
= cacheDir
+ psprintf("url_%i.wav", r
.Generate() % 1000000);
442 if (!PFile::Exists(fn
))
446 // open the cache file
448 if (!cacheFile
.Open(fn
, PFile::WriteOnly
)) {
449 PTRACE(2, "PVXML\tCannot create temporary cache file " << fn
);
453 // download the resource into the cache file
457 if (contentLength
== P_MAX_INDEX
)
458 len
= CACHE_BUFFER_SIZE
;
459 else if (offs
== contentLength
)
462 len
= PMIN(contentLength
= offs
, CACHE_BUFFER_SIZE
);
464 if (!client
.Read(offs
+ text
.GetPointer(offs
+ len
), len
))
467 len
= client
.GetLastReadCount();
468 if (!cacheFile
.Write(offs
+ (const BYTE
*)text
, len
))
474 // set the cache information
477 item
->loadTime
= PTime();
478 item
->contentType
= replyMIME(PHTTPClient::ContentTypeTag
);
480 // check the content type, maybe
481 if (!contentType
.IsEmpty() && (contentType
!= item
->contentType
))
489 // files on the local file system get loaded locally
490 else if (url
.GetScheme() *= "file") {
492 fn
= url
.AsFilePath();
496 // unknown schemes give an error
503 BOOL
PVXMLSession::RetrieveResource(const PURL
& url
, PBYTEArray
& text
, PString
& contentType
)
508 if (!RetrieveResource(url
, text
, contentType
, fn
))
511 // if data was already loaded, do nothing
512 if (text
.GetSize() != 0)
518 if (!file
.Open(fn
, PFile::ReadOnly
))
522 off_t len
= file
.GetLength();
523 if (!file
.Read(text
.GetPointer(len
), len
))
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";
537 PXMLElement
* PVXMLSession::FindForm(const PString
& id
)
539 // NOTE: should have some flag to know if it is loaded
540 PXMLElement
* root
= xmlFile
.GetRootElement();
544 // Only handle search of top level nodes for <form> element
546 for (i
= 0; i
< root
->GetSize(); i
++) {
547 PXMLObject
* xmlObject
= root
->GetElement(i
);
548 if (xmlObject
->IsElement()) {
549 PXMLElement
* xmlElement
= (PXMLElement
*)xmlObject
;
551 (xmlElement
->GetName() *= "form") &&
552 (id
.IsEmpty() || (xmlElement
->GetAttribute("id") *= id
))
561 BOOL
PVXMLSession::Open(BOOL isPCM
)
564 return Open(new PVXMLChannelPCM(*this, TRUE
), new PVXMLChannelPCM(*this, FALSE
));
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
))
575 PWaitAndSignal
m(sessionMutex
);
576 outgoingChannel
= out
;
577 incomingChannel
= in
;
582 BOOL
PVXMLSession::Close()
584 PWaitAndSignal
m(sessionMutex
);
586 if (vxmlThread
!= NULL
) {
587 vxmlThread
->WaitForTermination();
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();
614 // check to see if we are ending a call
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
))
631 // throw a thread to execute the VXML, because this can take some time
632 vxmlThread
= PThread::Create(PCREATE_NOTIFIER(DialogExecute
), 0, PThread::NoAutoDeleteThread
);
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
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
)
657 if (processGrammar
) {
658 PVXMLGrammar::GrammarState state
= activeGrammar
->GetState();
659 PString value
= activeGrammar
->GetValue();
664 if (outgoingChannel
!= NULL
) {
665 outgoingChannel
->FlushQueue();
668 // Figure out what happened
672 case PVXMLGrammar::FILLED
:
673 eventname
= "filled";
675 case PVXMLGrammar::NOINPUT
:
676 eventname
= "noinput";
678 case PVXMLGrammar::NOMATCH
:
679 eventname
= "nomatch";
682 ; //ERROR - unexpected grammar state
685 // Find the handler and move there
686 PXMLElement
* handler
= FindHandler(eventname
);
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())
702 PXMLElement
* element
= (PXMLElement
*)currentNode
;
703 PCaselessString nodeType
= element
->GetName();
704 PTRACE(3, "PVXML\t**** Processing VoiceXML element: <" << nodeType
<< "> ***");
706 if (nodeType
*= "audio") {
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") {
719 else if (nodeType
*= "disconnect") {
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") {
739 else if (nodeType
*= "grammar") {
740 TraverseGrammar(); // this will set activeGrammar
743 else if (nodeType
*= "record") {
747 else if (nodeType
*= "prompt") {
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") {
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
) {
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
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")) {
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()) {
810 BOOL
PVXMLSession::OnUserInput(const PString
& str
)
812 PWaitAndSignal
m(sessionMutex
);
822 else if (activeGrammar
!= NULL
&& activeGrammar
->OnUserInput(str
)) {
823 ExecuteWithoutLock();
830 BOOL
PVXMLSession::TraverseRecord()
832 if (currentNode
->IsElement()) {
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") {
851 GetBeepData(beepData
, 1000);
852 if (beepData
.GetSize() != 0)
856 // For some reason, if the file is there the create
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
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();
898 BOOL allDigits
= TRUE
;
899 for (i
= 0; i
< expr
.GetLength(); i
++) {
900 allDigits
= allDigits
&& isdigit(expr
[i
]);
909 PString
PVXMLSession::GetVar(const PString
& ostr
) const
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
)
937 PINDEX pos
= str
.Find('.');
938 if (pos
!= P_MAX_INDEX
) {
939 scope
= str
.Left(pos
);
940 str
= str
.Mid(pos
+1);
944 if (scope
.IsEmpty() || (scope
*= "session")) {
945 sessionVars
.SetAt(str
, val
);
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
);
965 BOOL
PVXMLSession::PlayCommand(const PString
& cmd
, PINDEX repeat
, PINDEX delay
)
967 if (outgoingChannel
!= NULL
) {
968 outgoingChannel
->QueueCommand(cmd
, repeat
, delay
);
975 BOOL
PVXMLSession::PlayData(const PBYTEArray
& data
, PINDEX repeat
, PINDEX delay
)
977 if (outgoingChannel
!= NULL
) {
978 outgoingChannel
->QueueData(data
, repeat
, delay
);
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
) {
1002 outgoingChannel
->QueueData(nothing
, 1, msecs
);
1009 BOOL
PVXMLSession::PlayResource(const PURL
& url
, PINDEX repeat
, PINDEX delay
)
1011 if (outgoingChannel
!= NULL
) {
1012 outgoingChannel
->QueueResource(url
, repeat
, delay
);
1019 BOOL
PVXMLSession::LoadGrammar(PVXMLGrammar
* grammar
)
1021 if (activeGrammar
!= NULL
) {
1022 delete activeGrammar
;
1023 activeGrammar
= FALSE
;
1026 activeGrammar
= grammar
;
1031 BOOL
PVXMLSession::PlayText(const PString
& text
, PTextToSpeech::TextType type
, PINDEX repeat
, PINDEX delay
)
1033 if (textToSpeech
!= NULL
) {
1034 PFilePath
tmpfname("tts", NULL
);
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
);
1040 BOOL spoken
= textToSpeech
->Speak(text
, type
);
1041 if (!textToSpeech
->Close()) {
1042 PTRACE(2, "PVXML\tcannot close TTS engine");
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
);
1049 PTRACE(2, "PVXML\tText queued");
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
)
1076 recordFn
= _recordFn
;
1077 recordDTMFTerm
= _recordDTMFTerm
;
1078 recordMaxTime
= _recordMaxTime
;
1079 recordFinalSilence
= _recordFinalSilence
;
1081 if (incomingChannel
!= NULL
)
1082 return incomingChannel
->StartRecording(recordFn
, (unsigned )recordFinalSilence
.GetMilliSeconds());
1087 void PVXMLSession::RecordEnd()
1090 recordSync
.Signal();
1093 BOOL
PVXMLSession::EndRecording()
1097 if (incomingChannel
!= NULL
)
1098 return incomingChannel
->EndRecording();
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()
1120 BOOL
PVXMLSession::TraverseAudio()
1122 if (!currentNode
->IsElement()) {
1123 PlayText(((PXMLData
*)currentNode
)->GetString());
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());
1151 else if (element
->HasAttribute("time")) {
1152 PTimeInterval time
= StringToTime(element
->GetAttribute("time"));
1156 else if (element
->HasAttribute("size")) {
1157 PString size
= element
->GetAttribute("size");
1160 else if (size
*= "small")
1161 PlaySilence(SMALL_BREAK_MSECS
);
1162 else if (size
*= "large")
1163 PlaySilence(LARGE_BREAK_MSECS
);
1165 PlaySilence(MEDIUM_BREAK_MSECS
);
1168 // default to medium pause
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] == '|')) {
1182 PlayCommand(str
.Mid(1));
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
;
1196 if (RetrieveResource(url
, data
, contentType
, fn
))
1200 // attempt to load the resource if a file
1201 else if (url
.GetScheme() *= "file") {
1202 fn
= url
.AsFilePath();
1206 // check the file type
1208 PWAVFile
* wavFile
= outgoingChannel
->CreateWAVFile(fn
);
1209 if (wavFile
== NULL
)
1210 PTRACE(3, "PVXML\tCannot create audio file " + fn
);
1211 else if (!wavFile
->IsOpen())
1221 // skip to the next node
1222 if (element
->HasSubObjects())
1223 currentNode
= element
->GetElement(element
->GetSize() - 1);
1229 PTRACE(3, "PVXML\tUnknown audio tag " << element
->GetName() << " encountered");
1236 BOOL
PVXMLSession::TraverseGoto() // <goto>
1238 PAssert(currentNode
!= NULL
, "ProcessGotoElement(): Expected valid node");
1239 if (currentNode
== NULL
)
1242 // LATER: handle expr, expritem, fetchaudio, fetchhint, fetchtimeout, maxage, maxstale
1244 PAssert(currentNode
->IsElement(), "ProcessGotoElement(): Expected element");
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
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'
1270 // LATER: handle '#' for picking the correct form (inside of the document)
1271 if (currentForm
== NULL
) {
1272 // LATER: throw 'error.badfetch'
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();
1327 // Invalid parameter skipped
1328 // LATER: throw 'error.semantic'
1331 newGrammar
= new PVXMLDigitsGrammar((PXMLElement
*)currentNode
, minDigits
, maxDigits
, "");
1334 // LATER: throw 'error.unsupported'
1339 if (newGrammar
!= NULL
)
1340 return LoadGrammar(newGrammar
);
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
)
1359 // Check for a <catch>
1360 if ((handler
= tmp
->GetElement("catch")) != NULL
) {
1361 PString strCond
= handler
->GetAttribute("cond");
1362 if (strCond
.Find(event
))
1366 tmp
= tmp
->GetParent();
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
,
1427 const PString
& fmtName
,
1431 const PString
& prefix
)
1433 isIncoming(incoming
),
1434 frameBytes(frBytes
),
1436 wavFileType(wavType
),
1437 wavFilePrefix(prefix
)
1439 PINDEX pos
= fmtName
.Find('/');
1440 if (pos
== P_MAX_INDEX
) {
1441 formatName
= fmtName
;
1442 sampleFrequency
= 8000;
1444 formatName
= fmtName
.Left(pos
);
1445 sampleFrequency
= fmtName
.Mid(pos
+1).AsUnsigned();
1451 frameLen
= frameOffs
= 0;
1452 silentCount
= 20; // wait 20 frames before playing the OGM
1457 PVXMLChannel::~PVXMLChannel()
1462 BOOL
PVXMLChannel::IsOpen() const
1467 BOOL
PVXMLChannel::Close()
1469 PWaitAndSignal
m(channelMutex
);
1471 PIndirectChannel::Close();
1476 PString
PVXMLChannel::AdjustWavFilename(const PString
& ofn
)
1478 if (wavFilePrefix
.IsEmpty())
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
;
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
;
1499 PWAVFile
* PVXMLChannel::CreateWAVFile(const PFilePath
& fn
)
1501 PWAVFile
* wav
= vxml
.CreateWAVFile(AdjustWavFilename(fn
),
1502 isIncoming
? PFile::WriteOnly
: PFile::ReadOnly
,
1507 PTRACE(1, "VXML\tCould not open WAV file " << wav
->GetName());
1509 // Check the wave file header
1510 if (!isIncoming
&& !wav
->IsValid())
1511 PTRACE(1, "VXML\tWAV file header invalid for " << wav
->GetName());
1513 if (wav
->GetFormat() != wavFileType
)
1514 PTRACE(1, "VXML\tIncorrect codec (is " << wav
->GetFormat()
1515 << " should be " << GetWavFileType()
1516 << ") in WAV file " << wav
->GetName());
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)");
1528 PTRACE(4, "VXML\tOpened WAV file " << wav
->GetName());
1540 BOOL
PVXMLChannel::Write(const void * buf
, PINDEX len
)
1542 PWaitAndSignal
mutex(channelMutex
);
1547 unsigned msecs
= (len
+frameBytes
-1)/frameBytes
* frameTime
;
1550 lastWriteCount
= len
;
1552 if (wavFile
== NULL
|| !wavFile
->IsOpen())
1555 // only look for silence if nothing is playing
1556 if (recording
&& !playing
) {
1557 if (!IsSilenceFrame(buf
, len
))
1560 silenceRun
+= msecs
;
1561 if (silenceRun
> finalSilence
) {
1562 PTRACE(3, "PVXML\tTriggering end of record due to silence timeout");
1568 return WriteFrame(buf
, len
);
1571 BOOL
PVXMLChannel::StartRecording(const PFilePath
& fn
, unsigned _finalSilence
)
1573 // if there is already a file open, close it
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
);
1585 PTRACE(3, "PVXML\tStarting recording to " << fn
);
1587 // save the number of seconds of silence that will terminate recording
1588 finalSilence
= _finalSilence
;
1595 BOOL
PVXMLChannel::EndRecording()
1597 PWaitAndSignal
mutex(channelMutex
);
1601 PTRACE(3, "PVXML\tRecording finished");
1603 if (wavFile
== NULL
)
1615 BOOL
PVXMLChannel::Read(void * buffer
, PINDEX amount
)
1617 PWaitAndSignal
m(channelMutex
);
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
);
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)
1646 // if a channel is already open, don't do silence
1647 else if (GetBaseReadChannel() != NULL
)
1654 PWaitAndSignal
m(queueMutex
);
1655 qSize
= playQueue
.GetSize();
1658 // if nothing in queue, then re-execute VXML
1660 if (!vxml
.Execute())
1664 // otherwise queue the next data item
1667 PWaitAndSignal
m(queueMutex
);
1668 PVXMLQueueItem
* qItem
= (PVXMLQueueItem
*)playQueue
.GetAt(0);
1678 // if not doing silence, try and read more data
1681 if (ReadFrame(amount
)) {
1682 frameBoundary
= AdjustFrame(buffer
, amount
);
1683 totalData
+= amount
;
1690 PTRACE(3, "PVXML\tFinished playing " << totalData
<< " bytes");
1691 PIndirectChannel::Close();
1693 // get the item that was just playing
1696 PWaitAndSignal
m(queueMutex
);
1697 PVXMLQueueItem
* qItem
= (PVXMLQueueItem
*)playQueue
.GetAt(0);
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) {
1706 delete playQueue
.Dequeue();
1710 // if delay required, then setup the delay
1712 PTRACE(3, "PVXML\tDelaying for " << delay
);
1716 // if no delay, re-evaluate VXML script
1720 PWaitAndSignal
m(queueMutex
);
1721 qSize
= playQueue
.GetSize();
1724 if (!vxml
.Execute())
1732 // start silence frame if required
1734 CreateSilenceFrame(amount
);
1735 frameBoundary
= AdjustFrame(buffer
, amount
);
1738 // delay to synchronise to frame boundary
1740 delay
.Delay((amount
+frameBytes
-1)/frameBytes
* frameTime
);
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
)
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
)
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
);
1832 void PVXMLChannelPCM::CreateSilenceFrame(PINDEX 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
1844 const short * pcm
= (const short *)buf
;
1845 const short * end
= pcm
+ len
/2;
1846 while (pcm
!= end
) {
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
)
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))
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*/)
1902 memset(frameBuffer
.GetPointer()+1, 0, 3);
1905 BOOL
PVXMLChannelG7231::IsSilenceFrame(const void * buf
, PINDEX len
) const
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*/)
1936 memset(frameBuffer
.GetPointer(), 0, 10);
1939 BOOL
PVXMLChannelG729::IsSilenceFrame(const void * /*buf*/, PINDEX
/*len*/) const
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
);
1954 PFile
* fileChan
= new PFile(fn
);
1955 if (fileChan
->Open(PFile::ReadOnly
))
1962 PTRACE(3, "PVXML\tCannot open file \"" << fn
<< "\"");
1964 PTRACE(3, "PVXML\tPlaying file \"" << fn
<< "\"");
1965 outgoingChannel
.SetReadChannel(chan
, TRUE
);
1969 void PVXMLQueueFilenameItem::OnStop()
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
);
1990 if (pipeCmd
== NULL
)
1991 PTRACE(3, "PVXML\tCannot open command \"" << cmd
<< "\"");
1994 PTRACE(3, "PVXML\tPlaying command \"" << cmd
<< "\"");
1995 outgoingChannel
.SetReadChannel(pipeCmd
, TRUE
);
1999 void PVXMLQueueCommandItem::OnStop()
2001 if (pipeCmd
!= NULL
) {
2002 pipeCmd
->WaitForTermination();
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
))
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
)
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
;
2073 // Otherwise add to the grammar and check to see if we're done
2075 if (value
.GetLength() == maxDigits
) {
2076 state
= PVXMLGrammar::FILLED
; // the grammar is filled!
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 //////////////////////////////////////////////////////////////////