1 /*
2  * vxml.cxx
3  *
4  * VXML engine for pwlib library
5  *
6  * Copyright (C) 2002 Equivalence Pty. Ltd.
7  *
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/
12  *
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.
17  *
18  * The Original Code is Portable Windows Library.
19  *
20  * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
21  *
22  * Contributor(s): ______________________________________.
23  *
24  * $Revision: 29069 $
25  * $Author: rjongbloed $
26  * $Date: 2013-02-11 23:11:12 -0600 (Mon, 11 Feb 2013) $
27  */
28 
29 #ifdef __GNUC__
30 #pragma implementation "vxml.h"
31 #endif
32 
33 #include <ptlib.h>
34 
35 #define P_DISABLE_FACTORY_INSTANCES
36 
37 #if P_VXML
38 
39 #include <ptclib/vxml.h>
40 #include <ptclib/memfile.h>
41 #include <ptclib/random.h>
42 #include <ptclib/http.h>
43 
44 
45 static const bool DefaultBargeIn = true;
46 
47 
48 class PVXMLChannelPCM : public PVXMLChannel
49 {
50   PCLASSINFO(PVXMLChannelPCM, PVXMLChannel);
51 
52   public:
53     PVXMLChannelPCM();
54 
55   protected:
56     // overrides from PVXMLChannel
57     virtual PBoolean WriteFrame(const void * buf, PINDEX len);
58     virtual PBoolean ReadFrame(void * buffer, PINDEX amount);
59     virtual PINDEX CreateSilenceFrame(void * buffer, PINDEX amount);
60     virtual PBoolean IsSilenceFrame(const void * buf, PINDEX len) const;
61     virtual void GetBeepData(PBYTEArray & data, unsigned ms);
62 };
63 
64 
65 class PVXMLChannelG7231 : public PVXMLChannel
66 {
67   PCLASSINFO(PVXMLChannelG7231, PVXMLChannel);
68   public:
69     PVXMLChannelG7231();
70 
71     // overrides from PVXMLChannel
72     virtual PBoolean WriteFrame(const void * buf, PINDEX len);
73     virtual PBoolean ReadFrame(void * buffer, PINDEX amount);
74     virtual PINDEX CreateSilenceFrame(void * buffer, PINDEX amount);
75     virtual PBoolean IsSilenceFrame(const void * buf, PINDEX len) const;
76 };
77 
78 
79 class PVXMLChannelG729 : public PVXMLChannel
80 {
81   PCLASSINFO(PVXMLChannelG729, PVXMLChannel);
82   public:
83     PVXMLChannelG729();
84 
85     // overrides from PVXMLChannel
86     virtual PBoolean WriteFrame(const void * buf, PINDEX len);
87     virtual PBoolean ReadFrame(void * buffer, PINDEX amount);
88     virtual PINDEX CreateSilenceFrame(void * buffer, PINDEX amount);
89     virtual PBoolean IsSilenceFrame(const void * buf, PINDEX len) const;
90 };
91 
92 
93 static PVXMLNodeFactory::Worker<PVXMLNodeHandler> BlockNodeHandler("Block", true);
94 
95 #define TRAVERSE_NODE(name) \
96   class PVXMLTraverse##name : public PVXMLNodeHandler { \
97     virtual bool Start(PVXMLSession & session, PXMLElement & element) const \
98     { return session.Traverse##name(element); } \
99   }; \
100   static PVXMLNodeFactory::Worker<PVXMLTraverse##name> name##NodeHandler(#name, true)
101 
102 TRAVERSE_NODE(Audio);
103 TRAVERSE_NODE(Break);
104 TRAVERSE_NODE(Value);
105 TRAVERSE_NODE(SayAs);
106 TRAVERSE_NODE(Goto);
107 TRAVERSE_NODE(Grammar);
108 TRAVERSE_NODE(If);
109 TRAVERSE_NODE(Exit);
110 TRAVERSE_NODE(Var);
111 TRAVERSE_NODE(Submit);
112 TRAVERSE_NODE(Choice);
113 TRAVERSE_NODE(Property);
114 TRAVERSE_NODE(Disconnect);
115 
116 #define TRAVERSE_NODE2(name) \
117   class PVXMLTraverse##name : public PVXMLNodeHandler { \
118     virtual bool Start(PVXMLSession & session, PXMLElement & element) const \
119     { return session.Traverse##name(element); } \
120     virtual bool Finish(PVXMLSession & session, PXMLElement & element) const \
121     { return session.Traversed##name(element); } \
122   }; \
123   static PVXMLNodeFactory::Worker<PVXMLTraverse##name> name##NodeHandler(#name, true)
124 
125 TRAVERSE_NODE2(Menu);
126 TRAVERSE_NODE2(Form);
127 TRAVERSE_NODE2(Field);
128 TRAVERSE_NODE2(Transfer);
129 TRAVERSE_NODE2(Record);
130 TRAVERSE_NODE2(Prompt);
131 
132 class PVXMLTraverseEvent : public PVXMLNodeHandler
133 {
Start(PVXMLSession &,PXMLElement & element) const134   virtual bool Start(PVXMLSession &, PXMLElement & element) const
135   {
136     return element.GetAttribute("fired") == "true";
137   }
138 
Finish(PVXMLSession &,PXMLElement & element) const139   virtual bool Finish(PVXMLSession &, PXMLElement & element) const
140   {
141     element.SetAttribute("fired", "false");
142     return true;
143   }
144 };
145 static PVXMLNodeFactory::Worker<PVXMLTraverseEvent> FilledNodeHandler("Filled", true);
146 static PVXMLNodeFactory::Worker<PVXMLTraverseEvent> NoInputNodeHandler("NoInput", true);
147 static PVXMLNodeFactory::Worker<PVXMLTraverseEvent> NoMatchNodeHandler("NoMatch", true);
148 static PVXMLNodeFactory::Worker<PVXMLTraverseEvent> ErrorNodeHandler("Error", true);
149 static PVXMLNodeFactory::Worker<PVXMLTraverseEvent> CatchNodeHandler("Catch", true);
150 
151 #if PTRACING
152 class PVXMLTraverseLog : public PVXMLNodeHandler {
Start(PVXMLSession & session,PXMLElement & node) const153   virtual bool Start(PVXMLSession & session, PXMLElement & node) const
154   {
155     unsigned level = node.GetAttribute("level").AsUnsigned();
156     if (level == 0)
157       level = 3;
158     PTRACE(level, "VXML-Log\t" + session.EvaluateExpr(node.GetAttribute("expr")));
159     return true;
160   }
161 };
162 static PVXMLNodeFactory::Worker<PVXMLTraverseLog> LogNodeHandler("Log", true);
163 #endif
164 
165 
166 #define new PNEW
167 
168 
169 #define SMALL_BREAK_MSECS   1000
170 #define MEDIUM_BREAK_MSECS  2500
171 #define LARGE_BREAK_MSECS   5000
172 
173 
174 //////////////////////////////////////////////////////////
175 
GetContentType(const PFilePath & fn)176 static PString GetContentType(const PFilePath & fn)
177 {
178   PString type = fn.GetType();
179 
180   if (type *= ".vxml")
181     return "text/vxml";
182 
183   if (type *= ".wav")
184     return "audio/x-wav";
185 
186   return PString::Empty();
187 }
188 
189 
190 ///////////////////////////////////////////////////////////////
191 
PVXMLPlayable()192 PVXMLPlayable::PVXMLPlayable()
193   : m_vxmlChannel(NULL)
194   , m_subChannel(NULL)
195   , m_repeat(1)
196   , m_delay(0)
197   , m_sampleFrequency(8000)
198   , m_autoDelete(false)
199   , m_delayDone(false)
200 {
201 }
202 
203 
Open(PVXMLChannel & channel,const PString &,PINDEX delay,PINDEX repeat,PBoolean autoDelete)204 PBoolean PVXMLPlayable::Open(PVXMLChannel & channel, const PString &, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
205 {
206   m_vxmlChannel = &channel;
207   m_delay = delay;
208   m_repeat = repeat;
209   m_autoDelete = autoDelete;
210   return true;
211 }
212 
213 
OnRepeat()214 bool PVXMLPlayable::OnRepeat()
215 {
216   if (PAssertNULL(m_vxmlChannel) == NULL)
217     return false;
218 
219   if (m_repeat <= 1)
220     return false;
221 
222   --m_repeat;
223   return true;
224 }
225 
226 
OnDelay()227 bool PVXMLPlayable::OnDelay()
228 {
229   if (m_delayDone)
230     return false;
231 
232   m_delayDone = true;
233   if (m_delay == 0)
234     return false;
235 
236   if (PAssertNULL(m_vxmlChannel) == NULL)
237     return false;
238 
239   m_vxmlChannel->SetSilence(m_delay);
240   return true;
241 }
242 
243 
OnStop()244 void PVXMLPlayable::OnStop()
245 {
246   if (m_vxmlChannel == NULL || m_subChannel == NULL)
247     return;
248 
249   if (m_vxmlChannel->GetReadChannel() == m_subChannel)
250     m_vxmlChannel->SetReadChannel(NULL, false, true);
251 
252   delete m_subChannel;
253 }
254 
255 
256 ///////////////////////////////////////////////////////////////
257 
OnStart()258 bool PVXMLPlayableStop::OnStart()
259 {
260   if (m_vxmlChannel == NULL)
261     return false;
262 
263   m_vxmlChannel->SetSilence(500);
264   return false; // Return false so always stops
265 }
266 
267 
268 ///////////////////////////////////////////////////////////////
269 
Open(PVXMLChannel & chan,const PString & fn,PINDEX delay,PINDEX repeat,PBoolean autoDelete)270 PBoolean PVXMLPlayableFile::Open(PVXMLChannel & chan, const PString & fn, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
271 {
272   m_filePath = chan.AdjustWavFilename(fn);
273   if (!PFile::Exists(m_filePath)) {
274     PTRACE(2, "VXML\tPlayable file \"" << m_filePath << "\" not found.");
275     return false;
276   }
277 
278   return PVXMLPlayable::Open(chan, fn, delay, repeat, autoDelete);
279 }
280 
281 
OnStart()282 bool PVXMLPlayableFile::OnStart()
283 {
284   if (PAssertNULL(m_vxmlChannel) == NULL)
285     return false;
286 
287   PFile * file = NULL;
288 
289 #if P_WAVFILE
290   // check the file extension and open a .wav or a raw (.sw or .g723) file
291   if (m_filePath.GetType() == ".wav") {
292     file = m_vxmlChannel->CreateWAVFile(m_filePath);
293     if (file == NULL) {
294       PTRACE(2, "VXML\tCannot open WAV file \"" << m_filePath << '"');
295       return false;
296     }
297   }
298   else
299 #endif // P_WAVFILE
300   {
301     // Assume file just has bytes of correct media format
302     file = new PFile(m_filePath);
303     if (!file->Open(PFile::ReadOnly)) {
304       PTRACE(2, "VXML\tCould not open audio file \"" << m_filePath << '"');
305       delete file;
306       return false;
307     }
308   }
309 
310   PTRACE(3, "VXML\tPlaying file \"" << m_filePath << "\", " << file->GetLength() << " bytes");
311   m_subChannel = file;
312   return m_vxmlChannel->SetReadChannel(file, false);
313 }
314 
315 
OnRepeat()316 bool PVXMLPlayableFile::OnRepeat()
317 {
318   if (!PVXMLPlayable::OnRepeat())
319     return false;
320 
321   PFile * file = dynamic_cast<PFile *>(m_subChannel);
322   return PAssert(file != NULL, PLogicError) && PAssertOS(file->SetPosition(0));
323 }
324 
325 
OnStop()326 void PVXMLPlayableFile::OnStop()
327 {
328   PVXMLPlayable::OnStop();
329 
330   if (m_autoDelete && !m_filePath.IsEmpty()) {
331     PTRACE(3, "VXML\tDeleting file \"" << m_filePath << "\"");
332     PFile::Remove(m_filePath);
333   }
334 }
335 
336 
337 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableFile> vxmlPlayableFilenameFactory("File");
338 
339 
340 ///////////////////////////////////////////////////////////////
341 
PVXMLPlayableFileList()342 PVXMLPlayableFileList::PVXMLPlayableFileList()
343   : m_currentIndex(0)
344 {
345 }
346 
347 
Open(PVXMLChannel & chan,const PString & list,PINDEX delay,PINDEX repeat,PBoolean autoDelete)348 PBoolean PVXMLPlayableFileList::Open(PVXMLChannel & chan, const PString & list, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
349 {
350   return Open(chan, list.Lines(), delay, repeat, autoDelete);
351 }
352 
353 
Open(PVXMLChannel & chan,const PStringArray & list,PINDEX delay,PINDEX repeat,PBoolean autoDelete)354 PBoolean PVXMLPlayableFileList::Open(PVXMLChannel & chan, const PStringArray & list, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
355 {
356   for (PINDEX i = 0; i < list.GetSize(); ++i) {
357     PString fn = chan.AdjustWavFilename(list[i]);
358     if (PFile::Exists(fn))
359       m_fileNames.AppendString(fn);
360     else {
361       PTRACE(2, "VXML\tAudio file \"" << fn << "\" does not exist.");
362     }
363   }
364 
365   if (m_fileNames.GetSize() == 0) {
366     PTRACE(2, "VXML\tNo files in list exist.");
367     return false;
368   }
369 
370   m_currentIndex = 0;
371 
372   return PVXMLPlayable::Open(chan, PString::Empty(), delay, ((repeat >= 0) ? repeat : 1) * m_fileNames.GetSize(), autoDelete);
373 }
374 
375 
OnStart()376 bool PVXMLPlayableFileList::OnStart()
377 {
378   if (!PAssert(!m_fileNames.IsEmpty(), PLogicError))
379     return false;
380 
381   m_filePath = m_fileNames[m_currentIndex++ % m_fileNames.GetSize()];
382   return PVXMLPlayableFile::OnStart();
383 }
384 
385 
OnRepeat()386 bool PVXMLPlayableFileList::OnRepeat()
387 {
388   return PVXMLPlayable::OnRepeat() && OnStart();
389 
390 }
391 
392 
OnStop()393 void PVXMLPlayableFileList::OnStop()
394 {
395   m_filePath.MakeEmpty();
396 
397   PVXMLPlayableFile::OnStop();
398 
399   if (m_autoDelete)  {
400     for (PINDEX i = 0; i < m_fileNames.GetSize(); ++i) {
401       PTRACE(3, "VXML\tDeleting file \"" << m_fileNames[i] << "\"");
402       PFile::Remove(m_fileNames[i]);
403     }
404   }
405 }
406 
407 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableFileList> vxmlPlayableFilenameListFactory("FileList");
408 
409 
410 ///////////////////////////////////////////////////////////////
411 
412 #if P_PIPECHAN
413 
Open(PVXMLChannel & chan,const PString & cmd,PINDEX delay,PINDEX repeat,PBoolean autoDelete)414 PBoolean PVXMLPlayableCommand::Open(PVXMLChannel & chan, const PString & cmd, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
415 {
416   if (cmd.IsEmpty()) {
417     PTRACE(2, "VXML\tEmpty command line.");
418     return false;
419   }
420 
421   m_command = cmd;
422   return PVXMLPlayable::Open(chan, cmd, delay, repeat, autoDelete);
423 }
424 
425 
OnStart()426 bool PVXMLPlayableCommand::OnStart()
427 {
428   if (PAssertNULL(m_vxmlChannel) == NULL)
429     return false;
430 
431   PString cmd = m_command;
432   cmd.Replace("%s", PString(PString::Unsigned, m_sampleFrequency));
433   cmd.Replace("%f", m_format);
434 
435   // execute a command and send the output through the stream
436   PPipeChannel * pipe = new PPipeChannel;
437   if (!pipe->Open(cmd, PPipeChannel::ReadOnly)) {
438     PTRACE(2, "VXML\tCannot open command \"" << cmd << '"');
439     delete pipe;
440     return false;
441   }
442 
443   if (!pipe->Execute()) {
444     PTRACE(2, "VXML\tCannot start command \"" << cmd << '"');
445     return false;
446   }
447 
448   PTRACE(3, "VXML\tPlaying command \"" << cmd << '"');
449   m_subChannel = pipe;
450   return m_vxmlChannel->SetReadChannel(pipe, false);
451 }
452 
453 
OnStop()454 void PVXMLPlayableCommand::OnStop()
455 {
456   PPipeChannel * pipe = dynamic_cast<PPipeChannel *>(m_subChannel);
457   if (PAssert(pipe != NULL, PLogicError))
458     pipe->WaitForTermination();
459 
460   PVXMLPlayable::OnStop();
461 }
462 
463 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableCommand> vxmlPlayableCommandFactory("Command");
464 
465 #endif
466 
467 
468 ///////////////////////////////////////////////////////////////
469 
Open(PVXMLChannel & chan,const PString & hex,PINDEX delay,PINDEX repeat,PBoolean autoDelete)470 PBoolean PVXMLPlayableData::Open(PVXMLChannel & chan, const PString & hex, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
471 {
472   return PVXMLPlayable::Open(chan, hex, delay, repeat, autoDelete);
473 }
474 
475 
SetData(const PBYTEArray & data)476 void PVXMLPlayableData::SetData(const PBYTEArray & data)
477 {
478   m_data = data;
479 }
480 
481 
OnStart()482 bool PVXMLPlayableData::OnStart()
483 {
484   if (PAssertNULL(m_vxmlChannel) == NULL)
485     return false;
486 
487   m_subChannel = new PMemoryFile(m_data);
488   PTRACE(3, "VXML\tPlaying " << m_data.GetSize() << " bytes of memory");
489   return m_vxmlChannel->SetReadChannel(m_subChannel, false);
490 }
491 
492 
OnRepeat()493 bool PVXMLPlayableData::OnRepeat()
494 {
495   if (!PVXMLPlayable::OnRepeat())
496     return false;
497 
498   PMemoryFile * memfile = dynamic_cast<PMemoryFile *>(m_subChannel);
499   return PAssert(memfile != NULL, PLogicError) && PAssertOS(memfile->SetPosition(0));
500 }
501 
502 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableData> vxmlPlayableDataFactory("PCM Data");
503 
504 
505 ///////////////////////////////////////////////////////////////
506 
507 #if P_DTMF
508 
Open(PVXMLChannel & chan,const PString & toneSpec,PINDEX delay,PINDEX repeat,PBoolean autoDelete)509 PBoolean PVXMLPlayableTone::Open(PVXMLChannel & chan, const PString & toneSpec, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
510 {
511   // populate the tone buffer
512   PTones tones;
513 
514   if (!tones.Generate(toneSpec)) {
515     PTRACE(2, "VXML\tCOuld not generate tones with \"" << toneSpec << '"');
516     return false;
517   }
518 
519   PINDEX len = tones.GetSize() * sizeof(short);
520   memcpy(m_data.GetPointer(len), tones.GetPointer(), len);
521 
522   return PVXMLPlayable::Open(chan, toneSpec, delay, repeat, autoDelete);
523 }
524 
525 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableTone> vxmlPlayableToneFactory("Tone");
526 
527 #endif // P_DTMF
528 
529 
530 ///////////////////////////////////////////////////////////////
531 
Open(PVXMLChannel & chan,const PString & url,PINDEX delay,PINDEX repeat,PBoolean autoDelete)532 PBoolean PVXMLPlayableURL::Open(PVXMLChannel & chan, const PString & url, PINDEX delay, PINDEX repeat, PBoolean autoDelete)
533 {
534   if (!m_url.Parse(url)) {
535     PTRACE(2, "VXML\tInvalid URL \"" << url << '"');
536     return false;
537   }
538 
539   return PVXMLPlayable::Open(chan, url, delay, repeat, autoDelete);
540 }
541 
542 
OnStart()543 bool PVXMLPlayableURL::OnStart()
544 {
545   if (PAssertNULL(m_vxmlChannel) == NULL)
546     return false;
547 
548   // open the resource
549   PHTTPClient * client = new PHTTPClient;
550   client->SetPersistent(false);
551   PMIMEInfo outMIME, replyMIME;
552   int code = client->GetDocument(m_url, outMIME, replyMIME);
553   if ((code != 200) || (replyMIME(PHTTP::TransferEncodingTag()) *= PHTTP::ChunkedTag())) {
554     delete client;
555     return false;
556   }
557 
558   m_subChannel = client;
559   return m_vxmlChannel->SetReadChannel(client, false);
560 }
561 
562 PFactory<PVXMLPlayable>::Worker<PVXMLPlayableURL> vxmlPlayableURLFactory("URL");
563 
564 
565 ///////////////////////////////////////////////////////////////
566 
PVXMLRecordable()567 PVXMLRecordable::PVXMLRecordable()
568   : m_finalSilence(3000)
569   , m_maxDuration(30000)
570 {
571 }
572 
573 
574 ///////////////////////////////////////////////////////////////
575 
Open(const PString & arg)576 PBoolean PVXMLRecordableFilename::Open(const PString & arg)
577 {
578   m_fileName = arg;
579   return true;
580 }
581 
582 
OnStart(PVXMLChannel & outgoingChannel)583 bool PVXMLRecordableFilename::OnStart(PVXMLChannel & outgoingChannel)
584 {
585   PFile * file = NULL;
586 
587 #if P_WAVFILE
588   // check the file extension and open a .wav or a raw (.sw or .g723) file
589   if (m_fileName.GetType() == ".wav") {
590     file = outgoingChannel.CreateWAVFile(m_fileName, true);
591     if (file == NULL) {
592       PTRACE(2, "VXML\tCannot open WAV file \"" << m_fileName << '"');
593       return false;
594     }
595   }
596   else
597 #endif // P_WAVFILE
598   {
599     file = new PFile(m_fileName);
600     if (!file->Open(PFile::WriteOnly)) {
601       PTRACE(2, "VXML\tCannot open audio file \"" << m_fileName << '"');
602       delete file;
603       return false;
604     }
605   }
606 
607   PTRACE(3, "VXML\tRecording to file \"" << m_fileName << '"');
608   outgoingChannel.SetWriteChannel(file, true);
609 
610   m_silenceTimer = m_finalSilence;
611   m_recordTimer = m_maxDuration;
612   return true;
613 }
614 
615 
OnFrame(PBoolean isSilence)616 PBoolean PVXMLRecordableFilename::OnFrame(PBoolean isSilence)
617 {
618   if (isSilence) {
619     if (m_silenceTimer.HasExpired()) {
620       PTRACE(4, "VXML\tRecording silence detected.");
621       return true;
622     }
623   }
624   else
625     m_silenceTimer = m_finalSilence;
626 
627   if (m_recordTimer.HasExpired()) {
628     PTRACE(3, "VXML\tRecording finished due to max time exceeded.");
629     return true;
630   }
631 
632   return false;
633 }
634 
635 
636 ///////////////////////////////////////////////////////////////
637 
PVXMLCache(const PDirectory & _directory)638 PVXMLCache::PVXMLCache(const PDirectory & _directory)
639   : directory(_directory)
640 {
641   if (!directory.Exists())
642     directory.Create();
643 }
644 
645 
MD5AsHex(const PString & str)646 static PString MD5AsHex(const PString & str)
647 {
648   PMessageDigest::Result digest;
649   PMessageDigest5::Encode(str, digest);
650 
651   PString hexStr;
652   const BYTE * data = digest.GetPointer();
653   for (PINDEX i = 0; i < digest.GetSize(); ++i)
654     hexStr.sprintf("%02x", (unsigned)data[i]);
655   return hexStr;
656 }
657 
658 
CreateFilename(const PString & prefix,const PString & key,const PString & fileType)659 PFilePath PVXMLCache::CreateFilename(const PString & prefix, const PString & key, const PString & fileType)
660 {
661   PString md5   = MD5AsHex(key);
662 
663   return directory + ((prefix + "_") + md5 + fileType);
664 }
665 
666 
Get(const PString & prefix,const PString & key,const PString & fileType,PString & contentType,PFilePath & dataFn)667 PBoolean PVXMLCache::Get(const PString & prefix,
668                      const PString & key,
669                      const PString & fileType,
670                            PString & contentType,
671                          PFilePath & dataFn)
672 {
673   PWaitAndSignal m(*this);
674 
675   dataFn = CreateFilename(prefix, key, "." + fileType);
676   PFilePath typeFn = CreateFilename(prefix, key, "_type.txt");
677   if (!PFile::Exists(dataFn) || !PFile::Exists(typeFn)) {
678     PTRACE(4, "VXML\tKey \"" << key << "\" not found in cache");
679     return false;
680   }
681 
682   {
683     PFile file(dataFn, PFile::ReadOnly);
684     if (!file.IsOpen() || (file.GetLength() == 0)) {
685       PTRACE(4, "VXML\tDeleting empty cache file for key " << key);
686       PFile::Remove(dataFn, true);
687       PFile::Remove(typeFn, true);
688       return false;
689     }
690   }
691 
692   PTextFile typeFile(typeFn, PFile::ReadOnly);
693   if (!typeFile.IsOpen()) {
694     PTRACE(4, "VXML\tCannot find type for cached key " << key << " in cache");
695     PFile::Remove(dataFn, true);
696     PFile::Remove(typeFn, true);
697     return false;
698   }
699 
700   typeFile.ReadLine(contentType);
701   contentType.Trim();
702   if (contentType.IsEmpty())
703     contentType = GetContentType(dataFn);
704 
705   return true;
706 }
707 
708 
Put(const PString & prefix,const PString & key,const PString & fileType,const PString & contentType,const PFilePath & fn,PFilePath & dataFn)709 void PVXMLCache::Put(const PString & prefix,
710                      const PString & key,
711                      const PString & fileType,
712                      const PString & contentType,
713                    const PFilePath & fn,
714                          PFilePath & dataFn)
715 {
716   PWaitAndSignal m(*this);
717 
718   // create the filename for the cache files
719   dataFn = CreateFilename(prefix, key, "." + fileType);
720   PFilePath typeFn = CreateFilename(prefix, key, "_type.txt");
721 
722   // write the content type file
723   PTextFile typeFile(typeFn, PFile::WriteOnly);
724   if (contentType.IsEmpty())
725     typeFile.WriteLine(GetContentType(fn));
726   else
727     typeFile.WriteLine(contentType);
728 
729   // rename the file to the correct name
730   PFile::Rename(fn, dataFn.GetFileName(), true);
731 }
732 
733 
GetResourceCache()734 PVXMLCache & PVXMLCache::GetResourceCache()
735 {
736   static PVXMLCache cache(PDirectory() + "cache");
737   return cache;
738 }
739 
740 
GetRandomFilename(const PString & prefix,const PString & fileType)741 PFilePath PVXMLCache::GetRandomFilename(const PString & prefix, const PString & fileType)
742 {
743   PFilePath fn;
744 
745   // create a random temporary filename
746   PRandom r;
747   for (;;) {
748     fn = directory + psprintf("%s_%i.%s", (const char *)prefix, r.Generate() % 1000000, (const char *)fileType);
749     if (!PFile::Exists(fn))
750       break;
751   }
752 
753   return fn;
754 }
755 
756 
757 //////////////////////////////////////////////////////////
758 
PVXMLSession(PTextToSpeech * tts,PBoolean autoDelete)759 PVXMLSession::PVXMLSession(PTextToSpeech * tts, PBoolean autoDelete)
760   : m_textToSpeech(tts)
761   , m_autoDeleteTextToSpeech(autoDelete)
762   , m_vxmlThread(NULL)
763   , m_abortVXML(false)
764   , m_currentNode(NULL)
765   , m_xmlChanged(false)
766   , m_speakNodeData(true)
767   , m_bargeIn(DefaultBargeIn)
768   , m_bargingIn(false)
769   , m_grammar(NULL)
770   , m_defaultMenuDTMF('N') /// Disabled
771   , m_recordingStatus(NotRecording)
772   , m_recordStopOnDTMF(false)
773   , m_transferStatus(NotTransfering)
774   , m_transferStartTime(0)
775 {
776   SetVar("property.timeout" , "10s");
777 }
778 
779 
~PVXMLSession()780 PVXMLSession::~PVXMLSession()
781 {
782   Close();
783 
784   if (m_autoDeleteTextToSpeech)
785     delete m_textToSpeech;
786 }
787 
788 
SetTextToSpeech(PTextToSpeech * tts,PBoolean autoDelete)789 PTextToSpeech * PVXMLSession::SetTextToSpeech(PTextToSpeech * tts, PBoolean autoDelete)
790 {
791   PWaitAndSignal mutex(m_sessionMutex);
792 
793   if (m_autoDeleteTextToSpeech)
794     delete m_textToSpeech;
795 
796   m_autoDeleteTextToSpeech = autoDelete;
797   return m_textToSpeech = tts;
798 }
799 
800 
SetTextToSpeech(const PString & ttsName)801 PTextToSpeech * PVXMLSession::SetTextToSpeech(const PString & ttsName)
802 {
803   PFactory<PTextToSpeech>::Key_T name = (const char *)ttsName;
804   if (ttsName.IsEmpty()) {
805     PFactory<PTextToSpeech>::KeyList_T engines = PFactory<PTextToSpeech>::GetKeyList();
806     if (engines.empty())
807       return SetTextToSpeech(NULL, false);
808 
809 #ifdef _WIN32
810     name = "Microsoft SAPI";
811     if (std::find(engines.begin(), engines.end(), name) == engines.end())
812 #endif
813       name = engines[0];
814   }
815 
816   return SetTextToSpeech(PFactory<PTextToSpeech>::CreateInstance(name), true);
817 }
818 
819 
Load(const PString & source)820 PBoolean PVXMLSession::Load(const PString & source)
821 {
822   // Lets try and guess what was passed, if file exists then is file
823   PFilePath file = source;
824   if (PFile::Exists(file))
825     return LoadFile(file);
826 
827   // see if looks like URL
828   PINDEX pos = source.Find(':');
829   if (pos != P_MAX_INDEX) {
830     PString scheme = source.Left(pos);
831     if ((scheme *= "http") || (scheme *= "https") || (scheme *= "file"))
832       return LoadURL(source);
833   }
834 
835   // See if is actual VXML
836   if (PCaselessString(source).Find("<vxml") != P_MAX_INDEX)
837     return LoadVXML(source);
838 
839   return false;
840 }
841 
842 
LoadFile(const PFilePath & filename,const PString & firstForm)843 PBoolean PVXMLSession::LoadFile(const PFilePath & filename, const PString & firstForm)
844 {
845   PTRACE(4, "VXML\tLoading file: " << filename);
846 
847   PTextFile file(filename, PFile::ReadOnly);
848   if (!file.IsOpen()) {
849     PTRACE(1, "VXML\tCannot open " << filename);
850     return false;
851   }
852 
853   m_rootURL = PURL(filename);
854   return InternalLoadVXML(file.ReadString(P_MAX_INDEX), firstForm);
855 }
856 
857 
LoadURL(const PURL & url)858 PBoolean PVXMLSession::LoadURL(const PURL & url)
859 {
860   PTRACE(4, "VXML\tLoading URL " << url);
861 
862   // retreive the document (may be a HTTP get)
863 
864   PString xmlStr;
865   if (url.LoadResource(xmlStr)) {
866     m_rootURL = url;
867     return InternalLoadVXML(xmlStr, url.GetFragment());
868   }
869 
870   PTRACE(1, "VXML\tCannot load document " << url);
871   return false;
872 }
873 
874 
LoadVXML(const PString & xmlText,const PString & firstForm)875 PBoolean PVXMLSession::LoadVXML(const PString & xmlText, const PString & firstForm)
876 {
877   m_rootURL = PString::Empty();
878   return InternalLoadVXML(xmlText, firstForm);
879 }
880 
881 
InternalLoadVXML(const PString & xmlText,const PString & firstForm)882 bool PVXMLSession::InternalLoadVXML(const PString & xmlText, const PString & firstForm)
883 {
884   {
885     PWaitAndSignal mutex(m_sessionMutex);
886 
887     m_xmlChanged = true;
888     LoadGrammar(NULL);
889 
890     // parse the XML
891     m_xml.RemoveAll();
892     if (!m_xml.Load(xmlText)) {
893       PTRACE(1, "VXML\tCannot parse root document: " << GetXMLError());
894       return false;
895     }
896 
897     PXMLElement * root = m_xml.GetRootElement();
898     if (root == NULL) {
899       PTRACE(1, "VXML\tNo root element");
900       return false;
901     }
902 
903     m_variableScope = m_variableScope.IsEmpty() ? "application" : "document";
904 
905     {
906       PINDEX idx = 0;
907       PXMLElement * element;
908       while ((element = root->GetElement("var", idx++)) != NULL)
909         TraverseVar(*element);
910     }
911 
912     // find the first form
913     if (!SetCurrentForm(firstForm, false)) {
914       PTRACE(1, "VXML\tNo form element");
915       m_xml.RemoveAll();
916       return false;
917     }
918   }
919 
920   return Execute();
921 }
922 
923 
NormaliseResourceName(const PString & src)924 PURL PVXMLSession::NormaliseResourceName(const PString & src)
925 {
926   PURL url;
927   if (url.Parse(src, NULL))
928     return url;
929 
930   if (m_rootURL.IsEmpty()) {
931     url.Parse(src, "file");
932     return url;
933   }
934 
935   // relative to scheme/path in root document
936   url = m_rootURL;
937   PStringArray path = url.GetPath();
938   if (src[0] == '/' || path.IsEmpty()) {
939     url.SetPathStr(src);
940     return url;
941   }
942 
943   PStringStream str;
944   for (PINDEX i = 0; i < path.GetSize()-1; i++)
945     str << path[i] << '/';
946   str << src;
947   url.SetPathStr(str);
948   return url;
949 }
950 
951 
RetreiveResource(const PURL & url,PString & contentType,PFilePath & dataFn,PBoolean useCache)952 PBoolean PVXMLSession::RetreiveResource(const PURL & url,
953                                        PString & contentType,
954                                      PFilePath & dataFn,
955                                             PBoolean useCache)
956 {
957   // files on the local file system get loaded locally
958   if (url.GetScheme() == "file" && url.GetHostName().IsEmpty()) {
959     dataFn = url.AsFilePath();
960     if (contentType.IsEmpty())
961       contentType = GetContentType(dataFn);
962     return true;
963   }
964 
965   PString fileType;
966   {
967     const PStringArray & path = url.GetPath();
968     if (!path.IsEmpty())
969       fileType = PFilePath(path[path.GetSize()-1]).GetType();
970   }
971 
972   if (useCache && PVXMLCache::GetResourceCache().Get("url", url.AsString(), fileType, contentType, dataFn))
973     return true;
974 
975   // get a random filename
976   PFilePath newFn = PVXMLCache::GetResourceCache().GetRandomFilename("url", fileType);
977 
978   // get the resource header information
979   PHTTPClient client;
980   PMIMEInfo outMIME, replyMIME;
981   if (!client.GetDocument(url, outMIME, replyMIME)) {
982     PTRACE(2, "VXML\tCannot load resource " << url);
983     return false;
984   }
985 
986   // Get the body of the response in a PBYTEArray (might be binary data)
987   PBYTEArray incomingData;
988   client.ReadContentBody(replyMIME, incomingData);
989   contentType = replyMIME(PHTTPClient::ContentTypeTag());
990 
991   // write the data in the file
992   PFile cacheFile(newFn, PFile::WriteOnly);
993   cacheFile.Write(incomingData.GetPointer(), incomingData.GetSize());
994 
995   // if we have a cache and we are using it, then save the data
996   if (useCache)
997     PVXMLCache::GetResourceCache().Put("url", url.AsString(), fileType, contentType, newFn, dataFn);
998 
999   // data is loaded
1000   return true;
1001 }
1002 
1003 
SetCurrentForm(const PString & searchId,bool fullURI)1004 bool PVXMLSession::SetCurrentForm(const PString & searchId, bool fullURI)
1005 {
1006   PString id = searchId;
1007 
1008   if (fullURI) {
1009     if (searchId.IsEmpty()) {
1010       PTRACE(3, "VXML\tFull URI required for this form/menu search");
1011       return false;
1012     }
1013 
1014     if (searchId[0] != '#') {
1015       PTRACE(4, "VXML\tSearching form/menu \"" << searchId << '"');
1016       return LoadURL(NormaliseResourceName(searchId));
1017     }
1018 
1019     id = searchId.Mid(1);
1020   }
1021 
1022   // Only handle search of top level nodes for <form>/<menu> element
1023   // NOTE: should have some flag to know if it is loaded
1024   PXMLElement * root = m_xml.GetRootElement();
1025   if (root != NULL) {
1026     for (PINDEX i = 0; i < root->GetSize(); i++) {
1027       PXMLObject * xmlObject = root->GetElement(i);
1028       if (xmlObject->IsElement()) {
1029         PXMLElement * xmlElement = (PXMLElement*)xmlObject;
1030         if (
1031               (xmlElement->GetName() == "form" || xmlElement->GetName() == "menu") &&
1032               (id.IsEmpty() || (xmlElement->GetAttribute("id") *= id))
1033            ) {
1034           PTRACE(3, "VXML\tFound <" << xmlElement->GetName() << " id=\"" << xmlElement->GetAttribute("id") << "\">");
1035 
1036           if (m_currentNode != NULL) {
1037             PXMLElement * element = m_currentNode->GetParent();
1038             while (element != NULL) {
1039               PCaselessString nodeType = element->GetName();
1040               PVXMLNodeHandler * handler = PVXMLNodeFactory::CreateInstance(nodeType);
1041               if (handler != NULL) {
1042                 handler->Finish(*this, *element);
1043                 PTRACE(4, "VXML\tProcessed VoiceXML element: <" << nodeType << '>');
1044               }
1045               element = element->GetParent();
1046             }
1047           }
1048 
1049           m_currentNode = xmlObject;
1050           return true;
1051         }
1052       }
1053     }
1054   }
1055 
1056   PTRACE(3, "VXML\tNo form/menu with id \"" << searchId << '"');
1057   return false;
1058 }
1059 
1060 
GetAndLockVXMLChannel()1061 PVXMLChannel * PVXMLSession::GetAndLockVXMLChannel()
1062 {
1063   m_sessionMutex.Wait();
1064   if (IsOpen())
1065     return GetVXMLChannel();
1066 
1067   m_sessionMutex.Signal();
1068   return NULL;
1069 }
1070 
1071 
Open(const PString & mediaFormat)1072 PBoolean PVXMLSession::Open(const PString & mediaFormat)
1073 {
1074   PVXMLChannel * chan = PFactory<PVXMLChannel>::CreateInstance(mediaFormat);
1075   if (chan == NULL) {
1076     PTRACE(1, "VXML\tCannot create VXML channel with format " << mediaFormat);
1077     return false;
1078   }
1079 
1080   if (!chan->Open(this)) {
1081     delete chan;
1082     return false;
1083   }
1084 
1085   // set the underlying channel
1086   if (!PIndirectChannel::Open(chan, chan))
1087     return false;
1088 
1089   return Execute();
1090 }
1091 
1092 
Execute()1093 PBoolean PVXMLSession::Execute()
1094 {
1095   PWaitAndSignal mutex(m_sessionMutex);
1096 
1097   if (IsLoaded()) {
1098     if (m_vxmlThread == NULL)
1099       m_vxmlThread = PThread::Create(PCREATE_NOTIFIER(VXMLExecute), "VXML");
1100     else
1101       Trigger();
1102   }
1103 
1104   return true;
1105 }
1106 
1107 
Close()1108 PBoolean PVXMLSession::Close()
1109 {
1110   PThread * thread = NULL;
1111 
1112   m_sessionMutex.Wait();
1113 
1114   LoadGrammar(NULL);
1115 
1116   if (PThread::Current() != m_vxmlThread) {
1117     thread = m_vxmlThread;
1118     m_vxmlThread = NULL;
1119   }
1120 
1121   m_sessionMutex.Signal();
1122 
1123   if (thread != NULL) {
1124     PTRACE(3, "VXML\tClosing session, fast forwarding through script");
1125 
1126     // Stop condition for thread
1127     m_abortVXML = true;
1128     Trigger();
1129 
1130     PAssert(thread->WaitForTermination(10000), "VXML thread did not exit in time.");
1131     delete thread;
1132   }
1133 
1134   return PIndirectChannel::Close();
1135 }
1136 
1137 
VXMLExecute(PThread &,INT)1138 void PVXMLSession::VXMLExecute(PThread &, INT)
1139 {
1140   PTRACE(4, "VXML\tExecution thread started");
1141 
1142   m_sessionMutex.Wait();
1143 
1144   while (!m_abortVXML) {
1145     // process current node in the VXML script
1146     if (ProcessNode()) {
1147       /* wait for something to happen, usually output of some audio. But under
1148          some circumstances we want to abort the script, but we  have to make
1149          sure the script has been run to the end so submit actions etc. can be
1150          performed. Record and audio and other user interaction commands can
1151          be skipped, so we don't wait for them */
1152       do {
1153         while (ProcessEvents())
1154           ;
1155       } while (NextNode(true));
1156     }
1157     else {
1158       // Wait till node finishes
1159       while (ProcessEvents())
1160         ;
1161 
1162       NextNode(false);
1163     }
1164 
1165     // Determine if we should quit
1166     if (m_currentNode != NULL)
1167       continue;
1168 
1169     PTRACE(3, "VXML\tEnd of VoiceXML elements.");
1170 
1171     m_sessionMutex.Signal();
1172     OnEndDialog();
1173     m_sessionMutex.Wait();
1174 
1175     // Wait for anything OnEndDialog plays to complete.
1176     while (ProcessEvents())
1177       ;
1178 
1179     if (m_currentNode == NULL)
1180       m_abortVXML = true;
1181   }
1182 
1183   m_sessionMutex.Signal();
1184 
1185   OnEndSession();
1186 
1187   PTRACE(4, "VXML\tExecution thread ended");
1188 }
1189 
1190 
OnEndDialog()1191 void PVXMLSession::OnEndDialog()
1192 {
1193 }
1194 
1195 
OnEndSession()1196 void PVXMLSession::OnEndSession()
1197 {
1198 }
1199 
1200 
ProcessEvents()1201 bool PVXMLSession::ProcessEvents()
1202 {
1203   // m_sessionMutex already locked
1204 
1205   if (m_abortVXML)
1206     return false;
1207 
1208   char ch;
1209 
1210   m_userInputMutex.Wait();
1211   if (m_userInputQueue.empty())
1212     ch = '\0';
1213   else {
1214     ch = m_userInputQueue.front();
1215     m_userInputQueue.pop();
1216     PTRACE(3, "VXML\tHandling user input " << ch);
1217   }
1218   m_userInputMutex.Signal();
1219 
1220   if (ch != '\0') {
1221     if (m_recordStopOnDTMF)
1222       EndRecording();
1223 
1224     if (m_bargeIn && IsOpen()) {
1225       PTRACE(4, "VXML\tBarging in");
1226       m_bargingIn = true;
1227       GetVXMLChannel()->FlushQueue();
1228     }
1229 
1230     if (m_grammar != NULL)
1231       m_grammar->OnUserInput(ch);
1232   }
1233 
1234   if (IsOpen() && GetVXMLChannel()->IsPlaying()) {
1235     PTRACE(4, "VXML\tIs playing, awaiting event");
1236   }
1237   else if (IsOpen() && GetVXMLChannel()->IsRecording()) {
1238     PTRACE(4, "VXML\tIs recording, awaiting event");
1239   }
1240   else if (m_grammar != NULL && m_grammar->GetState() == PVXMLGrammar::Started) {
1241     PTRACE(4, "VXML\tAwaiting input, awaiting event");
1242   }
1243   else if (m_transferStatus == TransferInProgress) {
1244     PTRACE(4, "VXML\tTransfer in progress, awaiting event");
1245   }
1246   else {
1247     PTRACE(4, "VXML\tNothing happening, processing next node");
1248     return false;
1249   }
1250 
1251   m_sessionMutex.Signal();
1252   m_waitForEvent.Wait();
1253   m_sessionMutex.Wait();
1254 
1255   if (!m_xmlChanged)
1256     return true;
1257 
1258   PTRACE(4, "VXML\tXML changed, flushing queue");
1259 
1260   // Clear out any audio being output, so can start fresh on new VXML.
1261   if (IsOpen())
1262     GetVXMLChannel()->FlushQueue();
1263 
1264   return false;
1265 }
1266 
1267 
NextNode(bool processChildren)1268 bool PVXMLSession::NextNode(bool processChildren)
1269 {
1270   // m_sessionMutex already locked
1271 
1272   if (m_abortVXML)
1273     return false;
1274 
1275   // No more nodes
1276   if (m_currentNode == NULL)
1277     return false;
1278 
1279   if (m_xmlChanged)
1280     return false;
1281 
1282   PXMLElement * element = dynamic_cast<PXMLElement *>(m_currentNode);
1283   if (element != NULL) {
1284     // if the current node has children, then process the first child
1285     if (processChildren && (m_currentNode = element->GetSubObject(0)) != NULL)
1286       return false;
1287   }
1288   else {
1289     // Data node
1290     PXMLObject * sibling = m_currentNode->GetNextObject();
1291     if (sibling != NULL) {
1292       m_currentNode = sibling;
1293       return false;
1294     }
1295     if ((element = m_currentNode->GetParent()) == NULL) {
1296       m_currentNode = NULL;
1297       return false;
1298     }
1299   }
1300 
1301   // No children, move to sibling
1302   do {
1303     PCaselessString nodeType = element->GetName();
1304     PVXMLNodeHandler * handler = PVXMLNodeFactory::CreateInstance(nodeType);
1305     if (handler != NULL) {
1306       if (!handler->Finish(*this, *element)) {
1307         PTRACE(4, "VXML\tContinue processing VoiceXML element: <" << nodeType << '>');
1308         return true;
1309       }
1310       PTRACE(4, "VXML\tProcessed VoiceXML element: <" << nodeType << '>');
1311     }
1312 
1313     if ((m_currentNode = element->GetNextObject()) != NULL)
1314       break;
1315 
1316   } while ((element = element->GetParent()) != NULL);
1317 
1318   return false;
1319 }
1320 
1321 
ProcessGrammar()1322 bool PVXMLSession::ProcessGrammar()
1323 {
1324   if (m_grammar == NULL) {
1325     PTRACE(4, "VXML\tNo grammar was created!");
1326     return true;
1327   }
1328 
1329   switch (m_grammar->GetState()) {
1330     case PVXMLGrammar::Idle :
1331       m_grammar->Start();
1332       return false;
1333 
1334     case PVXMLGrammar::Started :
1335       return false;
1336 
1337     default :
1338       PTRACE_IF(4, m_bargingIn, "VXML\tEnding barge in");
1339       m_bargingIn = false;
1340 
1341       PVXMLGrammar * grammar = m_grammar;
1342       m_grammar = NULL;
1343       PTRACE(2, "VXML\tProcessing grammar " << *grammar);
1344       bool nextNode = grammar->Process();
1345       delete grammar;
1346       return nextNode;
1347   }
1348 }
1349 
1350 
ProcessNode()1351 bool PVXMLSession::ProcessNode()
1352 {
1353   // m_sessionMutex already locked
1354 
1355   if (m_abortVXML)
1356     return false;
1357 
1358   if (m_currentNode == NULL)
1359     return false;
1360 
1361   if (m_bargingIn)
1362     return false;
1363 
1364   m_xmlChanged = false;
1365 
1366   PXMLData * nodeData = dynamic_cast<PXMLData *>(m_currentNode);
1367   if (nodeData != NULL) {
1368     if (m_speakNodeData)
1369       PlayText(nodeData->GetString().Trim());
1370     return true;
1371   }
1372 
1373   m_speakNodeData = true;
1374 
1375   PXMLElement * element = (PXMLElement*)m_currentNode;
1376   PCaselessString nodeType = element->GetName();
1377   PVXMLNodeHandler * handler = PVXMLNodeFactory::CreateInstance(nodeType);
1378   if (handler == NULL) {
1379     PTRACE(2, "VXML\tUnknown/unimplemented VoiceXML element: <" << nodeType << '>');
1380     return false;
1381   }
1382 
1383   PTRACE(3, "VXML\tProcessing VoiceXML element: <" << nodeType << '>');
1384   bool started = handler->Start(*this, *element);
1385   PTRACE_IF(4, !started, "VXML\tSkipping VoiceXML element: <" << nodeType << '>');
1386   return started;
1387 }
1388 
1389 
OnUserInput(const PString & str)1390 void PVXMLSession::OnUserInput(const PString & str)
1391 {
1392   {
1393     PWaitAndSignal mutex(m_userInputMutex);
1394     for (PINDEX i = 0; i < str.GetLength(); i++)
1395       m_userInputQueue.push(str[i]);
1396   }
1397   Trigger();
1398 }
1399 
1400 
TraverseRecord(PXMLElement &)1401 PBoolean PVXMLSession::TraverseRecord(PXMLElement &)
1402 {
1403   m_recordingStatus = NotRecording;
1404   return true;
1405 }
1406 
1407 
TraversedRecord(PXMLElement & element)1408 PBoolean PVXMLSession::TraversedRecord(PXMLElement & element)
1409 {
1410   if (m_abortVXML)
1411     return true;
1412 
1413   switch (m_recordingStatus) {
1414     case RecordingInProgress :
1415       return false;
1416 
1417     case RecordingComplete :
1418       return GoToEventHandler(element, "filled");
1419 
1420     default :
1421       break;
1422   }
1423 
1424   // see if we need a beep
1425   if (element.GetAttribute("beep").ToLower() *= "true") {
1426     PBYTEArray beepData;
1427     GetBeepData(beepData, 1000);
1428     if (beepData.GetSize() != 0)
1429       PlayData(beepData);
1430   }
1431 
1432   // Get the destination filename (dest)
1433   PURL destURL;
1434   if (element.HasAttribute("dest"))
1435     destURL = element.GetAttribute("dest");
1436 
1437   if (destURL.IsEmpty())
1438     destURL.Parse("recording_" + PTime().AsString("yyyyMMdd_hhmmss") + ".wav", "file");
1439 
1440   // Get max record time (maxtime)
1441   PTimeInterval maxTime = StringToTime(element.GetAttribute("maxtime"), INT_MAX);
1442 
1443   // Get terminating silence duration (finalsilence)
1444   PTimeInterval termTime = StringToTime(element.GetAttribute("finalsilence"), 3000);
1445 
1446   // Get dtmf term (dtmfterm)
1447   PBoolean dtmfTerm = true;
1448   if (element.HasAttribute("dtmfterm"))
1449     dtmfTerm = !(element.GetAttribute("dtmfterm").ToLower() *= "false");
1450 
1451   // create a semaphore, and then wait for the recording to terminate
1452   return !StartRecording(destURL.AsFilePath(), dtmfTerm, maxTime, termTime);
1453 }
1454 
1455 
GetXMLError() const1456 PString PVXMLSession::GetXMLError() const
1457 {
1458   return psprintf("(%i:%i) ", m_xml.GetErrorLine(), m_xml.GetErrorColumn()) + m_xml.GetErrorString();
1459 }
1460 
1461 
EvaluateExpr(const PString & expr)1462 PString PVXMLSession::EvaluateExpr(const PString & expr)
1463 {
1464   // Should be full ECMAScript but ...
1465   // We only support expressions of the form 'literal'+variable or all digits
1466 
1467   PString result;
1468 
1469   PINDEX pos = 0;
1470   while (pos < expr.GetLength()) {
1471     if (expr[pos] == '\'') {
1472       PINDEX quote = expr.Find('\'', ++pos);
1473       PTRACE_IF(2, quote == P_MAX_INDEX, "VXML\tMismatched quote, ignoring transfer");
1474       result += expr(pos, quote-1);
1475       pos = quote+1;
1476     }
1477     else if (isalpha(expr[pos])) {
1478       PINDEX span = expr.FindSpan("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.$", pos);
1479       result += GetVar(expr(pos, span-1));
1480       pos = span;
1481     }
1482     else if (isdigit(expr[pos])) {
1483       PINDEX span = expr.FindSpan("0123456789", pos);
1484       result += GetVar(expr(pos, span-1));
1485       pos = span;
1486     }
1487     else if (expr[pos] == '+' || isspace(expr[pos]))
1488       pos++;
1489     else {
1490       PTRACE(2, "VXML\tOnly '+' operator supported.");
1491       break;
1492     }
1493   }
1494 
1495   return result;
1496 }
1497 
1498 
GetVar(const PString & varName) const1499 PCaselessString PVXMLSession::GetVar(const PString & varName) const
1500 {
1501   PString fullVarName = varName;
1502   if (varName.Find('.') == P_MAX_INDEX)
1503     fullVarName = m_variableScope+'.'+varName;
1504 
1505   return m_variables(fullVarName);
1506 }
1507 
1508 
SetVar(const PString & varName,const PString & value)1509 void PVXMLSession::SetVar(const PString & varName, const PString & value)
1510 {
1511   PString fullVarName = varName;
1512   if (varName.Find('.') == P_MAX_INDEX)
1513     fullVarName = m_variableScope+'.'+varName;
1514 
1515   m_variables.SetAt(fullVarName, value);
1516 }
1517 
1518 
PlayFile(const PString & fn,PINDEX repeat,PINDEX delay,PBoolean autoDelete)1519 PBoolean PVXMLSession::PlayFile(const PString & fn, PINDEX repeat, PINDEX delay, PBoolean autoDelete)
1520 {
1521   return IsOpen() && GetVXMLChannel()->QueueFile(fn, repeat, delay, autoDelete);
1522 }
1523 
1524 
PlayCommand(const PString & cmd,PINDEX repeat,PINDEX delay)1525 PBoolean PVXMLSession::PlayCommand(const PString & cmd, PINDEX repeat, PINDEX delay)
1526 {
1527   return IsOpen() && GetVXMLChannel()->QueueCommand(cmd, repeat, delay);
1528 }
1529 
1530 
PlayData(const PBYTEArray & data,PINDEX repeat,PINDEX delay)1531 PBoolean PVXMLSession::PlayData(const PBYTEArray & data, PINDEX repeat, PINDEX delay)
1532 {
1533   return IsOpen() && GetVXMLChannel()->QueueData(data, repeat, delay);
1534 }
1535 
1536 
PlayTone(const PString & toneSpec,PINDEX repeat,PINDEX delay)1537 PBoolean PVXMLSession::PlayTone(const PString & toneSpec, PINDEX repeat, PINDEX delay)
1538 {
1539   return IsOpen() && GetVXMLChannel()->QueuePlayable("Tone", toneSpec, repeat, delay, true);
1540 }
1541 
1542 
PlayElement(PXMLElement & element)1543 PBoolean PVXMLSession::PlayElement(PXMLElement & element)
1544 {
1545   PString str = element.GetAttribute("src").Trim();
1546   if (str.IsEmpty()) {
1547     str = EvaluateExpr(element.GetAttribute("expr"));
1548     if (str.IsEmpty()) {
1549       PTRACE(2, "VXML\tNo src attribute to play element.");
1550       return false;
1551     }
1552   }
1553 
1554   if (str[0] == '|')
1555     return PlayCommand(str.Mid(1));
1556 
1557   // get a normalised name for the resource
1558   bool safe = GetVar("caching") == "safe" || (element.GetAttribute("caching") *= "safe");
1559 
1560   // load the resource from the cache
1561   PString contentType;
1562   PFilePath fn;
1563   if (RetreiveResource(NormaliseResourceName(str), contentType, fn, !safe))
1564     return PlayFile(fn, 0, 0, safe);   // make sure we delete the file if not cacheing
1565 
1566   return false;
1567 }
1568 
1569 
GetBeepData(PBYTEArray & data,unsigned ms)1570 void PVXMLSession::GetBeepData(PBYTEArray & data, unsigned ms)
1571 {
1572   if (IsOpen())
1573     GetVXMLChannel()->GetBeepData(data, ms);
1574 }
1575 
1576 
PlaySilence(const PTimeInterval & timeout)1577 PBoolean PVXMLSession::PlaySilence(const PTimeInterval & timeout)
1578 {
1579   return PlaySilence((PINDEX)timeout.GetMilliSeconds());
1580 }
1581 
1582 
PlaySilence(PINDEX msecs)1583 PBoolean PVXMLSession::PlaySilence(PINDEX msecs)
1584 {
1585   PBYTEArray nothing;
1586   return IsOpen() && GetVXMLChannel()->QueueData(nothing, 1, msecs);
1587 }
1588 
1589 
PlayStop()1590 PBoolean PVXMLSession::PlayStop()
1591 {
1592   return IsOpen() && GetVXMLChannel()->QueuePlayable(new PVXMLPlayableStop());
1593 }
1594 
1595 
PlayResource(const PURL & url,PINDEX repeat,PINDEX delay)1596 PBoolean PVXMLSession::PlayResource(const PURL & url, PINDEX repeat, PINDEX delay)
1597 {
1598   return IsOpen() && GetVXMLChannel()->QueueResource(url, repeat, delay);
1599 }
1600 
1601 
LoadGrammar(PVXMLGrammar * grammar)1602 PBoolean PVXMLSession::LoadGrammar(PVXMLGrammar * grammar)
1603 {
1604   PTRACE_IF(2, m_grammar != NULL && grammar == NULL, "VXML\tGrammar cleared from " << *m_grammar);
1605 
1606   delete m_grammar;
1607   m_grammar = grammar;
1608 
1609   PTRACE_IF(2, grammar != NULL, "VXML\tGrammar set to " << *grammar);
1610   return true;
1611 }
1612 
1613 
PlayText(const PString & textToPlay,PTextToSpeech::TextType type,PINDEX repeat,PINDEX delay)1614 PBoolean PVXMLSession::PlayText(const PString & textToPlay,
1615                     PTextToSpeech::TextType type,
1616                                      PINDEX repeat,
1617                                      PINDEX delay)
1618 {
1619   if (!IsOpen() || textToPlay.IsEmpty())
1620     return false;
1621 
1622   PTRACE(2, "VXML\tConverting \"" << textToPlay << "\" to speech");
1623 
1624   PStringArray list;
1625   bool useCache = GetVar("caching") != "safe";
1626   if (!ConvertTextToFilenameList(textToPlay, type, list, useCache) || (list.GetSize() == 0)) {
1627     PTRACE(1, "VXML\tCannot convert text to speech");
1628     return false;
1629   }
1630 
1631   PVXMLPlayableFileList * playable = new PVXMLPlayableFileList;
1632   if (!playable->Open(*GetVXMLChannel(), list, delay, repeat, !useCache)) {
1633     delete playable;
1634     PTRACE(1, "VXML\tCannot create playable for filename list");
1635     return false;
1636   }
1637 
1638   if (!GetVXMLChannel()->QueuePlayable(playable))
1639     return false;
1640 
1641   PTRACE(2, "VXML\tQueued filename list for playing");
1642 
1643   return true;
1644 }
1645 
1646 
ConvertTextToFilenameList(const PString & _text,PTextToSpeech::TextType type,PStringArray & filenameList,PBoolean useCache)1647 PBoolean PVXMLSession::ConvertTextToFilenameList(const PString & _text, PTextToSpeech::TextType type, PStringArray & filenameList, PBoolean useCache)
1648 {
1649   PString prefix = psprintf("tts%i", type);
1650 
1651   PStringArray lines = _text.Trim().Lines();
1652   for (PINDEX i = 0; i < lines.GetSize(); i++) {
1653 
1654     PString text = lines[i].Trim();
1655     if (text.IsEmpty())
1656       continue;
1657 
1658     PBoolean spoken = false;
1659     PFilePath dataFn;
1660 
1661     // see if we have converted this text before
1662     PString contentType = "audio/x-wav";
1663     if (useCache)
1664       spoken = PVXMLCache::GetResourceCache().Get(prefix, contentType + '\t' + text, "wav", contentType, dataFn);
1665 
1666     // if not cached, then use the text to speech converter
1667     if (spoken) {
1668      PTRACE(3, "VXML\tUsing cached audio file for " << _text);
1669     } else {
1670       PFilePath tmpfname;
1671       if (m_textToSpeech != NULL) {
1672         tmpfname = PVXMLCache::GetResourceCache().GetRandomFilename("tts", "wav");
1673         if (m_textToSpeech->OpenFile(tmpfname)) {
1674           spoken = m_textToSpeech->Speak(text, type);
1675           PTRACE(3, "VXML\tCreated new audio file for " << _text);
1676         }
1677         else {
1678           PTRACE(2, "VXML\tcannot open file " << tmpfname);
1679         }
1680         m_textToSpeech->Close();
1681         if (useCache)
1682           PVXMLCache::GetResourceCache().Put(prefix, text, "wav", contentType, tmpfname, dataFn);
1683         else
1684           dataFn = tmpfname;
1685       }
1686     }
1687 
1688     if (!spoken) {
1689       PTRACE(2, "VXML\tcannot speak text using TTS engine");
1690     } else
1691       filenameList.AppendString(dataFn);
1692   }
1693 
1694   return filenameList.GetSize() > 0;
1695 }
1696 
1697 
SetPause(PBoolean pause)1698 void PVXMLSession::SetPause(PBoolean pause)
1699 {
1700   if (IsOpen())
1701     GetVXMLChannel()->SetPause(pause);
1702 }
1703 
1704 
StartRecording(const PFilePath & recordFn,PBoolean recordDTMFTerm,const PTimeInterval & recordMaxTime,const PTimeInterval & recordFinalSilence)1705 PBoolean PVXMLSession::StartRecording(const PFilePath & recordFn,
1706                                                PBoolean recordDTMFTerm,
1707                                   const PTimeInterval & recordMaxTime,
1708                                   const PTimeInterval & recordFinalSilence)
1709 {
1710   if (!IsOpen())
1711     return false;
1712 
1713   if (recordFn.IsEmpty()) {
1714     PTRACE(1, "VXML\tNo destination file location");
1715     return true;
1716   }
1717 
1718   PFile::Remove(recordFn);
1719 
1720   m_recordStopOnDTMF = recordDTMFTerm;
1721 
1722   if (!GetVXMLChannel()->StartRecording(recordFn,
1723                                         (unsigned)recordFinalSilence.GetMilliSeconds(),
1724                                         (unsigned)recordMaxTime.GetMilliSeconds()))
1725     return false;
1726 
1727   m_recordingStatus = RecordingInProgress;
1728   return true;
1729 }
1730 
1731 
EndRecording()1732 PBoolean PVXMLSession::EndRecording()
1733 {
1734   return m_recordingStatus == RecordingInProgress && IsOpen() && GetVXMLChannel()->EndRecording();
1735 }
1736 
1737 
TraverseAudio(PXMLElement & element)1738 PBoolean PVXMLSession::TraverseAudio(PXMLElement & element)
1739 {
1740   return !PlayElement(element);
1741 }
1742 
1743 
TraverseBreak(PXMLElement & element)1744 PBoolean PVXMLSession::TraverseBreak(PXMLElement & element)
1745 {
1746   // msecs is VXML 1.0
1747   if (element.HasAttribute("msecs"))
1748     return PlaySilence(element.GetAttribute("msecs").AsInteger());
1749 
1750   // time is VXML 2.0
1751   if (element.HasAttribute("time"))
1752     return PlaySilence(StringToTime(element.GetAttribute("time"), 1000));
1753 
1754   if (element.HasAttribute("size")) {
1755     PString size = element.GetAttribute("size");
1756     if (size *= "none")
1757       return true;
1758     if (size *= "small")
1759       return PlaySilence(SMALL_BREAK_MSECS);
1760     if (size *= "large")
1761       return PlaySilence(LARGE_BREAK_MSECS);
1762     return PlaySilence(MEDIUM_BREAK_MSECS);
1763   }
1764 
1765   // default to medium pause
1766   return PlaySilence(MEDIUM_BREAK_MSECS);
1767 }
1768 
1769 
TraverseValue(PXMLElement & element)1770 PBoolean PVXMLSession::TraverseValue(PXMLElement & element)
1771 {
1772   PString className = element.GetAttribute("class");
1773   PString value = EvaluateExpr(element.GetAttribute("expr"));
1774   PString voice = element.GetAttribute("voice");
1775   if (voice.IsEmpty())
1776     voice = GetVar("voice");
1777   SayAs(className, value, voice);
1778   return true;
1779 }
1780 
1781 
TraverseSayAs(PXMLElement & element)1782 PBoolean PVXMLSession::TraverseSayAs(PXMLElement & element)
1783 {
1784   SayAs(element.GetAttribute("class"), element.GetData());
1785   return true;
1786 }
1787 
1788 
TraverseGoto(PXMLElement & element)1789 PBoolean PVXMLSession::TraverseGoto(PXMLElement & element)
1790 {
1791   bool fullURI = false;
1792   PString target;
1793 
1794   if (element.HasAttribute("nextitem"))
1795     target = element.GetAttribute("nextitem");
1796   else if (element.HasAttribute("expritem"))
1797     target = EvaluateExpr(element.GetAttribute("expritem"));
1798   else if (element.HasAttribute("expr")) {
1799     fullURI = true;
1800     target = EvaluateExpr(element.GetAttribute("expr"));
1801   }
1802   else if (element.HasAttribute("next")) {
1803     fullURI = true;
1804     target = element.GetAttribute("next");
1805   }
1806 
1807   if (SetCurrentForm(target, fullURI))
1808     return ProcessNode();
1809 
1810   // LATER: throw "error.semantic" or "error.badfetch" -- lookup which
1811   return false;
1812 }
1813 
1814 
TraverseGrammar(PXMLElement & element)1815 PBoolean PVXMLSession::TraverseGrammar(PXMLElement & element)
1816 {
1817   // LATER: A bunch of work to do here!
1818 
1819   // For now we only support the builtin digits type and do not parse any grammars.
1820 
1821   // NOTE: For now we will process both <grammar> and <field> here.
1822   // NOTE: Later there needs to be a check for <grammar> which will pull
1823   //       out the text and process a grammar like '1 | 2'
1824 
1825   // Right now we only support one active grammar.
1826   if (m_grammar != NULL) {
1827     PTRACE(2, "VXML\tWarning: can only process one grammar at a time, ignoring previous grammar");
1828     LoadGrammar(NULL);
1829   }
1830 
1831   m_speakNodeData = false;
1832 
1833   PCaselessString attrib = element.GetAttribute("mode");
1834   if (!attrib.IsEmpty() && attrib != "dtmf") {
1835     PTRACE(2, "VXML\tOnly DTMF mode supported for grammar");
1836     return false;
1837   }
1838 
1839   attrib = element.GetAttribute("type");
1840   if (!attrib.IsEmpty() && attrib != "X-OPAL/digits") {
1841     PTRACE(2, "VXML\tOnly \"digits\" type supported for grammar");
1842     return false;
1843   }
1844 
1845   PTRACE(4, "VXML\tLoading new grammar");
1846   PStringToString tokens;
1847   PURL::SplitVars(element.GetData(), tokens, ';', '=');
1848   return LoadGrammar(new PVXMLDigitsGrammar(*this,
1849                                             *element.GetParent(),
1850                                             tokens("minDigits", "1").AsUnsigned(),
1851                                             tokens("maxDigits", "10").AsUnsigned(),
1852                                             tokens("terminators", "#")));
1853 }
1854 
1855 
1856 // Finds the proper event hander for 'noinput', 'filled', 'nomatch' and 'error'
1857 // by searching the scope hiearchy from the current from
GoToEventHandler(PXMLElement & element,const PString & eventName)1858 bool PVXMLSession::GoToEventHandler(PXMLElement & element, const PString & eventName)
1859 {
1860   PXMLElement * level = &element;
1861   PXMLElement * handler = NULL;
1862 
1863   int actualCount = 1; // Need to increment this with state stored ... somewhere
1864 
1865   // Look in all the way up the tree for a handler either explicitly or in a catch
1866   for (;;) {
1867     for (int testCount = actualCount; testCount >= 0; --testCount) {
1868       // Check for an explicit hander - i.e. <error>, <filled>, <noinput>, <nomatch>, <help>
1869       PINDEX index = 0;
1870       if ((handler = level->GetElement(eventName)) != NULL &&
1871               handler->GetAttribute("count").AsInteger() == testCount)
1872         goto gotHandler;
1873 
1874       // Check for a <catch>
1875       index = 0;
1876       while ((handler = level->GetElement("catch", index++)) != NULL) {
1877         if ((handler->GetAttribute("event") *= eventName) &&
1878                 handler->GetAttribute("count").AsInteger() == testCount)
1879           goto gotHandler;
1880       }
1881     }
1882 
1883     level = level->GetParent();
1884     if (level == NULL) {
1885       PTRACE(4, "VXML\tNo event handler found for \"" << eventName << '"');
1886       return true;
1887     }
1888   }
1889 
1890 gotHandler:
1891   handler->SetAttribute("fired", "true");
1892   m_currentNode = handler;
1893   PTRACE(4, "VXML\tSetting event handler to node " << handler << " for \"" << eventName << '"');
1894   return false;
1895 }
1896 
1897 
SayAs(const PString & className,const PString & text)1898 void PVXMLSession::SayAs(const PString & className, const PString & text)
1899 {
1900   SayAs(className, text, GetVar("voice"));
1901 }
1902 
1903 
SayAs(const PString & className,const PString & textToSay,const PString & voice)1904 void PVXMLSession::SayAs(const PString & className, const PString & textToSay, const PString & voice)
1905 {
1906   if (m_textToSpeech != NULL)
1907     m_textToSpeech->SetVoice(voice);
1908 
1909   PString text = textToSay.Trim();
1910   if (!text.IsEmpty()) {
1911     PTextToSpeech::TextType type = PTextToSpeech::Literal;
1912 
1913     if (className *= "digits")
1914       type = PTextToSpeech::Digits;
1915 
1916     else if (className *= "literal")
1917       type = PTextToSpeech::Literal;
1918 
1919     else if (className *= "number")
1920       type = PTextToSpeech::Number;
1921 
1922     else if (className *= "currency")
1923       type = PTextToSpeech::Currency;
1924 
1925     else if (className *= "time")
1926       type = PTextToSpeech::Time;
1927 
1928     else if (className *= "date")
1929       type = PTextToSpeech::Date;
1930 
1931     else if (className *= "phone")
1932       type = PTextToSpeech::Phone;
1933 
1934     else if (className *= "ipaddress")
1935       type = PTextToSpeech::IPAddress;
1936 
1937     else if (className *= "duration")
1938       type = PTextToSpeech::Duration;
1939 
1940     PlayText(text, type);
1941   }
1942 }
1943 
1944 
StringToTime(const PString & str,int dflt)1945 PTimeInterval PVXMLSession::StringToTime(const PString & str, int dflt)
1946 {
1947   if (str.IsEmpty())
1948     return dflt;
1949 
1950   PCaselessString units = str.Mid(str.FindSpan("0123456789")).Trim();
1951   if (units ==  "s")
1952     return PTimeInterval(0, str.AsInteger());
1953   else if (units ==  "m")
1954     return PTimeInterval(0, 0, str.AsInteger());
1955   else if (units ==  "h")
1956     return PTimeInterval(0, 0, 0, str.AsInteger());
1957 
1958   return str.AsInt64();
1959 }
1960 
1961 
TraverseIf(PXMLElement & element)1962 PBoolean PVXMLSession::TraverseIf(PXMLElement & element)
1963 {
1964   // If 'cond' parameter evaluates to true, enter child entities, else
1965   // go to next element.
1966 
1967   PString condition = element.GetAttribute("cond");
1968 
1969   // Find comparison type
1970   PINDEX location = condition.Find("==");
1971   if (location == P_MAX_INDEX) {
1972     PTRACE(1, "VXML\t<if> element contains condition with operator other than ==, not implemented" );
1973     return false;
1974   }
1975 
1976   // Find var name
1977   PString varname = condition.Left(location);
1978 
1979   // Find value, skip '=' signs
1980   PString cond_value = condition.Mid(location + 3);
1981 
1982   // check if var value equals value from condition and if not skip child elements
1983   PCaselessString value = GetVar(varname);
1984   if (value == cond_value) {
1985     PTRACE(3, "VXML\tCondition matched \"" << condition << '"');
1986   }
1987   else {
1988     PTRACE(3, "VXMLSess\t\tCondition \"" << condition << "\"did not match, " << varname << " == " << value);
1989     if (element.HasSubObjects())
1990       // Step to last child element (really last element is NULL?)
1991       m_currentNode = element.GetElement(element.GetSize() - 1);
1992   }
1993 
1994   return true;
1995 }
1996 
1997 
TraverseExit(PXMLElement &)1998 PBoolean PVXMLSession::TraverseExit(PXMLElement &)
1999 {
2000   PTRACE(2, "VXML\tExiting, fast forwarding through script");
2001   m_abortVXML = true;
2002   Trigger();
2003   return true;
2004 }
2005 
2006 
TraverseSubmit(PXMLElement & element)2007 PBoolean PVXMLSession::TraverseSubmit(PXMLElement & element)
2008 {
2009   PURL url;
2010 
2011   if (element.HasAttribute("expr"))
2012     url.Parse(EvaluateExpr(element.GetAttribute("expr")));
2013   else if (element.HasAttribute("next"))
2014     url.Parse(element.GetAttribute("next"));
2015   else {
2016     PTRACE(1, "VXML\t<submit> does not contain \"next\" or \"expr\" attribute.");
2017     return false;
2018   }
2019   if (url.IsEmpty()) {
2020     PTRACE(1, "VXML\t<submit> has an invalid URL.");
2021     return false;
2022   }
2023 
2024   bool urlencoded;
2025   PCaselessString str = element.GetAttribute("enctype");
2026   if (str.IsEmpty() || str == "x-www-form-urlencoded")
2027     urlencoded = true;
2028   else if (str == "multipart/form-data")
2029     urlencoded = false;
2030   else {
2031     PTRACE(1, "VXML\t<submit> has unknown \"enctype\" attribute of \"" << str << '"');
2032     return false;
2033   }
2034 
2035   bool get;
2036   str = element.GetAttribute("method");
2037   if (str.IsEmpty())
2038     get = urlencoded;
2039   else if (str == "GET")
2040     get = true;
2041   else if (str == "POST")
2042     get = false;
2043   else {
2044     PTRACE(1, "VXML\t<submit> has unknown \"method\" attribute of \"" << str << '"');
2045     return false;
2046   }
2047 
2048   PHTTPClient client("PTLib VXML");
2049   client.SetReadTimeout(StringToTime(element.GetAttribute("fetchtimeout"), 10000));
2050 
2051   PStringArray namelist = element.GetAttribute("namelist").Tokenise(" \t", false);
2052 
2053   if (get) {
2054     if (namelist.IsEmpty())
2055       url.SetQueryVars(GetVariables());
2056     else {
2057       for (PINDEX i = 0; i < namelist.GetSize(); ++i)
2058         url.SetQueryVar(namelist[i], GetVar(namelist[i]));
2059     }
2060 
2061     PMIMEInfo replyMIME;
2062     if (client.GetDocument(url, replyMIME) && client.ReadContentBody(replyMIME))
2063       return true;
2064 
2065     PTRACE(1, "VXML\t<submit> GET " << url << " failed with "
2066            << client.GetLastResponseCode() << ' ' << client.GetLastResponseInfo());
2067     return false;
2068   }
2069 
2070   if (urlencoded) {
2071     PStringToString vars;
2072     if (namelist.IsEmpty())
2073       vars = GetVariables();
2074     else {
2075       for (PINDEX i = 0; i < namelist.GetSize(); ++i)
2076         vars.SetAt(namelist[i], GetVar(namelist[i]));
2077     }
2078 
2079     if (client.PostData(url, vars))
2080       return true;
2081 
2082     PTRACE(1, "VXML\t<submit> POST " << url << " failed with "
2083            << client.GetLastResponseCode() << ' ' << client.GetLastResponseInfo());
2084     return false;
2085   }
2086 
2087   PMIMEInfo sendMIME;
2088 
2089   // Put in boundary
2090   PString boundary = "--------012345678901234567890123458VXML";
2091   sendMIME.SetAt( PHTTP::ContentTypeTag(), "multipart/form-data; boundary=" + boundary);
2092 
2093   // After this all boundaries have a "--" prepended
2094   boundary.Splice("--", 0, 0);
2095 
2096   PStringStream entityBody;
2097 
2098   for (PINDEX i = 0; i < namelist.GetSize(); ++i) {
2099     if (GetVar(namelist[i] + ".type") != "audio/x-wav" ) {
2100       PTRACE(1, "VXML\t<submit> does not (yet) support submissions of types other than \"audio/x-wav\"");
2101       continue;
2102     }
2103 
2104     PFile file(GetVar(namelist[i] + ".filename"), PFile::ReadOnly);
2105     if (!file.IsOpen()) {
2106       PTRACE(1, "VXML\t<submit> could not find file \"" << file.GetFilePath() << '"');
2107       continue;
2108     }
2109 
2110     PMIMEInfo part1, part2;
2111     part1.Set(PMIMEInfo::ContentTypeTag, "audio/wav");
2112     part1.Set(PMIMEInfo::ContentDispositionTag,
2113               "form-data; name=\"voicemail\"; filename=\"" + file.GetFilePath().GetFileName() + '"');
2114     // Make PHP happy?
2115     // Anyway, this shows how to add more variables, for when namelist containes more elements
2116     part2.Set(PMIMEInfo::ContentDispositionTag, "form-data; name=\"MAX_FILE_SIZE\"\r\n\r\n3000000");
2117 
2118     entityBody << "--" << boundary << "\r\n"
2119                << part1 << "\r\n"
2120                << file.ReadString(file.GetLength())
2121                << "--" << boundary << "\r\n"
2122                << part2
2123                << "\r\n";
2124   }
2125 
2126   if (entityBody.IsEmpty()) {
2127     PTRACE(1, "VXML\t<submit> could not find anything to send using \"" << setfill(',') << namelist << '"');
2128     return false;
2129   }
2130 
2131   if (client.PostData(url, sendMIME, entityBody))
2132     return true;
2133 
2134   PTRACE(1, "VXML\t<submit> POST " << url << " failed with "
2135          << client.GetLastResponseCode() << ' ' << client.GetLastResponseInfo());
2136   return false;
2137 }
2138 
2139 
TraverseProperty(PXMLElement & element)2140 PBoolean PVXMLSession::TraverseProperty(PXMLElement & element)
2141 {
2142   if (element.HasAttribute("name"))
2143     SetVar("property." + element.GetAttribute("name"), element.GetAttribute("value"));
2144 
2145   return true;
2146 }
2147 
2148 
TraverseTransfer(PXMLElement &)2149 PBoolean PVXMLSession::TraverseTransfer(PXMLElement &)
2150 {
2151   m_transferStatus = NotTransfering;
2152   return true;
2153 }
2154 
2155 
TraversedTransfer(PXMLElement & element)2156 PBoolean PVXMLSession::TraversedTransfer(PXMLElement & element)
2157 {
2158   if (m_transferStatus == NotTransfering) {
2159     TransferType type = BridgedTransfer;
2160     if (element.GetAttribute("bridge") *= "false")
2161       type = BlindTransfer;
2162     else {
2163       PCaselessString typeStr = element.GetAttribute("type");
2164       if (typeStr == "blind")
2165         type = BlindTransfer;
2166       else if (typeStr == "consultation")
2167         type = ConsultationTransfer;
2168     }
2169 
2170     m_transferStartTime.SetCurrentTime();
2171 
2172     bool started = false;
2173     if (element.HasAttribute("dest"))
2174       started = OnTransfer(element.GetAttribute("dest"), type);
2175     else if (element.HasAttribute("destexpr"))
2176       started = OnTransfer(EvaluateExpr(element.GetAttribute("destexpr")), type);
2177 
2178     if (started) {
2179       m_transferStatus = TransferInProgress;
2180       return false;
2181     }
2182 
2183     m_transferStatus = TransferFailed;
2184   }
2185   else {
2186     PString name = element.GetAttribute("name");
2187     if (!name.IsEmpty())
2188       SetVar(name + "$.duration", PString(PString::Unsigned, (PTime() - m_transferStartTime).GetSeconds()));
2189   }
2190 
2191   return GoToEventHandler(element, m_transferStatus == TransferSuccessful ? "filled" : "error");
2192 }
2193 
2194 
SetTransferComplete(bool state)2195 void PVXMLSession::SetTransferComplete(bool state)
2196 {
2197   PTRACE(3, "VXML\tTransfer " << (state ? "completed" : "failed"));
2198   m_transferStatus = state ? TransferSuccessful : TransferFailed;
2199   Trigger();
2200 }
2201 
2202 
TraverseMenu(PXMLElement & element)2203 PBoolean PVXMLSession::TraverseMenu(PXMLElement & element)
2204 {
2205   LoadGrammar(new PVXMLMenuGrammar(*this, element));
2206   m_defaultMenuDTMF = (element.GetAttribute("dtmf") *= "true") ? '1' : 'N';
2207   return true;
2208 }
2209 
2210 
TraversedMenu(PXMLElement &)2211 PBoolean PVXMLSession::TraversedMenu(PXMLElement &)
2212 {
2213   return ProcessGrammar();
2214 }
2215 
2216 
TraverseChoice(PXMLElement & element)2217 PBoolean PVXMLSession::TraverseChoice(PXMLElement & element)
2218 {
2219   if (!element.HasAttribute("dtmf") && m_defaultMenuDTMF <= '9')
2220     element.SetAttribute("dtmf", PString(m_defaultMenuDTMF++));
2221 
2222   return true;
2223 }
2224 
2225 
TraverseVar(PXMLElement & element)2226 PBoolean PVXMLSession::TraverseVar(PXMLElement & element)
2227 {
2228   PString name = element.GetAttribute("name");
2229   PString expr = element.GetAttribute("expr");
2230 
2231   if (name.IsEmpty() || expr.IsEmpty()) {
2232     PTRACE(1, "VXML\t<var> must have both \"name=\" and \"expr=\" attributes." );
2233     return false;
2234   }
2235 
2236   SetVar(name, EvaluateExpr(expr));
2237   return true;
2238 }
2239 
2240 
TraverseDisconnect(PXMLElement &)2241 PBoolean PVXMLSession::TraverseDisconnect(PXMLElement &)
2242 {
2243   m_currentNode = NULL;
2244   return true;
2245 }
2246 
2247 
TraverseForm(PXMLElement &)2248 PBoolean PVXMLSession::TraverseForm(PXMLElement &)
2249 {
2250   m_variableScope = "dialog";
2251   return true;
2252 }
2253 
2254 
TraversedForm(PXMLElement &)2255 PBoolean PVXMLSession::TraversedForm(PXMLElement &)
2256 {
2257   m_variableScope = "application";
2258   return true;
2259 }
2260 
2261 
TraversePrompt(PXMLElement & element)2262 PBoolean PVXMLSession::TraversePrompt(PXMLElement & element)
2263 {
2264   // LATER:
2265   // check 'cond' attribute to see if the children of this node should be processed
2266   // check 'count' attribute to see if this node should be processed
2267 
2268   // Update timeout of current recognition (if 'timeout' attribute is set)
2269   if (m_grammar != NULL)
2270     m_grammar->SetTimeout(StringToTime(element.GetAttribute("timeout")));
2271 
2272   m_bargeIn = !(element.GetAttribute("bargein") *= "false"); // Defaults to true
2273   return true;
2274 }
2275 
2276 
TraversedPrompt(PXMLElement &)2277 PBoolean PVXMLSession::TraversedPrompt(PXMLElement &)
2278 {
2279   m_bargeIn = DefaultBargeIn;
2280   return true;
2281 }
2282 
2283 
TraverseField(PXMLElement &)2284 PBoolean PVXMLSession::TraverseField(PXMLElement &)
2285 {
2286   return true;
2287 }
2288 
2289 
TraversedField(PXMLElement &)2290 PBoolean PVXMLSession::TraversedField(PXMLElement &)
2291 {
2292   return ProcessGrammar();
2293 }
2294 
2295 
OnEndRecording()2296 void PVXMLSession::OnEndRecording()
2297 {
2298   //SetVar(channelName + ".size", PString(incomingChannel->GetWAVFile()->GetDataLength() ) );
2299   //SetVar(channelName + ".type", "audio/x-wav" );
2300   //SetVar(channelName + ".filename", incomingChannel->GetWAVFile()->GetName() );
2301   m_recordingStatus = RecordingComplete;
2302   Trigger();
2303 }
2304 
2305 
Trigger()2306 void PVXMLSession::Trigger()
2307 {
2308   PTRACE(4, "VXML\tEvent triggered");
2309   m_waitForEvent.Signal();
2310 }
2311 
2312 
2313 
2314 /////////////////////////////////////////////////////////////////////////////////////////
2315 
PVXMLGrammar(PVXMLSession & session,PXMLElement & field)2316 PVXMLGrammar::PVXMLGrammar(PVXMLSession & session, PXMLElement & field)
2317   : m_session(session)
2318   , m_field(field)
2319   , m_state(Idle)
2320   , m_timeout(PVXMLSession::StringToTime(session.GetVar("property.timeout"), 10000))
2321 {
2322   m_timer.SetNotifier(PCREATE_NOTIFIER(OnTimeout));
2323 }
2324 
2325 
SetTimeout(const PTimeInterval & timeout)2326 void PVXMLGrammar::SetTimeout(const PTimeInterval & timeout)
2327 {
2328   if (timeout > 0) {
2329     m_timeout = timeout;
2330     if (m_timer.IsRunning())
2331       m_timer = timeout;
2332   }
2333 }
2334 
2335 
Start()2336 void PVXMLGrammar::Start()
2337 {
2338   m_state = Started;
2339   m_timer = m_timeout;
2340   PTRACE(3, "VXML\tStarted grammar " << *this << ", timeout=" << m_timeout);
2341 }
2342 
2343 
OnTimeout(PTimer &,INT)2344 void PVXMLGrammar::OnTimeout(PTimer &, INT)
2345 {
2346   PTRACE(3, "VXML\tTimeout for grammar " << *this);
2347   m_mutex.Wait();
2348 
2349   if (m_state == Started) {
2350     m_state = NoInput;
2351     m_session.Trigger();
2352   }
2353 
2354   m_mutex.Signal();
2355 }
2356 
2357 
Process()2358 bool PVXMLGrammar::Process()
2359 {
2360   // Figure out what happened
2361   switch (m_state) {
2362     case Filled:
2363       if (m_field.HasAttribute("name"))
2364         m_session.SetVar(m_field.GetAttribute("name"), m_value);
2365       return m_session.GoToEventHandler(m_field, "filled");
2366 
2367     case PVXMLGrammar::NoInput:
2368       return m_session.GoToEventHandler(m_field, "noinput");
2369 
2370     case PVXMLGrammar::NoMatch:
2371       return m_session.GoToEventHandler(m_field, "nomatch");
2372 
2373     default:
2374       break; //ERROR - unexpected grammar state
2375   }
2376 
2377   return true; // Next node
2378 }
2379 
2380 
2381 //////////////////////////////////////////////////////////////////
2382 
PVXMLMenuGrammar(PVXMLSession & session,PXMLElement & field)2383 PVXMLMenuGrammar::PVXMLMenuGrammar(PVXMLSession & session, PXMLElement & field)
2384   : PVXMLGrammar(session, field)
2385 {
2386 }
2387 
2388 
OnUserInput(const char ch)2389 void PVXMLMenuGrammar::OnUserInput(const char ch)
2390 {
2391   m_mutex.Wait();
2392 
2393   m_value = ch;
2394   m_state = PVXMLGrammar::Filled;
2395 
2396   m_mutex.Signal();
2397 }
2398 
2399 
Process()2400 bool PVXMLMenuGrammar::Process()
2401 {
2402   if (m_state == Filled) {
2403     PXMLElement * choice;
2404     PINDEX index = 0;
2405     while ((choice = m_field.GetElement("choice", index++)) != NULL) {
2406       // Check if DTMF value for grammarResult matches the DTMF value for the choice
2407       if (choice->GetAttribute("dtmf") == m_value) {
2408         PTRACE(3, "VXML\tMatched menu choice: " << m_value);
2409         PString next = choice->GetAttribute("next");
2410         if (next.IsEmpty())
2411           next = m_session.EvaluateExpr(choice->GetAttribute("expr"));
2412         if (m_session.SetCurrentForm(next, true))
2413           return false;
2414 
2415         return m_session.GoToEventHandler(m_field, choice->GetAttribute("event"));
2416       }
2417     }
2418 
2419     m_state = NoMatch;
2420   }
2421 
2422   return PVXMLGrammar::Process();
2423 }
2424 
2425 
2426 //////////////////////////////////////////////////////////////////
2427 
PVXMLDigitsGrammar(PVXMLSession & session,PXMLElement & field,PINDEX minDigits,PINDEX maxDigits,PString terminators)2428 PVXMLDigitsGrammar::PVXMLDigitsGrammar(PVXMLSession & session,
2429                                        PXMLElement & field,
2430                                        PINDEX minDigits,
2431                                        PINDEX maxDigits,
2432                                        PString terminators)
2433   : PVXMLGrammar(session, field)
2434   , m_minDigits(minDigits)
2435   , m_maxDigits(maxDigits)
2436   , m_terminators(terminators)
2437 {
2438   PAssert(minDigits <= maxDigits, PInvalidParameter);
2439 }
2440 
2441 
OnUserInput(const char ch)2442 void PVXMLDigitsGrammar::OnUserInput(const char ch)
2443 {
2444   PWaitAndSignal mutex(m_mutex);
2445 
2446   // Ignore any keys until we are running
2447   if (m_state != Started)
2448     return;
2449 
2450   PINDEX len = m_value.GetLength();
2451 
2452   // is this char the terminator?
2453   if (m_terminators.Find(ch) != P_MAX_INDEX) {
2454     m_state = (len >= m_minDigits && len <= m_maxDigits) ? Filled : NoMatch;
2455     return;
2456   }
2457 
2458   // Otherwise add to the grammar and check to see if we're done
2459   m_value += ch;
2460   if (++len >= m_maxDigits)
2461     m_state = PVXMLGrammar::Filled;   // the grammar is filled!
2462 }
2463 
2464 
2465 //////////////////////////////////////////////////////////////////
2466 
PVXMLChannel(unsigned frameDelay,PINDEX frameSize)2467 PVXMLChannel::PVXMLChannel(unsigned frameDelay, PINDEX frameSize)
2468   : PDelayChannel(DelayReadsAndWrites, frameDelay, frameSize)
2469   , m_vxmlSession(NULL)
2470   , m_sampleFrequency(8000)
2471   , m_closed(false)
2472   , m_paused(false)
2473   , m_totalData(0)
2474   , m_recordable(NULL)
2475   , m_currentPlayItem(NULL)
2476 {
2477 }
2478 
2479 
Open(PVXMLSession * session)2480 PBoolean PVXMLChannel::Open(PVXMLSession * session)
2481 {
2482   m_currentPlayItem = NULL;
2483   m_vxmlSession = session;
2484   m_silenceTimer.SetInterval(500); // 1/2 a second delay before we start outputting stuff
2485   PTRACE(4, "VXML\tOpening channel " << this);
2486   return true;
2487 }
2488 
2489 
~PVXMLChannel()2490 PVXMLChannel::~PVXMLChannel()
2491 {
2492   Close();
2493 }
2494 
2495 
IsOpen() const2496 PBoolean PVXMLChannel::IsOpen() const
2497 {
2498   return !m_closed;
2499 }
2500 
2501 
Close()2502 PBoolean PVXMLChannel::Close()
2503 {
2504   if (!m_closed) {
2505     PTRACE(4, "VXML\tClosing channel " << this);
2506 
2507     EndRecording();
2508     FlushQueue();
2509 
2510     m_closed = true;
2511 
2512     PDelayChannel::Close();
2513   }
2514 
2515   return true;
2516 }
2517 
2518 
AdjustWavFilename(const PString & ofn)2519 PString PVXMLChannel::AdjustWavFilename(const PString & ofn)
2520 {
2521   if (wavFilePrefix.IsEmpty())
2522     return ofn;
2523 
2524   PString fn = ofn;
2525 
2526   // add in suffix required for channel format, if any
2527   PINDEX pos = ofn.FindLast('.');
2528   if (pos == P_MAX_INDEX) {
2529     if (fn.Right(wavFilePrefix.GetLength()) != wavFilePrefix)
2530       fn += wavFilePrefix;
2531   }
2532   else {
2533     PString basename = ofn.Left(pos);
2534     PString ext      = ofn.Mid(pos+1);
2535     if (basename.Right(wavFilePrefix.GetLength()) != wavFilePrefix)
2536       basename += wavFilePrefix;
2537     fn = basename + "." + ext;
2538   }
2539   return fn;
2540 }
2541 
2542 
2543 #if P_WAVFILE
2544 
CreateWAVFile(const PFilePath & fn,PBoolean recording)2545 PWAVFile * PVXMLChannel::CreateWAVFile(const PFilePath & fn, PBoolean recording)
2546 {
2547   PWAVFile * wav = new PWAVFile;
2548   if (!wav->SetFormat(mediaFormat)) {
2549     PTRACE(1, "VXML\tWAV file format " << mediaFormat << " not known");
2550     delete wav;
2551     return NULL;
2552   }
2553 
2554   wav->SetAutoconvert();
2555   if (!wav->Open(fn,
2556                  recording ? PFile::WriteOnly : PFile::ReadOnly,
2557                  PFile::ModeDefault))
2558     PTRACE(2, "VXML\tCould not open WAV file " << wav->GetName());
2559 
2560   else if (recording) {
2561     wav->SetChannels(1);
2562     wav->SetSampleRate(8000);
2563     wav->SetSampleSize(16);
2564     return wav;
2565   }
2566 
2567   else if (!wav->IsValid())
2568     PTRACE(2, "VXML\tWAV file header invalid for " << wav->GetName());
2569 
2570   else if (wav->GetSampleRate() != GetSampleFrequency())
2571     PTRACE(2, "VXML\tWAV file has unsupported sample frequency " << wav->GetSampleRate());
2572 
2573   else if (wav->GetChannels() != 1)
2574     PTRACE(2, "VXML\tWAV file has unsupported channel count " << wav->GetChannels());
2575 
2576   else {
2577     wav->SetAutoconvert();   /// enable autoconvert
2578     PTRACE(3, "VXML\tOpened WAV file " << wav->GetName());
2579     return wav;
2580   }
2581 
2582   delete wav;
2583   return NULL;
2584 }
2585 
2586 #endif // P_WAVFILE
2587 
2588 
Write(const void * buf,PINDEX len)2589 PBoolean PVXMLChannel::Write(const void * buf, PINDEX len)
2590 {
2591   if (m_closed)
2592     return false;
2593 
2594   m_channelWriteMutex.Wait();
2595 
2596   // let the recordable do silence detection
2597   if (m_recordable != NULL && m_recordable->OnFrame(IsSilenceFrame(buf, len)))
2598     EndRecording();
2599 
2600   m_channelWriteMutex.Signal();
2601 
2602   // write the data and do the correct delay
2603   if (WriteFrame(buf, len))
2604     m_totalData += lastWriteCount;
2605   else {
2606     EndRecording();
2607     lastWriteCount = len;
2608     Wait(len, nextWriteTick);
2609   }
2610 
2611   return true;
2612 }
2613 
2614 
StartRecording(const PFilePath & fn,unsigned _finalSilence,unsigned _maxDuration)2615 PBoolean PVXMLChannel::StartRecording(const PFilePath & fn, unsigned _finalSilence, unsigned _maxDuration)
2616 {
2617   PVXMLRecordableFilename * recordable = new PVXMLRecordableFilename();
2618   if (!recordable->Open(fn)) {
2619     delete recordable;
2620     return false;
2621   }
2622 
2623   recordable->SetFinalSilence(_finalSilence);
2624   recordable->SetMaxDuration(_maxDuration);
2625   return QueueRecordable(recordable);
2626 }
2627 
2628 
QueueRecordable(PVXMLRecordable * newItem)2629 PBoolean PVXMLChannel::QueueRecordable(PVXMLRecordable * newItem)
2630 {
2631   m_totalData = 0;
2632 
2633   // shutdown any existing recording
2634   EndRecording();
2635 
2636   // insert the new recordable
2637   PWaitAndSignal mutex(m_channelWriteMutex);
2638   m_recordable = newItem;
2639   m_totalData = 0;
2640   SetReadTimeout(frameDelay);
2641   return newItem->OnStart(*this);
2642 }
2643 
2644 
EndRecording()2645 PBoolean PVXMLChannel::EndRecording()
2646 {
2647   PWaitAndSignal mutex(m_channelWriteMutex);
2648 
2649   if (m_recordable == NULL)
2650     return false;
2651 
2652   PTRACE(3, "VXML\tFinished recording " << m_totalData << " bytes");
2653   m_recordable->OnStop();
2654   delete m_recordable;
2655   m_recordable = NULL;
2656   m_vxmlSession->OnEndRecording();
2657 
2658   return true;
2659 }
2660 
2661 
Read(void * buffer,PINDEX amount)2662 PBoolean PVXMLChannel::Read(void * buffer, PINDEX amount)
2663 {
2664   for (;;) {
2665     if (m_closed)
2666       return false;
2667 
2668     if (m_paused || m_silenceTimer.IsRunning())
2669       break;
2670 
2671     // if the read succeeds, we are done
2672     if (ReadFrame(buffer, amount)) {
2673       m_totalData += lastReadCount;
2674       return true; // Already done real time delay
2675     }
2676 
2677     // if a timeout, send silence, try again in a bit
2678     if (GetErrorCode(LastReadError) == Timeout)
2679       break;
2680 
2681     // Other errors mean end of the playable
2682     PWaitAndSignal mutex(m_channelReadMutex);
2683 
2684     // if current item still active, check for trailing actions
2685     if (m_currentPlayItem != NULL) {
2686       PTRACE(3, "VXML\tFinished playing " << *m_currentPlayItem << ", " << m_totalData << " bytes");
2687 
2688       if (m_currentPlayItem->OnRepeat())
2689         continue;
2690 
2691       // see if end of queue delay specified
2692       if (m_currentPlayItem->OnDelay())
2693         break;
2694 
2695       // stop the current item
2696       m_currentPlayItem->OnStop();
2697       delete m_currentPlayItem;
2698       m_currentPlayItem = NULL;
2699       m_vxmlSession->Trigger();
2700     }
2701 
2702     for (;;) {
2703       // check the queue for the next action, if none, send silence
2704       m_currentPlayItem = m_playQueue.Dequeue();
2705       if (m_currentPlayItem == NULL)
2706         goto double_break;
2707 
2708       // start the new item
2709       if (m_currentPlayItem->OnStart())
2710         break;
2711 
2712       delete m_currentPlayItem;
2713     }
2714 
2715     PTRACE(4, "VXML\tStarted playing " << *m_currentPlayItem);
2716     SetReadTimeout(frameDelay);
2717     m_totalData = 0;
2718   }
2719 
2720 double_break:
2721   lastReadCount = CreateSilenceFrame(buffer, amount);
2722   Wait(lastReadCount, nextReadTick);
2723   return true;
2724 }
2725 
2726 
SetSilence(unsigned msecs)2727 void PVXMLChannel::SetSilence(unsigned msecs)
2728 {
2729   PTRACE(3, "VXML\tPlaying silence for " << msecs << "ms");
2730   m_silenceTimer.SetInterval(msecs);
2731 }
2732 
2733 
QueuePlayable(const PString & type,const PString & arg,PINDEX repeat,PINDEX delay,PBoolean autoDelete)2734 PBoolean PVXMLChannel::QueuePlayable(const PString & type,
2735                                  const PString & arg,
2736                                  PINDEX repeat,
2737                                  PINDEX delay,
2738                                  PBoolean autoDelete)
2739 {
2740   if (repeat <= 0)
2741     repeat = 1;
2742 
2743   PVXMLPlayable * item = PFactory<PVXMLPlayable>::CreateInstance(type);
2744   if (item == NULL) {
2745     PTRACE(2, "VXML\tCannot find playable of type " << type);
2746     return false;
2747   }
2748 
2749   if (item->Open(*this, arg, delay, repeat, autoDelete)) {
2750     PTRACE(3, "VXML\tEnqueueing playable " << type << " with arg \"" << arg
2751            << "\" for playing " << repeat << " times, followed by " << delay << "ms silence");
2752     return QueuePlayable(item);
2753   }
2754 
2755   delete item;
2756   return false;
2757 }
2758 
2759 
QueuePlayable(PVXMLPlayable * newItem)2760 PBoolean PVXMLChannel::QueuePlayable(PVXMLPlayable * newItem)
2761 {
2762   if (!IsOpen()) {
2763     delete newItem;
2764     return false;
2765   }
2766 
2767   newItem->SetSampleFrequency(GetSampleFrequency());
2768   m_channelReadMutex.Wait();
2769   m_playQueue.Enqueue(newItem);
2770   m_channelReadMutex.Signal();
2771   return true;
2772 }
2773 
2774 
QueueResource(const PURL & url,PINDEX repeat,PINDEX delay)2775 PBoolean PVXMLChannel::QueueResource(const PURL & url, PINDEX repeat, PINDEX delay)
2776 {
2777   if (url.GetScheme() *= "file")
2778     return QueuePlayable("File", url.AsFilePath(), repeat, delay, false);
2779   else
2780     return QueuePlayable("URL", url.AsString(), repeat, delay);
2781 }
2782 
2783 
QueueData(const PBYTEArray & data,PINDEX repeat,PINDEX delay)2784 PBoolean PVXMLChannel::QueueData(const PBYTEArray & data, PINDEX repeat, PINDEX delay)
2785 {
2786   PTRACE(3, "VXML\tEnqueueing " << data.GetSize() << " bytes for playing, followed by " << delay << "ms silence");
2787   PVXMLPlayableData * item = PFactory<PVXMLPlayable>::CreateInstanceAs<PVXMLPlayableData>("PCM Data");
2788   if (item == NULL) {
2789     PTRACE(2, "VXML\tCannot find playable of type 'PCM Data'");
2790     delete item;
2791     return false;
2792   }
2793 
2794   if (!item->Open(*this, "", delay, repeat, true)) {
2795     PTRACE(2, "VXML\tCannot open playable of type 'PCM Data'");
2796     delete item;
2797     return false;
2798   }
2799 
2800   item->SetData(data);
2801 
2802   return QueuePlayable(item);
2803 }
2804 
2805 
FlushQueue()2806 void PVXMLChannel::FlushQueue()
2807 {
2808   PTRACE(4, "VXML\tFlushing playable queue");
2809 
2810   PWaitAndSignal mutex(m_channelReadMutex);
2811 
2812   PVXMLPlayable * qItem;
2813   while ((qItem = m_playQueue.Dequeue()) != NULL) {
2814     qItem->OnStop();
2815     delete qItem;
2816   }
2817 
2818   if (m_currentPlayItem != NULL) {
2819     m_currentPlayItem->OnStop();
2820     delete m_currentPlayItem;
2821     m_currentPlayItem = NULL;
2822   }
2823 
2824   m_silenceTimer.Stop();
2825 
2826   PTRACE(4, "VXML\tFlushed playable queue");
2827 }
2828 
2829 
2830 ///////////////////////////////////////////////////////////////
2831 
2832 PFactory<PVXMLChannel>::Worker<PVXMLChannelPCM> pcmVXMLChannelFactory(VXML_PCM16);
2833 
PVXMLChannelPCM()2834 PVXMLChannelPCM::PVXMLChannelPCM()
2835   : PVXMLChannel(10, 160)
2836 {
2837   mediaFormat    = VXML_PCM16;
2838   wavFilePrefix  = PString::Empty();
2839 }
2840 
2841 
WriteFrame(const void * buf,PINDEX len)2842 PBoolean PVXMLChannelPCM::WriteFrame(const void * buf, PINDEX len)
2843 {
2844   return PDelayChannel::Write(buf, len);
2845 }
2846 
2847 
ReadFrame(void * buffer,PINDEX amount)2848 PBoolean PVXMLChannelPCM::ReadFrame(void * buffer, PINDEX amount)
2849 {
2850   PINDEX len = 0;
2851   while (len < amount)  {
2852     if (!PDelayChannel::Read(len + (char *)buffer, amount-len))
2853       return false;
2854     len += GetLastReadCount();
2855   }
2856 
2857   return true;
2858 }
2859 
2860 
CreateSilenceFrame(void * buffer,PINDEX amount)2861 PINDEX PVXMLChannelPCM::CreateSilenceFrame(void * buffer, PINDEX amount)
2862 {
2863   memset(buffer, 0, amount);
2864   return amount;
2865 }
2866 
2867 
IsSilenceFrame(const void * buf,PINDEX len) const2868 PBoolean PVXMLChannelPCM::IsSilenceFrame(const void * buf, PINDEX len) const
2869 {
2870   // Calculate the average signal level of this frame
2871   int sum = 0;
2872 
2873   const short * pcm = (const short *)buf;
2874   const short * end = pcm + len/2;
2875   while (pcm != end) {
2876     if (*pcm < 0)
2877       sum -= *pcm++;
2878     else
2879       sum += *pcm++;
2880   }
2881 
2882   // calc average
2883   unsigned level = sum / (len / 2);
2884 
2885   return level < 500; // arbitrary level
2886 }
2887 
2888 
2889 static short beepData[] = { 0, 18784, 30432, 30400, 18784, 0, -18784, -30432, -30400, -18784 };
2890 
2891 
GetBeepData(PBYTEArray & data,unsigned ms)2892 void PVXMLChannelPCM::GetBeepData(PBYTEArray & data, unsigned ms)
2893 {
2894   data.SetSize(0);
2895   while (data.GetSize() < (PINDEX)(ms * 16)) {
2896     PINDEX len = data.GetSize();
2897     data.SetSize(len + sizeof(beepData));
2898     memcpy(len + data.GetPointer(), beepData, sizeof(beepData));
2899   }
2900 }
2901 
2902 ///////////////////////////////////////////////////////////////
2903 
2904 PFactory<PVXMLChannel>::Worker<PVXMLChannelG7231> g7231VXMLChannelFactory(VXML_G7231);
2905 
PVXMLChannelG7231()2906 PVXMLChannelG7231::PVXMLChannelG7231()
2907   : PVXMLChannel(30, 0)
2908 {
2909   mediaFormat     = VXML_G7231;
2910   wavFilePrefix  = "_g7231";
2911 }
2912 
2913 
2914 static const PINDEX g7231Lens[] = { 24, 20, 4, 1 };
2915 
WriteFrame(const void * buffer,PINDEX actualLen)2916 PBoolean PVXMLChannelG7231::WriteFrame(const void * buffer, PINDEX actualLen)
2917 {
2918   PINDEX len = g7231Lens[(*(BYTE *)buffer)&3];
2919   if (len > actualLen)
2920     return false;
2921 
2922   return PDelayChannel::Write(buffer, len);
2923 }
2924 
2925 
ReadFrame(void * buffer,PINDEX)2926 PBoolean PVXMLChannelG7231::ReadFrame(void * buffer, PINDEX /*amount*/)
2927 {
2928   if (!PDelayChannel::Read(buffer, 1))
2929     return false;
2930 
2931   PINDEX len = g7231Lens[(*(BYTE *)buffer)&3];
2932   if (len != 1) {
2933     if (!PIndirectChannel::Read(1+(BYTE *)buffer, len-1))
2934       return false;
2935     lastReadCount++;
2936   }
2937 
2938   return true;
2939 }
2940 
2941 
CreateSilenceFrame(void * buffer,PINDEX)2942 PINDEX PVXMLChannelG7231::CreateSilenceFrame(void * buffer, PINDEX /* len */)
2943 {
2944   ((BYTE *)buffer)[0] = 2;
2945   memset(((BYTE *)buffer)+1, 0, 3);
2946   return 4;
2947 }
2948 
2949 
IsSilenceFrame(const void * buf,PINDEX len) const2950 PBoolean PVXMLChannelG7231::IsSilenceFrame(const void * buf, PINDEX len) const
2951 {
2952   if (len == 4)
2953     return true;
2954   if (buf == NULL)
2955     return false;
2956   return ((*(const BYTE *)buf)&3) == 2;
2957 }
2958 
2959 ///////////////////////////////////////////////////////////////
2960 
2961 PFactory<PVXMLChannel>::Worker<PVXMLChannelG729> g729VXMLChannelFactory(VXML_G729);
2962 
PVXMLChannelG729()2963 PVXMLChannelG729::PVXMLChannelG729()
2964   : PVXMLChannel(10, 0)
2965 {
2966   mediaFormat    = VXML_G729;
2967   wavFilePrefix  = "_g729";
2968 }
2969 
2970 
WriteFrame(const void * buf,PINDEX)2971 PBoolean PVXMLChannelG729::WriteFrame(const void * buf, PINDEX /*len*/)
2972 {
2973   return PDelayChannel::Write(buf, 10);
2974 }
2975 
ReadFrame(void * buffer,PINDEX)2976 PBoolean PVXMLChannelG729::ReadFrame(void * buffer, PINDEX /*amount*/)
2977 {
2978   return PDelayChannel::Read(buffer, 10); // No silence frames so always 10 bytes
2979 }
2980 
2981 
CreateSilenceFrame(void * buffer,PINDEX)2982 PINDEX PVXMLChannelG729::CreateSilenceFrame(void * buffer, PINDEX /* len */)
2983 {
2984   memset(buffer, 0, 10);
2985   return 10;
2986 }
2987 
2988 
IsSilenceFrame(const void *,PINDEX) const2989 PBoolean PVXMLChannelG729::IsSilenceFrame(const void * /*buf*/, PINDEX /*len*/) const
2990 {
2991   return false;
2992 }
2993 
2994 
2995 //////////////////////////////////////////////////////////////////////////////////
2996 
2997 class TextToSpeech_Sample : public PTextToSpeech
2998 {
2999   public:
3000     TextToSpeech_Sample();
3001     PStringArray GetVoiceList();
3002     PBoolean SetVoice(const PString & voice);
3003     PBoolean SetRate(unsigned rate);
3004     unsigned GetRate();
3005     PBoolean SetVolume(unsigned volume);
3006     unsigned GetVolume();
3007     PBoolean OpenFile   (const PFilePath & fn);
3008     PBoolean OpenChannel(PChannel * chanel);
IsOpen()3009     PBoolean IsOpen()    { return opened; }
3010     PBoolean Close();
3011     PBoolean Speak(const PString & text, TextType hint = Default);
3012     PBoolean SpeakNumber(unsigned number);
3013 
3014     PBoolean SpeakFile(const PString & text);
3015 
3016   protected:
3017     //PTextToSpeech * defaultEngine;
3018 
3019     PMutex mutex;
3020     PBoolean opened;
3021     PBoolean usingFile;
3022     PString text;
3023     PFilePath path;
3024     unsigned volume, rate;
3025     PString voice;
3026 
3027     std::vector<PFilePath> filenames;
3028 };
3029 
3030 
TextToSpeech_Sample()3031 TextToSpeech_Sample::TextToSpeech_Sample()
3032 {
3033   PWaitAndSignal m(mutex);
3034   usingFile = opened = false;
3035   rate = 8000;
3036   volume = 100;
3037 }
3038 
3039 
GetVoiceList()3040 PStringArray TextToSpeech_Sample::GetVoiceList()
3041 {
3042   PStringArray r;
3043   return r;
3044 }
3045 
3046 
SetVoice(const PString & v)3047 PBoolean TextToSpeech_Sample::SetVoice(const PString & v)
3048 {
3049   voice = v;
3050   return true;
3051 }
3052 
3053 
SetRate(unsigned v)3054 PBoolean TextToSpeech_Sample::SetRate(unsigned v)
3055 {
3056   rate = v;
3057   return true;
3058 }
3059 
3060 
GetRate()3061 unsigned TextToSpeech_Sample::GetRate()
3062 {
3063   return rate;
3064 }
3065 
3066 
SetVolume(unsigned v)3067 PBoolean TextToSpeech_Sample::SetVolume(unsigned v)
3068 {
3069   volume = v;
3070   return true;
3071 }
3072 
3073 
GetVolume()3074 unsigned TextToSpeech_Sample::GetVolume()
3075 {
3076   return volume;
3077 }
3078 
3079 
OpenFile(const PFilePath & fn)3080 PBoolean TextToSpeech_Sample::OpenFile(const PFilePath & fn)
3081 {
3082   PWaitAndSignal m(mutex);
3083 
3084   Close();
3085   usingFile = true;
3086   path = fn;
3087   opened = true;
3088 
3089   PTRACE(3, "TTS\tWriting speech to " << fn);
3090 
3091   return true;
3092 }
3093 
3094 
OpenChannel(PChannel *)3095 PBoolean TextToSpeech_Sample::OpenChannel(PChannel * /*chanel*/)
3096 {
3097   PWaitAndSignal m(mutex);
3098 
3099   Close();
3100   usingFile = false;
3101   opened = false;
3102 
3103   return true;
3104 }
3105 
3106 
Close()3107 PBoolean TextToSpeech_Sample::Close()
3108 {
3109   PWaitAndSignal m(mutex);
3110 
3111   if (!opened)
3112     return true;
3113 
3114   PBoolean stat = true;
3115 
3116 #if P_WAVFILE
3117   if (usingFile) {
3118     PWAVFile outputFile("PCM-16", path, PFile::WriteOnly);
3119     if (!outputFile.IsOpen()) {
3120       PTRACE(1, "TTS\tCannot create output file " << path);
3121       stat = false;
3122     }
3123     else {
3124       std::vector<PFilePath>::const_iterator r;
3125       for (r = filenames.begin(); r != filenames.end(); ++r) {
3126         PFilePath f = *r;
3127         PWAVFile file;
3128         file.SetAutoconvert();
3129         if (!file.Open(f, PFile::ReadOnly)) {
3130           PTRACE(1, "TTS\tCannot open input file " << f);
3131           stat = false;
3132         } else {
3133           PTRACE(1, "TTS\tReading from " << f);
3134           BYTE buffer[1024];
3135           for (;;) {
3136             if (!file.Read(buffer, 1024))
3137               break;
3138             outputFile.Write(buffer, file.GetLastReadCount());
3139           }
3140         }
3141       }
3142     }
3143     filenames.erase(filenames.begin(), filenames.end());
3144   }
3145 #endif // P_WAVFILE
3146 
3147   opened = false;
3148   return stat;
3149 }
3150 
3151 
SpeakNumber(unsigned number)3152 PBoolean TextToSpeech_Sample::SpeakNumber(unsigned number)
3153 {
3154   return Speak(PString(PString::Signed, number), Number);
3155 }
3156 
3157 
Speak(const PString & text,TextType hint)3158 PBoolean TextToSpeech_Sample::Speak(const PString & text, TextType hint)
3159 {
3160   // break into lines
3161   PStringArray lines = text.Lines();
3162   PINDEX i;
3163   for (i = 0; i < lines.GetSize(); ++i) {
3164 
3165     PString line = lines[i].Trim();
3166     if (line.IsEmpty())
3167       continue;
3168 
3169     PTRACE(3, "TTS\tAsked to speak " << text << " with type " << hint);
3170 
3171     if (hint == DateAndTime) {
3172       PTRACE(3, "TTS\tSpeaking date and time");
3173       Speak(text, Date);
3174       Speak(text, Time);
3175       continue;
3176     }
3177 
3178     if (hint == Date) {
3179       PTime time(line);
3180       if (time.IsValid()) {
3181         PTRACE(4, "TTS\tSpeaking date " << time);
3182         SpeakFile(time.GetDayName(time.GetDayOfWeek(), PTime::FullName));
3183         SpeakNumber(time.GetDay());
3184         SpeakFile(time.GetMonthName(time.GetMonth(), PTime::FullName));
3185         SpeakNumber(time.GetYear());
3186       }
3187       continue;
3188     }
3189 
3190     if (hint == Time) {
3191       PTime time(line);
3192       if (time.IsValid()) {
3193         PTRACE(4, "TTS\tSpeaking time " << time);
3194         int hour = time.GetHour();
3195         if (hour < 13) {
3196           SpeakNumber(hour);
3197           SpeakNumber(time.GetMinute());
3198           SpeakFile(PTime::GetTimeAM());
3199         }
3200         else {
3201           SpeakNumber(hour-12);
3202           SpeakNumber(time.GetMinute());
3203           SpeakFile(PTime::GetTimePM());
3204         }
3205       }
3206       continue;
3207     }
3208 
3209     if (hint == Default) {
3210       PBoolean isTime = false;
3211       PBoolean isDate = false;
3212 
3213       for (i = 0; !isDate && i < 7; ++i) {
3214         isDate = isDate || (line.Find(PTime::GetDayName((PTime::Weekdays)i, PTime::FullName)) != P_MAX_INDEX);
3215         isDate = isDate || (line.Find(PTime::GetDayName((PTime::Weekdays)i, PTime::Abbreviated)) != P_MAX_INDEX);
3216         PTRACE(4, "TTS\t " << isDate << " - " << PTime::GetDayName((PTime::Weekdays)i, PTime::FullName) << "," << PTime::GetDayName((PTime::Weekdays)i, PTime::Abbreviated));
3217       }
3218       for (i = 1; !isDate && i <= 12; ++i) {
3219         isDate = isDate || (line.Find(PTime::GetMonthName((PTime::Months)i, PTime::FullName)) != P_MAX_INDEX);
3220         isDate = isDate || (line.Find(PTime::GetMonthName((PTime::Months)i, PTime::Abbreviated)) != P_MAX_INDEX);
3221         PTRACE(4, "TTS\t " << isDate << " - " << PTime::GetMonthName((PTime::Months)i, PTime::FullName) << "," << PTime::GetMonthName((PTime::Months)i, PTime::Abbreviated));
3222       }
3223 
3224       if (!isTime)
3225         isTime = line.Find(PTime::GetTimeSeparator()) != P_MAX_INDEX;
3226       if (!isDate)
3227         isDate = line.Find(PTime::GetDateSeparator()) != P_MAX_INDEX;
3228 
3229       if (isDate && isTime) {
3230         PTRACE(4, "TTS\tDefault changed to DateAndTime");
3231         Speak(line, DateAndTime);
3232         continue;
3233       }
3234       if (isDate) {
3235         PTRACE(4, "TTS\tDefault changed to Date");
3236         Speak(line, Date);
3237         continue;
3238       }
3239       else if (isTime) {
3240         PTRACE(4, "TTS\tDefault changed to Time");
3241         Speak(line, Time);
3242         continue;
3243       }
3244     }
3245 
3246     PStringArray tokens = line.Tokenise("\t ", false);
3247     for (PINDEX j = 0; j < tokens.GetSize(); ++j) {
3248       PString word = tokens[j].Trim();
3249       if (word.IsEmpty())
3250         continue;
3251       PTRACE(4, "TTS\tSpeaking word " << word << " as " << hint);
3252       switch (hint) {
3253 
3254         case Time:
3255         case Date:
3256         case DateAndTime:
3257           PAssertAlways("Logic error");
3258           break;
3259 
3260         default:
3261         case Default:
3262         case Literal:
3263           {
3264             PBoolean isDigits = true;
3265             PBoolean isIpAddress = true;
3266 
3267             PINDEX k;
3268             for (k = 0; k < word.GetLength(); ++k) {
3269               if (word[k] == '.')
3270                 isDigits = false;
3271               else if (!isdigit(word[k]))
3272                 isDigits = isIpAddress = false;
3273             }
3274 
3275             if (isIpAddress) {
3276               PTRACE(4, "TTS\tDefault changed to IPAddress");
3277               Speak(word, IPAddress);
3278             } else if (isDigits) {
3279               PTRACE(4, "TTS\tDefault changed to Number");
3280               Speak(word, Number);
3281             } else {
3282               PTRACE(4, "TTS\tDefault changed to Spell");
3283               Speak(word, Spell);
3284             }
3285           }
3286           break;
3287 
3288         case Spell:
3289           PTRACE(4, "TTS\tSpelling " << text);
3290           for (PINDEX i = 0; i < text.GetLength(); ++i)
3291             SpeakFile(PString(text[i]));
3292           break;
3293 
3294         case Phone:
3295         case Digits:
3296           PTRACE(4, "TTS\tSpeaking digits " << text);
3297           for (PINDEX i = 0; i < text.GetLength(); ++i) {
3298             if (isdigit(text[i]))
3299               SpeakFile(PString(text[i]));
3300           }
3301           break;
3302 
3303         case Duration:
3304         case Currency:
3305         case Number:
3306           {
3307             int number = atoi(line);
3308             PTRACE(4, "TTS\tSpeaking number " << number);
3309             if (number < 0) {
3310               SpeakFile("negative");
3311               number = -number;
3312             }
3313             else if (number == 0) {
3314               SpeakFile("0");
3315             }
3316             else {
3317               if (number >= 1000000) {
3318                 int millions = number / 1000000;
3319                 number = number % 1000000;
3320                 SpeakNumber(millions);
3321                 SpeakFile("million");
3322               }
3323               if (number >= 1000) {
3324                 int thousands = number / 1000;
3325                 number = number % 1000;
3326                 SpeakNumber(thousands);
3327                 SpeakFile("thousand");
3328               }
3329               if (number >= 100) {
3330                 int hundreds = number / 100;
3331                 number = number % 100;
3332                 SpeakNumber(hundreds);
3333                 SpeakFile("hundred");
3334               }
3335               if (!SpeakFile(PString(PString::Signed, number))) {
3336                 int tens = number / 10;
3337                 number = number % 10;
3338                 if (tens > 0)
3339                   SpeakFile(PString(PString::Signed, tens*10));
3340                 if (number > 0)
3341                   SpeakFile(PString(PString::Signed, number));
3342               }
3343             }
3344           }
3345           break;
3346 
3347         case IPAddress:
3348           {
3349             PIPSocket::Address addr(line);
3350             PTRACE(4, "TTS\tSpeaking IP address " << addr);
3351             for (PINDEX i = 0; i < 4; ++i) {
3352               int octet = addr[i];
3353               if (octet < 100)
3354                 SpeakNumber(octet);
3355               else
3356                 Speak(octet, Digits);
3357               if (i != 3)
3358                 SpeakFile("dot");
3359             }
3360           }
3361           break;
3362       }
3363     }
3364   }
3365 
3366   return true;
3367 }
3368 
3369 
SpeakFile(const PString & text)3370 PBoolean TextToSpeech_Sample::SpeakFile(const PString & text)
3371 {
3372   PFilePath f = PDirectory(voice) + (text.ToLower() + ".wav");
3373   if (!PFile::Exists(f)) {
3374     PTRACE(2, "TTS\tUnable to find explicit file for " << text);
3375     return false;
3376   }
3377   filenames.push_back(f);
3378   return true;
3379 }
3380 
3381 PFactory<PTextToSpeech>::Worker<TextToSpeech_Sample> sampleTTSFactory("sampler", false);
3382 
3383 
3384 #endif   // P_VXML
3385 
3386 
3387 ///////////////////////////////////////////////////////////////
3388