├── .gitignore ├── AUTHORS ├── CHANGES ├── COPYING ├── INSTALL ├── Makefile.am ├── README ├── acinclude.m4 ├── app-unimrcp ├── Makefile.am ├── app_datastore.c ├── app_datastore.h ├── app_mrcprecog.c ├── app_mrcpsynth.c ├── app_synthandrecog.c ├── app_unimrcp.c ├── ast_unimrcp_framework.c ├── ast_unimrcp_framework.h ├── audio_queue.c ├── audio_queue.h ├── speech_channel.c └── speech_channel.h ├── bootstrap ├── build ├── acmacros │ ├── apr_common.m4 │ ├── asterisk.m4 │ ├── ax_compiler_vendor.m4 │ └── unimrcp.m4 └── xmldocs │ └── get_documentation ├── conf ├── mrcp.conf ├── mrcp_sample_apps.conf └── res-speech-unimrcp.conf ├── configure.ac ├── include └── ast_compat_defs.h └── res-speech-unimrcp ├── Makefile.am └── res_speech_unimrcp.c /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.o 3 | *.so 4 | *.lo 5 | *.a 6 | *.la 7 | *.loT 8 | *.orig 9 | *.rej 10 | .libs 11 | .deps 12 | 13 | # / 14 | /configure 15 | /config.log 16 | /config.nice 17 | /config.status 18 | /autom4te.cache 19 | /aclocal.m4 20 | /libtool 21 | /Makefile.in 22 | /Makefile 23 | 24 | # /app-unimrcp/ 25 | /app-unimrcp/.libs 26 | /app-unimrcp/.deps 27 | /app-unimrcp/.xmldocs 28 | /app-unimrcp/Makefile.in 29 | /app-unimrcp/Makefile 30 | 31 | # /build/ 32 | /build/ltmain.sh 33 | /build/depcomp 34 | /build/missing 35 | /build/config.guess 36 | /build/config.sub 37 | /build/compile 38 | /build/install-sh 39 | /build/Makefile.in 40 | /build/Makefile 41 | 42 | # /build/acmacros/ 43 | /build/acmacros/libtool.m4 44 | /build/acmacros/ltoptions.m4 45 | /build/acmacros/ltsugar.m4 46 | /build/acmacros/ltversion.m4 47 | /build/acmacros/lt~obsolete.m4 48 | 49 | # /res-speech-unimrcp/ 50 | /res-speech-unimrcp/.libs 51 | /res-speech-unimrcp/.deps 52 | /res-speech-unimrcp/Makefile.in 53 | /res-speech-unimrcp/Makefile 54 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Author(s): 2 | Arsen Chaloyan 3 | J.W.F. Thirion 4 | 5 | 6 | Contributor(s): 7 | Assanta Sope 8 | Raymond Menard 9 | Igor Goncharovsky 10 | Borja Sixto 11 | Javier Sixto 12 | Anthony Castiglione 13 | Stephen George 14 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Changes for Asterisk UniMRCP Modules 1.10.0 2 | 3 | 1. Generic Speech Recognition API (res_speech_unimrcp.so) 4 | 5 | * N/A. 6 | 7 | 2. Dialplan Applications (app_unimrcp.so) 8 | 9 | 2.1. MRCPSynth() 10 | 11 | * If channel state is not SPEECH_CHANNEL_READY, wait for completion of the previous request 12 | before proceeding with the new one (applicable to persistent MRCP sessions). 13 | 14 | 2.2. MRCPRecog() 15 | 16 | * If channel state is not SPEECH_CHANNEL_READY, wait for completion of the previous request 17 | before proceeding with the new one (applicable to persistent MRCP sessions). 18 | 19 | 2.3. SynthAndRecog() 20 | 21 | * If channel state is not SPEECH_CHANNEL_READY, wait for completion of the previous request 22 | before proceeding with the new one (applicable to persistent MRCP sessions). 23 | 24 | 2.4. Framework 25 | 26 | * N/A. 27 | 28 | 3. Miscellaneous 29 | 30 | * Added support for UniMRCP 1.8.0. 31 | * Added support for G722. 32 | 33 | 34 | Changes for Asterisk UniMRCP Modules 1.9.0 35 | 36 | 1. Generic Speech Recognition API (res_speech_unimrcp.so) 37 | 38 | * N/A. 39 | 40 | 2. Dialplan Applications (app_unimrcp.so) 41 | 42 | 2.1. MRCPSynth() 43 | 44 | * N/A. 45 | 46 | 2.2. MRCPRecog() 47 | 48 | * Introduced new helper functions RECOG_INPUT_MODE() and RECOG_INPUT_CONFIDENCE(). 49 | * If the option 'nif' is set to 'json' and the path provided as an input parameter to RECOG_INSTANCE() does not exist, then return 'null'. 50 | 51 | 2.3. SynthAndRecog() 52 | 53 | * Introduced new helper functions RECOG_INPUT_MODE() and RECOG_INPUT_CONFIDENCE(). 54 | * If the option 'nif' is set to 'json' and the path provided as an input parameter to RECOG_INSTANCE() does not exist, then return 'null'. 55 | 56 | 2.4. Framework 57 | 58 | * N/A. 59 | 60 | 3. Miscellaneous 61 | 62 | * Added support for Asterisk 19. 63 | 64 | 65 | Changes for Asterisk UniMRCP Modules 1.8.0 66 | 67 | 1. Generic Speech Recognition API (res_speech_unimrcp.so) 68 | 69 | * N/A. 70 | 71 | 2. Dialplan Applications (app_unimrcp.so) 72 | 73 | 2.1. MRCPSynth() 74 | 75 | * Added a new option 'vsp' allowing to specify Vendor-Specific-Parameters in SPEAK. 76 | * Reset the write translator before setting a new write format. 77 | * Fixed the write format path for codecs requiring a translation from the native format to be used on the MRCP leg. 78 | 79 | 2.2. MRCPRecog() 80 | 81 | * Extended RECOG_INSTANCE() to optionally return individual fields from the element represented in either XML or JSON. 82 | * Added a new option 'vsp' allowing to specify Vendor-Specific-Parameters in RECOGNIZE. 83 | * Reset the read translator before setting a new read format. 84 | * Fixed the read format path for codecs requiring a translation from the native format to be used on the MRCP leg. 85 | 86 | 2.3. SynthAndRecog() 87 | 88 | * Extended RECOG_INSTANCE() to optionally return individual fields from the element represented in either XML or JSON. 89 | * Added new options 'vsp', 'vsprec' and 'vspsyn' allowing to specify Vendor-Specific-Parameters in SPEAK and/or RECOGNIZE. 90 | * Reset the read/write translators before setting new read/write formats. 91 | * Fixed the read/write format paths for codecs requiring a translation from the native format to be used on the MRCP leg. 92 | 93 | 2.4. Framework 94 | 95 | * N/A. 96 | 97 | 3. Miscellaneous 98 | 99 | * Added support for Asterisk 18. 100 | * Added sample profiles ums2 and ums1 to the default configuration file mrcp.conf. 101 | * Added comments to the default configuration file res-speech-unimrcp.conf. 102 | 103 | 104 | Changes for Asterisk UniMRCP Modules 1.7.0 105 | 106 | 1. Generic Speech Recognition API (res_speech_unimrcp.so) 107 | 108 | * N/A. 109 | 110 | 2. Dialplan Applications (app_unimrcp.so) 111 | 112 | 2.1. MRCPSynth() 113 | 114 | * Added a new option 'plt' allowing to have persistent session lifetime, where an MRCP session is created on demand, reused and destroyed on hang-up. 115 | * Added a new option 'dse' allowing to specify a particular datastore entry. A datastore entry is associated to an MRCP session. 116 | There can be multiple datastore entries used in the scope of the same call. If the parameter is not set, then the default datastore entry is used. 117 | * Added a new option 'sbs' indicating whether or not to always stop barged synthesis request. Thanks @sfgeorge. (Pull request #36) 118 | 119 | 2.2. MRCPRecog() 120 | 121 | * Added a new option 'plt' allowing to have persistent session lifetime, where an MRCP session is created on demand, reused and destroyed on hang-up. 122 | * Added a new option 'dse' allowing to specify a particular datastore entry. A datastore entry is associated to an MRCP session. 123 | There can be multiple datastore entries used in the scope of the same call. If the parameter is not set, then the default datastore entry is used. 124 | 125 | 2.3. SynthAndRecog() 126 | 127 | * Added a new option 'plt' allowing to have persistent session lifetime, where an MRCP session is created on demand, reused and destroyed on hang-up. 128 | * Added a new option 'dse' allowing to specify a particular datastore entry. A datastore entry is associated to an MRCP session. 129 | There can be multiple datastore entries used in the scope of the same call. If the parameter is not set, then the default datastore entry is used. 130 | * Set end_of_prompt when audio file complete on Asterisk >= 11. Thanks @sfgeorge. 131 | * Added a new log statement "File is over" for Asterisk >= 11. Thanks @sfgeorge. 132 | * Added a new option 'sbs' indicating whether or not to always stop barged synthesis request. Thanks @sfgeorge. (Pull request #36) 133 | 134 | 2.4. Framework 135 | 136 | * Load max-shared-count and feature tags from mrcp.conf. 137 | 138 | 3. Miscellaneous 139 | 140 | * Added support for Asterisk 17. 141 | 142 | 143 | Changes for Asterisk UniMRCP Modules 1.5.2 144 | 145 | 1. Generic Speech Recognition API (res_speech_unimrcp.so) 146 | 147 | * Use ast_strdup() instead of strdup() to address compilation error while building against Asterisk 16. Issue #27. 148 | 149 | 2. Dialplan Applications (app_unimrcp.so) 150 | 151 | * N/A. 152 | 153 | 3. Miscellaneous 154 | 155 | * Added support for Asterisk 16. 156 | 157 | 158 | Changes for Asterisk UniMRCP Modules 1.5.1 159 | 160 | 1. Generic Speech Recognition API (res_speech_unimrcp.so) 161 | 162 | * N/A. 163 | 164 | 2. Dialplan Applications (app_unimrcp.so) 165 | 166 | 2.1. MRCPSynth() 167 | 168 | * N/A. 169 | 170 | 2.2. MRCPRecog() 171 | 172 | * When recognition fails, set RECOG_COMPLETION_CAUSE variable, if available. Issue #19. 173 | * Skip voice frames with zero length fed by Asterisk under certain circumstances. 174 | * When recognition completes, make sure to stop the ongoing prompt, if any. This problem was encountered when 175 | START-OF-INPUT and RECOGNITION-COMPLETE events were received almost instantaneously. 176 | 177 | 2.3. SynthAndRecog() 178 | 179 | * When recognition fails, set RECOG_COMPLETION_CAUSE variable, if available. Issue #19. 180 | * Skip voice frames with zero length fed by Asterisk under certain circumstances. 181 | * Allow different profiles be specified for synthesis and recognition. Merged a pull request submitted by Mihail. Thanks. 182 | * When recognition completes, make sure to stop the ongoing prompt, if any. This problem was encountered when 183 | START-OF-INPUT and RECOGNITION-COMPLETE events were received almost instantaneously. 184 | 185 | 2.4. Framework 186 | 187 | * N/A. 188 | 189 | 3. Miscellaneous 190 | 191 | * Added support for Asterisk 15. 192 | 193 | 194 | Changes for Asterisk UniMRCP Modules 1.5.0 195 | 196 | 1. Generic Speech Recognition API (res_speech_unimrcp.so) 197 | 198 | * Allow built-in and HTTP grammars be specified with SpeechActivateGrammar(). 199 | * Fixed a crash in processing of on_terminate_event() occurred when the MRCP server unexpectedly disconnects 200 | while recognition is in progress. Issue #13. 201 | * Fixed a compilation warning by taking out an unused variable. 202 | 203 | 2. Dialplan Applications (app_unimrcp.so) 204 | 205 | 2.1. MRCPSynth() 206 | 207 | * Initialized all the dispatcher event handlers to prevent an unconditional move reported by valgrind, 208 | which could occur on an unexpected session termination initiated by the MRCP server. 209 | * Instead of calculating bytes per sample on every written frame, do it once per speech channel creation. 210 | 211 | 2.2. MRCPRecog() 212 | 213 | * Fixed a possible invalid read attempt reported by valgrind, which could occur as a result of mrcp_application_session_id_get() 214 | being called after mrcp_application_channel_add() completed with an error. 215 | * Initialized all the dispatcher event handlers to prevent an unconditional move reported by valgrind, 216 | which could occur on an unexpected session termination initiated by the MRCP server. 217 | * Fixed the name of grammar delimiters option in documentation (not a functional change). 218 | * Improved the detection of end of file play by using ast_channel_streamid() and ast_channel_timingfunc(), when available. 219 | Fixed issue #14. Thanks scgm11. 220 | 221 | 2.3. SynthAndRecog() 222 | 223 | * Fixed a possible invalid read attempt reported by valgrind, which could occur as a result of mrcp_application_session_id_get() 224 | being called after mrcp_application_channel_add() completed with an error. 225 | * Initialized all the dispatcher event handlers to prevent an unconditional move reported by valgrind, 226 | which could occur on an unexpected session termination initiated by the MRCP server. 227 | * Improved the detection of end of file play by using ast_channel_streamid() and ast_channel_timingfunc(), when available. 228 | Fixed issue #14. Thanks scgm11. 229 | 230 | 2.4. Framework 231 | 232 | * Fixed the application options bitmasks. Issue #7. Thanks Borja. 233 | * Replaced hard-coded SPEECH_CHANNEL_TIMEOUT by a new configuration parameter speech-channel-timeout, which can be set from mrcp.conf. 234 | Merged pull request #9. Thanks Userator. 235 | * Added support for 16 kHz sampling rate. 236 | * Use UniMRCP var directory instead of data for SPEECH_CHANNEL_DUMP_DIR. 237 | * Fixed a transcoding issue when Asterisk 13 and above is used with a native format being neither PCMU, PCMA or Linear PCM. 238 | * Added support for a new grammar type "application/xml" used to dynamically load an XML-based speech context for the Google SR plugin. 239 | 240 | 3. Miscellaneous 241 | 242 | * Enhanced the version detection routine in asterisk.m4 by stripping off additional trailing characters, if present. 243 | * Added a test for Asterisk include directory in asterisk.m4 by returning an error, if not found. 244 | * Added a new configure option --with-asterisk-doc, which allows to explicitly specify the location of Asterisk XML documentation 245 | directory. If not set, the directory is implicitly determined as before. 246 | * Added support for Asterisk 14. Fixed issue #15. 247 | 248 | 249 | Changes for Asterisk UniMRCP Modules 1.4.0 250 | 251 | 1. Generic Speech Recognition API (res_speech_unimrcp.so) 252 | 253 | * N/A. 254 | 255 | 2. Dialplan Applications (app_unimrcp.so) 256 | 257 | 2.1. MRCPSynth() 258 | 259 | * Added a new dialplan variable SYNTH_COMPLETION_CAUSE to get the Completion-Cause header field value received with the SPEAK-COMPLETE event. 260 | Merged pull request #3. Thanks kkocaerkek. 261 | * Fixed support for the option "f" (Issue-4). 262 | 263 | 2.2. MRCPRecog() 264 | 265 | * Added a new dialplan variable RECOG_SID set upon application termination, when available. 266 | 267 | 2.3. SynthAndRecog() 268 | 269 | * Added a new dialplan variable RECOG_SID set upon application termination, when available. 270 | 271 | 2.4. Framework 272 | 273 | * Added a new function speech_channel_get_id() to retrieve an associated MRCP session identifier, when available. 274 | * Allow sip-t1, sip-t2, sip-t4 and sip-t1x64 timeouts be specified via mrcp.conf. 275 | * Allow NTATAG_TIMER_C be specified via mrcp.conf. 276 | * Encapsulated the function ast_write() into the speech channel interface. 277 | 278 | 3. Miscellaneous 279 | 280 | * N/A. 281 | 282 | 283 | Changes for Asterisk UniMRCP Modules 1.3.1 284 | 285 | 1. Generic Speech Recognition API (res_speech_unimrcp.so) 286 | 287 | * N/A. 288 | 289 | 2. Dialplan Applications (app_unimrcp.so) 290 | 291 | 2.1. MRCPSynth() 292 | 293 | * Re-factored the application to feed synthesized speech to Asterisk directly from MPF callback. 294 | 295 | 2.2. MRCPRecog() 296 | 297 | * Fixed support for the option i=any (Issue-168). 298 | 299 | 3. Miscellaneous 300 | 301 | * Updated README of Asterisk modules to include references to GitHub. 302 | 303 | 304 | Changes for Asterisk UniMRCP Modules 1.3.0 305 | 306 | 1. Generic Speech Recognition API (res_speech_unimrcp.so) 307 | 308 | * Fixed compilation against Asterisk 1.6.0 and 1.6.1 branches. Thanks Javier (issue-174). 309 | * Adapted to the change in UniMRCP dir_layout interface. 310 | * Made res_speech_unimrcp compatible with Asterisk 12. 311 | * Added support for Asterisk 13. 312 | 313 | 2. Dialplan Applications (app_unimrcp.so) 314 | 315 | 2.1. MRCPSynth() 316 | 317 | * Fixed a Coverity issue: a variable was declared and assigned to itself because of a typo. 318 | 319 | 2.2. MRCPRecog() 320 | 321 | * Reincarnated the old or added a new option 'sit', which allows to specify a policy for the use of input timers. 322 | * Set channel status to INTERRUPTED when user hangs up on a none-bargeinable prompt. Thanks Tony. 323 | * Fixed a Coverity issue: a variable was declared and assigned to itself because of a type. 324 | * Added support for multiple prompts. 325 | * Individual options passed to MRCPRecog can now be quoted with <> (r2263). 326 | 327 | 2.3. SynthAndRecog() 328 | 329 | * Corrected a misspelled option name ("dt" -> "gd") in the XML documentation. 330 | * Added support for multiple prompts. 331 | * Filter out silence and write only audio frames to Asterisk channel. 332 | * Added support for native audio file play. 333 | * Use the same synth channel for consecutive TTS prompts and destroy it on an audio file prompt. 334 | * Reincarnated the old or added a new option 'sit', which allows to specify a policy for the use of input timers. 335 | * Set channel status to INTERRUPTED when user hangs up on a none-bargeinable prompt. Thanks Tony. 336 | 337 | 2.4. Framework 338 | 339 | * Fixed a regression in loading of MRCPv1 profiles introduced since 1.2.0 release (issue-173). 340 | * Print out the associated Asterisk channel name while creating a new speech channel. 341 | * Retrieve and print out status and response code in case of an error in on_channel_add(). 342 | * Took out unused handler on_channel_remove(). 343 | * Fixed a potential race condition issue, where the applications could get stuck indefinitely in speech_channel_open() (r2168). 344 | * Fixed compilation against Asterisk 1.6.0 and 1.6.1 branches. Thanks Javier (issue-174). 345 | * Fixed a Coverity issue: dereference after NULL check. 346 | * Added support for Asterisk 13. 347 | 348 | 3. Miscellaneous 349 | 350 | * Use AM_CPPFLAGS instead of deprecated INCLUDES. 351 | * Fixed support for out of source build. 352 | * Test and create asterisk conf and xmldoc dirs on make install, if needed. 353 | 354 | 355 | Changes for Asterisk UniMRCP Modules 1.2.0 356 | 357 | 1. Generic Speech Recognition API (res_speech_unimrcp.so) 358 | 359 | * Take into consideration CONFIG_STATUS_FILEINVALID, which ast_config_load() may return since Asterisk 1.6 and above. 360 | 361 | 2. Dialplan Applications (app_unimrcp.so) 362 | 363 | 2.1. MRCPSynth() 364 | 365 | * If ast_write() fails, store such an occurrence in the logs but do not set the SYNTHSTATUS to ERROR. 366 | 367 | 2.2. MRCPRecog() 368 | 369 | * Fixed the detection of file play complete event. 370 | 371 | 2.3. SynthAndRecog() 372 | 373 | * Added missing tag in XML documentation. 374 | 375 | 2.4. Framework 376 | 377 | * Pass along loaded request-timeout to the RTSP client stack too. 378 | * Added telephone-event to the list of supported codecs in the default configuration file mrcp.conf. 379 | * Provided more comments in the default configuration file mrcp.conf. 380 | * Encapsulated ast_config routine in the function load_mrcp_config(). 381 | * Took out local functions from header file and declare them static. 382 | * Hand over file and line markers received from unimrcp logger to ast_log(). 383 | * Simplified speech_channel_read() and speech_channel_write() a bit. All the sanity checks are performed down the road, anyway (Issue-172). 384 | * Added debugging capabilities to speech_channel. 385 | * Mapped APT_PRIO_INFO (UniMRCP log level) to __LOG_NOTICE (Asterisk log level) to help rule out common configuration mistakes faster. 386 | 387 | 3. Miscellaneous 388 | 389 | * Added/fixed support for DESTDIR. 390 | * Enable silent build rules (--enable-silent-rules)and use silent build by default. 391 | * Set ac_macro_dir variable manually, since newer versions of autoconf don't do that. 392 | * Take into consideration m4 macro files generated for libtool 2. 393 | * Fixed help string displayed for the option --with-asterisk-version. 394 | * Pass no-define to AM_INIT_AUTOMAKE in order not to define PACKAGE and VERSION. 395 | * Do not use autoconf generated compiler DEFS by replacing confdefs.h after AC_INIT, AM_INIT_AUTOMAKE and AC_PROG_LIBTOLL getting called. 396 | * Updated definition of the macro AX_COMPILER_VENDOR and moved it out from configure.ac to a separate m4 file. 397 | * Disabled DEFAULT_INCLUDES provided by automake. 398 | * Added generic apr_common.m4 in order to use helper m4 macros APR_ADDTO() and APR_CONFIG_NICE(). 399 | * Generate ./config.nice to reuse ./configure command-line. 400 | * Turn off the build of static libraries for Asterisk modules. 401 | * Moved the target install-data-local to module specific Makefiles to install the default config file only when the corresponding module is enabled. 402 | * Enhanced ./configure report. 403 | 404 | 405 | Changes for Asterisk UniMRCP Modules 1.1.0 406 | 407 | 1. Generic Speech Recognition API (res_speech_unimrcp.so) 408 | 409 | * Use the new NLSML parser interface to build ast_speech_result based on the parsed nlsml_result_t structure. 410 | * Added support for alternate interpretations (N-Best-List) as well as multiple instances per interpretation. 411 | 412 | 2. Dialplan Applications (app_unimrcp.so) 413 | 414 | 2.1. MRCPSynth() 415 | 416 | * Added support for prompts referenced by a URI. 417 | * Differentiate the cases when synthesis is terminated due to an error or caller hang-up. In the latter case, the variable "SYNTHSTATUS" is set to the new status "INTERRUPTED". 418 | * Fixed a possible file handle leak. 419 | 420 | 2.2. MRCPRecog() 421 | 422 | * Modified the default value of the option "i" (interrupt) in order to enable the DTMF support by default. In order to disable DTMFs, use the option i=disable. The other settings/values remain intact. 423 | * Added a new option "uer" (URI-encoded results). By default, this option is disabled (uer=0). If the option is enabled (uer=1), the variable "RECOG_RESULT" holds URI-encoded results. This matters in case of the AMI use. 424 | * If the application creation fails on start-up, set the corresponding variable "mrcprecog" to NULL. 425 | * Differentiate the cases when recognition is terminated due to an error or caller hang-up. In the latter case, the variable "RECOGSTATUS" is set to the new status "INTERRUPTED". 426 | * Dropped support for the barge-in handled on/by Asterisk (bargein=2). The option bargein can now simply be enabled (1) or disabled (0). 427 | * Set the dialplan variables "RECOG_COMPLETION_CAUSE", "RECOG_RESULT", and optionally "RECOG_WAVEFORM_URI" in accordance with the application SynthAndRecog(). 428 | * Added support for the method "Start-Input-Timers". 429 | * Took out the input option "sit", since the header field "START-INPUT-TIMERS" is now set and controlled implicitly by the application. 430 | 431 | 2.3. SynthAndRecog() 432 | 433 | * Added support for prompts referenced by a URI. 434 | * Added a new option "uer" (URI-encoded results). By default, this option is disabled (uer=0). If the option is enabled (uer=1), the variable "RECOG_RESULT" holds URI-encoded results. This matters in case of the AMI use. 435 | * Differentiate the cases when recognition is terminated due to an error or caller hang-up. In the latter case, the variable "RECOG_STATUS" is set to the new status "INTERRUPTED". 436 | 437 | 3. Miscellaneous 438 | 439 | * Terminate the MRCP session when negotiated codec descriptor cannot be retrieved by a user application. 440 | * Prevent a possible segfault of Asterisk caused by malformed input parameters (Issue-157). 441 | * Added a new enumeration speech_channel_status_t used by the applications to set the corresponding status variable on exit. 442 | * Fixed possible memory leaks in the processing of ast_frames. 443 | * Added new dialplan functions RECOG_CONFIDENCE(), RECOG_GRAMMAR(), RECOG_INPUT(), and RECOG_INSTANCE() to retrieve recognition results set by the applications MRCPRecog() and SynthAndRecog(). 444 | * Updated the XML documentation of the dialplan applications and custom functions. 445 | * Improved the log statements. 446 | * Added more usage examples and helper macros which output recognition results by using the new dialplan functions. 447 | * Normalize application input arguments by stripping any leading and trailing whitespaces and skipping the quotes. 448 | 449 | 450 | Changes for Asterisk UniMRCP Modules 1.0.0 451 | 452 | 1. Generic Speech Recognition API (res_speech_unimrcp.so) 453 | 454 | * Handled change to the apt_log_file_open() function. Thanks J.W.F. Thirion. 455 | * Added the ability to implicitly stop an in-progress recognition request. Applied a reworked patch submitted to Issue-105. Thanks Borja. 456 | * Added support for loading and activating multiple grammars for a recognition request. Applied a reworked patch submitted to Issue-105. Thanks Borja. 457 | * Enhanced parsing of NLSML results. Applied a patch submitted to Issue-105. Thanks Borja. 458 | * Fixed support for built-in grammars. Thanks Renato. 459 | 460 | 2. Dialplan Applications (app_unimrcp.so) 461 | 462 | 2.1. MRCPSynth() 463 | 464 | * Fixed a DTMF generator issue. Thanks J.W.F. Thirion. 465 | * Added the ability to load a plain text or an SSML content from the specified file. Applied a reworked patch submitted to Issue-151. Thanks Borja. 466 | 467 | 2.2. MRCPRecog() 468 | 469 | * Fixed a misspelled name of the header field "Input-Waveform-URI". 470 | * Use the option "sl" for the header field "Sensitivity-Level" and "spl" for "Speech-Language". 471 | * Fixed a crash in speech_channel_destroy(). Thanks Evan, Stephen, and Rodolfo. 472 | * Fixed a memory leak in processing of recognition results. A recognition result is now allocated from the channel memory pool using apr_pstrdup(). Otherwise, the duplicated string should have been explicitly freed. 473 | * Fixed the grammar type detection routine. 474 | * Added the ability to load a prompt and/or a grammar from the specified files. 475 | * Added support for loading and activating multiple grammars for a recognition request. The grammars can be specified as a comma-separated list of input parameters. 476 | * Added a new application option: exit-on-play-error "epe". If the option is enabled and the specified prompt file cannot be played, the application exits with the "RECOGSTATUS" variable set to "ERROR". 477 | 478 | 2.3. SynthAndRecog() 479 | 480 | This is a new diaplan application which plays a synthesized prompt to the user and waits for speech to be recognized. The application supports the following features: 481 | 482 | * Plain text and SSML prompts. 483 | * Inline (SRGS XML, SRGS ABNF, JSGF), built-in, and URI grammars. Inline grammars can be loaded from a file. A list of comma-separated grammars can be used for a recognition request. 484 | * Barge-in, and an option for non-bargeinable prompts. 485 | * Recognition timers. 486 | * Recognition results. 487 | 488 | 3. Miscellaneous 489 | 490 | * Enhanced the version detection routine of Asterisk (asterisk.m4). 491 | * Added a new optional parameter to the configure script --with-asterisk-version, which allows to explicitly specify a version string in case the version cannot be determined or retrieved implicitly. 492 | * Included asterisk.m4 from acinclude.m4. 493 | * Added a new header file ast_compat_def.h, which provides backward compatible macros, definitions, and utility functions for Asterisk. 494 | * Added support for Asterisk 1.8, 10, and 11 to the modules res_speech-unimrcp and app_unimrcp. The former versions are also supported. 495 | * Added support for auto-generated XML doc files introduced since Asterisk 1.6. The XML doc files are generated from the corresponding tags declared in source files. 496 | * Split the module app_unimrcp into several integral parts. The source file app_unimrcp.c is now the entry point of the module, and the applications reside in separate source files. 497 | * Fixed loading of the configuration parameter "offer-new-connection" to accept both boolean "true" or "false" and integer "1" or "0" values. 498 | * Added a checking for pkg-config to the configure script. 499 | * Added a new sample dialplan applications file mrcp_sample_apps.conf which provides numerous usage examples. Removed the old file say-digit.conf. 500 | * For logging purposes, set the name of a UniMRCP session object to schannel->name. 501 | * Using transparent header fields to apply application options. 502 | * Retained backward compatibility with UniMRCP 1.0.0 and above, but dropped the support for earlier versions. 503 | * Updated the README file to include the statement for the GPLv2 license. Added the INSTALL and COPYING files. 504 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | 2 | GNU GENERAL PUBLIC LICENSE 3 | Version 2, June 1991 4 | 5 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 6 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 7 | Everyone is permitted to copy and distribute verbatim copies 8 | of this license document, but changing it is not allowed. 9 | 10 | Preamble 11 | 12 | The licenses for most software are designed to take away your 13 | freedom to share and change it. By contrast, the GNU General Public 14 | License is intended to guarantee your freedom to share and change free 15 | software--to make sure the software is free for all its users. This 16 | General Public License applies to most of the Free Software 17 | Foundation's software and to any other program whose authors commit to 18 | using it. (Some other Free Software Foundation software is covered by 19 | the GNU Library General Public License instead.) You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | this service if you wish), that you receive source code or can get it 26 | if you want it, that you can change the software or use pieces of it 27 | in new free programs; and that you know you can do these things. 28 | 29 | To protect your rights, we need to make restrictions that forbid 30 | anyone to deny you these rights or to ask you to surrender the rights. 31 | These restrictions translate to certain responsibilities for you if you 32 | distribute copies of the software, or if you modify it. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must give the recipients all the rights that 36 | you have. You must make sure that they, too, receive or can get the 37 | source code. And you must show them these terms so they know their 38 | rights. 39 | 40 | We protect your rights with two steps: (1) copyright the software, and 41 | (2) offer you this license which gives you legal permission to copy, 42 | distribute and/or modify the software. 43 | 44 | Also, for each author's protection and ours, we want to make certain 45 | that everyone understands that there is no warranty for this free 46 | software. If the software is modified by someone else and passed on, we 47 | want its recipients to know that what they have is not the original, so 48 | that any problems introduced by others will not reflect on the original 49 | authors' reputations. 50 | 51 | Finally, any free program is threatened constantly by software 52 | patents. We wish to avoid the danger that redistributors of a free 53 | program will individually obtain patent licenses, in effect making the 54 | program proprietary. To prevent this, we have made it clear that any 55 | patent must be licensed for everyone's free use or not licensed at all. 56 | 57 | The precise terms and conditions for copying, distribution and 58 | modification follow. 59 | 60 | GNU GENERAL PUBLIC LICENSE 61 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 62 | 63 | 0. This License applies to any program or other work which contains 64 | a notice placed by the copyright holder saying it may be distributed 65 | under the terms of this General Public License. The "Program", below, 66 | refers to any such program or work, and a "work based on the Program" 67 | means either the Program or any derivative work under copyright law: 68 | that is to say, a work containing the Program or a portion of it, 69 | either verbatim or with modifications and/or translated into another 70 | language. (Hereinafter, translation is included without limitation in 71 | the term "modification".) Each licensee is addressed as "you". 72 | 73 | Activities other than copying, distribution and modification are not 74 | covered by this License; they are outside its scope. The act of 75 | running the Program is not restricted, and the output from the Program 76 | is covered only if its contents constitute a work based on the 77 | Program (independent of having been made by running the Program). 78 | Whether that is true depends on what the Program does. 79 | 80 | 1. You may copy and distribute verbatim copies of the Program's 81 | source code as you receive it, in any medium, provided that you 82 | conspicuously and appropriately publish on each copy an appropriate 83 | copyright notice and disclaimer of warranty; keep intact all the 84 | notices that refer to this License and to the absence of any warranty; 85 | and give any other recipients of the Program a copy of this License 86 | along with the Program. 87 | 88 | You may charge a fee for the physical act of transferring a copy, and 89 | you may at your option offer warranty protection in exchange for a fee. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion 92 | of it, thus forming a work based on the Program, and copy and 93 | distribute such modifications or work under the terms of Section 1 94 | above, provided that you also meet all of these conditions: 95 | 96 | a) You must cause the modified files to carry prominent notices 97 | stating that you changed the files and the date of any change. 98 | 99 | b) You must cause any work that you distribute or publish, that in 100 | whole or in part contains or is derived from the Program or any 101 | part thereof, to be licensed as a whole at no charge to all third 102 | parties under the terms of this License. 103 | 104 | c) If the modified program normally reads commands interactively 105 | when run, you must cause it, when started running for such 106 | interactive use in the most ordinary way, to print or display an 107 | announcement including an appropriate copyright notice and a 108 | notice that there is no warranty (or else, saying that you provide 109 | a warranty) and that users may redistribute the program under 110 | these conditions, and telling the user how to view a copy of this 111 | License. (Exception: if the Program itself is interactive but 112 | does not normally print such an announcement, your work based on 113 | the Program is not required to print an announcement.) 114 | 115 | These requirements apply to the modified work as a whole. If 116 | identifiable sections of that work are not derived from the Program, 117 | and can be reasonably considered independent and separate works in 118 | themselves, then this License, and its terms, do not apply to those 119 | sections when you distribute them as separate works. But when you 120 | distribute the same sections as part of a whole which is a work based 121 | on the Program, the distribution of the whole must be on the terms of 122 | this License, whose permissions for other licensees extend to the 123 | entire whole, and thus to each and every part regardless of who wrote it. 124 | 125 | Thus, it is not the intent of this section to claim rights or contest 126 | your rights to work written entirely by you; rather, the intent is to 127 | exercise the right to control the distribution of derivative or 128 | collective works based on the Program. 129 | 130 | In addition, mere aggregation of another work not based on the Program 131 | with the Program (or with a work based on the Program) on a volume of 132 | a storage or distribution medium does not bring the other work under 133 | the scope of this License. 134 | 135 | 3. You may copy and distribute the Program (or a work based on it, 136 | under Section 2) in object code or executable form under the terms of 137 | Sections 1 and 2 above provided that you also do one of the following: 138 | 139 | a) Accompany it with the complete corresponding machine-readable 140 | source code, which must be distributed under the terms of Sections 141 | 1 and 2 above on a medium customarily used for software interchange; or, 142 | 143 | b) Accompany it with a written offer, valid for at least three 144 | years, to give any third party, for a charge no more than your 145 | cost of physically performing source distribution, a complete 146 | machine-readable copy of the corresponding source code, to be 147 | distributed under the terms of Sections 1 and 2 above on a medium 148 | customarily used for software interchange; or, 149 | 150 | c) Accompany it with the information you received as to the offer 151 | to distribute corresponding source code. (This alternative is 152 | allowed only for noncommercial distribution and only if you 153 | received the program in object code or executable form with such 154 | an offer, in accord with Subsection b above.) 155 | 156 | The source code for a work means the preferred form of the work for 157 | making modifications to it. For an executable work, complete source 158 | code means all the source code for all modules it contains, plus any 159 | associated interface definition files, plus the scripts used to 160 | control compilation and installation of the executable. However, as a 161 | special exception, the source code distributed need not include 162 | anything that is normally distributed (in either source or binary 163 | form) with the major components (compiler, kernel, and so on) of the 164 | operating system on which the executable runs, unless that component 165 | itself accompanies the executable. 166 | 167 | If distribution of executable or object code is made by offering 168 | access to copy from a designated place, then offering equivalent 169 | access to copy the source code from the same place counts as 170 | distribution of the source code, even though third parties are not 171 | compelled to copy the source along with the object code. 172 | 173 | 4. You may not copy, modify, sublicense, or distribute the Program 174 | except as expressly provided under this License. Any attempt 175 | otherwise to copy, modify, sublicense or distribute the Program is 176 | void, and will automatically terminate your rights under this License. 177 | However, parties who have received copies, or rights, from you under 178 | this License will not have their licenses terminated so long as such 179 | parties remain in full compliance. 180 | 181 | 5. You are not required to accept this License, since you have not 182 | signed it. However, nothing else grants you permission to modify or 183 | distribute the Program or its derivative works. These actions are 184 | prohibited by law if you do not accept this License. Therefore, by 185 | modifying or distributing the Program (or any work based on the 186 | Program), you indicate your acceptance of this License to do so, and 187 | all its terms and conditions for copying, distributing or modifying 188 | the Program or works based on it. 189 | 190 | 6. Each time you redistribute the Program (or any work based on the 191 | Program), the recipient automatically receives a license from the 192 | original licensor to copy, distribute or modify the Program subject to 193 | these terms and conditions. You may not impose any further 194 | restrictions on the recipients' exercise of the rights granted herein. 195 | You are not responsible for enforcing compliance by third parties to 196 | this License. 197 | 198 | 7. If, as a consequence of a court judgment or allegation of patent 199 | infringement or for any other reason (not limited to patent issues), 200 | conditions are imposed on you (whether by court order, agreement or 201 | otherwise) that contradict the conditions of this License, they do not 202 | excuse you from the conditions of this License. If you cannot 203 | distribute so as to satisfy simultaneously your obligations under this 204 | License and any other pertinent obligations, then as a consequence you 205 | may not distribute the Program at all. For example, if a patent 206 | license would not permit royalty-free redistribution of the Program by 207 | all those who receive copies directly or indirectly through you, then 208 | the only way you could satisfy both it and this License would be to 209 | refrain entirely from distribution of the Program. 210 | 211 | If any portion of this section is held invalid or unenforceable under 212 | any particular circumstance, the balance of the section is intended to 213 | apply and the section as a whole is intended to apply in other 214 | circumstances. 215 | 216 | It is not the purpose of this section to induce you to infringe any 217 | patents or other property right claims or to contest validity of any 218 | such claims; this section has the sole purpose of protecting the 219 | integrity of the free software distribution system, which is 220 | implemented by public license practices. Many people have made 221 | generous contributions to the wide range of software distributed 222 | through that system in reliance on consistent application of that 223 | system; it is up to the author/donor to decide if he or she is willing 224 | to distribute software through any other system and a licensee cannot 225 | impose that choice. 226 | 227 | This section is intended to make thoroughly clear what is believed to 228 | be a consequence of the rest of this License. 229 | 230 | 8. If the distribution and/or use of the Program is restricted in 231 | certain countries either by patents or by copyrighted interfaces, the 232 | original copyright holder who places the Program under this License 233 | may add an explicit geographical distribution limitation excluding 234 | those countries, so that distribution is permitted only in or among 235 | countries not thus excluded. In such case, this License incorporates 236 | the limitation as if written in the body of this License. 237 | 238 | 9. The Free Software Foundation may publish revised and/or new versions 239 | of the General Public License from time to time. Such new versions will 240 | be similar in spirit to the present version, but may differ in detail to 241 | address new problems or concerns. 242 | 243 | Each version is given a distinguishing version number. If the Program 244 | specifies a version number of this License which applies to it and "any 245 | later version", you have the option of following the terms and conditions 246 | either of that version or of any later version published by the Free 247 | Software Foundation. If the Program does not specify a version number of 248 | this License, you may choose any version ever published by the Free Software 249 | Foundation. 250 | 251 | 10. If you wish to incorporate parts of the Program into other free 252 | programs whose distribution conditions are different, write to the author 253 | to ask for permission. For software which is copyrighted by the Free 254 | Software Foundation, write to the Free Software Foundation; we sometimes 255 | make exceptions for this. Our decision will be guided by the two goals 256 | of preserving the free status of all derivatives of our free software and 257 | of promoting the sharing and reuse of software generally. 258 | 259 | NO WARRANTY 260 | 261 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 262 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 263 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 264 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 265 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 266 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 267 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 268 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 269 | REPAIR OR CORRECTION. 270 | 271 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 272 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 273 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 274 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 275 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 276 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 277 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 278 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 279 | POSSIBILITY OF SUCH DAMAGES. 280 | 281 | END OF TERMS AND CONDITIONS 282 | 283 | How to Apply These Terms to Your New Programs 284 | 285 | If you develop a new program, and you want it to be of the greatest 286 | possible use to the public, the best way to achieve this is to make it 287 | free software which everyone can redistribute and change under these terms. 288 | 289 | To do so, attach the following notices to the program. It is safest 290 | to attach them to the start of each source file to most effectively 291 | convey the exclusion of warranty; and each file should have at least 292 | the "copyright" line and a pointer to where the full notice is found. 293 | 294 | 295 | Copyright (C) 19yy 296 | 297 | This program is free software; you can redistribute it and/or modify 298 | it under the terms of the GNU General Public License as published by 299 | the Free Software Foundation; either version 2 of the License, or 300 | (at your option) any later version. 301 | 302 | This program is distributed in the hope that it will be useful, 303 | but WITHOUT ANY WARRANTY; without even the implied warranty of 304 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 305 | GNU General Public License for more details. 306 | 307 | You should have received a copy of the GNU General Public License 308 | along with this program; if not, write to the Free Software 309 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 310 | 311 | 312 | Also add information on how to contact you by electronic and paper mail. 313 | 314 | If the program is interactive, make it output a short notice like this 315 | when it starts in an interactive mode: 316 | 317 | Gnomovision version 69, Copyright (C) 19yy name of author 318 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 319 | This is free software, and you are welcome to redistribute it 320 | under certain conditions; type `show c' for details. 321 | 322 | The hypothetical commands `show w' and `show c' should show the appropriate 323 | parts of the General Public License. Of course, the commands you use may 324 | be called something other than `show w' and `show c'; they could even be 325 | mouse-clicks or menu items--whatever suits your program. 326 | 327 | You should also get your employer (if you work as a programmer) or your 328 | school, if any, to sign a "copyright disclaimer" for the program, if 329 | necessary. Here is a sample; alter the names: 330 | 331 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 332 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 333 | 334 | , 1 April 1989 335 | Ty Coon, President of Vice 336 | 337 | This General Public License does not permit incorporating your program into 338 | proprietary programs. If your program is a subroutine library, you may 339 | consider it more useful to permit linking proprietary applications with the 340 | library. If this is what you want to do, use the GNU Library General 341 | Public License instead of this License. 342 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | BUILD REQUIREMENTS 2 | ================== 3 | 4 | In order to build the UniMRCP modules for Asterisk, both the UniMRCP and Asterisk projects must be 5 | installed first. 6 | 7 | 1. Asterisk 8 | 9 | Asterisk is an open source communication platform which can be used as a PBX, an IVR, or a conference 10 | bridge. 11 | 12 | Project website: 13 | 14 | http://www.asterisk.org 15 | 16 | Compatible versions: 17 | 18 | Asterisk 1.6, 1.8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 19 | 20 | 2. UniMRCP 21 | 22 | UniMRCP is an open source project compliant with the IETF RFC6787 (MRCPv2) and RFC4463 (MRCPv1) 23 | specifications. 24 | 25 | Project website: 26 | 27 | https://www.unimrcp.org 28 | 29 | Compatible versions: 30 | 31 | UniMRCP 1.1.0 and above 32 | 33 | 34 | GNU BUILD 35 | ========= 36 | 37 | Prerequisites: 38 | 39 | autoconf 2.59 or newer 40 | automake 41 | libtool 1.4 or newer 42 | gcc 43 | pkg-config 44 | 45 | Procedure: 46 | 47 | If the source is checked out from the repository, the "bootstrap" script must be run first 48 | in order to generate the "configure" script and other required files. 49 | 50 | ./bootstrap 51 | 52 | The usual "configure", "make", "make install" sequence of commands should follow in order to build 53 | and install the modules from source. 54 | 55 | ./configure 56 | make 57 | make install 58 | 59 | As a result, the modules res_speech_unimrcp.so and app_unimrcp.so will be installed in the modules 60 | directory of Asterisk such as /usr/lib/asterisk/modules by default. Similarly, the configuration 61 | files res-speech-unimrcp.conf and mrcp.conf will be placed in /etc/asterisk. 62 | 63 | Configure options: 64 | 65 | To explicitly specify where to look for Asterisk, use the option "--with-asterisk=". For example, 66 | if Asterisk is installed in /usr/local/asterisk-11, use: 67 | 68 | ./configure --with-asterisk=/usr/local/asterisk-11 69 | 70 | To explicitly specify where the Asterisk configuration files are located, use the option 71 | "--with-asterisk-conf". For example: 72 | 73 | ./configure --with-asterisk-conf=/usr/local/asterisk/conf 74 | 75 | To explicitly specify the Asterisk version, use the option "--with-asterisk-version=". For example: 76 | 77 | ./configure --with-asterisk-version=11.2.1 78 | 79 | To explicitly specify where to install modules, use the option "--prefix". For example: 80 | 81 | ./configure --prefix=/usr/lib64/asterisk/modules 82 | 83 | To explicitly specify where to look for UniMRCP, use the option "--with-unimrcp=". For example, 84 | if UniMRCP is installed in /opt/unimrcp, use: 85 | 86 | ./configure --with-unimrcp=/opt/unimrcp 87 | 88 | To exclude the module res_speech_unimrcp.so from build, use: 89 | 90 | ./configure --disable-res-speech-unimrcp 91 | 92 | To exclude the module app_unimrcp.so from build, use: 93 | 94 | ./configure --disable-app-unimrcp 95 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | macrodir = @ac_macro_dir@ 2 | auxdir = @ac_aux_dir@ 3 | 4 | AUX_DIST = $(auxdir)/config.guess \ 5 | $(auxdir)/config.sub \ 6 | $(auxdir)/install-sh \ 7 | $(auxdir)/ltconfig \ 8 | $(auxdir)/ltmain.sh \ 9 | $(auxdir)/depcomp \ 10 | $(auxdir)/missing 11 | 12 | MACRO_DIST = $(macrodir)/libtool.m4 \ 13 | $(macrodir)/ltoptions.m4 \ 14 | $(macrodir)/ltsugar.m4 \ 15 | $(macrodir)/ltversion.m4 \ 16 | $(macrodir)/lt~obsolete.m4 17 | 18 | EXTRA_DIST = bootstrap 19 | 20 | AUTOMAKE_OPTIONS = foreign 21 | MAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure config.nice $(AUX_DIST) $(MACRO_DIST) 22 | 23 | ACLOCAL = aclocal -I $(macrodir) 24 | 25 | SUBDIRS = 26 | 27 | if RES_SPEECH_UNIMRCP 28 | SUBDIRS += res-speech-unimrcp 29 | endif 30 | 31 | if APP_UNIMRCP 32 | SUBDIRS += app-unimrcp 33 | endif 34 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | INTRODUCTION 2 | ============ 3 | 4 | This document provides a brief description of the UniMRCP modules for Asterisk. 5 | 6 | * Generic Speech Recognition API 7 | 8 | The module res_speech_unimrcp.so is an implementation of the Generic Speech Recognition API of 9 | Asterisk based on the UniMRCP client library. 10 | 11 | * Dialplan Applications 12 | 13 | The module app_unimrcp.so is a suite of speech recognition and synthesis applications for Asterisk. 14 | 15 | 16 | INSTALLATION 17 | ============ 18 | 19 | See the file INSTALL for installation tips. 20 | 21 | 22 | REFERENCES 23 | ========== 24 | 25 | Websites: 26 | http://www.unimrcp.org/asterisk 27 | http://www.asterisk.org 28 | 29 | Downloads: 30 | http://www.unimrcp.org/project/component-view/asterisk 31 | 32 | GitHub: 33 | https://github.com/unispeech/asterisk-unimrcp 34 | 35 | Issue Tracker: 36 | https://github.com/unispeech/asterisk-unimrcp/issues 37 | 38 | Discussion Group: 39 | http://groups.google.com/group/unimrcp 40 | 41 | Source Changes: 42 | https://github.com/unispeech/asterisk-unimrcp/commits/master 43 | http://groups.google.com/group/unimrcp-svn-commits 44 | 45 | 46 | LICENSING 47 | ========= 48 | 49 | Since Asterisk is distributed under the GPLv2 license, and the UniMRCP modules are loaded by and 50 | directly interface with Asterisk, the GPLv2 license applies to the UniMRCP modules too. 51 | 52 | See the file COPYING for more information regarding the license. 53 | 54 | Copyright 2008 - 2022 Arsen Chaloyan 55 | -------------------------------------------------------------------------------- /acinclude.m4: -------------------------------------------------------------------------------- 1 | m4_include([build/acmacros/unimrcp.m4]) 2 | m4_include([build/acmacros/asterisk.m4]) 3 | m4_include([build/acmacros/apr_common.m4]) 4 | m4_include([build/acmacros/ax_compiler_vendor.m4]) 5 | -------------------------------------------------------------------------------- /app-unimrcp/Makefile.am: -------------------------------------------------------------------------------- 1 | MAINTAINERCLEANFILES = Makefile.in 2 | 3 | AM_CPPFLAGS = -I$(top_srcdir)/include $(UNIMRCP_INCLUDES) $(ASTERISK_INCLUDES) 4 | AM_CFLAGS = -DAST_MODULE_SELF_SYM="__internal_app_unimrcp" 5 | 6 | mod_LTLIBRARIES = app_unimrcp.la 7 | 8 | app_unimrcp_la_SOURCES = audio_queue.c \ 9 | speech_channel.c \ 10 | ast_unimrcp_framework.c \ 11 | app_datastore.c \ 12 | app_mrcpsynth.c \ 13 | app_mrcprecog.c \ 14 | app_synthandrecog.c \ 15 | app_unimrcp.c 16 | app_unimrcp_la_LDFLAGS = -avoid-version -no-undefined -module 17 | app_unimrcp_la_LIBADD = $(UNIMRCP_LIBS) 18 | 19 | XMLDOC_FILES = app_mrcpsynth.c \ 20 | app_mrcprecog.c \ 21 | app_synthandrecog.c \ 22 | app_datastore.c 23 | 24 | all-local: .xmldocs/app_unimrcp-en_US.xml 25 | 26 | .xmldocs/app_unimrcp-en_US.xml: $(XMLDOC_FILES) 27 | mkdir -p .xmldocs 28 | @printf "Building Documentation: $@" 29 | @echo "" > $@ 30 | @echo "" >> $@ 31 | @echo "" >> $@ 32 | @for i in $(XMLDOC_FILES); do \ 33 | $(AWK) -f $(top_srcdir)/build/xmldocs/get_documentation $$i >> $@ ; \ 34 | done ; 35 | @echo 36 | @echo "" >> $@ 37 | 38 | clean-local: 39 | rm -rf .xmldocs 40 | 41 | install-data-local: 42 | test -d $(DESTDIR)$(asterisk_xmldoc_dir) || $(mkinstalldirs) $(DESTDIR)$(asterisk_xmldoc_dir) 43 | test -d $(DESTDIR)$(asterisk_conf_dir) || $(mkinstalldirs) $(DESTDIR)$(asterisk_conf_dir) 44 | $(INSTALL) -m 644 .xmldocs/*.xml $(DESTDIR)$(asterisk_xmldoc_dir) 45 | test -f $(DESTDIR)$(asterisk_conf_dir)/mrcp.conf || $(INSTALL) -m 644 $(top_srcdir)/conf/mrcp.conf $(DESTDIR)$(asterisk_conf_dir) 46 | 47 | load: 48 | asterisk -rx "module load app_unimrcp.so" 49 | 50 | unload: 51 | asterisk -rx "module unload app_unimrcp.so" 52 | -------------------------------------------------------------------------------- /app-unimrcp/app_datastore.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Asterisk -- An open source telephony toolkit. 3 | * 4 | * See http://www.asterisk.org for more information about 5 | * the Asterisk project. Please do not directly contact 6 | * any of the maintainers of this project for assistance; 7 | * the project provides a web site, mailing lists and IRC 8 | * channels for your use. 9 | * 10 | * This program is free software, distributed under the terms of 11 | * the GNU General Public License Version 2. See the LICENSE file 12 | * at the top of the source tree. 13 | * 14 | * Please follow coding guidelines 15 | * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES 16 | */ 17 | 18 | #include 19 | #include 20 | #include "app_datastore.h" 21 | #include "asterisk/pbx.h" 22 | #include "apt_pool.h" 23 | #ifdef WITH_AST_JSON 24 | #include "asterisk/json.h" 25 | typedef struct ast_json ast_json; 26 | typedef struct ast_json_error ast_json_error; 27 | #endif 28 | 29 | /*** DOCUMENTATION 30 | 31 | 32 | Get the confidence score of an interpretation. 33 | 34 | 35 | 36 | The parameter nbest_number specifies the index in the list of interpretations sorted best-first. 37 | This parameter defaults to 0, if not specified. 38 | 39 | 40 | 41 | This function returns the confidence score of the specified interpretation. 42 | 43 | 44 | RECOG_GRAMMAR 45 | RECOG_INPUT 46 | RECOG_INPUT_MODE 47 | RECOG_INPUT_CONFIDENCE 48 | RECOG_INSTANCE 49 | 50 | 51 | 52 | 53 | Get the matched grammar of an interpretation. 54 | 55 | 56 | 57 | The parameter nbest_number specifies the index in the list of interpretations sorted best-first. 58 | This parameter defaults to 0, if not specified. 59 | 60 | 61 | 62 | This function returns the matched grammar of the specified interpretation. 63 | 64 | 65 | RECOG_CONFIDENCE 66 | RECOG_INPUT 67 | RECOG_INPUT_MODE 68 | RECOG_INPUT_CONFIDENCE 69 | RECOG_INSTANCE 70 | 71 | 72 | 73 | 74 | Get the spoken input. 75 | 76 | 77 | 78 | The parameter nbest_number specifies the index in the list of interpretations sorted best-first. 79 | This parameter defaults to 0, if not specified. 80 | 81 | 82 | 83 | This function returns the spoken input. 84 | 85 | 86 | RECOG_CONFIDENCE 87 | RECOG_GRAMMAR 88 | RECOG_INPUT_MODE 89 | RECOG_INPUT_CONFIDENCE 90 | RECOG_INSTANCE 91 | 92 | 93 | 94 | 95 | Get the mode of an input. 96 | 97 | 98 | 99 | The parameter nbest_number specifies the index in the list of interpretations sorted best-first. 100 | This parameter defaults to 0, if not specified. 101 | 102 | 103 | 104 | This function returns the mode of the specified input. 105 | 106 | 107 | RECOG_CONFIDENCE 108 | RECOG_GRAMMAR 109 | RECOG_INPUT 110 | RECOG_INPUT_CONFIDENCE 111 | RECOG_INSTANCE 112 | 113 | 114 | 115 | 116 | Get the confidence score of an input. 117 | 118 | 119 | 120 | The parameter nbest_number specifies the index in the list of interpretations sorted best-first. 121 | This parameter defaults to 0, if not specified. 122 | 123 | 124 | 125 | This function returns the confidence score of the specified input. 126 | 127 | 128 | RECOG_CONFIDENCE 129 | RECOG_GRAMMAR 130 | RECOG_INPUT 131 | RECOG_INPUT_MODE 132 | RECOG_INSTANCE 133 | 134 | 135 | 136 | 137 | Get the interpreted instance. 138 | 139 | 140 | 141 | The parameter nbest_number specifies the index in the list of interpretations sorted best-first. 142 | This parameter defaults to 0, if not specified. 143 | 144 | 145 | The parameter instance_number specifies the index in the list of instances for a particular interpretation. 146 | This parameter defaults to 0, if not specified. 147 | 148 | 149 | The parameter path specifies a particular nested element to return, if used. 150 | 151 | 152 | 153 | This function returns the interpreted instance. 154 | 155 | 156 | RECOG_CONFIDENCE 157 | RECOG_GRAMMAR 158 | RECOG_INPUT 159 | RECOG_INPUT_MODE 160 | RECOG_INPUT_CONFIDENCE 161 | 162 | 163 | ***/ 164 | 165 | /* Helper function to destroy application session */ 166 | static void app_session_destroy(app_session_t *app_session) 167 | { 168 | if(app_session) { 169 | if (app_session->synth_channel) { 170 | speech_channel_destroy(app_session->synth_channel); 171 | } 172 | 173 | if (app_session->recog_channel) { 174 | speech_channel_destroy(app_session->recog_channel); 175 | } 176 | } 177 | } 178 | 179 | /* Helper function used by datastores to destroy the application data structure upon hangup */ 180 | static void app_datastore_destroy(void *data) 181 | { 182 | app_datastore_t *app_data = (app_datastore_t*) data; 183 | if (!app_data || !app_data->pool) { 184 | return; 185 | } 186 | 187 | void *val; 188 | apr_hash_index_t *it = apr_hash_first(app_data->pool, app_data->session_table); 189 | for(; it; it = apr_hash_next(it)) { 190 | apr_hash_this(it, NULL, NULL, &val); 191 | app_session_destroy(val); 192 | } 193 | 194 | ast_log(LOG_DEBUG, "Destroy app datastore on %s\n", app_data->name); 195 | apr_pool_destroy(app_data->pool); 196 | } 197 | 198 | /* Static structure for datastore information */ 199 | static const struct ast_datastore_info app_unimrcp_datastore = { 200 | .type = "app_unimrcp", 201 | .destroy = app_datastore_destroy 202 | }; 203 | 204 | app_datastore_t* app_datastore_get(struct ast_channel *chan) 205 | { 206 | app_datastore_t *app_datastore = NULL; 207 | struct ast_datastore *datastore; 208 | 209 | datastore = ast_channel_datastore_find(chan, &app_unimrcp_datastore, NULL); 210 | if (datastore) { 211 | app_datastore = datastore->data; 212 | } 213 | else { 214 | apr_pool_t *pool; 215 | ast_log(LOG_DEBUG, "Create app datastore on %s\n", ast_channel_name(chan)); 216 | datastore = ast_datastore_alloc(&app_unimrcp_datastore, NULL); 217 | if (!datastore) { 218 | ast_log(LOG_ERROR, "Unable to create app datastore on %s\n", ast_channel_name(chan)); 219 | return NULL; 220 | } 221 | 222 | if ((pool = apt_pool_create()) == NULL) { 223 | ast_datastore_free(datastore); 224 | ast_log(LOG_ERROR, "Unable to create memory pool for app datastore on %s\n", ast_channel_name(chan)); 225 | return NULL; 226 | } 227 | 228 | app_datastore = apr_palloc(pool, sizeof(app_datastore_t)); 229 | app_datastore->pool = pool; 230 | app_datastore->chan = chan; 231 | app_datastore->session_table = apr_hash_make(pool); 232 | app_datastore->name = apr_pstrdup(pool, ast_channel_name(chan)); 233 | app_datastore->last_recog_entry = NULL; 234 | 235 | datastore->data = app_datastore; 236 | ast_channel_datastore_add(chan, datastore); 237 | } 238 | 239 | return app_datastore; 240 | } 241 | 242 | app_session_t* app_datastore_session_add(app_datastore_t* app_datastore, const char *entry) 243 | { 244 | app_session_t *session; 245 | if (!app_datastore || !entry) 246 | return NULL; 247 | 248 | session = apr_hash_get(app_datastore->session_table, entry, APR_HASH_KEY_STRING); 249 | if (session) { 250 | ast_log(LOG_DEBUG, "Ref entry %s from datastore on %s\n", entry, ast_channel_name(app_datastore->chan)); 251 | } 252 | else { 253 | session = apr_palloc(app_datastore->pool, sizeof(app_session_t)); 254 | 255 | session->pool = app_datastore->pool; 256 | session->schannel_number = get_next_speech_channel_number(); 257 | session->lifetime = APP_SESSION_LIFETIME_DYNAMIC; 258 | session->recog_channel = NULL; 259 | session->synth_channel = NULL; 260 | session->readformat = NULL; 261 | session->rawreadformat = NULL; 262 | session->writeformat = NULL; 263 | session->rawwriteformat = NULL; 264 | session->nreadformat = NULL; 265 | session->nwriteformat = NULL; 266 | session->nlsml_result = NULL; 267 | session->stop_barged_synth = FALSE; 268 | session->instance_format = NLSML_INSTANCE_FORMAT_XML; 269 | session->replace_new_lines = 0; 270 | ast_log(LOG_DEBUG, "Add entry %s to datastore on %s\n", entry, ast_channel_name(app_datastore->chan)); 271 | apr_hash_set(app_datastore->session_table, entry, APR_HASH_KEY_STRING, session); 272 | } 273 | 274 | session->prompts = NULL; 275 | session->cur_prompt = 0; 276 | session->filestream = NULL; 277 | session->max_filelength = 0; 278 | session->it_policy = 0; 279 | return session; 280 | } 281 | 282 | /* Helper function used to find the application data structure attached to a channel */ 283 | static app_session_t* app_datastore_session_find(struct ast_channel *chan) 284 | { 285 | app_datastore_t *app_datastore = NULL; 286 | struct ast_datastore *datastore; 287 | 288 | datastore = ast_channel_datastore_find(chan, &app_unimrcp_datastore, NULL); 289 | if (datastore) { 290 | app_datastore = datastore->data; 291 | } 292 | 293 | if (!app_datastore) { 294 | ast_log(LOG_ERROR, "Unable to find app datastore on %s\n", ast_channel_name(chan)); 295 | return NULL; 296 | } 297 | 298 | if (!app_datastore->last_recog_entry) { 299 | ast_log(LOG_ERROR, "Unable to find last session in app datastore on %s\n", ast_channel_name(chan)); 300 | return NULL; 301 | } 302 | 303 | app_session_t *session = apr_hash_get(app_datastore->session_table, app_datastore->last_recog_entry, APR_HASH_KEY_STRING); 304 | if (!session) { 305 | ast_log(LOG_ERROR, "Unable to find entry %s in app datastore on %s\n", app_datastore->last_recog_entry, ast_channel_name(chan)); 306 | return NULL; 307 | } 308 | 309 | return session; 310 | } 311 | 312 | /* Helper function used to find an interpretation by specified nbest alternative */ 313 | static nlsml_interpretation_t* recog_interpretation_find(app_session_t *app_session, const char *nbest_num) 314 | { 315 | int index = 0; 316 | nlsml_interpretation_t *interpretation; 317 | 318 | if(!app_session || !app_session->nlsml_result) 319 | return NULL; 320 | 321 | if (nbest_num) 322 | index = atoi(nbest_num); 323 | 324 | interpretation = nlsml_first_interpretation_get(app_session->nlsml_result); 325 | while (interpretation) { 326 | if (index == 0) 327 | break; 328 | 329 | index --; 330 | interpretation = nlsml_next_interpretation_get(app_session->nlsml_result, interpretation); 331 | } 332 | 333 | return interpretation; 334 | } 335 | 336 | /* Helper function used to find an instance by specified nbest alternative and index */ 337 | static nlsml_instance_t* recog_instance_find(app_session_t *app_session, const char *num, const char **path) 338 | { 339 | int interpretation_index = 0; 340 | int instance_index = 0; 341 | nlsml_interpretation_t *interpretation; 342 | nlsml_instance_t *instance; 343 | 344 | if (!app_session || !app_session->nlsml_result) 345 | return NULL; 346 | 347 | if (path) { 348 | *path = NULL; 349 | } 350 | 351 | if (num) { 352 | char *tmp = NULL; 353 | if ((tmp = strchr(num, '/'))) { 354 | *tmp++ = '\0'; 355 | interpretation_index = atoi(num); 356 | instance_index = atoi(tmp); 357 | if ((tmp = strchr(tmp, '/'))) { 358 | *tmp++ = '\0'; 359 | if (path) { 360 | *path = tmp; 361 | } 362 | } 363 | } else { 364 | instance_index = atoi(num); 365 | } 366 | } 367 | 368 | interpretation = nlsml_first_interpretation_get(app_session->nlsml_result); 369 | while (interpretation) { 370 | if (interpretation_index == 0) 371 | break; 372 | 373 | interpretation_index --; 374 | interpretation = nlsml_next_interpretation_get(app_session->nlsml_result, interpretation); 375 | } 376 | 377 | if(!interpretation) 378 | return NULL; 379 | 380 | instance = nlsml_interpretation_first_instance_get(interpretation); 381 | while (instance) { 382 | if (instance_index == 0) 383 | break; 384 | 385 | instance_index --; 386 | instance = nlsml_interpretation_next_instance_get(interpretation, instance); 387 | } 388 | 389 | return instance; 390 | } 391 | 392 | /* RECOG_CONFIDENCE() Dialplan Function */ 393 | static int recog_confidence(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) 394 | { 395 | app_session_t *app_session = app_datastore_session_find(chan); 396 | if(!app_session) 397 | return -1; 398 | 399 | nlsml_interpretation_t *interpretation = recog_interpretation_find(app_session, data); 400 | char tmp[128]; 401 | 402 | if (!interpretation) 403 | return -1; 404 | 405 | snprintf(tmp, sizeof(tmp), "%.2f", nlsml_interpretation_confidence_get(interpretation)); 406 | ast_copy_string(buf, tmp, len); 407 | return 0; 408 | } 409 | 410 | static struct ast_custom_function recog_confidence_function = { 411 | .name = "RECOG_CONFIDENCE", 412 | .read = recog_confidence, 413 | .write = NULL, 414 | }; 415 | 416 | /* RECOG_GRAMMAR() Dialplan Function */ 417 | static int recog_grammar(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) 418 | { 419 | app_session_t *app_session = app_datastore_session_find(chan); 420 | if(!app_session) 421 | return -1; 422 | 423 | nlsml_interpretation_t *interpretation = recog_interpretation_find(app_session, data); 424 | const char *grammar; 425 | 426 | if (!interpretation) 427 | return -1; 428 | 429 | grammar = nlsml_interpretation_grammar_get(interpretation); 430 | if(!grammar) 431 | return -1; 432 | 433 | ast_copy_string(buf, grammar, len); 434 | return 0; 435 | } 436 | 437 | static struct ast_custom_function recog_grammar_function = { 438 | .name = "RECOG_GRAMMAR", 439 | .read = recog_grammar, 440 | .write = NULL, 441 | }; 442 | 443 | /* RECOG_INPUT() Dialplan Function */ 444 | static int recog_input(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) 445 | { 446 | app_session_t *app_session = app_datastore_session_find(chan); 447 | if(!app_session) 448 | return -1; 449 | 450 | nlsml_interpretation_t *interpretation = recog_interpretation_find(app_session, data); 451 | nlsml_input_t *input; 452 | const char *text; 453 | 454 | if (!interpretation) 455 | return -1; 456 | 457 | input = nlsml_interpretation_input_get(interpretation); 458 | if(!input) 459 | return -1; 460 | 461 | text = nlsml_input_content_generate(input, app_session->pool); 462 | if(!text) 463 | return -1; 464 | 465 | ast_copy_string(buf, text, len); 466 | return 0; 467 | } 468 | 469 | static struct ast_custom_function recog_input_function = { 470 | .name = "RECOG_INPUT", 471 | .read = recog_input, 472 | .write = NULL, 473 | }; 474 | 475 | /* RECOG_INPUT_MODE() Dialplan Function */ 476 | static int recog_input_mode(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) 477 | { 478 | app_session_t *app_session = app_datastore_session_find(chan); 479 | if(!app_session) 480 | return -1; 481 | 482 | nlsml_interpretation_t *interpretation = recog_interpretation_find(app_session, data); 483 | nlsml_input_t *input; 484 | const char *mode; 485 | 486 | if (!interpretation) 487 | return -1; 488 | 489 | input = nlsml_interpretation_input_get(interpretation); 490 | if(!input) 491 | return -1; 492 | 493 | mode = nlsml_input_mode_get(input); 494 | if(!mode) 495 | return -1; 496 | 497 | ast_copy_string(buf, mode, len); 498 | return 0; 499 | } 500 | 501 | static struct ast_custom_function recog_input_mode_function = { 502 | .name = "RECOG_INPUT_MODE", 503 | .read = recog_input_mode, 504 | .write = NULL, 505 | }; 506 | 507 | /* RECOG_INPUT_CONFIDENCE() Dialplan Function */ 508 | static int recog_input_confidence(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) 509 | { 510 | app_session_t *app_session = app_datastore_session_find(chan); 511 | if(!app_session) 512 | return -1; 513 | 514 | nlsml_interpretation_t *interpretation = recog_interpretation_find(app_session, data); 515 | nlsml_input_t *input; 516 | char tmp[128]; 517 | 518 | if (!interpretation) 519 | return -1; 520 | 521 | input = nlsml_interpretation_input_get(interpretation); 522 | if(!input) 523 | return -1; 524 | 525 | snprintf(tmp, sizeof(tmp), "%.2f", nlsml_input_confidence_get(input)); 526 | ast_copy_string(buf, tmp, len); 527 | return 0; 528 | } 529 | 530 | static struct ast_custom_function recog_input_confidence_function = { 531 | .name = "RECOG_INPUT_CONFIDENCE", 532 | .read = recog_input_confidence, 533 | .write = NULL, 534 | }; 535 | 536 | /* Helper recursive function used to find a nested element based on specified path */ 537 | static const apr_xml_elem* recog_instance_find_elem(const apr_xml_elem *elem, const char **path) 538 | { 539 | char *tmp; 540 | if ((tmp = strchr(*path, '/'))) { 541 | *tmp++ = '\0'; 542 | } 543 | 544 | const apr_xml_elem *child_elem; 545 | for (child_elem = elem->first_child; child_elem; child_elem = child_elem->next) { 546 | if (strcasecmp(child_elem->name, *path) == 0) { 547 | if (tmp) { 548 | *path = tmp; 549 | return recog_instance_find_elem(child_elem, path); 550 | } 551 | return child_elem; 552 | } 553 | } 554 | return NULL; 555 | } 556 | 557 | static int recog_instance_replace_char(char *str, char find_char, char replace_char) 558 | { 559 | char *ptr = str; 560 | int n = 0; 561 | while ((ptr = strchr(ptr, find_char)) != NULL) { 562 | *ptr++ = replace_char; 563 | n++; 564 | } 565 | return n; 566 | } 567 | 568 | /* Helper function used to process XML data in NLSML instance */ 569 | static int recog_instance_process_xml(app_session_t *app_session, nlsml_instance_t *instance, const char *path, const char **text) 570 | { 571 | const apr_xml_elem *child_elem; 572 | const apr_xml_elem *elem = nlsml_instance_elem_get(instance); 573 | if (!elem) 574 | return -1; 575 | 576 | child_elem = recog_instance_find_elem(elem, &path); 577 | if(child_elem) { 578 | apr_size_t size; 579 | apr_xml_to_text(app_session->pool, child_elem, APR_XML_X2T_INNER, NULL, NULL, text, &size); 580 | } 581 | return 0; 582 | } 583 | 584 | #ifdef WITH_AST_JSON 585 | /* Helper recursive function used to find a nested object based on specified path */ 586 | static ast_json* recog_instance_find_json_object(ast_json *json, const char **path) 587 | { 588 | char *tmp; 589 | if ((tmp = strchr(*path, '/'))) { 590 | *tmp++ = '\0'; 591 | } 592 | 593 | ast_json *child_json = NULL; 594 | if (ast_json_typeof(json) == AST_JSON_ARRAY) { 595 | int index = atoi(*path); 596 | child_json = ast_json_array_get(json, index); 597 | } 598 | else { 599 | child_json = ast_json_object_get(json, *path); 600 | } 601 | 602 | if (!child_json){ 603 | ast_log(LOG_DEBUG, "No such JSON object %s\n", *path); 604 | child_json = ast_json_null(); 605 | } 606 | 607 | if (tmp) { 608 | *path = tmp; 609 | return recog_instance_find_json_object(child_json, path); 610 | } 611 | 612 | return child_json; 613 | } 614 | 615 | /* Helper function used to process JSON data in NLSML instance */ 616 | static int recog_instance_process_json(app_session_t *app_session, nlsml_instance_t *instance, const char *path, const char **text) 617 | { 618 | const char *json_string = nlsml_instance_content_generate(instance, app_session->pool); 619 | if (!json_string) { 620 | return -1; 621 | } 622 | 623 | ast_json_error error; 624 | ast_json *child_json; 625 | ast_json *json = ast_json_load_string(json_string, &error); 626 | if (!json) { 627 | ast_log(LOG_ERROR, "Unable to load JSON: %s\n", error.text); 628 | return -1; 629 | } 630 | 631 | char* buf = NULL; 632 | child_json = recog_instance_find_json_object(json, &path); 633 | if (child_json) { 634 | switch (ast_json_typeof(child_json)) 635 | { 636 | case AST_JSON_NULL: 637 | buf = apr_pstrdup(app_session->pool, "null"); 638 | break; 639 | case AST_JSON_TRUE: 640 | buf = apr_pstrdup(app_session->pool, "true"); 641 | break; 642 | case AST_JSON_FALSE: 643 | buf = apr_pstrdup(app_session->pool, "false"); 644 | break; 645 | case AST_JSON_INTEGER: 646 | buf = apr_psprintf(app_session->pool, "%ld", ast_json_integer_get(child_json)); 647 | break; 648 | case AST_JSON_REAL: 649 | buf = apr_psprintf(app_session->pool, "%.3f", ast_json_real_get(child_json)); 650 | break; 651 | case AST_JSON_STRING: 652 | { 653 | const char *str = ast_json_string_get(child_json); 654 | if (str) 655 | buf = apr_pstrdup(app_session->pool, str); 656 | break; 657 | } 658 | case AST_JSON_OBJECT: 659 | case AST_JSON_ARRAY: 660 | { 661 | char *str = ast_json_dump_string(child_json); 662 | if (str) { 663 | buf = apr_pstrdup(app_session->pool, str); 664 | ast_json_free(str); 665 | } 666 | break; 667 | } 668 | default: 669 | break; 670 | } 671 | } 672 | 673 | if (buf) { 674 | *text = buf; 675 | } 676 | 677 | return 0; 678 | } 679 | #else 680 | static int recog_instance_process_json(app_session_t *app_session, nlsml_instance_t *instance, const char *path, const char **text) 681 | { 682 | ast_log(LOG_NOTICE, "JSON support is not available\n"); 683 | return -1; 684 | } 685 | #endif 686 | 687 | /* RECOG_INSTANCE() Dialplan Function */ 688 | static int recog_instance(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) 689 | { 690 | app_session_t *app_session = app_datastore_session_find(chan); 691 | if(!app_session) 692 | return -1; 693 | 694 | const char *path = NULL; 695 | nlsml_instance_t *instance = recog_instance_find(app_session, data, &path); 696 | if (!instance) 697 | return -1; 698 | 699 | const char *text = NULL; 700 | if (path) { 701 | if (app_session->instance_format == NLSML_INSTANCE_FORMAT_XML) { 702 | recog_instance_process_xml(app_session, instance, path, &text); 703 | } 704 | else if (app_session->instance_format == NLSML_INSTANCE_FORMAT_JSON) { 705 | recog_instance_process_json(app_session, instance, path, &text); 706 | } 707 | } 708 | else { 709 | text = nlsml_instance_content_generate(instance, app_session->pool); 710 | } 711 | if(!text) 712 | return -1; 713 | 714 | ast_copy_string(buf, text, len); 715 | if (app_session->replace_new_lines) { 716 | recog_instance_replace_char(buf, '\n', app_session->replace_new_lines); 717 | } 718 | return 0; 719 | } 720 | 721 | static struct ast_custom_function recog_instance_function = { 722 | .name = "RECOG_INSTANCE", 723 | .read = recog_instance, 724 | .write = NULL, 725 | }; 726 | 727 | /* Register custom dialplan functions */ 728 | int app_datastore_functions_register(struct ast_module *mod) 729 | { 730 | int res = 0; 731 | 732 | res |= __ast_custom_function_register(&recog_confidence_function, mod); 733 | res |= __ast_custom_function_register(&recog_grammar_function, mod); 734 | res |= __ast_custom_function_register(&recog_input_function, mod); 735 | res |= __ast_custom_function_register(&recog_input_mode_function, mod); 736 | res |= __ast_custom_function_register(&recog_input_confidence_function, mod); 737 | res |= __ast_custom_function_register(&recog_instance_function, mod); 738 | 739 | return res; 740 | } 741 | 742 | /* Unregister custom dialplan functions */ 743 | int app_datastore_functions_unregister(struct ast_module *mod) 744 | { 745 | int res = 0; 746 | 747 | res |= ast_custom_function_unregister(&recog_confidence_function); 748 | res |= ast_custom_function_unregister(&recog_grammar_function); 749 | res |= ast_custom_function_unregister(&recog_input_function); 750 | res |= ast_custom_function_unregister(&recog_input_mode_function); 751 | res |= ast_custom_function_unregister(&recog_input_confidence_function); 752 | res |= ast_custom_function_unregister(&recog_instance_function); 753 | 754 | return res; 755 | } 756 | -------------------------------------------------------------------------------- /app-unimrcp/app_datastore.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Asterisk -- An open source telephony toolkit. 3 | * 4 | * See http://www.asterisk.org for more information about 5 | * the Asterisk project. Please do not directly contact 6 | * any of the maintainers of this project for assistance; 7 | * the project provides a web site, mailing lists and IRC 8 | * channels for your use. 9 | * 10 | * This program is free software, distributed under the terms of 11 | * the GNU General Public License Version 2. See the LICENSE file 12 | * at the top of the source tree. 13 | * 14 | * Please follow coding guidelines 15 | * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES 16 | */ 17 | 18 | #ifndef APP_DATASTORE_H 19 | #define APP_DATASTORE_H 20 | 21 | /* Asterisk includes. */ 22 | #include "ast_compat_defs.h" 23 | #include "asterisk/module.h" 24 | #include "asterisk/channel.h" 25 | 26 | /* UniMRCP includes. */ 27 | #include "ast_unimrcp_framework.h" 28 | #include "audio_queue.h" 29 | #include "speech_channel.h" 30 | #include "apt_nlsml_doc.h" 31 | 32 | #define DEFAULT_DATASTORE_ENTRY "_default" 33 | 34 | /* The enumeration of session lifetimes. */ 35 | enum app_session_lifetime { 36 | APP_SESSION_LIFETIME_DYNAMIC, /* session is created and destroyed per each request */ 37 | APP_SESSION_LIFETIME_PERSISTENT /* session is created on demand, reused and destroyed with Asterisk channel */ 38 | }; 39 | 40 | /* The enumeration of NLSML instance formats. */ 41 | enum nlsml_instance_format { 42 | NLSML_INSTANCE_FORMAT_XML, /* NLSML instance is represented in XML */ 43 | NLSML_INSTANCE_FORMAT_JSON /* NLSML instance is represented in JSON */ 44 | }; 45 | 46 | /* The application session. */ 47 | struct app_session_t { 48 | apr_pool_t *pool; /* memory pool */ 49 | int lifetime; /* session lifetime */ 50 | apr_uint32_t schannel_number; /* speech channel number */ 51 | speech_channel_t *recog_channel; /* recognition channel */ 52 | speech_channel_t *synth_channel; /* synthesis channel, if any */ 53 | ast_format_compat *readformat; /* old read format, to be restored */ 54 | ast_format_compat *rawreadformat; /* old raw read format, to be restored (>= Asterisk 13) */ 55 | ast_format_compat *writeformat; /* old write format, to be restored */ 56 | ast_format_compat *rawwriteformat; /* old raw write format, to be restored (>= Asterisk 13) */ 57 | ast_format_compat *nreadformat; /* new read format used for recognition */ 58 | ast_format_compat *nwriteformat; /* new write format used for synthesis */ 59 | apr_array_header_t *prompts; /* list of prompt items */ 60 | int cur_prompt; /* current prompt index */ 61 | struct ast_filestream *filestream; /* filestream, if any */ 62 | off_t max_filelength; /* max file length used with file playing, if any */ 63 | int it_policy; /* input timers policy (sar_it_policies) */ 64 | nlsml_result_t *nlsml_result; /* parsed NLSML result */ 65 | apt_bool_t stop_barged_synth; /* whether or not to always stop barged synthesis request */ 66 | enum nlsml_instance_format instance_format; /* NLSML instance format */ 67 | char replace_new_lines; /* replace new lines in NLSML instance */ 68 | }; 69 | 70 | typedef struct app_session_t app_session_t; 71 | 72 | /* The structure holding the application data store */ 73 | struct app_datastore_t { 74 | apr_pool_t *pool; /* memory pool */ 75 | struct ast_channel *chan; /* asterisk channel */ 76 | apr_hash_t *session_table; /* session table (const char*, app_session_t*) */ 77 | const char *name; /* associated channel name */ 78 | const char *last_recog_entry; /* entry of last recognition session to get results for */ 79 | }; 80 | 81 | typedef struct app_datastore_t app_datastore_t; 82 | 83 | /* Register custom dialplan functions */ 84 | int app_datastore_functions_register(struct ast_module *mod); 85 | 86 | /* Unregister custom dialplan functions */ 87 | int app_datastore_functions_unregister(); 88 | 89 | /* Get application data from datastore */ 90 | app_datastore_t* app_datastore_get(struct ast_channel *chan); 91 | 92 | /* Add application session to datastore */ 93 | app_session_t* app_datastore_session_add(app_datastore_t* datastore, const char *entry); 94 | 95 | #endif /* APP_DATASTORE_H */ 96 | -------------------------------------------------------------------------------- /app-unimrcp/app_mrcpsynth.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Asterisk -- An open source telephony toolkit. 3 | * 4 | * Copyright (C) 2009, Molo Afrika Speech Technologies (Pty) Ltd 5 | * 6 | * J.W.F. Thirion 7 | * 8 | * See http://www.asterisk.org for more information about 9 | * the Asterisk project. Please do not directly contact 10 | * any of the maintainers of this project for assistance; 11 | * the project provides a web site, mailing lists and IRC 12 | * channels for your use. 13 | * 14 | * This program is free software, distributed under the terms of 15 | * the GNU General Public License Version 2. See the LICENSE file 16 | * at the top of the source tree. 17 | * 18 | * Please follow coding guidelines 19 | * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES 20 | */ 21 | 22 | /* By Molo Afrika Speech Technologies (Pty) Ltd 23 | * See: http://www.molo.co.za 24 | * 25 | * Ideas, concepts and code borrowed from UniMRCP's example programs 26 | * and the FreeSWITCH mod_unimrcp ASR/TTS module. 27 | * 28 | * Authors of these are: 29 | * UniMRCP: 30 | * Arsen Chaloyan 31 | * FreeSWITCH: mod_unimrcp 32 | * Christopher M. Rienzo 33 | * 34 | * See: 35 | * http://www.unimrcp.org 36 | * http://www.freeswitch.org 37 | */ 38 | 39 | /*! \file 40 | * 41 | * \brief MRCPSynth application 42 | * 43 | * \author\verbatim J.W.F. Thirion \endverbatim 44 | * 45 | * MRCPSynth application 46 | * \ingroup applications 47 | */ 48 | 49 | /* Asterisk includes. */ 50 | #include "ast_compat_defs.h" 51 | 52 | #include "asterisk/channel.h" 53 | #include "asterisk/pbx.h" 54 | #include "asterisk/lock.h" 55 | #include "asterisk/file.h" 56 | #include "asterisk/app.h" 57 | 58 | /* UniMRCP includes. */ 59 | #include "app_datastore.h" 60 | 61 | /*** DOCUMENTATION 62 | 63 | 64 | MRCP synthesis application. 65 | 66 | 67 | 68 | A prompt specified as a plain text, an SSML content, or by means of a file or URI reference. 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | This application establishes an MRCP session for speech synthesis. 94 | If synthesis completed, the variable ${SYNTHSTATUS} is set to "OK"; otherwise, if an error occurred, 95 | the variable ${SYNTHSTATUS} is set to "ERROR". If the caller hung up while the synthesis was in-progress, 96 | the variable ${SYNTHSTATUS} is set to "INTERRUPTED". 97 | The variable ${SYNTH_COMPLETION_CAUSE} indicates whether synthesis completed normally or with an error. 98 | ("000" - normal, "001" - barge-in, "002" - parse-failure, ...) 99 | 100 | 101 | MRCPRecog 102 | SynthAndRecog 103 | 104 | 105 | ***/ 106 | 107 | /* The name of the application. */ 108 | static const char *app_synth = "MRCPSynth"; 109 | 110 | /* The application instance. */ 111 | static ast_mrcp_application_t *mrcpsynth = NULL; 112 | 113 | /* The enumeration of application options (excluding the MRCP params). */ 114 | enum mrcpsynth_option_flags { 115 | MRCPSYNTH_PROFILE = (1 << 0), 116 | MRCPSYNTH_INTERRUPT = (1 << 1), 117 | MRCPSYNTH_FILENAME = (1 << 2), 118 | MRCPSYNTH_PERSISTENT_LIFETIME = (1 << 3), 119 | MRCPSYNTH_DATASTORE_ENTRY = (1 << 4), 120 | MRCPSYNTH_STOP_BARGED_SYNTH = (1 << 5) 121 | }; 122 | 123 | /* The enumeration of option arguments. */ 124 | enum mrcpsynth_option_args { 125 | OPT_ARG_PROFILE = 0, 126 | OPT_ARG_INTERRUPT = 1, 127 | OPT_ARG_FILENAME = 2, 128 | OPT_ARG_PERSISTENT_LIFETIME = 3, 129 | OPT_ARG_DATASTORE_ENTRY = 4, 130 | OPT_ARG_STOP_BARGED_SYNTH = 5, 131 | 132 | /* This MUST be the last value in this enum! */ 133 | OPT_ARG_ARRAY_SIZE = 6 134 | }; 135 | 136 | /* The structure which holds the application options (including the MRCP params). */ 137 | struct mrcpsynth_options_t { 138 | apr_hash_t *synth_hfs; 139 | 140 | int flags; 141 | const char *params[OPT_ARG_ARRAY_SIZE]; 142 | }; 143 | 144 | typedef struct mrcpsynth_options_t mrcpsynth_options_t; 145 | 146 | /* --- MRCP SPEECH CHANNEL INTERFACE TO UNIMRCP --- */ 147 | 148 | /* Get speech channel associated with provided MRCP session. */ 149 | static APR_INLINE speech_channel_t * get_speech_channel(mrcp_session_t *session) 150 | { 151 | if (session) 152 | return (speech_channel_t *)mrcp_application_session_object_get(session); 153 | 154 | return NULL; 155 | } 156 | 157 | /* Handle the UniMRCP responses sent to session terminate requests. */ 158 | static apt_bool_t speech_on_session_terminate(mrcp_application_t *application, mrcp_session_t *session, mrcp_sig_status_code_e status) 159 | { 160 | speech_channel_t *schannel = get_speech_channel(session); 161 | if (!schannel) { 162 | ast_log(LOG_ERROR, "speech_on_session_terminate: unknown channel error!\n"); 163 | return FALSE; 164 | } 165 | 166 | ast_log(LOG_DEBUG, "(%s) speech_on_session_terminate\n", schannel->name); 167 | 168 | ast_log(LOG_DEBUG, "(%s) Destroying MRCP session\n", schannel->name); 169 | if (!mrcp_application_session_destroy(session)) 170 | ast_log(LOG_WARNING, "(%s) Unable to destroy application session\n", schannel->name); 171 | 172 | speech_channel_set_state(schannel, SPEECH_CHANNEL_CLOSED); 173 | return TRUE; 174 | } 175 | 176 | /* Handle the UniMRCP responses sent to channel add requests. */ 177 | static apt_bool_t speech_on_channel_add(mrcp_application_t *application, mrcp_session_t *session, mrcp_channel_t *channel, mrcp_sig_status_code_e status) 178 | { 179 | speech_channel_t *schannel = get_speech_channel(session); 180 | if (!schannel || !channel) { 181 | ast_log(LOG_ERROR, "speech_on_channel_add: unknown channel error!\n"); 182 | return FALSE; 183 | } 184 | 185 | ast_log(LOG_DEBUG, "(%s) speech_on_channel_add\n", schannel->name); 186 | 187 | if (status == MRCP_SIG_STATUS_CODE_SUCCESS) { 188 | const mpf_codec_descriptor_t *descriptor = mrcp_application_sink_descriptor_get(channel); 189 | if (!descriptor) { 190 | ast_log(LOG_ERROR, "(%s) Unable to determine codec descriptor\n", schannel->name); 191 | speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); 192 | return FALSE; 193 | } 194 | 195 | schannel->rate = descriptor->sampling_rate; 196 | const char *codec_name = NULL; 197 | if (descriptor->name.length > 0) 198 | codec_name = descriptor->name.buf; 199 | else 200 | codec_name = "unknown"; 201 | 202 | ast_log(LOG_NOTICE, "(%s) Channel ready, codec=%s, sample rate=%d\n", 203 | schannel->name, 204 | codec_name, 205 | schannel->rate); 206 | speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); 207 | } else { 208 | int rc = mrcp_application_session_response_code_get(session); 209 | ast_log(LOG_ERROR, "(%s) Channel error status=%d, response code=%d!\n", schannel->name, status, rc); 210 | speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); 211 | } 212 | 213 | return TRUE; 214 | } 215 | 216 | /* --- MRCP TTS --- */ 217 | 218 | /* Process UniMRCP messages for the synthesizer application. All MRCP synthesizer callbacks start here first. */ 219 | static apt_bool_t synth_message_handler(const mrcp_app_message_t *app_message) 220 | { 221 | /* Call the appropriate callback in the dispatcher function table based on the app_message received. */ 222 | if (app_message) 223 | return mrcp_application_message_dispatch(&mrcpsynth->dispatcher, app_message); 224 | 225 | ast_log(LOG_ERROR, "(unknown) app_message error!\n"); 226 | return TRUE; 227 | } 228 | 229 | /* Handle the MRCP synthesizer responses/events from UniMRCP. */ 230 | static apt_bool_t synth_on_message_receive(mrcp_application_t *application, mrcp_session_t *session, mrcp_channel_t *channel, mrcp_message_t *message) 231 | { 232 | speech_channel_t *schannel = get_speech_channel(session); 233 | if (!schannel || !message) { 234 | ast_log(LOG_ERROR, "synth_on_message_receive: unknown channel error!\n"); 235 | return FALSE; 236 | } 237 | 238 | mrcp_synth_header_t *synth_header = (mrcp_synth_header_t *)mrcp_resource_header_get(message); 239 | 240 | if (message->start_line.message_type == MRCP_MESSAGE_TYPE_RESPONSE) { 241 | /* Received MRCP response. */ 242 | if (message->start_line.method_id == SYNTHESIZER_SPEAK) { 243 | /* received the response to SPEAK request */ 244 | if (message->start_line.request_state == MRCP_REQUEST_STATE_INPROGRESS) { 245 | /* Waiting for SPEAK-COMPLETE event. */ 246 | ast_log(LOG_DEBUG, "(%s) REQUEST IN PROGRESS\n", schannel->name); 247 | speech_channel_set_state(schannel, SPEECH_CHANNEL_PROCESSING); 248 | } else { 249 | /* Received unexpected request_state. */ 250 | ast_log(LOG_DEBUG, "(%s) Unexpected SPEAK response, request_state = %d\n", schannel->name, message->start_line.request_state); 251 | speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); 252 | } 253 | } else if (message->start_line.method_id == SYNTHESIZER_STOP) { 254 | /* Received response to the STOP request. */ 255 | if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { 256 | /* Got COMPLETE. */ 257 | ast_log(LOG_DEBUG, "(%s) COMPLETE\n", schannel->name); 258 | speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); 259 | } else { 260 | /* Received unexpected request state. */ 261 | ast_log(LOG_DEBUG, "(%s) Unexpected STOP response, request_state = %d\n", schannel->name, message->start_line.request_state); 262 | speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); 263 | } 264 | } else if (message->start_line.method_id == SYNTHESIZER_BARGE_IN_OCCURRED) { 265 | /* Received response to the BARGE_IN_OCCURRED request. */ 266 | if (message->start_line.request_state == MRCP_REQUEST_STATE_COMPLETE) { 267 | /* Got COMPLETE. */ 268 | ast_log(LOG_DEBUG, "(%s) COMPLETE\n", schannel->name); 269 | speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); 270 | } else { 271 | /* Received unexpected request state. */ 272 | ast_log(LOG_DEBUG, "(%s) Unexpected BARGE-IN-OCCURRED response, request_state = %d\n", schannel->name, message->start_line.request_state); 273 | speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); 274 | } 275 | } else { 276 | /* Received unexpected response. */ 277 | ast_log(LOG_DEBUG, "(%s) Unexpected response, method_id = %d\n", schannel->name, (int)message->start_line.method_id); 278 | speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); 279 | } 280 | } else if (message->start_line.message_type == MRCP_MESSAGE_TYPE_EVENT) { 281 | /* Received MRCP event. */ 282 | if (message->start_line.method_id == SYNTHESIZER_SPEAK_COMPLETE) { 283 | /* Got SPEAK-COMPLETE. */ 284 | const char *completion_cause = apr_psprintf(schannel->pool, "%03d", synth_header->completion_cause); 285 | pbx_builtin_setvar_helper(schannel->chan, "SYNTH_COMPLETION_CAUSE", completion_cause); 286 | ast_log(LOG_DEBUG, "(%s) SPEAK-COMPLETE\n", schannel->name); 287 | speech_channel_set_state(schannel, SPEECH_CHANNEL_READY); 288 | } else { 289 | ast_log(LOG_DEBUG, "(%s) Unexpected event, method_id = %d\n", schannel->name, (int)message->start_line.method_id); 290 | speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); 291 | } 292 | } else { 293 | ast_log(LOG_DEBUG, "(%s) Unexpected message type, message_type = %d\n", schannel->name, message->start_line.message_type); 294 | speech_channel_set_state(schannel, SPEECH_CHANNEL_ERROR); 295 | } 296 | 297 | return TRUE; 298 | } 299 | 300 | /* Incoming TTS data from UniMRCP. */ 301 | static apt_bool_t synth_stream_write(mpf_audio_stream_t *stream, const mpf_frame_t *frame) 302 | { 303 | speech_channel_t *schannel; 304 | 305 | if (stream) 306 | schannel = (speech_channel_t *)stream->obj; 307 | else 308 | schannel = NULL; 309 | 310 | if(!schannel || !frame) { 311 | ast_log(LOG_ERROR, "synth_stream_write: unknown channel error!\n"); 312 | return FALSE; 313 | } 314 | 315 | if (frame->codec_frame.size > 0 && (frame->type & MEDIA_FRAME_TYPE_AUDIO) == MEDIA_FRAME_TYPE_AUDIO) { 316 | speech_channel_ast_write(schannel, frame->codec_frame.buffer, frame->codec_frame.size); 317 | } 318 | 319 | return TRUE; 320 | } 321 | 322 | static APR_INLINE apt_bool_t speech_channel_wait_for_ready(speech_channel_t *schannel) 323 | { 324 | apr_thread_cond_timedwait(schannel->cond, schannel->mutex, globals.speech_channel_timeout); 325 | 326 | return (schannel->state == SPEECH_CHANNEL_READY) ? TRUE : FALSE; 327 | } 328 | 329 | /* Send SPEAK request to synthesizer. */ 330 | static int synth_channel_speak(speech_channel_t *schannel, const char *content, const char *content_type, apr_hash_t *header_fields) 331 | { 332 | int status = 0; 333 | mrcp_message_t *mrcp_message = NULL; 334 | mrcp_generic_header_t *generic_header = NULL; 335 | mrcp_synth_header_t *synth_header = NULL; 336 | 337 | if (!schannel || !content || !content_type) { 338 | ast_log(LOG_ERROR, "synth_channel_speak: unknown channel error!\n"); 339 | return -1; 340 | } 341 | 342 | apr_thread_mutex_lock(schannel->mutex); 343 | 344 | if (schannel->state != SPEECH_CHANNEL_READY) { 345 | ast_log(LOG_DEBUG, "(%s) Wait for completion of previous request\n", schannel->name); 346 | /* Wait for completion of previous request. */ 347 | if (speech_channel_wait_for_ready(schannel) == FALSE) { 348 | ast_log(LOG_DEBUG, "(%s) Speech channel not ready\n", schannel->name); 349 | apr_thread_mutex_unlock(schannel->mutex); 350 | return -1; 351 | } 352 | } 353 | 354 | if ((mrcp_message = mrcp_application_message_create(schannel->unimrcp_session, schannel->unimrcp_channel, SYNTHESIZER_SPEAK)) == NULL) { 355 | ast_log(LOG_ERROR, "(%s) Failed to create SPEAK message\n", schannel->name); 356 | 357 | apr_thread_mutex_unlock(schannel->mutex); 358 | return -1; 359 | } 360 | 361 | /* Set generic header fields (content-type). */ 362 | if ((generic_header = (mrcp_generic_header_t *)mrcp_generic_header_prepare(mrcp_message)) == NULL) { 363 | apr_thread_mutex_unlock(schannel->mutex); 364 | return -1; 365 | } 366 | 367 | apt_string_assign(&generic_header->content_type, content_type, mrcp_message->pool); 368 | mrcp_generic_header_property_add(mrcp_message, GENERIC_HEADER_CONTENT_TYPE); 369 | 370 | /* Set synthesizer header fields (voice, rate, etc.). */ 371 | if ((synth_header = (mrcp_synth_header_t *)mrcp_resource_header_prepare(mrcp_message)) == NULL) { 372 | apr_thread_mutex_unlock(schannel->mutex); 373 | return -1; 374 | } 375 | 376 | /* Add params to MRCP message. */ 377 | speech_channel_set_params(schannel, mrcp_message, header_fields); 378 | 379 | /* Set body (plain text or SSML). */ 380 | apt_string_assign(&mrcp_message->body, content, schannel->pool); 381 | 382 | /* Empty audio queue and send SPEAK to MRCP server. */ 383 | audio_queue_clear(schannel->audio_queue); 384 | 385 | if (!mrcp_application_message_send(schannel->unimrcp_session, schannel->unimrcp_channel, mrcp_message)) { 386 | ast_log(LOG_ERROR,"(%s) Failed to send SPEAK message", schannel->name); 387 | 388 | apr_thread_mutex_unlock(schannel->mutex); 389 | return -1; 390 | } 391 | 392 | /* Wait for IN PROGRESS. */ 393 | apr_thread_cond_timedwait(schannel->cond, schannel->mutex, globals.speech_channel_timeout); 394 | 395 | if (schannel->state != SPEECH_CHANNEL_PROCESSING) { 396 | apr_thread_mutex_unlock(schannel->mutex); 397 | return -1; 398 | } 399 | 400 | apr_thread_mutex_unlock(schannel->mutex); 401 | return status; 402 | } 403 | 404 | /* Apply application options. */ 405 | static int mrcpsynth_option_apply(mrcpsynth_options_t *options, const char *key, const char *value) 406 | { 407 | if (strcasecmp(key, "p") == 0) { 408 | options->flags |= MRCPSYNTH_PROFILE; 409 | options->params[OPT_ARG_PROFILE] = value; 410 | } else if (strcasecmp(key, "i") == 0) { 411 | options->flags |= MRCPSYNTH_INTERRUPT; 412 | options->params[OPT_ARG_INTERRUPT] = value; 413 | } else if (strcasecmp(key, "f") == 0) { 414 | options->flags |= MRCPSYNTH_FILENAME; 415 | options->params[OPT_ARG_FILENAME] = value; 416 | } else if (strcasecmp(key, "l") == 0) { 417 | apr_hash_set(options->synth_hfs, "Speech-Language", APR_HASH_KEY_STRING, value); 418 | } else if (strcasecmp(key, "ll") == 0) { 419 | apr_hash_set(options->synth_hfs, "Load-Lexicon", APR_HASH_KEY_STRING, value); 420 | } else if (strcasecmp(key, "pv") == 0) { 421 | apr_hash_set(options->synth_hfs, "Prosody-Volume", APR_HASH_KEY_STRING, value); 422 | } else if (strcasecmp(key, "pr") == 0) { 423 | apr_hash_set(options->synth_hfs, "Prosody-Rate", APR_HASH_KEY_STRING, value); 424 | } else if (strcasecmp(key, "v") == 0) { 425 | apr_hash_set(options->synth_hfs, "Voice-Name", APR_HASH_KEY_STRING, value); 426 | } else if (strcasecmp(key, "vv") == 0) { 427 | apr_hash_set(options->synth_hfs, "Voice-Variant", APR_HASH_KEY_STRING, value); 428 | } else if (strcasecmp(key, "g") == 0) { 429 | apr_hash_set(options->synth_hfs, "Voice-Gender", APR_HASH_KEY_STRING, value); 430 | } else if (strcasecmp(key, "a") == 0) { 431 | apr_hash_set(options->synth_hfs, "Voice-Age", APR_HASH_KEY_STRING, value); 432 | } else if (strcasecmp(key, "vsp") == 0) { 433 | apr_hash_set(options->synth_hfs, "Vendor-Specific-Parameters", APR_HASH_KEY_STRING, value); 434 | } else if (strcasecmp(key, "plt") == 0) { 435 | options->flags |= MRCPSYNTH_PERSISTENT_LIFETIME; 436 | options->params[OPT_ARG_PERSISTENT_LIFETIME] = value; 437 | } else if (strcasecmp(key, "dse") == 0) { 438 | options->flags |= MRCPSYNTH_DATASTORE_ENTRY; 439 | options->params[OPT_ARG_DATASTORE_ENTRY] = value; 440 | } else if (strcasecmp(key, "sbs") == 0) { 441 | options->flags |= MRCPSYNTH_STOP_BARGED_SYNTH; 442 | options->params[OPT_ARG_STOP_BARGED_SYNTH] = value; 443 | } else { 444 | ast_log(LOG_WARNING, "Unknown option: %s\n", key); 445 | } 446 | return 0; 447 | } 448 | 449 | /* Parse application options. */ 450 | static int mrcpsynth_options_parse(char *str, mrcpsynth_options_t *options, apr_pool_t *pool) 451 | { 452 | char *s; 453 | char *name, *value; 454 | 455 | if (!str) 456 | return 0; 457 | 458 | if ((options->synth_hfs = apr_hash_make(pool)) == NULL) { 459 | return -1; 460 | } 461 | 462 | while ((s = strsep(&str, "&"))) { 463 | value = s; 464 | if ((name = strsep(&value, "=")) && value) { 465 | ast_log(LOG_DEBUG, "Apply option %s: %s\n", name, value); 466 | mrcpsynth_option_apply(options, name, value); 467 | } 468 | } 469 | return 0; 470 | } 471 | 472 | /* Exit the application. */ 473 | static int mrcpsynth_exit(struct ast_channel *chan, app_session_t *app_session, speech_channel_status_t status) 474 | { 475 | if (app_session) { 476 | if (app_session->writeformat && app_session->rawwriteformat) 477 | ast_set_write_format_path(chan, app_session->writeformat, app_session->rawwriteformat); 478 | 479 | if (app_session->synth_channel) { 480 | if (app_session->lifetime == APP_SESSION_LIFETIME_DYNAMIC) { 481 | if (app_session->stop_barged_synth == TRUE) { 482 | speech_channel_stop(app_session->synth_channel); 483 | } 484 | speech_channel_destroy(app_session->synth_channel); 485 | app_session->synth_channel = NULL; 486 | } 487 | } 488 | } 489 | 490 | const char *status_str = speech_channel_status_to_string(status); 491 | pbx_builtin_setvar_helper(chan, "SYNTHSTATUS", status_str); 492 | ast_log(LOG_NOTICE, "%s() exiting status: %s on %s\n", app_synth, status_str, ast_channel_name(chan)); 493 | return 0; 494 | } 495 | 496 | /* The entry point of the application. */ 497 | static int app_synth_exec(struct ast_channel *chan, ast_app_data data) 498 | { 499 | struct ast_frame *f; 500 | apr_uint32_t speech_channel_number = get_next_speech_channel_number(); 501 | const char *name; 502 | speech_channel_status_t status; 503 | char *parse; 504 | int i; 505 | mrcpsynth_options_t mrcpsynth_options; 506 | 507 | AST_DECLARE_APP_ARGS(args, 508 | AST_APP_ARG(prompt); 509 | AST_APP_ARG(options); 510 | ); 511 | 512 | if (ast_strlen_zero(data)) { 513 | ast_log(LOG_WARNING, "%s() requires an argument (prompt[,options])\n", app_synth); 514 | return mrcpsynth_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); 515 | } 516 | 517 | /* We need to make a copy of the input string if we are going to modify it! */ 518 | parse = ast_strdupa(data); 519 | AST_STANDARD_APP_ARGS(args, parse); 520 | 521 | if (ast_strlen_zero(args.prompt)) { 522 | ast_log(LOG_WARNING, "%s() requires a prompt argument (prompt[,options])\n", app_synth); 523 | return mrcpsynth_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); 524 | } 525 | 526 | args.prompt = normalize_input_string(args.prompt); 527 | ast_log(LOG_NOTICE, "%s() prompt: %s\n", app_synth, args.prompt); 528 | 529 | app_datastore_t* datastore = app_datastore_get(chan); 530 | if (!datastore) { 531 | ast_log(LOG_ERROR, "Unable to retrieve data from app datastore on %s\n", ast_channel_name(chan)); 532 | return mrcpsynth_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); 533 | } 534 | 535 | mrcpsynth_options.synth_hfs = NULL; 536 | mrcpsynth_options.flags = 0; 537 | for (i=0; ipool, args.options); 544 | mrcpsynth_options_parse(options_buf, &mrcpsynth_options, datastore->pool); 545 | } 546 | 547 | int dtmf_enable = 0; 548 | if ((mrcpsynth_options.flags & MRCPSYNTH_INTERRUPT) == MRCPSYNTH_INTERRUPT) { 549 | if (!ast_strlen_zero(mrcpsynth_options.params[OPT_ARG_INTERRUPT])) { 550 | dtmf_enable = 1; 551 | 552 | if (strcasecmp(mrcpsynth_options.params[OPT_ARG_INTERRUPT], "any") == 0) { 553 | mrcpsynth_options.params[OPT_ARG_INTERRUPT] = AST_DIGIT_ANY; 554 | } else if (strcasecmp(mrcpsynth_options.params[OPT_ARG_INTERRUPT], "none") == 0) 555 | dtmf_enable = 0; 556 | } 557 | } 558 | 559 | /* Answer if it's not already going. */ 560 | if (ast_channel_state(chan) != AST_STATE_UP) 561 | ast_answer(chan); 562 | 563 | /* Ensure no streams are currently playing. */ 564 | ast_stopstream(chan); 565 | 566 | /* Set default lifetime to dynamic. */ 567 | int lifetime = APP_SESSION_LIFETIME_DYNAMIC; 568 | 569 | /* Get datastore entry. */ 570 | const char *entry = DEFAULT_DATASTORE_ENTRY; 571 | if ((mrcpsynth_options.flags & MRCPSYNTH_DATASTORE_ENTRY) == MRCPSYNTH_DATASTORE_ENTRY) { 572 | if (!ast_strlen_zero(mrcpsynth_options.params[OPT_ARG_DATASTORE_ENTRY])) { 573 | entry = mrcpsynth_options.params[OPT_ARG_DATASTORE_ENTRY]; 574 | lifetime = APP_SESSION_LIFETIME_PERSISTENT; 575 | } 576 | } 577 | 578 | /* Check session lifetime. */ 579 | if ((mrcpsynth_options.flags & MRCPSYNTH_PERSISTENT_LIFETIME) == MRCPSYNTH_PERSISTENT_LIFETIME) { 580 | if (!ast_strlen_zero(mrcpsynth_options.params[OPT_ARG_PERSISTENT_LIFETIME])) { 581 | lifetime = (atoi(mrcpsynth_options.params[OPT_ARG_PERSISTENT_LIFETIME]) == 0) ? 582 | APP_SESSION_LIFETIME_DYNAMIC : APP_SESSION_LIFETIME_PERSISTENT; 583 | } 584 | } 585 | 586 | /* Get application datastore. */ 587 | app_session_t *app_session = app_datastore_session_add(datastore, entry); 588 | if (!app_session) { 589 | return mrcpsynth_exit(chan, NULL, SPEECH_CHANNEL_STATUS_ERROR); 590 | } 591 | app_session->lifetime = lifetime; 592 | 593 | /* Check whether or not to always stop barged synthesis request. */ 594 | app_session->stop_barged_synth = FALSE; 595 | if ((mrcpsynth_options.flags & MRCPSYNTH_STOP_BARGED_SYNTH) == MRCPSYNTH_STOP_BARGED_SYNTH) { 596 | if (!ast_strlen_zero(mrcpsynth_options.params[OPT_ARG_STOP_BARGED_SYNTH])) { 597 | app_session->stop_barged_synth = (atoi(mrcpsynth_options.params[OPT_ARG_STOP_BARGED_SYNTH]) == 0) ? FALSE : TRUE; 598 | } 599 | } 600 | 601 | if(!app_session->synth_channel) { 602 | const char *filename = NULL; 603 | if ((mrcpsynth_options.flags & MRCPSYNTH_FILENAME) == MRCPSYNTH_FILENAME) { 604 | filename = mrcpsynth_options.params[OPT_ARG_FILENAME]; 605 | } 606 | 607 | /* Get new write format. */ 608 | app_session->nwriteformat = ast_channel_get_speechwriteformat(chan, app_session->pool); 609 | 610 | name = apr_psprintf(app_session->pool, "TTS-%lu", (unsigned long int)speech_channel_number); 611 | 612 | /* Create speech channel for synthesis. */ 613 | app_session->synth_channel = speech_channel_create( 614 | app_session->pool, 615 | name, 616 | SPEECH_CHANNEL_SYNTHESIZER, 617 | mrcpsynth, 618 | app_session->nwriteformat, 619 | filename, 620 | chan); 621 | if (!app_session->synth_channel) { 622 | return mrcpsynth_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); 623 | } 624 | 625 | const char *profile_name = NULL; 626 | if ((mrcpsynth_options.flags & MRCPSYNTH_PROFILE) == MRCPSYNTH_PROFILE) { 627 | if (!ast_strlen_zero(mrcpsynth_options.params[OPT_ARG_PROFILE])) { 628 | profile_name = mrcpsynth_options.params[OPT_ARG_PROFILE]; 629 | } 630 | } 631 | 632 | /* Get synthesis profile. */ 633 | ast_mrcp_profile_t *profile = get_synth_profile(profile_name); 634 | if (!profile) { 635 | ast_log(LOG_ERROR, "(%s) Can't find profile, %s\n", name, profile_name); 636 | return mrcpsynth_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); 637 | } 638 | 639 | /* Open synthesis channel. */ 640 | if (speech_channel_open(app_session->synth_channel, profile) != 0) { 641 | return mrcpsynth_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); 642 | } 643 | } 644 | else { 645 | name = app_session->synth_channel->name; 646 | } 647 | 648 | /* Get old write format. */ 649 | ast_format_compat *owriteformat = ast_channel_get_writeformat(chan, app_session->pool); 650 | ast_format_compat *orawwriteformat = ast_channel_get_rawwriteformat(chan, app_session->pool); 651 | 652 | /* Set write format. */ 653 | ast_set_write_format_path(chan, app_session->nwriteformat, orawwriteformat); 654 | 655 | /* Store old write format. */ 656 | app_session->writeformat = owriteformat; 657 | app_session->rawwriteformat = orawwriteformat; 658 | 659 | const char *content = NULL; 660 | const char *content_type = NULL; 661 | if (determine_synth_content_type(app_session->synth_channel, args.prompt, &content, &content_type) != 0) { 662 | ast_log(LOG_WARNING, "(%s) Unable to determine synthesis content type\n", name); 663 | return mrcpsynth_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); 664 | } 665 | 666 | ast_log(LOG_NOTICE, "(%s) Synthesizing, enable DTMFs: %d\n", name, dtmf_enable); 667 | 668 | if (synth_channel_speak(app_session->synth_channel, content, content_type, mrcpsynth_options.synth_hfs) != 0) { 669 | ast_log(LOG_WARNING, "(%s) Unable to start synthesis\n", name); 670 | return mrcpsynth_exit(chan, app_session, SPEECH_CHANNEL_STATUS_ERROR); 671 | } 672 | 673 | int ms; 674 | int running; 675 | status = SPEECH_CHANNEL_STATUS_OK; 676 | do { 677 | ms = ast_waitfor(chan, 100); 678 | if (ms < 0) { 679 | ast_log(LOG_DEBUG, "(%s) Hangup detected\n", name); 680 | return mrcpsynth_exit(chan, app_session, SPEECH_CHANNEL_STATUS_INTERRUPTED); 681 | } 682 | 683 | f = ast_read(chan); 684 | if (!f) { 685 | ast_log(LOG_DEBUG, "(%s) Null frame == hangup() detected\n", name); 686 | return mrcpsynth_exit(chan, app_session, SPEECH_CHANNEL_STATUS_INTERRUPTED); 687 | } 688 | 689 | running = 1; 690 | if (dtmf_enable && f->frametype == AST_FRAME_DTMF) { 691 | int dtmfkey = ast_frame_get_dtmfkey(f); 692 | 693 | ast_log(LOG_DEBUG, "(%s) User pressed a key (%d)\n", name, dtmfkey); 694 | if (mrcpsynth_options.params[OPT_ARG_INTERRUPT] && strchr(mrcpsynth_options.params[OPT_ARG_INTERRUPT], dtmfkey)) { 695 | status = SPEECH_CHANNEL_STATUS_INTERRUPTED; 696 | running = 0; 697 | 698 | ast_log(LOG_DEBUG, "(%s) Sending BARGE-IN-OCCURRED\n", app_session->synth_channel->name); 699 | if (speech_channel_bargeinoccurred(app_session->synth_channel) != 0) { 700 | ast_log(LOG_ERROR, "(%s) Failed to send BARGE-IN-OCCURRED\n", app_session->synth_channel->name); 701 | } 702 | } 703 | } 704 | 705 | ast_frfree(f); 706 | 707 | if (app_session->synth_channel->state != SPEECH_CHANNEL_PROCESSING) { 708 | /* end of prompt */ 709 | running = 0; 710 | } 711 | } 712 | while (running); 713 | 714 | return mrcpsynth_exit(chan, app_session, status); 715 | } 716 | 717 | int load_mrcpsynth_app() 718 | { 719 | apr_pool_t *pool = globals.pool; 720 | 721 | if (pool == NULL) { 722 | ast_log(LOG_ERROR, "Memory pool is NULL\n"); 723 | return -1; 724 | } 725 | 726 | if(mrcpsynth) { 727 | ast_log(LOG_ERROR, "Application %s is already loaded\n", app_synth); 728 | return -1; 729 | } 730 | 731 | mrcpsynth = (ast_mrcp_application_t*) apr_palloc(pool, sizeof(ast_mrcp_application_t)); 732 | mrcpsynth->name = app_synth; 733 | mrcpsynth->exec = app_synth_exec; 734 | #if !AST_VERSION_AT_LEAST(1,6,2) 735 | mrcpsynth->synopsis = NULL; 736 | mrcpsynth->description = NULL; 737 | #endif 738 | 739 | /* Create the synthesizer application and link its callbacks */ 740 | if ((mrcpsynth->app = mrcp_application_create(synth_message_handler, (void *)0, pool)) == NULL) { 741 | ast_log(LOG_ERROR, "Unable to create synthesizer MRCP application %s\n", app_synth); 742 | mrcpsynth = NULL; 743 | return -1; 744 | } 745 | 746 | mrcpsynth->dispatcher.on_session_update = NULL; 747 | mrcpsynth->dispatcher.on_session_terminate = speech_on_session_terminate; 748 | mrcpsynth->dispatcher.on_channel_add = speech_on_channel_add; 749 | mrcpsynth->dispatcher.on_channel_remove = NULL; 750 | mrcpsynth->dispatcher.on_message_receive = synth_on_message_receive; 751 | mrcpsynth->dispatcher.on_terminate_event = NULL; 752 | mrcpsynth->dispatcher.on_resource_discover = NULL; 753 | mrcpsynth->audio_stream_vtable.destroy = NULL; 754 | mrcpsynth->audio_stream_vtable.open_rx = NULL; 755 | mrcpsynth->audio_stream_vtable.close_rx = NULL; 756 | mrcpsynth->audio_stream_vtable.read_frame = NULL; 757 | mrcpsynth->audio_stream_vtable.open_tx = NULL; 758 | mrcpsynth->audio_stream_vtable.close_tx = NULL; 759 | mrcpsynth->audio_stream_vtable.write_frame = synth_stream_write; 760 | mrcpsynth->audio_stream_vtable.trace = NULL; 761 | 762 | if (!mrcp_client_application_register(globals.mrcp_client, mrcpsynth->app, app_synth)) { 763 | ast_log(LOG_ERROR, "Unable to register synthesizer MRCP application %s\n", app_synth); 764 | if (!mrcp_application_destroy(mrcpsynth->app)) 765 | ast_log(LOG_WARNING, "Unable to destroy synthesizer MRCP application %s\n", app_synth); 766 | mrcpsynth = NULL; 767 | return -1; 768 | } 769 | 770 | apr_hash_set(globals.apps, app_synth, APR_HASH_KEY_STRING, mrcpsynth); 771 | 772 | return 0; 773 | } 774 | 775 | int unload_mrcpsynth_app() 776 | { 777 | if(!mrcpsynth) { 778 | ast_log(LOG_ERROR, "Application %s doesn't exist\n", app_synth); 779 | return -1; 780 | } 781 | 782 | apr_hash_set(globals.apps, app_synth, APR_HASH_KEY_STRING, NULL); 783 | mrcpsynth = NULL; 784 | 785 | return 0; 786 | } 787 | -------------------------------------------------------------------------------- /app-unimrcp/app_unimrcp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Asterisk -- An open source telephony toolkit. 3 | * 4 | * Copyright (C) 2009, Molo Afrika Speech Technologies (Pty) Ltd 5 | * 6 | * J.W.F. Thirion 7 | * 8 | * See http://www.asterisk.org for more information about 9 | * the Asterisk project. Please do not directly contact 10 | * any of the maintainers of this project for assistance; 11 | * the project provides a web site, mailing lists and IRC 12 | * channels for your use. 13 | * 14 | * This program is free software, distributed under the terms of 15 | * the GNU General Public License Version 2. See the LICENSE file 16 | * at the top of the source tree. 17 | * 18 | * Please follow coding guidelines 19 | * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES 20 | */ 21 | 22 | /* By Molo Afrika Speech Technologies (Pty) Ltd 23 | * See: http://www.molo.co.za 24 | * 25 | * Ideas, concepts and code borrowed from UniMRCP's example programs 26 | * and the FreeSWITCH mod_unimrcp ASR/TTS module. 27 | * 28 | * Authors of these are: 29 | * UniMRCP: 30 | * Arsen Chaloyan 31 | * FreeSWITCH: mod_unimrcp 32 | * Christopher M. Rienzo 33 | * 34 | * See: 35 | * http://www.unimrcp.org 36 | * http://www.freeswitch.org 37 | */ 38 | 39 | /*! \file 40 | * 41 | * \brief MRCP suite of applications 42 | * 43 | * \author\verbatim J.W.F. Thirion \endverbatim 44 | * \author\verbatim Arsen Chaloyan \endverbatim 45 | * 46 | * MRCP suite of applications 47 | * \ingroup applications 48 | */ 49 | 50 | /*** MODULEINFO 51 | yes 52 | unimrcp 53 | apr 54 | ***/ 55 | 56 | /* Asterisk includes. */ 57 | #include "ast_compat_defs.h" 58 | 59 | #if AST_VERSION_AT_LEAST(1,4,0) 60 | #define AST_COMPAT_STATIC static 61 | #else /* 1.2 */ 62 | #define AST_MODULE_LOAD_DECLINE -1 63 | #define AST_COMPAT_STATIC 64 | #endif 65 | 66 | #if !AST_VERSION_AT_LEAST(1,6,0) 67 | #include 68 | #include 69 | #endif 70 | 71 | ASTERISK_REGISTER_FILE() 72 | 73 | #define AST_MODULE "app_unimrcp" 74 | #include "asterisk/module.h" 75 | 76 | /* UniMRCP includes. */ 77 | #include "ast_unimrcp_framework.h" 78 | #include "app_datastore.h" 79 | 80 | /* The configuration file to read. */ 81 | #define MRCP_CONFIG "mrcp.conf" 82 | 83 | static int apr_initialized = 0; 84 | 85 | /* MRCPSynth application. */ 86 | int load_mrcpsynth_app(); 87 | int unload_mrcpsynth_app(); 88 | 89 | /* MRCPRecog application. */ 90 | int load_mrcprecog_app(); 91 | int unload_mrcprecog_app(); 92 | 93 | /* SynthAndRecog application. */ 94 | int load_synthandrecog_app(); 95 | int unload_synthandrecog_app(); 96 | 97 | /* Connects UniMRCP logging to Asterisk. */ 98 | static apt_bool_t unimrcp_log(const char *file, int line, const char *id, apt_log_priority_e priority, const char *format, va_list arg_ptr) 99 | { 100 | /* Asterisk log level mapped to UniMRCP log priority. */ 101 | int level; 102 | /* Same size as MAX_LOG_ENTRY_SIZE in UniMRCP apt_log.c. */ 103 | char log_message[4096] = { 0 }; 104 | 105 | if (strlen(format) == 0) 106 | return TRUE; 107 | 108 | /* Assume apr_vsnprintf supports format extensions required by UniMRCP. */ 109 | apr_vsnprintf(log_message, sizeof(log_message) - 1, format, arg_ptr); 110 | log_message[sizeof(log_message) - 1] = '\0'; 111 | 112 | switch(priority) { 113 | case APT_PRIO_EMERGENCY: 114 | case APT_PRIO_ALERT: 115 | case APT_PRIO_CRITICAL: 116 | case APT_PRIO_ERROR: 117 | level = __LOG_ERROR; 118 | break; 119 | case APT_PRIO_WARNING: 120 | level = __LOG_WARNING; 121 | break; 122 | case APT_PRIO_NOTICE: 123 | case APT_PRIO_INFO: 124 | level = __LOG_NOTICE; 125 | break; 126 | case APT_PRIO_DEBUG: 127 | level = __LOG_DEBUG; 128 | break; 129 | default: 130 | level = __LOG_DEBUG; 131 | break; 132 | } 133 | 134 | ast_log(level, file, line, NULL, "%s\n", log_message); 135 | return TRUE; 136 | } 137 | 138 | AST_COMPAT_STATIC int load_module(void) 139 | { 140 | int res = 0; 141 | apr_hash_index_t *hi; 142 | 143 | if (apr_initialized == 0) { 144 | if (apr_initialize() != APR_SUCCESS) { 145 | ast_log(LOG_ERROR, "Unable to initialize APR\n"); 146 | apr_terminate(); 147 | apr_initialized = 0; 148 | return AST_MODULE_LOAD_DECLINE; 149 | } else { 150 | ast_log(LOG_DEBUG, "APR initialized\n"); 151 | apr_initialized = 1; 152 | } 153 | } 154 | 155 | /* Initialize globals. */ 156 | if (globals_init() != 0) { 157 | ast_log(LOG_DEBUG, "Unable to initialize globals\n"); 158 | apr_terminate(); 159 | apr_initialized = 0; 160 | return AST_MODULE_LOAD_DECLINE; 161 | } 162 | 163 | /* Load the configuration file mrcp.conf. */ 164 | if (load_mrcp_config(MRCP_CONFIG, AST_MODULE) != 0) { 165 | globals_destroy(); 166 | apr_terminate(); 167 | apr_initialized = 0; 168 | return AST_MODULE_LOAD_DECLINE; 169 | } 170 | 171 | /* Link UniMRCP logs to Asterisk. */ 172 | ast_log(LOG_NOTICE, "UniMRCP log level = %s\n", globals.unimrcp_log_level); 173 | apt_log_priority_e log_priority = apt_log_priority_translate(globals.unimrcp_log_level); 174 | if (apt_log_instance_create(APT_LOG_OUTPUT_NONE, log_priority, globals.pool) == FALSE) { 175 | /* Already created. */ 176 | apt_log_priority_set(log_priority); 177 | } 178 | apt_log_ext_handler_set(unimrcp_log); 179 | 180 | /* Create the MRCP client. */ 181 | if ((globals.mrcp_client = mod_unimrcp_client_create(globals.pool)) == NULL) { 182 | ast_log(LOG_ERROR, "Failed to create MRCP client\n"); 183 | if (!apt_log_instance_destroy()) 184 | ast_log(LOG_WARNING, "Unable to destroy UniMRCP logger instance\n"); 185 | globals_destroy(); 186 | apr_terminate(); 187 | apr_initialized = 0; 188 | return AST_MODULE_LOAD_DECLINE; 189 | } 190 | 191 | /* Load the applications. */ 192 | load_mrcpsynth_app(); 193 | load_mrcprecog_app(); 194 | load_synthandrecog_app(); 195 | 196 | /* Start the client stack. */ 197 | if (!mrcp_client_start(globals.mrcp_client)) { 198 | ast_log(LOG_ERROR, "Failed to start MRCP client stack processing\n"); 199 | if (!mrcp_client_destroy(globals.mrcp_client)) 200 | ast_log(LOG_WARNING, "Unable to destroy MRCP client stack\n"); 201 | else 202 | ast_log(LOG_DEBUG, "MRCP client stack destroyed\n"); 203 | globals.mrcp_client = NULL; 204 | if (!apt_log_instance_destroy()) 205 | ast_log(LOG_WARNING, "Unable to destroy UniMRCP logger instance\n"); 206 | globals_destroy(); 207 | apr_terminate(); 208 | apr_initialized = 0; 209 | return AST_MODULE_LOAD_DECLINE; 210 | } 211 | 212 | /* Register the applications. */ 213 | for (hi = apr_hash_first(NULL, globals.apps); hi; hi = apr_hash_next(hi)) { 214 | const void *key; 215 | void *val; 216 | const char *name; 217 | ast_mrcp_application_t *application; 218 | 219 | apr_hash_this(hi, &key, NULL, &val); 220 | 221 | name = (const char *) key; 222 | application = (ast_mrcp_application_t *) val; 223 | 224 | #if AST_VERSION_AT_LEAST(1,6,2) 225 | res |= ast_register_application_xml(name, application->exec); 226 | #else 227 | res |= ast_register_application(name, application->exec, application->synopsis, application->description); 228 | #endif 229 | } 230 | 231 | /* Register the custom functions. */ 232 | res |= app_datastore_functions_register(ast_module_info->self); 233 | 234 | return res; 235 | } 236 | 237 | AST_COMPAT_STATIC int unload_module(void) 238 | { 239 | int res = 0; 240 | apr_hash_index_t *hi; 241 | 242 | /* First unregister the applications so no more calls arrive. */ 243 | for (hi = apr_hash_first(NULL, globals.apps); hi; hi = apr_hash_next(hi)) { 244 | const void *key; 245 | const char *name; 246 | 247 | apr_hash_this(hi, &key, NULL, NULL); 248 | 249 | name = (const char *) key; 250 | 251 | res |= ast_unregister_application(name); 252 | } 253 | 254 | /* Unregister the custom functions. */ 255 | res |= app_datastore_functions_unregister(); 256 | 257 | /* Unload the applications. */ 258 | unload_mrcpsynth_app(); 259 | unload_mrcprecog_app(); 260 | unload_synthandrecog_app(); 261 | 262 | /* Stop the MRCP client stack. */ 263 | if (globals.mrcp_client != NULL) { 264 | if (!mrcp_client_shutdown(globals.mrcp_client)) 265 | ast_log(LOG_WARNING, "Unable to shutdown MRCP client stack processing\n"); 266 | else 267 | ast_log(LOG_DEBUG, "MRCP client stack processing shutdown\n"); 268 | 269 | if (!mrcp_client_destroy(globals.mrcp_client)) 270 | ast_log(LOG_WARNING, "Unable to destroy MRCP client stack\n"); 271 | else 272 | ast_log(LOG_DEBUG, "MRCP client stack destroyed\n"); 273 | 274 | globals.mrcp_client = NULL; 275 | } 276 | 277 | if (!apt_log_instance_destroy()) 278 | ast_log(LOG_WARNING, "Unable to destroy UniMRCP logger instance\n"); 279 | 280 | /* Destroy globals. */ 281 | globals_destroy(); 282 | 283 | if ((res == 0) && (apr_initialized != 0)) { 284 | apr_terminate(); 285 | apr_initialized = 0; 286 | } 287 | 288 | return res; 289 | } 290 | 291 | AST_COMPAT_STATIC int reload(void) 292 | { 293 | return 0; 294 | } 295 | 296 | #if AST_VERSION_AT_LEAST(1,4,0) 297 | AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "MRCP suite of applications", 298 | .load = load_module, 299 | .unload = unload_module, 300 | .reload = reload 301 | ); 302 | #endif 303 | 304 | /* TO DO: 305 | * 306 | * ( ) 1. Support for other codecs, fallback to LPCM if MRCP server doesn't support codec 307 | * ( ) 2. Documentation 308 | * ( ) install guide ( ), configuration guide ( ), user guide ( ), doxygen documentation ( ), application console+help ( ), etc. 309 | * ( ) 3. Fetching of grammar, SSML, etc. as URI - support for http, https, ftp, file, odbc, etc. using CURL - flag to indicate if MRCP server should fetch or if we should and then inline the result 310 | * ( ) 4. Caching of prompts for TTS, functions in console to manage cache, config for settings, etc. - cache to memory, file system or database 311 | * ( ) 5. Caching of grammar, SSML, etc. - TTS cache, SSML cache, etc. 312 | * ( ) 6. Example applications 313 | * ( ) 7. Packaging into a libmrcp library with callbacks for Asterisk specific features 314 | * ( ) 8. Resources/applications for Speaker Verification, Speaker Recognition, Speech Recording 315 | * 316 | * NOTE: If you want DTMF recognised, remember to set "codecs = PCMU PCMA L16/96/8000 PCMU/97/16000 telephone-event/101/8000" as telephone-event is important 317 | */ 318 | 319 | /* For Emacs: 320 | * Local Variables: 321 | * mode:c 322 | * indent-tabs-mode:t 323 | * tab-width:4 324 | * c-basic-offset:4 325 | * End: 326 | * For VIM: 327 | * vim:set softtabstop=4 shiftwidth=4 tabstop=4: 328 | */ 329 | -------------------------------------------------------------------------------- /app-unimrcp/ast_unimrcp_framework.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Asterisk -- An open source telephony toolkit. 3 | * 4 | * See http://www.asterisk.org for more information about 5 | * the Asterisk project. Please do not directly contact 6 | * any of the maintainers of this project for assistance; 7 | * the project provides a web site, mailing lists and IRC 8 | * channels for your use. 9 | * 10 | * This program is free software, distributed under the terms of 11 | * the GNU General Public License Version 2. See the LICENSE file 12 | * at the top of the source tree. 13 | * 14 | * Please follow coding guidelines 15 | * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES 16 | */ 17 | 18 | #ifndef AST_UNIMRCP_FRAMEWORK_H 19 | #define AST_UNIMRCP_FRAMEWORK_H 20 | 21 | /* UniMRCP includes. */ 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "apt.h" 27 | #include "apt_log.h" 28 | #include "apt_net.h" 29 | #include "apt_pool.h" 30 | #include "unimrcp_client.h" 31 | #include "mrcp_application.h" 32 | #include "mrcp_session.h" 33 | #include "mrcp_message.h" 34 | #include "mrcp_generic_header.h" 35 | #include "mrcp_synth_header.h" 36 | #include "mrcp_synth_resource.h" 37 | #include "mrcp_recog_header.h" 38 | #include "mrcp_recog_resource.h" 39 | #include "uni_version.h" 40 | #include "mrcp_resource_loader.h" 41 | #include "mpf_engine.h" 42 | #include "mpf_codec_manager.h" 43 | #include "mpf_dtmf_generator.h" 44 | #include "mpf_rtp_termination_factory.h" 45 | #include "mrcp_sofiasip_client_agent.h" 46 | #include "mrcp_unirtsp_client_agent.h" 47 | #include "mrcp_client_connection.h" 48 | 49 | typedef int (*app_exec_f)(struct ast_channel *chan, ast_app_data data); 50 | 51 | /* MRCP application. */ 52 | struct ast_mrcp_application_t { 53 | /* Application name. */ 54 | const char *name; 55 | /* Pointer to function which executes the application. */ 56 | app_exec_f exec; 57 | #if !AST_VERSION_AT_LEAST(1,6,2) 58 | /* Application synopsis. */ 59 | const char *synopsis; 60 | /* Application description. */ 61 | const char *description; 62 | #endif 63 | /* UniMRCP application. */ 64 | mrcp_application_t *app; 65 | /* MRCP callbacks from UniMRCP to this module's application. */ 66 | mrcp_app_message_dispatcher_t dispatcher; 67 | /* Audio callbacks from UniMRCP to this module's application. */ 68 | mpf_audio_stream_vtable_t audio_stream_vtable; 69 | }; 70 | typedef struct ast_mrcp_application_t ast_mrcp_application_t; 71 | 72 | /* MRCP globals configuration and variables. */ 73 | struct ast_mrcp_globals_t { 74 | /* The memory pool to use. */ 75 | apr_pool_t* pool; 76 | 77 | /* The max-connection-count configuration. */ 78 | char *unimrcp_max_connection_count; 79 | /* The max-shared-use-count configuration. */ 80 | char *unimrcp_max_shared_use_count; 81 | /* The offer-new-connection configuration. */ 82 | char *unimrcp_offer_new_connection; 83 | /* The rx-buffer-size configuration. */ 84 | char *unimrcp_rx_buffer_size; 85 | /* The tx-buffer-size configuration. */ 86 | char *unimrcp_tx_buffer_size; 87 | /* The reqest timeout configuration. */ 88 | char *unimrcp_request_timeout; 89 | /* The default text-to-speech profile to use. */ 90 | char *unimrcp_default_synth_profile; 91 | /* The default speech recognition profile to use. */ 92 | char *unimrcp_default_recog_profile; 93 | /* Log level to use for the UniMRCP library. */ 94 | char *unimrcp_log_level; 95 | /* The speech channel timeout configuration. */ 96 | apr_interval_time_t speech_channel_timeout; 97 | 98 | /* The MRCP client stack. */ 99 | mrcp_client_t *mrcp_client; 100 | 101 | /* The available applications. */ 102 | apr_hash_t *apps; 103 | 104 | /* Mutex to be used for speech channel numbering. */ 105 | apr_thread_mutex_t *mutex; 106 | /* Next available speech channel number. */ 107 | apr_uint32_t speech_channel_number; 108 | /* The available profiles. */ 109 | apr_hash_t *profiles; 110 | }; 111 | typedef struct ast_mrcp_globals_t ast_mrcp_globals_t; 112 | 113 | /* Profile-specific configuration. This allows us to handle differing MRCP 114 | * server behavior on a per-profile basis. 115 | */ 116 | struct ast_mrcp_profile_t { 117 | /* Name of the profile. */ 118 | char *name; 119 | /* MRCP version of the profile. */ 120 | char *version; 121 | /* MIME type to use for JSGF grammars. */ 122 | const char *jsgf_mime_type; 123 | /* MIME type to use for XML applications. */ 124 | const char *xml_mime_type; 125 | /* MIME type to use for GSL grammars. */ 126 | const char *gsl_mime_type; 127 | /* MIME type to use for SRGS XML grammars. */ 128 | const char *srgs_xml_mime_type; 129 | /* MIME type to use for SRGS ABNF grammars. */ 130 | const char *srgs_mime_type; 131 | /* MIME type to use for SSML (TTS) */ 132 | const char *ssml_mime_type; 133 | /* The profile configuration. */ 134 | apr_hash_t *cfg; 135 | }; 136 | typedef struct ast_mrcp_profile_t ast_mrcp_profile_t; 137 | 138 | extern ast_mrcp_globals_t globals; 139 | 140 | void globals_destroy(void); 141 | 142 | int globals_init(void); 143 | 144 | apr_uint32_t get_next_speech_channel_number(void); 145 | 146 | ast_mrcp_profile_t* get_synth_profile(const char *option_profile); 147 | 148 | ast_mrcp_profile_t* get_recog_profile(const char *option_profile); 149 | 150 | int profile_create(ast_mrcp_profile_t **profile, const char *name, const char *version, apr_pool_t *pool); 151 | 152 | mrcp_client_t *mod_unimrcp_client_create(apr_pool_t *mod_pool); 153 | 154 | int load_mrcp_config(const char *filename, const char *who_asked); 155 | 156 | #endif /* AST_UNIMRCP_FRAMEWORK_H */ 157 | -------------------------------------------------------------------------------- /app-unimrcp/audio_queue.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Asterisk -- An open source telephony toolkit. 3 | * 4 | * See http://www.asterisk.org for more information about 5 | * the Asterisk project. Please do not directly contact 6 | * any of the maintainers of this project for assistance; 7 | * the project provides a web site, mailing lists and IRC 8 | * channels for your use. 9 | * 10 | * This program is free software, distributed under the terms of 11 | * the GNU General Public License Version 2. See the LICENSE file 12 | * at the top of the source tree. 13 | * 14 | * Please follow coding guidelines 15 | * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES 16 | */ 17 | 18 | /* By Molo Afrika Speech Technologies (Pty) Ltd 19 | * See: http://www.molo.co.za 20 | * 21 | * Ideas, concepts and code borrowed from UniMRCP's example programs 22 | * and the FreeSWITCH mod_unimrcp ASR/TTS module. 23 | * 24 | * Authors of these are: 25 | * UniMRCP: 26 | * Arsen Chaloyan 27 | * FreeSWITCH: mod_unimrcp 28 | * Christopher M. Rienzo 29 | * 30 | * See: 31 | * http://www.unimrcp.org 32 | * http://www.freeswitch.org 33 | */ 34 | 35 | /* Asterisk includes. */ 36 | #include "ast_compat_defs.h" 37 | 38 | #include "audio_queue.h" 39 | #include "apt_pool.h" 40 | 41 | 42 | /* Default audio buffer size: 43 | * 44 | * 8000 samples/sec * 2 bytes/sample (16-bit) * 1 second = 16000 bytes 45 | * 16000 samples/sec * 2 bytes/sample (16-bit) * 1 second = 32000 bytes 46 | * 47 | * Make provision for 16kHz sample rates with 16-bit samples, 1 second audio. 48 | */ 49 | #define AUDIO_QUEUE_SIZE (16000 * 2) 50 | 51 | #define AUDIO_QUEUE_READ_TIMEOUT_USEC (30 * 1000000) 52 | 53 | /* --- AUDIO BUFFER --- */ 54 | 55 | static int audio_buffer_create(audio_buffer_t **buffer, apr_size_t max_len) 56 | { 57 | apr_pool_t *pool; 58 | audio_buffer_t *new_buffer; 59 | 60 | if (buffer == NULL) 61 | return -1; 62 | else 63 | *buffer = NULL; 64 | 65 | if ((pool = apt_pool_create()) == NULL) 66 | return -1; 67 | 68 | if ((max_len > 0) && ((new_buffer = apr_palloc(pool, sizeof(audio_buffer_t))) != NULL) && ((new_buffer->data = apr_palloc(pool, max_len)) != NULL)) { 69 | new_buffer->datalen = max_len; 70 | new_buffer->pool = pool; 71 | new_buffer->used = 0; 72 | *buffer = new_buffer; 73 | return 0; 74 | } 75 | 76 | apr_pool_destroy(pool); 77 | return -1; 78 | } 79 | 80 | static void audio_buffer_destroy(audio_buffer_t *buffer) 81 | { 82 | if (buffer != NULL && buffer->pool != NULL) { 83 | apr_pool_destroy(buffer->pool); 84 | } 85 | } 86 | 87 | static apr_size_t audio_buffer_inuse(audio_buffer_t *buffer) 88 | { 89 | if (buffer != NULL) 90 | return buffer->used; 91 | else 92 | return 0; 93 | } 94 | 95 | static apr_size_t audio_buffer_read(audio_buffer_t *buffer, void *data, apr_size_t datalen) 96 | { 97 | apr_size_t reading = 0; 98 | 99 | if ((buffer == NULL) || (buffer->data == NULL) || (data == NULL) || (datalen == 0)) 100 | return 0; 101 | 102 | if (buffer->used < 1) { 103 | buffer->used = 0; 104 | return 0; 105 | } else if (buffer->used >= datalen) 106 | reading = datalen; 107 | else 108 | reading = buffer->used; 109 | 110 | memcpy(data, buffer->data, reading); 111 | buffer->used = buffer->used - reading; 112 | memmove(buffer->data, buffer->data + reading, buffer->used); 113 | 114 | return reading; 115 | } 116 | 117 | static apr_size_t audio_buffer_write(audio_buffer_t *buffer, const void *data, apr_size_t datalen) 118 | { 119 | apr_size_t freespace; 120 | 121 | if ((buffer == NULL) || (buffer->data == NULL) || (data == NULL)) 122 | return 0; 123 | 124 | if (datalen == 0) 125 | return buffer->used; 126 | 127 | freespace = buffer->datalen - buffer->used; 128 | 129 | if (freespace < datalen) 130 | return 0; 131 | 132 | memcpy(buffer->data + buffer->used, data, datalen); 133 | buffer->used = buffer->used + datalen; 134 | 135 | return buffer->used; 136 | } 137 | 138 | static void audio_buffer_zero(audio_buffer_t *buffer) 139 | { 140 | if (buffer != NULL) 141 | buffer->used = 0; 142 | } 143 | 144 | /* --- AUDIO QUEUE --- */ 145 | 146 | /* Empty the queue. */ 147 | int audio_queue_clear(audio_queue_t *queue) 148 | { 149 | if (queue->mutex != NULL) 150 | apr_thread_mutex_lock(queue->mutex); 151 | 152 | if (queue->buffer != NULL) 153 | audio_buffer_zero(queue->buffer); 154 | 155 | if (queue->cond != NULL) 156 | apr_thread_cond_signal(queue->cond); 157 | 158 | if (queue->mutex != NULL) 159 | apr_thread_mutex_unlock(queue->mutex); 160 | 161 | return 0; 162 | } 163 | 164 | /* Destroy the audio queue. */ 165 | int audio_queue_destroy(audio_queue_t *queue) 166 | { 167 | if (queue != NULL) { 168 | char *name = queue->name; 169 | if ((name == NULL) || (strlen(name) == 0)) 170 | name = ""; 171 | 172 | if (queue->buffer != NULL) { 173 | audio_buffer_zero(queue->buffer); 174 | audio_buffer_destroy(queue->buffer); 175 | queue->buffer = NULL; 176 | } 177 | 178 | if (queue->cond != NULL) { 179 | if (apr_thread_cond_destroy(queue->cond) != APR_SUCCESS) 180 | ast_log(LOG_WARNING, "(%s) Unable to destroy audio queue condition variable\n", name); 181 | 182 | queue->cond = NULL; 183 | } 184 | 185 | if (queue->mutex != NULL) { 186 | if (apr_thread_mutex_destroy(queue->mutex) != APR_SUCCESS) 187 | ast_log(LOG_WARNING, "(%s) Unable to destroy audio queue mutex\n", name); 188 | 189 | queue->mutex = NULL; 190 | } 191 | 192 | queue->name = NULL; 193 | queue->read_bytes = 0; 194 | queue->waiting = 0; 195 | queue->write_bytes = 0; 196 | 197 | ast_log(LOG_DEBUG, "(%s) Audio queue destroyed\n", name); 198 | if (queue->pool != NULL) { 199 | apr_pool_destroy(queue->pool); 200 | } 201 | } 202 | 203 | return 0; 204 | } 205 | 206 | /* Create the audio queue. */ 207 | int audio_queue_create(audio_queue_t **audio_queue, const char *name) 208 | { 209 | int status = 0; 210 | audio_queue_t *laudio_queue = NULL; 211 | char *lname = ""; 212 | apr_pool_t *pool; 213 | 214 | if (audio_queue == NULL) 215 | return -1; 216 | else 217 | *audio_queue = NULL; 218 | 219 | if ((pool = apt_pool_create()) == NULL) 220 | return -1; 221 | 222 | if ((name == NULL) || (strlen(name) == 0)) 223 | lname = ""; 224 | else 225 | lname = apr_pstrdup(pool, name); 226 | if (lname == NULL) 227 | lname = ""; 228 | 229 | if ((laudio_queue = (audio_queue_t *)apr_palloc(pool, sizeof(audio_queue_t))) == NULL) { 230 | ast_log(LOG_ERROR, "(%s) Unable to create audio queue\n", lname); 231 | return -1; 232 | } else { 233 | laudio_queue->buffer = NULL; 234 | laudio_queue->cond = NULL; 235 | laudio_queue->mutex = NULL; 236 | laudio_queue->name = lname; 237 | laudio_queue->pool = pool; 238 | laudio_queue->read_bytes = 0; 239 | laudio_queue->waiting = 0; 240 | laudio_queue->write_bytes = 0; 241 | 242 | if (audio_buffer_create(&laudio_queue->buffer, AUDIO_QUEUE_SIZE) != 0) { 243 | ast_log(LOG_ERROR, "(%s) Unable to create audio queue buffer\n", laudio_queue->name); 244 | status = -1; 245 | } else if (apr_thread_mutex_create(&laudio_queue->mutex, APR_THREAD_MUTEX_UNNESTED, pool) != APR_SUCCESS) { 246 | ast_log(LOG_ERROR, "(%s) Unable to create audio queue mutex\n", laudio_queue->name); 247 | status = -1; 248 | } else if (apr_thread_cond_create(&laudio_queue->cond, pool) != APR_SUCCESS) { 249 | ast_log(LOG_ERROR, "(%s) Unable to create audio queue condition variable\n", laudio_queue->name); 250 | status = -1; 251 | } else { 252 | *audio_queue = laudio_queue; 253 | ast_log(LOG_DEBUG, "(%s) Audio queue created\n", laudio_queue->name); 254 | } 255 | } 256 | 257 | if (status != 0) 258 | audio_queue_destroy(laudio_queue); 259 | 260 | return status; 261 | } 262 | 263 | /* Read from the audio queue. */ 264 | apr_status_t audio_queue_read(audio_queue_t *queue, void *data, apr_size_t *data_len, int block) 265 | { 266 | apr_size_t requested; 267 | int status = 0; 268 | 269 | if ((queue == NULL) || (data == NULL) || (data_len == NULL)) 270 | return -1; 271 | else 272 | requested = *data_len; 273 | 274 | if (queue->mutex != NULL) 275 | apr_thread_mutex_lock(queue->mutex); 276 | 277 | /* Wait for data, if allowed. */ 278 | if (block != 0) { 279 | if (audio_buffer_inuse(queue->buffer) < requested) { 280 | queue->waiting = requested; 281 | 282 | if ((queue->mutex != NULL) && (queue->cond != NULL)) 283 | apr_thread_cond_timedwait(queue->cond, queue->mutex, AUDIO_QUEUE_READ_TIMEOUT_USEC); 284 | 285 | } 286 | 287 | queue->waiting = 0; 288 | } 289 | 290 | if (audio_buffer_inuse(queue->buffer) < requested) 291 | requested = audio_buffer_inuse(queue->buffer); 292 | 293 | if (requested == 0) { 294 | *data_len = 0; 295 | status = -1; 296 | } else { 297 | /* Read the data. */ 298 | *data_len = audio_buffer_read(queue->buffer, data, requested); 299 | queue->read_bytes = queue->read_bytes + *data_len; 300 | } 301 | 302 | if (queue->mutex != NULL) 303 | apr_thread_mutex_unlock(queue->mutex); 304 | 305 | return status; 306 | } 307 | 308 | /* Write to the audio queue. */ 309 | int audio_queue_write(audio_queue_t *queue, void *data, apr_size_t *data_len) 310 | { 311 | int status = 0; 312 | 313 | if ((queue == NULL) || (data == NULL) || (data_len == NULL)) 314 | return -1; 315 | 316 | if (queue->mutex != NULL) 317 | apr_thread_mutex_lock(queue->mutex); 318 | 319 | if (audio_buffer_write(queue->buffer, data, *data_len) > 0) { 320 | queue->write_bytes = queue->write_bytes + *data_len; 321 | 322 | if (queue->waiting <= audio_buffer_inuse(queue->buffer)) { 323 | if (queue->cond != NULL) 324 | apr_thread_cond_signal(queue->cond); 325 | } 326 | } else { 327 | ast_log(LOG_WARNING, "(%s) Audio queue overflow!\n", queue->name); 328 | *data_len = 0; 329 | status = -1; 330 | } 331 | 332 | if (queue->mutex != NULL) 333 | apr_thread_mutex_unlock(queue->mutex); 334 | 335 | return status; 336 | } 337 | -------------------------------------------------------------------------------- /app-unimrcp/audio_queue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Asterisk -- An open source telephony toolkit. 3 | * 4 | * See http://www.asterisk.org for more information about 5 | * the Asterisk project. Please do not directly contact 6 | * any of the maintainers of this project for assistance; 7 | * the project provides a web site, mailing lists and IRC 8 | * channels for your use. 9 | * 10 | * This program is free software, distributed under the terms of 11 | * the GNU General Public License Version 2. See the LICENSE file 12 | * at the top of the source tree. 13 | * 14 | * Please follow coding guidelines 15 | * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES 16 | */ 17 | 18 | #ifndef AUDIO_QUEUE_H 19 | #define AUDIO_QUEUE_H 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | struct audio_buffer_t { 26 | /* The memory pool. */ 27 | apr_pool_t *pool; 28 | /* The actual data. */ 29 | apr_byte_t *data; 30 | /* Number of bytes used in the buffer. */ 31 | apr_size_t used; 32 | /* Size of the buffer. */ 33 | apr_size_t datalen; 34 | }; 35 | typedef struct audio_buffer_t audio_buffer_t; 36 | 37 | struct audio_queue_t { 38 | /* The memory pool. */ 39 | apr_pool_t *pool; 40 | /* The buffer of audio data. */ 41 | audio_buffer_t *buffer; 42 | /* Synchronizes access to queue. */ 43 | apr_thread_mutex_t *mutex; 44 | /* Signaling for blocked readers/writers. */ 45 | apr_thread_cond_t *cond; 46 | /* Total bytes written. */ 47 | apr_size_t write_bytes; 48 | /* Total bytes read. */ 49 | apr_size_t read_bytes; 50 | /* Number of bytes reader is waiting for. */ 51 | apr_size_t waiting; 52 | /* Name of this queue (for logging). */ 53 | char *name; 54 | }; 55 | typedef struct audio_queue_t audio_queue_t; 56 | 57 | 58 | /* --- AUDIO QUEUE --- */ 59 | 60 | /* Empty the queue. */ 61 | int audio_queue_clear(audio_queue_t *queue); 62 | 63 | /* Destroy the audio queue. */ 64 | int audio_queue_destroy(audio_queue_t *queue); 65 | 66 | /* Create the audio queue. */ 67 | int audio_queue_create(audio_queue_t **audio_queue, const char *name); 68 | 69 | /* Read from the audio queue. */ 70 | apr_status_t audio_queue_read(audio_queue_t *queue, void *data, apr_size_t *data_len, int block); 71 | 72 | /* Write to the audio queue. */ 73 | int audio_queue_write(audio_queue_t *queue, void *data, apr_size_t *data_len); 74 | 75 | #endif /* AUDIO_QUEUE_H */ 76 | -------------------------------------------------------------------------------- /app-unimrcp/speech_channel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Asterisk -- An open source telephony toolkit. 3 | * 4 | * See http://www.asterisk.org for more information about 5 | * the Asterisk project. Please do not directly contact 6 | * any of the maintainers of this project for assistance; 7 | * the project provides a web site, mailing lists and IRC 8 | * channels for your use. 9 | * 10 | * This program is free software, distributed under the terms of 11 | * the GNU General Public License Version 2. See the LICENSE file 12 | * at the top of the source tree. 13 | * 14 | * Please follow coding guidelines 15 | * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES 16 | */ 17 | 18 | #ifndef SPEECH_CHANNEL_H 19 | #define SPEECH_CHANNEL_H 20 | 21 | /* 22 | * Set SPEECH_CHANNEL_DUMP to 1 to store input and output streams 23 | * in raw header-less files for further debugging. 24 | */ 25 | #define SPEECH_CHANNEL_DUMP 0 26 | 27 | /* 28 | * Specifies the output directory to store streams in, used if 29 | * SPEECH_CHANNEL_DUMP is enabled. 30 | */ 31 | #define SPEECH_CHANNEL_DUMP_DIR UNIMRCP_DIR_LOCATION"/var" 32 | 33 | /* 34 | * Set SPEECH_CHANNEL_TRACE to 1 to trace a statement per 35 | * channel read or write attempt. 36 | */ 37 | #define SPEECH_CHANNEL_TRACE 0 38 | 39 | /* Type of MRCP channel. */ 40 | enum speech_channel_type_t { 41 | SPEECH_CHANNEL_SYNTHESIZER, 42 | SPEECH_CHANNEL_RECOGNIZER 43 | }; 44 | typedef enum speech_channel_type_t speech_channel_type_t; 45 | 46 | /* Channel states. */ 47 | enum speech_channel_state_t { 48 | /* Closed. */ 49 | SPEECH_CHANNEL_CLOSED, 50 | /* Ready for speech request. */ 51 | SPEECH_CHANNEL_READY, 52 | /* Processing speech request. */ 53 | SPEECH_CHANNEL_PROCESSING, 54 | /* Error opening channel. */ 55 | SPEECH_CHANNEL_ERROR 56 | }; 57 | typedef enum speech_channel_state_t speech_channel_state_t; 58 | 59 | /* Channel operation status. */ 60 | enum speech_channel_status_t { 61 | /* Success. */ 62 | SPEECH_CHANNEL_STATUS_OK, 63 | /* Error. */ 64 | SPEECH_CHANNEL_STATUS_ERROR, 65 | /* Interrupted. */ 66 | SPEECH_CHANNEL_STATUS_INTERRUPTED 67 | }; 68 | typedef enum speech_channel_status_t speech_channel_status_t; 69 | 70 | /* An MRCP speech channel. */ 71 | struct speech_channel_t { 72 | /* The name of this channel (for logging). */ 73 | char *name; 74 | /* The profile used by this channel. */ 75 | ast_mrcp_profile_t *profile; 76 | /* Type of channel. */ 77 | speech_channel_type_t type; 78 | /* Application this channel is running. */ 79 | ast_mrcp_application_t *application; 80 | /* UniMRCP session. */ 81 | mrcp_session_t *unimrcp_session; 82 | /* UniMRCP channel. */ 83 | mrcp_channel_t *unimrcp_channel; 84 | /* UniMRCP stream object. */ 85 | mpf_audio_stream_t *stream; 86 | /* UniMRCP DTMF digit generator. */ 87 | mpf_dtmf_generator_t *dtmf_generator; 88 | /* MRCP session identifier. */ 89 | char *session_id; 90 | /* Memory pool. */ 91 | apr_pool_t *pool; 92 | /* Synchronizes channel state/ */ 93 | apr_thread_mutex_t *mutex; 94 | /* Wait on channel states. */ 95 | apr_thread_cond_t *cond; 96 | /* Channel state. */ 97 | speech_channel_state_t state; 98 | /* UniMRCP <--> Asterisk audio buffer. */ 99 | audio_queue_t *audio_queue; 100 | /* Speech format. */ 101 | ast_format_compat *format; 102 | /* Codec. */ 103 | const char *codec; 104 | /* Rate. */ 105 | apr_uint16_t rate; 106 | /* Bits per sample. */ 107 | apr_uint16_t bits_per_sample; 108 | /* Silence byte. */ 109 | apr_byte_t silence; 110 | /* App specific data. */ 111 | void *data; 112 | /* Asterisk channel. Needed to stop playback on barge-in. */ 113 | struct ast_channel *chan; 114 | /* File to store data streamed to Asterisk. */ 115 | FILE *rec_file; 116 | 117 | #if SPEECH_CHANNEL_DUMP 118 | FILE *stream_in; 119 | FILE *stream_out; 120 | #endif 121 | }; 122 | typedef struct speech_channel_t speech_channel_t; 123 | 124 | /* Type of the grammar. */ 125 | enum grammar_type_t { 126 | GRAMMAR_TYPE_UNKNOWN, 127 | /* text/uri-list. */ 128 | GRAMMAR_TYPE_URI, 129 | /* application/srgs. */ 130 | GRAMMAR_TYPE_SRGS, 131 | /* application/srgs+xml. */ 132 | GRAMMAR_TYPE_SRGS_XML, 133 | /* application/x-nuance-gsl. */ 134 | GRAMMAR_TYPE_NUANCE_GSL, 135 | /* application/x-jsgf. */ 136 | GRAMMAR_TYPE_JSGF, 137 | /* application/xml. */ 138 | GRAMMAR_TYPE_XML 139 | }; 140 | typedef enum grammar_type_t grammar_type_t; 141 | 142 | /* A grammar for recognition. */ 143 | struct grammar_t { 144 | /* Name of this grammar. */ 145 | char *name; 146 | /* Grammar MIME type. */ 147 | grammar_type_t type; 148 | /* The grammar or its URI, depending on type. */ 149 | char *data; 150 | }; 151 | typedef struct grammar_t grammar_t; 152 | 153 | /* Data specific to the recognizer. */ 154 | struct recognizer_data_t { 155 | /* The available grammars. */ 156 | apr_hash_t *grammars; 157 | /* Recognition result. */ 158 | const char *result; 159 | /* Completion cause. */ 160 | int completion_cause; 161 | /* Waveform URI [optional]. */ 162 | const char *waveform_uri; 163 | /* True, if voice has started. */ 164 | int start_of_input; 165 | /* True, if input timers have started. */ 166 | int timers_started; 167 | }; 168 | typedef struct recognizer_data_t recognizer_data_t; 169 | 170 | /* Use this function to set the current channel state without locking the 171 | * speech channel. Do this if you already have the speech channel locked. 172 | */ 173 | void speech_channel_set_state_unlocked(speech_channel_t *schannel, speech_channel_state_t state); 174 | 175 | /* Set the current channel state. */ 176 | void speech_channel_set_state(speech_channel_t *schannel, speech_channel_state_t state); 177 | 178 | /* Send BARGE-IN-OCCURRED. */ 179 | int speech_channel_bargeinoccurred(speech_channel_t *schannel); 180 | 181 | /* Create a new speech channel. */ 182 | speech_channel_t *speech_channel_create( 183 | apr_pool_t *pool, 184 | const char *name, 185 | speech_channel_type_t type, 186 | ast_mrcp_application_t *app, 187 | ast_format_compat *format, 188 | const char *rec_file_path, 189 | struct ast_channel *chan); 190 | 191 | /* Destroy the speech channel. */ 192 | int speech_channel_destroy(speech_channel_t *schannel); 193 | 194 | /* Open the speech channel. */ 195 | int speech_channel_open(speech_channel_t *schannel, ast_mrcp_profile_t *profile); 196 | 197 | /* Stop SPEAK/RECOGNIZE request on speech channel. */ 198 | int speech_channel_stop(speech_channel_t *schannel); 199 | 200 | /* Set parameters in an MRCP header. */ 201 | int speech_channel_set_params(speech_channel_t *schannel, mrcp_message_t *msg, apr_hash_t *header_fields); 202 | 203 | /* Read synthesized speech / speech to be recognized. */ 204 | int speech_channel_read(speech_channel_t *schannel, void *data, apr_size_t *len, int block); 205 | 206 | /* Write synthesized speech / speech to be recognized. */ 207 | int speech_channel_write(speech_channel_t *schannel, void *data, apr_size_t *len); 208 | 209 | /* Write synthesized speech to Asterisk. */ 210 | int speech_channel_ast_write(speech_channel_t *schannel, void *data, apr_size_t len); 211 | 212 | /* Convert channel status to string. */ 213 | const char *speech_channel_status_to_string(speech_channel_status_t status); 214 | 215 | /* 216 | * Determine synthesis content type by specified text. 217 | * @param schannel the speech channel to use 218 | * @param text the input text 219 | * @param content the output content 220 | * @param content_type the output content type 221 | */ 222 | int determine_synth_content_type(speech_channel_t *schannel, const char *text, const char **content, const char **content_type); 223 | 224 | /* 225 | * Determine grammar type by specified grammar data. 226 | * @param schannel the speech channel to use 227 | * @param grammar_data the input grammar data 228 | * @param grammar_content the output grammar content 229 | * @param grammar_type the output grammar type 230 | */ 231 | int determine_grammar_type(speech_channel_t *schannel, const char *grammar_data, const char **grammar_content, grammar_type_t *grammar_type); 232 | 233 | /* 234 | * Determine prompt type by specified text. 235 | * @param text the input text 236 | * @param content the output content 237 | * @param is_audio_file the flag to specify an audio file prompt 238 | */ 239 | int determine_prompt_type(const char *text, const char **content, int *is_audio_file); 240 | 241 | /* Playback the specified sound file. */ 242 | struct ast_filestream* astchan_stream_file(struct ast_channel *chan, const char *filename, off_t *filelength_out); 243 | 244 | /* Create a grammar object to reference in recognition requests. */ 245 | int grammar_create(grammar_t **grammar, const char *name, grammar_type_t type, const char *data, apr_pool_t *pool); 246 | 247 | /* Get the MIME type for this grammar type. */ 248 | const char *grammar_type_to_mime(grammar_type_t type, const ast_mrcp_profile_t *profile); 249 | 250 | /* Trim any leading and trailing whitespaces and unquote the input string. */ 251 | char *normalize_input_string(char *str); 252 | 253 | #endif /* SPEECH_CHANNEL_H */ 254 | -------------------------------------------------------------------------------- /bootstrap: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | case `uname` in 4 | Darwin) libtoolize=glibtoolize ;; 5 | *) libtoolize=libtoolize ;; 6 | esac 7 | 8 | set -x 9 | $libtoolize --force --automake --copy 10 | aclocal -I build/acmacros 11 | automake --foreign --add-missing --copy 12 | autoconf 13 | 14 | rm -rf autom4te.cache 15 | -------------------------------------------------------------------------------- /build/acmacros/apr_common.m4: -------------------------------------------------------------------------------- 1 | dnl -------------------------------------------------------- -*- autoconf -*- 2 | dnl Licensed to the Apache Software Foundation (ASF) under one or more 3 | dnl contributor license agreements. See the NOTICE file distributed with 4 | dnl this work for additional information regarding copyright ownership. 5 | dnl The ASF licenses this file to You under the Apache License, Version 2.0 6 | dnl (the "License"); you may not use this file except in compliance with 7 | dnl the License. You may obtain a copy of the License at 8 | dnl 9 | dnl http://www.apache.org/licenses/LICENSE-2.0 10 | dnl 11 | dnl Unless required by applicable law or agreed to in writing, software 12 | dnl distributed under the License is distributed on an "AS IS" BASIS, 13 | dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | dnl See the License for the specific language governing permissions and 15 | dnl limitations under the License. 16 | 17 | dnl 18 | dnl apr_common.m4: APR's general-purpose autoconf macros 19 | dnl 20 | 21 | dnl 22 | dnl APR_CONFIG_NICE(filename) 23 | dnl 24 | dnl Saves a snapshot of the configure command-line for later reuse 25 | dnl 26 | AC_DEFUN([APR_CONFIG_NICE], [ 27 | rm -f $1 28 | cat >$1<> $1 36 | fi 37 | if test -n "$CFLAGS"; then 38 | echo "CFLAGS=\"$CFLAGS\"; export CFLAGS" >> $1 39 | fi 40 | if test -n "$CPPFLAGS"; then 41 | echo "CPPFLAGS=\"$CPPFLAGS\"; export CPPFLAGS" >> $1 42 | fi 43 | if test -n "$LDFLAGS"; then 44 | echo "LDFLAGS=\"$LDFLAGS\"; export LDFLAGS" >> $1 45 | fi 46 | if test -n "$LTFLAGS"; then 47 | echo "LTFLAGS=\"$LTFLAGS\"; export LTFLAGS" >> $1 48 | fi 49 | if test -n "$LIBS"; then 50 | echo "LIBS=\"$LIBS\"; export LIBS" >> $1 51 | fi 52 | if test -n "$INCLUDES"; then 53 | echo "INCLUDES=\"$INCLUDES\"; export INCLUDES" >> $1 54 | fi 55 | if test -n "$NOTEST_CFLAGS"; then 56 | echo "NOTEST_CFLAGS=\"$NOTEST_CFLAGS\"; export NOTEST_CFLAGS" >> $1 57 | fi 58 | if test -n "$NOTEST_CPPFLAGS"; then 59 | echo "NOTEST_CPPFLAGS=\"$NOTEST_CPPFLAGS\"; export NOTEST_CPPFLAGS" >> $1 60 | fi 61 | if test -n "$NOTEST_LDFLAGS"; then 62 | echo "NOTEST_LDFLAGS=\"$NOTEST_LDFLAGS\"; export NOTEST_LDFLAGS" >> $1 63 | fi 64 | if test -n "$NOTEST_LIBS"; then 65 | echo "NOTEST_LIBS=\"$NOTEST_LIBS\"; export NOTEST_LIBS" >> $1 66 | fi 67 | 68 | # Retrieve command-line arguments. 69 | eval "set x $[0] $ac_configure_args" 70 | shift 71 | 72 | for arg 73 | do 74 | APR_EXPAND_VAR(arg, $arg) 75 | echo "\"[$]arg\" \\" >> $1 76 | done 77 | echo '"[$]@"' >> $1 78 | chmod +x $1 79 | ])dnl 80 | 81 | dnl APR_MKDIR_P_CHECK(fallback-mkdir-p) 82 | dnl checks whether mkdir -p works 83 | AC_DEFUN([APR_MKDIR_P_CHECK], [ 84 | AC_CACHE_CHECK(for working mkdir -p, ac_cv_mkdir_p,[ 85 | test -d conftestdir && rm -rf conftestdir 86 | mkdir -p conftestdir/somedir >/dev/null 2>&1 87 | if test -d conftestdir/somedir; then 88 | ac_cv_mkdir_p=yes 89 | else 90 | ac_cv_mkdir_p=no 91 | fi 92 | rm -rf conftestdir 93 | ]) 94 | if test "$ac_cv_mkdir_p" = "yes"; then 95 | mkdir_p="mkdir -p" 96 | else 97 | mkdir_p="$1" 98 | fi 99 | ]) 100 | 101 | dnl 102 | dnl APR_SUBDIR_CONFIG(dir [, sub-package-cmdline-args, args-to-drop]) 103 | dnl 104 | dnl dir: directory to find configure in 105 | dnl sub-package-cmdline-args: arguments to add to the invocation (optional) 106 | dnl args-to-drop: arguments to drop from the invocation (optional) 107 | dnl 108 | dnl Note: This macro relies on ac_configure_args being set properly. 109 | dnl 110 | dnl The args-to-drop argument is shoved into a case statement, so 111 | dnl multiple arguments can be separated with a |. 112 | dnl 113 | dnl Note: Older versions of autoconf do not single-quote args, while 2.54+ 114 | dnl places quotes around every argument. So, if you want to drop the 115 | dnl argument called --enable-layout, you must pass the third argument as: 116 | dnl [--enable-layout=*|\'--enable-layout=*] 117 | dnl 118 | dnl Trying to optimize this is left as an exercise to the reader who wants 119 | dnl to put up with more autoconf craziness. I give up. 120 | dnl 121 | AC_DEFUN([APR_SUBDIR_CONFIG], [ 122 | # save our work to this point; this allows the sub-package to use it 123 | AC_CACHE_SAVE 124 | 125 | echo "configuring package in $1 now" 126 | ac_popdir=`pwd` 127 | apr_config_subdirs="$1" 128 | test -d $1 || $mkdir_p $1 129 | ac_abs_srcdir=`(cd $srcdir/$1 && pwd)` 130 | cd $1 131 | 132 | changequote(, )dnl 133 | # A "../" for each directory in /$config_subdirs. 134 | ac_dots=`echo $apr_config_subdirs|sed -e 's%^\./%%' -e 's%[^/]$%&/%' -e 's%[^/]*/%../%g'` 135 | changequote([, ])dnl 136 | 137 | # Make the cache file pathname absolute for the subdirs 138 | # required to correctly handle subdirs that might actually 139 | # be symlinks 140 | case "$cache_file" in 141 | /*) # already absolute 142 | ac_sub_cache_file=$cache_file ;; 143 | *) # Was relative path. 144 | ac_sub_cache_file="$ac_popdir/$cache_file" ;; 145 | esac 146 | 147 | ifelse($3, [], [apr_configure_args=$ac_configure_args],[ 148 | apr_configure_args= 149 | apr_sep= 150 | for apr_configure_arg in $ac_configure_args 151 | do 152 | case "$apr_configure_arg" in 153 | $3) 154 | continue ;; 155 | esac 156 | apr_configure_args="$apr_configure_args$apr_sep'$apr_configure_arg'" 157 | apr_sep=" " 158 | done 159 | ]) 160 | 161 | dnl autoconf doesn't add --silent to ac_configure_args; explicitly pass it 162 | test "x$silent" = "xyes" && apr_configure_args="$apr_configure_args --silent" 163 | 164 | dnl AC_CONFIG_SUBDIRS silences option warnings, emulate this for 2.62 165 | apr_configure_args="--disable-option-checking $apr_configure_args" 166 | 167 | dnl The eval makes quoting arguments work - specifically the second argument 168 | dnl where the quoting mechanisms used is "" rather than []. 169 | dnl 170 | dnl We need to execute another shell because some autoconf/shell combinations 171 | dnl will choke after doing repeated APR_SUBDIR_CONFIG()s. (Namely Solaris 172 | dnl and autoconf-2.54+) 173 | if eval $SHELL $ac_abs_srcdir/configure $apr_configure_args --cache-file=$ac_sub_cache_file --srcdir=$ac_abs_srcdir $2 174 | then : 175 | echo "$1 configured properly" 176 | else 177 | echo "configure failed for $1" 178 | exit 1 179 | fi 180 | 181 | cd $ac_popdir 182 | 183 | # grab any updates from the sub-package 184 | AC_CACHE_LOAD 185 | ])dnl 186 | 187 | dnl 188 | dnl APR_SAVE_THE_ENVIRONMENT(variable_name) 189 | dnl 190 | dnl Stores the variable (usually a Makefile macro) for later restoration 191 | dnl 192 | AC_DEFUN([APR_SAVE_THE_ENVIRONMENT], [ 193 | apr_ste_save_$1="$$1" 194 | ])dnl 195 | 196 | dnl 197 | dnl APR_RESTORE_THE_ENVIRONMENT(variable_name, prefix_) 198 | dnl 199 | dnl Uses the previously saved variable content to figure out what configure 200 | dnl has added to the variable, moving the new bits to prefix_variable_name 201 | dnl and restoring the original variable contents. This makes it possible 202 | dnl for a user to override configure when it does something stupid. 203 | dnl 204 | AC_DEFUN([APR_RESTORE_THE_ENVIRONMENT], [ 205 | dnl Check whether $apr_ste_save_$1 is empty or 206 | dnl only whitespace. The verbatim "X" is token number 1, 207 | dnl the following whitespace will be ignored. 208 | set X $apr_ste_save_$1 209 | if test ${#} -eq 1; then 210 | $2$1="$$1" 211 | $1= 212 | else 213 | if test "x$apr_ste_save_$1" = "x$$1"; then 214 | $2$1= 215 | else 216 | $2$1=`echo "$$1" | sed -e "s%${apr_ste_save_$1}%%"` 217 | $1="$apr_ste_save_$1" 218 | fi 219 | fi 220 | if test "x$silent" != "xyes"; then 221 | echo " restoring $1 to \"$$1\"" 222 | echo " setting $2$1 to \"$$2$1\"" 223 | fi 224 | AC_SUBST($2$1) 225 | ])dnl 226 | 227 | dnl 228 | dnl APR_SETIFNULL(variable, value) 229 | dnl 230 | dnl Set variable iff it's currently null 231 | dnl 232 | AC_DEFUN([APR_SETIFNULL], [ 233 | if test -z "$$1"; then 234 | test "x$silent" != "xyes" && echo " setting $1 to \"$2\"" 235 | $1="$2" 236 | fi 237 | ])dnl 238 | 239 | dnl 240 | dnl APR_SETVAR(variable, value) 241 | dnl 242 | dnl Set variable no matter what 243 | dnl 244 | AC_DEFUN([APR_SETVAR], [ 245 | test "x$silent" != "xyes" && echo " forcing $1 to \"$2\"" 246 | $1="$2" 247 | ])dnl 248 | 249 | dnl 250 | dnl APR_ADDTO(variable, value) 251 | dnl 252 | dnl Add value to variable 253 | dnl 254 | AC_DEFUN([APR_ADDTO], [ 255 | if test "x$$1" = "x"; then 256 | test "x$silent" != "xyes" && echo " setting $1 to \"$2\"" 257 | $1="$2" 258 | else 259 | apr_addto_bugger="$2" 260 | for i in $apr_addto_bugger; do 261 | apr_addto_duplicate="0" 262 | for j in $$1; do 263 | if test "x$i" = "x$j"; then 264 | apr_addto_duplicate="1" 265 | break 266 | fi 267 | done 268 | if test $apr_addto_duplicate = "0"; then 269 | test "x$silent" != "xyes" && echo " adding \"$i\" to $1" 270 | $1="$$1 $i" 271 | fi 272 | done 273 | fi 274 | ])dnl 275 | 276 | dnl 277 | dnl APR_REMOVEFROM(variable, value) 278 | dnl 279 | dnl Remove a value from a variable 280 | dnl 281 | AC_DEFUN([APR_REMOVEFROM], [ 282 | if test "x$$1" = "x$2"; then 283 | test "x$silent" != "xyes" && echo " nulling $1" 284 | $1="" 285 | else 286 | apr_new_bugger="" 287 | apr_removed=0 288 | for i in $$1; do 289 | if test "x$i" != "x$2"; then 290 | apr_new_bugger="$apr_new_bugger $i" 291 | else 292 | apr_removed=1 293 | fi 294 | done 295 | if test $apr_removed = "1"; then 296 | test "x$silent" != "xyes" && echo " removed \"$2\" from $1" 297 | $1=$apr_new_bugger 298 | fi 299 | fi 300 | ]) dnl 301 | 302 | dnl 303 | dnl APR_CHECK_DEFINE_FILES( symbol, header_file [header_file ...] ) 304 | dnl 305 | AC_DEFUN([APR_CHECK_DEFINE_FILES], [ 306 | AC_CACHE_CHECK([for $1 in $2],ac_cv_define_$1,[ 307 | ac_cv_define_$1=no 308 | for curhdr in $2 309 | do 310 | AC_EGREP_CPP(YES_IS_DEFINED, [ 311 | #include <$curhdr> 312 | #ifdef $1 313 | YES_IS_DEFINED 314 | #endif 315 | ], ac_cv_define_$1=yes) 316 | done 317 | ]) 318 | if test "$ac_cv_define_$1" = "yes"; then 319 | AC_DEFINE(HAVE_$1, 1, [Define if $1 is defined]) 320 | fi 321 | ]) 322 | 323 | 324 | dnl 325 | dnl APR_CHECK_DEFINE(symbol, header_file) 326 | dnl 327 | AC_DEFUN([APR_CHECK_DEFINE], [ 328 | AC_CACHE_CHECK([for $1 in $2],ac_cv_define_$1,[ 329 | AC_EGREP_CPP(YES_IS_DEFINED, [ 330 | #include <$2> 331 | #ifdef $1 332 | YES_IS_DEFINED 333 | #endif 334 | ], ac_cv_define_$1=yes, ac_cv_define_$1=no) 335 | ]) 336 | if test "$ac_cv_define_$1" = "yes"; then 337 | AC_DEFINE(HAVE_$1, 1, [Define if $1 is defined in $2]) 338 | fi 339 | ]) 340 | 341 | dnl 342 | dnl APR_CHECK_APR_DEFINE( symbol ) 343 | dnl 344 | AC_DEFUN([APR_CHECK_APR_DEFINE], [ 345 | apr_old_cppflags=$CPPFLAGS 346 | CPPFLAGS="$CPPFLAGS $INCLUDES" 347 | AC_EGREP_CPP(YES_IS_DEFINED, [ 348 | #include 349 | #if $1 350 | YES_IS_DEFINED 351 | #endif 352 | ], ac_cv_define_$1=yes, ac_cv_define_$1=no) 353 | CPPFLAGS=$apr_old_cppflags 354 | ]) 355 | 356 | dnl APR_CHECK_FILE(filename); set ac_cv_file_filename to 357 | dnl "yes" if 'filename' is readable, else "no". 358 | dnl @deprecated! - use AC_CHECK_FILE instead 359 | AC_DEFUN([APR_CHECK_FILE], [ 360 | dnl Pick a safe variable name 361 | define([apr_cvname], ac_cv_file_[]translit([$1], [./+-], [__p_])) 362 | AC_CACHE_CHECK([for $1], [apr_cvname], 363 | [if test -r $1; then 364 | apr_cvname=yes 365 | else 366 | apr_cvname=no 367 | fi]) 368 | ]) 369 | 370 | define(APR_IFALLYES,[dnl 371 | ac_rc=yes 372 | for ac_spec in $1; do 373 | ac_type=`echo "$ac_spec" | sed -e 's/:.*$//'` 374 | ac_item=`echo "$ac_spec" | sed -e 's/^.*://'` 375 | case $ac_type in 376 | header ) 377 | ac_item=`echo "$ac_item" | sed 'y%./+-%__p_%'` 378 | ac_var="ac_cv_header_$ac_item" 379 | ;; 380 | file ) 381 | ac_item=`echo "$ac_item" | sed 'y%./+-%__p_%'` 382 | ac_var="ac_cv_file_$ac_item" 383 | ;; 384 | func ) ac_var="ac_cv_func_$ac_item" ;; 385 | struct ) ac_var="ac_cv_struct_$ac_item" ;; 386 | define ) ac_var="ac_cv_define_$ac_item" ;; 387 | custom ) ac_var="$ac_item" ;; 388 | esac 389 | eval "ac_val=\$$ac_var" 390 | if test ".$ac_val" != .yes; then 391 | ac_rc=no 392 | break 393 | fi 394 | done 395 | if test ".$ac_rc" = .yes; then 396 | : 397 | $2 398 | else 399 | : 400 | $3 401 | fi 402 | ]) 403 | 404 | 405 | define(APR_BEGIN_DECISION,[dnl 406 | ac_decision_item='$1' 407 | ac_decision_msg='FAILED' 408 | ac_decision='' 409 | ]) 410 | 411 | 412 | AC_DEFUN([APR_DECIDE],[dnl 413 | dnl Define the flag (or not) in apr_private.h via autoheader 414 | AH_TEMPLATE($1, [Define if $2 will be used]) 415 | ac_decision='$1' 416 | ac_decision_msg='$2' 417 | ac_decision_$1=yes 418 | ac_decision_$1_msg='$2' 419 | ]) 420 | 421 | 422 | define(APR_DECISION_OVERRIDE,[dnl 423 | ac_decision='' 424 | for ac_item in $1; do 425 | eval "ac_decision_this=\$ac_decision_${ac_item}" 426 | if test ".$ac_decision_this" = .yes; then 427 | ac_decision=$ac_item 428 | eval "ac_decision_msg=\$ac_decision_${ac_item}_msg" 429 | fi 430 | done 431 | ]) 432 | 433 | 434 | define(APR_DECISION_FORCE,[dnl 435 | ac_decision="$1" 436 | eval "ac_decision_msg=\"\$ac_decision_${ac_decision}_msg\"" 437 | ]) 438 | 439 | 440 | define(APR_END_DECISION,[dnl 441 | if test ".$ac_decision" = .; then 442 | echo "[$]0:Error: decision on $ac_decision_item failed" 1>&2 443 | exit 1 444 | else 445 | if test ".$ac_decision_msg" = .; then 446 | ac_decision_msg="$ac_decision" 447 | fi 448 | AC_DEFINE_UNQUOTED(${ac_decision_item}) 449 | AC_MSG_RESULT([decision on $ac_decision_item... $ac_decision_msg]) 450 | fi 451 | ]) 452 | 453 | 454 | dnl 455 | dnl APR_CHECK_SIZEOF_EXTENDED(INCLUDES, TYPE [, CROSS_SIZE]) 456 | dnl 457 | dnl A variant of AC_CHECK_SIZEOF which allows the checking of 458 | dnl sizes of non-builtin types 459 | dnl 460 | AC_DEFUN([APR_CHECK_SIZEOF_EXTENDED], 461 | [changequote(<<, >>)dnl 462 | dnl The name to #define. 463 | define(<>, translit(sizeof_$2, [a-z *], [A-Z_P]))dnl 464 | dnl The cache variable name. 465 | define(<>, translit(ac_cv_sizeof_$2, [ *], [_p]))dnl 466 | changequote([, ])dnl 467 | AC_MSG_CHECKING(size of $2) 468 | AC_CACHE_VAL(AC_CV_NAME, 469 | [AC_TRY_RUN([#include 470 | $1 471 | #ifdef WIN32 472 | #define binmode "b" 473 | #else 474 | #define binmode 475 | #endif 476 | main() 477 | { 478 | FILE *f=fopen("conftestval", "w" binmode); 479 | if (!f) exit(1); 480 | fprintf(f, "%d\n", sizeof($2)); 481 | exit(0); 482 | }], AC_CV_NAME=`cat conftestval`, AC_CV_NAME=0, ifelse([$3],,, 483 | AC_CV_NAME=$3))])dnl 484 | AC_MSG_RESULT($AC_CV_NAME) 485 | AC_DEFINE_UNQUOTED(AC_TYPE_NAME, $AC_CV_NAME, [The size of ]$2) 486 | undefine([AC_TYPE_NAME])dnl 487 | undefine([AC_CV_NAME])dnl 488 | ]) 489 | 490 | 491 | dnl 492 | dnl APR_TRY_COMPILE_NO_WARNING(INCLUDES, FUNCTION-BODY, 493 | dnl [ACTIONS-IF-NO-WARNINGS], [ACTIONS-IF-WARNINGS]) 494 | dnl 495 | dnl Tries a compile test with warnings activated so that the result 496 | dnl is false if the code doesn't compile cleanly. For compilers 497 | dnl where it is not known how to activate a "fail-on-error" mode, 498 | dnl it is undefined which of the sets of actions will be run. 499 | dnl 500 | AC_DEFUN([APR_TRY_COMPILE_NO_WARNING], 501 | [apr_save_CFLAGS=$CFLAGS 502 | CFLAGS="$CFLAGS $CFLAGS_WARN" 503 | if test "$ac_cv_prog_gcc" = "yes"; then 504 | CFLAGS="$CFLAGS -Werror" 505 | fi 506 | AC_COMPILE_IFELSE( 507 | [AC_LANG_SOURCE( 508 | [#include "confdefs.h" 509 | ] 510 | [[$1]] 511 | [int main(int argc, const char *const *argv) {] 512 | [[$2]] 513 | [ return 0; }] 514 | )], 515 | [$3], [$4]) 516 | CFLAGS=$apr_save_CFLAGS 517 | ]) 518 | 519 | dnl 520 | dnl APR_CHECK_STRERROR_R_RC 521 | dnl 522 | dnl Decide which style of retcode is used by this system's 523 | dnl strerror_r(). It either returns int (0 for success, -1 524 | dnl for failure), or it returns a pointer to the error 525 | dnl string. 526 | dnl 527 | dnl 528 | AC_DEFUN([APR_CHECK_STRERROR_R_RC], [ 529 | AC_MSG_CHECKING(for type of return code from strerror_r) 530 | AC_TRY_RUN([ 531 | #include 532 | #include 533 | #include 534 | main() 535 | { 536 | char buf[1024]; 537 | if (strerror_r(ERANGE, buf, sizeof buf) < 1) { 538 | exit(0); 539 | } 540 | else { 541 | exit(1); 542 | } 543 | }], [ 544 | ac_cv_strerror_r_rc_int=yes ], [ 545 | ac_cv_strerror_r_rc_int=no ], [ 546 | ac_cv_strerror_r_rc_int=no ] ) 547 | if test "x$ac_cv_strerror_r_rc_int" = xyes; then 548 | AC_DEFINE(STRERROR_R_RC_INT, 1, [Define if strerror returns int]) 549 | msg="int" 550 | else 551 | msg="pointer" 552 | fi 553 | AC_MSG_RESULT([$msg]) 554 | ] ) 555 | 556 | dnl 557 | dnl APR_CHECK_DIRENT_INODE 558 | dnl 559 | dnl Decide if d_fileno or d_ino are available in the dirent 560 | dnl structure on this platform. Single UNIX Spec says d_ino, 561 | dnl BSD uses d_fileno. Undef to find the real beast. 562 | dnl 563 | AC_DEFUN([APR_CHECK_DIRENT_INODE], [ 564 | AC_CACHE_CHECK([for inode member of struct dirent], apr_cv_dirent_inode, [ 565 | apr_cv_dirent_inode=no 566 | AC_TRY_COMPILE([ 567 | #include 568 | #include 569 | ],[ 570 | #ifdef d_ino 571 | #undef d_ino 572 | #endif 573 | struct dirent de; de.d_fileno; 574 | ], apr_cv_dirent_inode=d_fileno) 575 | if test "$apr_cv_dirent_inode" = "no"; then 576 | AC_TRY_COMPILE([ 577 | #include 578 | #include 579 | ],[ 580 | #ifdef d_fileno 581 | #undef d_fileno 582 | #endif 583 | struct dirent de; de.d_ino; 584 | ], apr_cv_dirent_inode=d_ino) 585 | fi 586 | ]) 587 | if test "$apr_cv_dirent_inode" != "no"; then 588 | AC_DEFINE_UNQUOTED(DIRENT_INODE, $apr_cv_dirent_inode, 589 | [Define if struct dirent has an inode member]) 590 | fi 591 | ]) 592 | 593 | dnl 594 | dnl APR_CHECK_DIRENT_TYPE 595 | dnl 596 | dnl Decide if d_type is available in the dirent structure 597 | dnl on this platform. Not part of the Single UNIX Spec. 598 | dnl Note that this is worthless without DT_xxx macros, so 599 | dnl look for one while we are at it. 600 | dnl 601 | AC_DEFUN([APR_CHECK_DIRENT_TYPE], [ 602 | AC_CACHE_CHECK([for file type member of struct dirent], apr_cv_dirent_type,[ 603 | apr_cv_dirent_type=no 604 | AC_TRY_COMPILE([ 605 | #include 606 | #include 607 | ],[ 608 | struct dirent de; de.d_type = DT_REG; 609 | ], apr_cv_dirent_type=d_type) 610 | ]) 611 | if test "$apr_cv_dirent_type" != "no"; then 612 | AC_DEFINE_UNQUOTED(DIRENT_TYPE, $apr_cv_dirent_type, 613 | [Define if struct dirent has a d_type member]) 614 | fi 615 | ]) 616 | 617 | dnl the following is a newline, a space, a tab, and a backslash (the 618 | dnl backslash is used by the shell to skip newlines, but m4 sees it; 619 | dnl treat it like whitespace). 620 | dnl WARNING: don't reindent these lines, or the space/tab will be lost! 621 | define([apr_whitespace],[ 622 | \]) 623 | 624 | dnl 625 | dnl APR_COMMA_ARGS(ARG1 ...) 626 | dnl convert the whitespace-separated arguments into comman-separated 627 | dnl arguments. 628 | dnl 629 | dnl APR_FOREACH(CODE-BLOCK, ARG1, ARG2, ...) 630 | dnl subsitute CODE-BLOCK for each ARG[i]. "eachval" will be set to ARG[i] 631 | dnl within each iteration. 632 | dnl 633 | changequote({,}) 634 | define({APR_COMMA_ARGS},{patsubst([$}{1],[[}apr_whitespace{]+],[,])}) 635 | define({APR_FOREACH}, 636 | {ifelse($}{2,,, 637 | [define([eachval], 638 | $}{2)$}{1[]APR_FOREACH([$}{1], 639 | builtin([shift], 640 | builtin([shift], $}{@)))])}) 641 | changequote([,]) 642 | 643 | dnl APR_FLAG_HEADERS(HEADER-FILE ... [, FLAG-TO-SET ] [, "yes" ]) 644 | dnl we set FLAG-TO-SET to 1 if we find HEADER-FILE, otherwise we set to 0 645 | dnl if FLAG-TO-SET is null, we automagically determine it's name 646 | dnl by changing all "/" to "_" in the HEADER-FILE and dropping 647 | dnl all "." and "-" chars. If the 3rd parameter is "yes" then instead of 648 | dnl setting to 1 or 0, we set FLAG-TO-SET to yes or no. 649 | dnl 650 | AC_DEFUN([APR_FLAG_HEADERS], [ 651 | AC_CHECK_HEADERS($1) 652 | for aprt_i in $1 653 | do 654 | ac_safe=`echo "$aprt_i" | sed 'y%./+-%__p_%'` 655 | aprt_2=`echo "$aprt_i" | sed -e 's%/%_%g' -e 's/\.//g' -e 's/-//g'` 656 | if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then 657 | eval "ifelse($2,,$aprt_2,$2)=ifelse($3,yes,yes,1)" 658 | else 659 | eval "ifelse($2,,$aprt_2,$2)=ifelse($3,yes,no,0)" 660 | fi 661 | done 662 | ]) 663 | 664 | dnl APR_FLAG_FUNCS(FUNC ... [, FLAG-TO-SET] [, "yes" ]) 665 | dnl if FLAG-TO-SET is null, we automagically determine it's name 666 | dnl prepending "have_" to the function name in FUNC, otherwise 667 | dnl we use what's provided as FLAG-TO-SET. If the 3rd parameter 668 | dnl is "yes" then instead of setting to 1 or 0, we set FLAG-TO-SET 669 | dnl to yes or no. 670 | dnl 671 | AC_DEFUN([APR_FLAG_FUNCS], [ 672 | AC_CHECK_FUNCS($1) 673 | for aprt_j in $1 674 | do 675 | aprt_3="have_$aprt_j" 676 | if eval "test \"`echo '$ac_cv_func_'$aprt_j`\" = yes"; then 677 | eval "ifelse($2,,$aprt_3,$2)=ifelse($3,yes,yes,1)" 678 | else 679 | eval "ifelse($2,,$aprt_3,$2)=ifelse($3,yes,no,0)" 680 | fi 681 | done 682 | ]) 683 | 684 | dnl Iteratively interpolate the contents of the second argument 685 | dnl until interpolation offers no new result. Then assign the 686 | dnl final result to $1. 687 | dnl 688 | dnl Example: 689 | dnl 690 | dnl foo=1 691 | dnl bar='${foo}/2' 692 | dnl baz='${bar}/3' 693 | dnl APR_EXPAND_VAR(fraz, $baz) 694 | dnl $fraz is now "1/2/3" 695 | dnl 696 | AC_DEFUN([APR_EXPAND_VAR], [ 697 | ap_last= 698 | ap_cur="$2" 699 | while test "x${ap_cur}" != "x${ap_last}"; 700 | do 701 | ap_last="${ap_cur}" 702 | ap_cur=`eval "echo ${ap_cur}"` 703 | done 704 | $1="${ap_cur}" 705 | ]) 706 | 707 | dnl 708 | dnl Removes the value of $3 from the string in $2, strips of any leading 709 | dnl slashes, and returns the value in $1. 710 | dnl 711 | dnl Example: 712 | dnl orig_path="${prefix}/bar" 713 | dnl APR_PATH_RELATIVE(final_path, $orig_path, $prefix) 714 | dnl $final_path now contains "bar" 715 | AC_DEFUN([APR_PATH_RELATIVE], [ 716 | ap_stripped=`echo $2 | sed -e "s#^$3##"` 717 | # check if the stripping was successful 718 | if test "x$2" != "x${ap_stripped}"; then 719 | # it was, so strip of any leading slashes 720 | $1="`echo ${ap_stripped} | sed -e 's#^/*##'`" 721 | else 722 | # it wasn't so return the original 723 | $1="$2" 724 | fi 725 | ]) 726 | 727 | dnl APR_HELP_STRING(LHS, RHS) 728 | dnl Autoconf 2.50 can not handle substr correctly. It does have 729 | dnl AC_HELP_STRING, so let's try to call it if we can. 730 | dnl Note: this define must be on one line so that it can be properly returned 731 | dnl as the help string. When using this macro with a multi-line RHS, ensure 732 | dnl that you surround the macro invocation with []s 733 | AC_DEFUN([APR_HELP_STRING], [ifelse(regexp(AC_ACVERSION, 2\.1), -1, AC_HELP_STRING([$1],[$2]),[ ][$1] substr([ ],len($1))[$2])]) 734 | 735 | dnl 736 | dnl APR_LAYOUT(configlayout, layoutname [, extravars]) 737 | dnl 738 | AC_DEFUN([APR_LAYOUT], [ 739 | if test ! -f $srcdir/config.layout; then 740 | echo "** Error: Layout file $srcdir/config.layout not found" 741 | echo "** Error: Cannot use undefined layout '$LAYOUT'" 742 | exit 1 743 | fi 744 | # Catch layout names including a slash which will otherwise 745 | # confuse the heck out of the sed script. 746 | case $2 in 747 | */*) 748 | echo "** Error: $2 is not a valid layout name" 749 | exit 1 ;; 750 | esac 751 | pldconf=./config.pld 752 | changequote({,}) 753 | sed -e "1s/[ ]*<[lL]ayout[ ]*$2[ ]*>[ ]*//;1t" \ 754 | -e "1,/[ ]*<[lL]ayout[ ]*$2[ ]*>[ ]*/d" \ 755 | -e '/[ ]*<\/Layout>[ ]*/,$d' \ 756 | -e "s/^[ ]*//g" \ 757 | -e "s/:[ ]*/=\'/g" \ 758 | -e "s/[ ]*$/'/g" \ 759 | $1 > $pldconf 760 | layout_name=$2 761 | if test ! -s $pldconf; then 762 | echo "** Error: unable to find layout $layout_name" 763 | exit 1 764 | fi 765 | . $pldconf 766 | rm $pldconf 767 | for var in prefix exec_prefix bindir sbindir libexecdir mandir \ 768 | sysconfdir datadir includedir localstatedir runtimedir \ 769 | logfiledir libdir installbuilddir libsuffix $3; do 770 | eval "val=\"\$$var\"" 771 | case $val in 772 | *+) 773 | val=`echo $val | sed -e 's;\+$;;'` 774 | eval "$var=\"\$val\"" 775 | autosuffix=yes 776 | ;; 777 | *) 778 | autosuffix=no 779 | ;; 780 | esac 781 | val=`echo $val | sed -e 's:\(.\)/*$:\1:'` 782 | val=`echo $val | sed -e 's:[\$]\([a-z_]*\):${\1}:g'` 783 | if test "$autosuffix" = "yes"; then 784 | if echo $val | grep apache >/dev/null; then 785 | addtarget=no 786 | else 787 | addtarget=yes 788 | fi 789 | if test "$addtarget" = "yes"; then 790 | val="$val/apache2" 791 | fi 792 | fi 793 | eval "$var='$val'" 794 | done 795 | changequote([,]) 796 | ])dnl 797 | 798 | dnl 799 | dnl APR_ENABLE_LAYOUT(default layout name [, extra vars]) 800 | dnl 801 | AC_DEFUN([APR_ENABLE_LAYOUT], [ 802 | AC_ARG_ENABLE(layout, 803 | [ --enable-layout=LAYOUT],[ 804 | LAYOUT=$enableval 805 | ]) 806 | 807 | if test -z "$LAYOUT"; then 808 | LAYOUT="$1" 809 | fi 810 | APR_LAYOUT($srcdir/config.layout, $LAYOUT, $2) 811 | 812 | AC_MSG_CHECKING(for chosen layout) 813 | AC_MSG_RESULT($layout_name) 814 | ]) 815 | 816 | 817 | dnl 818 | dnl APR_PARSE_ARGUMENTS 819 | dnl a reimplementation of autoconf's argument parser, 820 | dnl used here to allow us to co-exist layouts and argument based 821 | dnl set ups. 822 | AC_DEFUN([APR_PARSE_ARGUMENTS], [ 823 | ac_prev= 824 | # Retrieve the command-line arguments. The eval is needed because 825 | # the arguments are quoted to preserve accuracy. 826 | eval "set x $ac_configure_args" 827 | shift 828 | for ac_option 829 | do 830 | # If the previous option needs an argument, assign it. 831 | if test -n "$ac_prev"; then 832 | eval "$ac_prev=\$ac_option" 833 | ac_prev= 834 | continue 835 | fi 836 | 837 | ac_optarg=`expr "x$ac_option" : 'x[[^=]]*=\(.*\)'` 838 | 839 | case $ac_option in 840 | 841 | -bindir | --bindir | --bindi | --bind | --bin | --bi) 842 | ac_prev=bindir ;; 843 | -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) 844 | bindir="$ac_optarg" ;; 845 | 846 | -datadir | --datadir | --datadi | --datad | --data | --dat | --da) 847 | ac_prev=datadir ;; 848 | -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \ 849 | | --da=*) 850 | datadir="$ac_optarg" ;; 851 | 852 | -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ 853 | | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ 854 | | --exec | --exe | --ex) 855 | ac_prev=exec_prefix ;; 856 | -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ 857 | | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ 858 | | --exec=* | --exe=* | --ex=*) 859 | exec_prefix="$ac_optarg" ;; 860 | 861 | -includedir | --includedir | --includedi | --included | --include \ 862 | | --includ | --inclu | --incl | --inc) 863 | ac_prev=includedir ;; 864 | -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ 865 | | --includ=* | --inclu=* | --incl=* | --inc=*) 866 | includedir="$ac_optarg" ;; 867 | 868 | -infodir | --infodir | --infodi | --infod | --info | --inf) 869 | ac_prev=infodir ;; 870 | -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) 871 | infodir="$ac_optarg" ;; 872 | 873 | -libdir | --libdir | --libdi | --libd) 874 | ac_prev=libdir ;; 875 | -libdir=* | --libdir=* | --libdi=* | --libd=*) 876 | libdir="$ac_optarg" ;; 877 | 878 | -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ 879 | | --libexe | --libex | --libe) 880 | ac_prev=libexecdir ;; 881 | -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ 882 | | --libexe=* | --libex=* | --libe=*) 883 | libexecdir="$ac_optarg" ;; 884 | 885 | -localstatedir | --localstatedir | --localstatedi | --localstated \ 886 | | --localstate | --localstat | --localsta | --localst \ 887 | | --locals | --local | --loca | --loc | --lo) 888 | ac_prev=localstatedir ;; 889 | -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ 890 | | --localstate=* | --localstat=* | --localsta=* | --localst=* \ 891 | | --locals=* | --local=* | --loca=* | --loc=* | --lo=*) 892 | localstatedir="$ac_optarg" ;; 893 | 894 | -mandir | --mandir | --mandi | --mand | --man | --ma | --m) 895 | ac_prev=mandir ;; 896 | -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) 897 | mandir="$ac_optarg" ;; 898 | 899 | -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) 900 | ac_prev=prefix ;; 901 | -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) 902 | prefix="$ac_optarg" ;; 903 | 904 | -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) 905 | ac_prev=sbindir ;; 906 | -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ 907 | | --sbi=* | --sb=*) 908 | sbindir="$ac_optarg" ;; 909 | 910 | -sharedstatedir | --sharedstatedir | --sharedstatedi \ 911 | | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ 912 | | --sharedst | --shareds | --shared | --share | --shar \ 913 | | --sha | --sh) 914 | ac_prev=sharedstatedir ;; 915 | -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ 916 | | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ 917 | | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ 918 | | --sha=* | --sh=*) 919 | sharedstatedir="$ac_optarg" ;; 920 | 921 | -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ 922 | | --syscon | --sysco | --sysc | --sys | --sy) 923 | ac_prev=sysconfdir ;; 924 | -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ 925 | | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) 926 | sysconfdir="$ac_optarg" ;; 927 | 928 | esac 929 | done 930 | 931 | # Be sure to have absolute paths. 932 | for ac_var in exec_prefix prefix 933 | do 934 | eval ac_val=$`echo $ac_var` 935 | case $ac_val in 936 | [[\\/$]]* | ?:[[\\/]]* | NONE | '' ) ;; 937 | *) AC_MSG_ERROR([expected an absolute path for --$ac_var: $ac_val]);; 938 | esac 939 | done 940 | 941 | ])dnl 942 | 943 | dnl 944 | dnl APR_CHECK_DEPEND 945 | dnl 946 | dnl Determine what program we can use to generate .deps-style dependencies 947 | dnl 948 | AC_DEFUN([APR_CHECK_DEPEND], [ 949 | dnl Try to determine what depend program we can use 950 | dnl All GCC-variants should have -MM. 951 | dnl If not, then we can check on those, too. 952 | if test "$GCC" = "yes"; then 953 | MKDEP='$(CC) -MM' 954 | else 955 | rm -f conftest.c 956 | dnl should be available everywhere! 957 | cat > conftest.c < 959 | int main() { return 0; } 960 | EOF 961 | MKDEP="true" 962 | for i in "$CC -MM" "$CC -M" "$CPP -MM" "$CPP -M" "cpp -M"; do 963 | AC_MSG_CHECKING([if $i can create proper make dependencies]) 964 | if $i conftest.c 2>/dev/null | grep 'conftest.o: conftest.c' >/dev/null; then 965 | MKDEP=$i 966 | AC_MSG_RESULT(yes) 967 | break; 968 | fi 969 | AC_MSG_RESULT(no) 970 | done 971 | rm -f conftest.c 972 | fi 973 | 974 | AC_SUBST(MKDEP) 975 | ]) 976 | 977 | dnl 978 | dnl APR_CHECK_TYPES_COMPATIBLE(TYPE-1, TYPE-2, [ACTION-IF-TRUE]) 979 | dnl 980 | dnl Try to determine whether two types are the same. Only works 981 | dnl for gcc and icc. 982 | dnl 983 | AC_DEFUN([APR_CHECK_TYPES_COMPATIBLE], [ 984 | define([apr_cvname], apr_cv_typematch_[]translit([$1], [ ], [_])_[]translit([$2], [ ], [_])) 985 | AC_CACHE_CHECK([whether $1 and $2 are the same], apr_cvname, [ 986 | AC_TRY_COMPILE(AC_INCLUDES_DEFAULT, [ 987 | int foo[0 - !__builtin_types_compatible_p($1, $2)]; 988 | ], [apr_cvname=yes 989 | $3], [apr_cvname=no])]) 990 | ]) 991 | -------------------------------------------------------------------------------- /build/acmacros/asterisk.m4: -------------------------------------------------------------------------------- 1 | dnl FIND_ASTERISK: find where Asterisk is located 2 | AC_DEFUN([FIND_ASTERISK], [ 3 | 4 | AC_MSG_NOTICE([Asterisk configuration]) 5 | 6 | AC_ARG_WITH([asterisk], 7 | [ --with-asterisk=DIR specify location of Asterisk], 8 | [asterisk_dir=$withval], 9 | [asterisk_dir=""]) 10 | 11 | AC_ARG_WITH([asterisk-conf], 12 | [ --with-asterisk-conf=DIR specify location of Asterisk config dir], 13 | [asterisk_conf_dir=$withval], 14 | [asterisk_conf_dir=""]) 15 | 16 | AC_ARG_WITH([asterisk-doc], 17 | [ --with-asterisk-doc=DIR specify location of Asterisk doc dir], 18 | [asterisk_xmldoc_dir=$withval], 19 | [asterisk_xmldoc_dir=""]) 20 | 21 | AC_ARG_WITH([asterisk-version], 22 | [ --with-asterisk-version=VER specify version of Asterisk], 23 | [asterisk_version=$withval], 24 | [asterisk_version=""]) 25 | 26 | dnl Test and set Asterisk directories. 27 | if test "${asterisk_dir}" = ""; then 28 | asterisk_dir="/usr" 29 | if test "${asterisk_conf_dir}" = ""; then 30 | asterisk_conf_dir="/etc/asterisk" 31 | fi 32 | if test "${asterisk_xmldoc_dir}" = ""; then 33 | asterisk_xmldoc_dir="/var/lib/asterisk/documentation/thirdparty" 34 | fi 35 | else 36 | if test "${asterisk_conf_dir}" = ""; then 37 | asterisk_conf_dir="${asterisk_dir}/etc/asterisk" 38 | fi 39 | if test "${asterisk_xmldoc_dir}" = ""; then 40 | asterisk_xmldoc_dir="${asterisk_dir}/var/lib/asterisk/documentation/thirdparty" 41 | fi 42 | fi 43 | asterisk_mod_dir="${asterisk_dir}/lib/asterisk/modules" 44 | asterisk_include_dir="${asterisk_dir}/include" 45 | 46 | dnl Determine Asterisk version. 47 | if test "${asterisk_version}" = ""; then 48 | if test -f "$asterisk_dir/sbin/asterisk"; then 49 | asterisk_version=$($asterisk_dir/sbin/asterisk -V | grep Asterisk | cut -d' ' -f2) 50 | else 51 | if test -f "$asterisk_include_dir/asterisk/version.h"; then 52 | echo "Asterisk binary not found, using version.h to determine version" 53 | asterisk_version=$(cat $asterisk_include_dir/asterisk/version.h | sed -n 's/#define ASTERISK_VERSION "\(.*\)"/\1/p') 54 | fi 55 | fi 56 | fi 57 | 58 | if test "${asterisk_version}" = ""; then 59 | AC_MSG_ERROR([Could not determine Asterisk version, please explicitly specify version string using the option --with-asterisk-version=major.minor.patch]) 60 | fi 61 | 62 | AC_MSG_RESULT([$asterisk_version]) 63 | 64 | dnl Strip off trailing characters, if present. 65 | asterisk_version=$(echo $asterisk_version | cut -d- -f1 | cut -d~ -f1) 66 | 67 | case $asterisk_version in 68 | SVN*) 69 | dnl The version number is supposed to be explicitly specified in case an SVN revision of Asterisk is used. 70 | AC_MSG_ERROR([Could not determine major, minor, and patch version numbers of Asterisk, please explicitly specify version string using the option --with-asterisk-version=major.minor.patch]) 71 | ;; 72 | esac 73 | 74 | dnl Set major, minor, and patch version numbers. 75 | ASTERISK_MAJOR_VERSION=$(echo $asterisk_version | cut -d. -f1) 76 | ASTERISK_MINOR_VERSION=$(echo $asterisk_version | cut -d. -f2) 77 | ASTERISK_PATCH_VERSION=$(echo $asterisk_version | cut -d. -f3) 78 | 79 | AC_DEFINE_UNQUOTED(ASTERISK_MAJOR_VERSION, $ASTERISK_MAJOR_VERSION) 80 | AC_DEFINE_UNQUOTED(ASTERISK_MINOR_VERSION, $ASTERISK_MINOR_VERSION) 81 | AC_DEFINE_UNQUOTED(ASTERISK_PATCH_VERSION, $ASTERISK_PATCH_VERSION) 82 | 83 | dnl Test Asterisk include directory and header file. 84 | if test -f "$asterisk_include_dir/asterisk.h"; then 85 | if test -d "$asterisk_include_dir/asterisk"; then 86 | ASTERISK_INCLUDES="-I$asterisk_include_dir" 87 | else 88 | AC_MSG_ERROR([Could not find Asterisk include directory, make sure Asterisk development package is installed]) 89 | fi 90 | else 91 | AC_MSG_ERROR([Could not find asterisk.h, make sure Asterisk development package is installed]) 92 | fi 93 | 94 | dnl Set Asterisk includes, conf and xmldoc directories. 95 | AC_SUBST(ASTERISK_INCLUDES) 96 | AC_SUBST(asterisk_conf_dir) 97 | AC_SUBST(asterisk_xmldoc_dir) 98 | ]) 99 | -------------------------------------------------------------------------------- /build/acmacros/ax_compiler_vendor.m4: -------------------------------------------------------------------------------- 1 | dnl Determine the vendor of the C/C++ compiler, e.g., gnu, intel, ibm, sun, 2 | dnl hp, borland, comeau, dec, cray, kai, lcc, metrowerks, sgi, microsoft, 3 | dnl watcom, etc. The vendor is returned in the cache variable 4 | dnl $ax_cv_c_compiler_vendor for C and $ax_cv_cxx_compiler_vendor for C++. 5 | 6 | AC_DEFUN([AX_COMPILER_VENDOR], 7 | [AC_CACHE_CHECK([for _AC_LANG compiler vendor], ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor, 8 | [dnl note: don't check for gcc first since some other compilers define __GNUC__ 9 | vendors="intel: __ICC,__ECC,__INTEL_COMPILER 10 | ibm: __xlc__,__xlC__,__IBMC__,__IBMCPP__ 11 | pathscale: __PATHCC__,__PATHSCALE__ 12 | clang: __clang__ 13 | gnu: __GNUC__ 14 | sun: __SUNPRO_C,__SUNPRO_CC 15 | hp: __HP_cc,__HP_aCC 16 | dec: __DECC,__DECCXX,__DECC_VER,__DECCXX_VER 17 | borland: __BORLANDC__,__TURBOC__ 18 | comeau: __COMO__ 19 | cray: _CRAYC 20 | kai: __KCC 21 | lcc: __LCC__ 22 | sgi: __sgi,sgi 23 | microsoft: _MSC_VER 24 | metrowerks: __MWERKS__ 25 | watcom: __WATCOMC__ 26 | portland: __PGI 27 | unknown: UNKNOWN" 28 | for ventest in $vendors; do 29 | case $ventest in 30 | *:) vendor=$ventest; continue ;; 31 | *) vencpp="defined("`echo $ventest | sed 's/,/) || defined(/g'`")" ;; 32 | esac 33 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM(,[ 34 | #if !($vencpp) 35 | thisisanerror; 36 | #endif 37 | ])], [break]) 38 | done 39 | ax_cv_[]_AC_LANG_ABBREV[]_compiler_vendor=`echo $vendor | cut -d: -f1` 40 | ]) 41 | ]) 42 | -------------------------------------------------------------------------------- /build/acmacros/unimrcp.m4: -------------------------------------------------------------------------------- 1 | dnl FIND_UNIMRCP: find where UniMRCP is located (or use bundled) 2 | AC_DEFUN([FIND_UNIMRCP], [ 3 | 4 | AC_MSG_NOTICE([UniMRCP configuration]) 5 | 6 | AC_ARG_WITH([unimrcp], 7 | [ --with-unimrcp=DIR specify UniMRCP location, or 'builtin'], 8 | [unimrcp_dir=$withval], 9 | [unimrcp_dir="/usr/local/unimrcp"]) 10 | 11 | dnl Whether unimrcp_dir points to source or installed dir 12 | is_source_dir=1 13 | 14 | if test "$unimrcp_dir" = "builtin"; then 15 | dnl bundled (builtin) subdir 16 | unimrcp_dir="`pwd`/unimrcp" 17 | else 18 | unimrcp_config=$unimrcp_dir/lib/pkgconfig/unimrcpclient.pc 19 | if test -f $unimrcp_config; then 20 | is_source_dir=0 21 | fi 22 | fi 23 | 24 | if test $is_source_dir = 1; then 25 | AC_MSG_ERROR([Only installed dir location is supported now]) 26 | else 27 | if test -n "$PKG_CONFIG"; then 28 | UNIMRCP_INCLUDES="`$PKG_CONFIG --cflags $unimrcp_config`" 29 | UNIMRCP_LIBS="`$PKG_CONFIG --libs $unimrcp_config`" 30 | uni_version="`$PKG_CONFIG --modversion $unimrcp_config`" 31 | else 32 | AC_MSG_ERROR([pkg-config is not available]) 33 | fi 34 | fi 35 | 36 | AC_DEFINE_UNQUOTED(UNIMRCP_DIR_LOCATION,"$unimrcp_dir") 37 | AC_MSG_RESULT([$uni_version]) 38 | 39 | AC_SUBST(UNIMRCP_INCLUDES) 40 | AC_SUBST(UNIMRCP_LIBS) 41 | ]) 42 | -------------------------------------------------------------------------------- /build/xmldocs/get_documentation: -------------------------------------------------------------------------------- 1 | /\/\*\*\* DOCUMENTATION/ {printit=1; next} 2 | /\*\*\*\// {if (printit) exit} 3 | {if (printit) print} 4 | -------------------------------------------------------------------------------- /conf/mrcp.conf: -------------------------------------------------------------------------------- 1 | ; 2 | ; The configuration file of the app_unimrcp module. 3 | ; 4 | ; The configuration consists of general settings and MRCP profiles. One or more 5 | ; MRCP profiles can be specified. The default configuration file includes MRCP 6 | ; v2 and v1 profiles for: 7 | ; 8 | ; - UniMRCP Server (UMS) 9 | ; - Nuance Speech Server (NSS) 10 | ; 11 | ; Notes: 12 | ; - the default value of the parameter(s) server-ip must be replaced with the 13 | ; IP address of the corresponding MRCP server 14 | ; - the default value of the parameters client-ip and rtp-ip must be 15 | ; replaced with the IP address of the MRCP client (Asterisk) 16 | ; 17 | 18 | ; 19 | ; General settings 20 | ; 21 | [general] 22 | ; Default ASR and TTS profiles. 23 | default-asr-profile = ums2 24 | default-tts-profile = ums2 25 | ; UniMRCP logging level to appear in Asterisk logs. Options are: 26 | ; EMERGENCY|ALERT|CRITICAL|ERROR|WARNING|NOTICE|INFO|DEBUG --> 27 | log-level = DEBUG 28 | max-connection-count = 100 29 | max-shared-count = 100 30 | offer-new-connection = 1 31 | ; rx-buffer-size = 1024 32 | ; tx-buffer-size = 1024 33 | ; request-timeout = 5000 34 | ; speech-channel-timeout = 30000 35 | 36 | ; 37 | ; Profile for UniMRCP Server [MRCPv2] 38 | ; 39 | [ums2] 40 | ; MRCP settings 41 | version = 2 42 | ; 43 | ; SIP settings 44 | server-ip = 10.0.0.2 45 | server-port = 8060 46 | ; SIP user agent 47 | client-ip = 10.0.0.1 48 | client-port = 25097 49 | sip-transport = udp 50 | ; 51 | ; RTP factory 52 | rtp-ip = 10.0.0.1 53 | rtp-port-min = 28000 54 | rtp-port-max = 29000 55 | ; 56 | ; Jitter buffer settings 57 | playout-delay = 50 58 | max-playout-delay = 200 59 | ; RTP settings 60 | ptime = 20 61 | codecs = PCMU PCMA G722 L16/96/8000 telephone-event/101/8000 62 | ; RTCP settings 63 | rtcp = 0 64 | 65 | ; 66 | ; Profile for UniMRCP Server [MRCPv1] 67 | ; 68 | [ums1] 69 | ; MRCP settings 70 | version = 1 71 | ; 72 | ; RTSP settings 73 | server-ip = 10.0.0.2 74 | server-port = 1554 75 | resource-location = media 76 | speechsynth = speechsynthesizer 77 | speechrecog = speechrecognizer 78 | ; 79 | ; RTP factory 80 | rtp-ip = 10.0.0.1 81 | rtp-port-min = 27000 82 | rtp-port-max = 28000 83 | ; 84 | ; Jitter buffer settings 85 | playout-delay = 50 86 | max-playout-delay = 200 87 | ; RTP settings 88 | ptime = 20 89 | codecs = PCMU PCMA G722 L16/96/8000 telephone-event/101/8000 90 | ; RTCP settings 91 | rtcp = 0 92 | 93 | ; 94 | ; Profile for Nuance Speech Server [MRCPv2] 95 | ; 96 | [speech-nuance5-mrcp2] 97 | ; MRCP version. 98 | version = 2 99 | 100 | ; === SIP settings === 101 | ; Must be set to the IP address of the MRCP server. 102 | server-ip = 10.0.0.3 103 | ; SIP port on the MRCP server. 104 | server-port = 5060 105 | ; server-username = test 106 | force-destination = 1 107 | 108 | ; === SIP agent === 109 | ; Must be set to the IP address of the MRCP client. 110 | client-ip = 10.0.0.1 111 | ; client-ext-ip = auto 112 | ; SIP port on the MRCP client. 113 | client-port = 5093 114 | ; SIP transport either UDP or TCP. 115 | sip-transport = udp 116 | ; ua-name = Asterisk 117 | ; sdp-origin = Asterisk 118 | ; sip-t1 = 500 119 | ; sip-t2 = 4000 120 | ; sip-t4 = 4000 121 | ; sip-t1x64 = 32000 122 | ; sip-timer-c = 185000 123 | 124 | ; === RTP factory === 125 | ; Must be set to the IP address of the MRCP client. 126 | rtp-ip = 10.0.0.1 127 | ; rtp-ext-ip = auto 128 | ; RTP port range on the MRCP client. 129 | rtp-port-min = 4000 130 | rtp-port-max = 5000 131 | 132 | ; === Jitter buffer settings === 133 | playout-delay = 50 134 | ; min-playout-delay = 20 135 | max-playout-delay = 200 136 | 137 | ; === RTP settings === 138 | ptime = 20 139 | codecs = PCMU PCMA L16/96/8000 telephone-event/101/8000 140 | 141 | ; === RTCP settings === 142 | rtcp = 1 143 | rtcp-bye = 2 144 | rtcp-tx-interval = 5000 145 | rtcp-rx-resolution = 1000 146 | 147 | ; 148 | ; Profile for Nuance Speech Server [MRCPv1] 149 | ; 150 | [speech-nuance5-mrcp1] 151 | ; MRCP version. 152 | version = 1 153 | 154 | ; === RTSP settings === 155 | ; Must be set to the IP address of the MRCP server. 156 | server-ip = 10.0.0.3 157 | ; RTSP port on the MRCP server. 158 | server-port = 4900 159 | ; force-destination = 1 160 | resource-location = media 161 | speechsynth = speechsynthesizer 162 | speechrecog = speechrecognizer 163 | 164 | ; === RTP factory === 165 | ; Must be set to the IP address of the MRCP client. 166 | rtp-ip = 10.0.0.1 167 | ; rtp-ext-ip = auto 168 | ; RTP port range on the MRCP client. 169 | rtp-port-min = 4000 170 | rtp-port-max = 5000 171 | 172 | ; === Jitter buffer settings === 173 | playout-delay = 50 174 | ; min-playout-delay = 20 175 | max-playout-delay = 200 176 | 177 | ; === RTP settings === 178 | ptime = 20 179 | codecs = PCMU PCMA L16/96/8000 telephone-event/101/8000 180 | 181 | ; === RTCP settings === 182 | rtcp = 1 183 | rtcp-bye = 2 184 | rtcp-tx-interval = 5000 185 | rtcp-rx-resolution = 1000 186 | -------------------------------------------------------------------------------- /conf/mrcp_sample_apps.conf: -------------------------------------------------------------------------------- 1 | ; UniMRCP applications for Asterisk. 2 | ; 3 | ; http://www.unimrcp.org 4 | ; 5 | ; This file provides sample dialplan contexts which demonstrate how to use the applications 6 | ; SynthAndRecog(), MRCPRecog(), and MRCPSynth() included in the module app_unimrcp.so. 7 | ; There is also a usage example of the Generic Speech Recognition API implemented via 8 | ; the module res_speech_unimrcp.so. 9 | 10 | ; 11 | ; SynthAndRecog() examples. 12 | ; 13 | 14 | ; This context demonstrates how to use the application SynthAndRecog() with a plain-text prompt and a built-in speech grammar. 15 | [synthandrecog-app1] 16 | exten => s,1,Answer 17 | exten => s,2,SynthAndRecog(Please say a number,builtin:grammar/number,t=5000&b=1&ct=0.7&spl=en-US) 18 | exten => s,3,Goto(synthandrecog-output,s,1) 19 | 20 | ; This context demonstrates how to use the application SynthAndRecog() with a plain-text prompt and a built-in DTMF grammar. 21 | [synthandrecog-app2] 22 | exten => s,1,Answer 23 | exten => s,2,SynthAndRecog(Please input a number,builtin:dtmf/number,t=5000&b=1&ct=0.7&spl=en-US) 24 | exten => s,3,Goto(synthandrecog-output,s,1) 25 | 26 | ; This context demonstrates how to use the application SynthAndRecog() with an SSML prompt and an inline SRGS XML speech grammar. 27 | [synthandrecog-app3] 28 | exten => s,1,Answer 29 | exten => s,2,SynthAndRecog(Please pick a color: red green or blue ,redgreenblue,t=5000&b=1&ct=0.7) 30 | exten => s,3,Goto(synthandrecog-output,s,1) 31 | 32 | ; This context demonstrates how to use the application SynthAndRecog() with an SSML prompt and an inline SRGS XML DTMF grammar. 33 | [synthandrecog-app4] 34 | exten => s,1,Answer 35 | exten => s,2,SynthAndRecog(Please pick a color: for red press 1 for green press 2 for blue press 3 ,1red2green3blue,t=5000&b=1&ct=0.7) 36 | exten => s,3,Goto(synthandrecog-output,s,1) 37 | 38 | ; This context demonstrates how to use the application SynthAndRecog() with a non-bargeinable prompt. 39 | [synthandrecog-app5] 40 | exten => s,1,Answer 41 | exten => s,2,SynthAndRecog("This is a non-bargeinable prompt. When the prompt is finished, say a number",builtin:grammar/number,t=5000&b=0&ct=0.7&spl=en-US) 42 | exten => s,3,Goto(synthandrecog-output,s,1) 43 | 44 | ; This context demonstrates how to use the application SynthAndRecog() with a prompt and a grammar referenced from files. 45 | [synthandrecog-app6] 46 | exten => s,1,Answer 47 | exten => s,2,SynthAndRecog(/usr/local/unimrcp/data/speak.xml,/usr/local/unimrcp/data/grammar.xml,t=5000&b=1&ct=0.7&spl=en-US) 48 | exten => s,3,Goto(synthandrecog-output,s,1) 49 | 50 | ; This context demonstrates how to use the application SynthAndRecog() with a grammar specified by an HTTP URI. 51 | [synthandrecog-app7] 52 | exten => s,1,Answer 53 | exten => s,2,SynthAndRecog(Please say a digit,http://unimrcp.net/data/grammar.xml,t=5000&b=1&ct=0.7&spl=en-US) 54 | exten => s,3,Goto(synthandrecog-output,s,1) 55 | 56 | ; This context demonstrates how to use the application SynthAndRecog() with multiple grammars specified 57 | ; using a comma-separated list. 58 | [synthandrecog-app8] 59 | exten => s,1,Answer 60 | exten => s,2,SynthAndRecog(Please say a number,"builtin:grammar/number,builtin:dtmf/number",t=5000&b=1&ct=0.7&spl=en-US) 61 | exten => s,3,Goto(synthandrecog-output,s,1) 62 | 63 | ; This context demonstrates how to request more than one alternative recognition result (N-Best-List-Length > 1) 64 | ; using the application SynthAndRecog(). 65 | [synthandrecog-app9] 66 | exten => s,1,Answer 67 | exten => s,2,SynthAndRecog(Please say a number,builtin:grammar/number,t=5000&b=1&ct=0.4&nb=3&spl=en-US) 68 | exten => s,3,Goto(synthandrecog-nbest-output,s,1) 69 | 70 | ; This macro outputs recognition results produced by the application SynthAndRecog() 71 | [synthandrecog-output] 72 | exten => s,1,Verbose(1, status: ${RECOG_STATUS}, completion-cause: ${RECOG_COMPLETION_CAUSE}, result: ${RECOG_RESULT}) 73 | exten => s,2,GotoIf($["${RECOG_STATUS}" = "OK"]?3:5) 74 | exten => s,3,GotoIf($["${RECOG_COMPLETION_CAUSE}" = "000"]?4:5) 75 | exten => s,4,Verbose(1, confidence: ${RECOG_CONFIDENCE(0)}, grammar: ${RECOG_GRAMMAR(0)}, input: ${RECOG_INPUT(0)}, instance: ${RECOG_INSTANCE(0/0)}) 76 | exten => s,5,Hangup 77 | 78 | ; This macro outputs alternate recognition results produced by the application SynthAndRecog() 79 | [synthandrecog-nbest-output] 80 | exten => s,1,Verbose(1, status: ${RECOG_STATUS}, completion-cause: ${RECOG_COMPLETION_CAUSE}, result: ${RECOG_RESULT}) 81 | exten => s,2,GotoIf($["${RECOG_STATUS}" = "OK"]?3:9) 82 | exten => s,3,GotoIf($["${RECOG_COMPLETION_CAUSE}" = "000"]?4:9) 83 | exten => s,4,Set(i=0) 84 | exten => s,5,While(${RECOG_INPUT(${i})}) 85 | exten => s,6,Verbose(1, confidence: ${RECOG_CONFIDENCE(${i})}, grammar: ${RECOG_GRAMMAR(${i})}, input: ${RECOG_INPUT(${i})}, instance: ${RECOG_INSTANCE(${i}/0)}) 86 | exten => s,7,Set(i=$[${i} + 1]) 87 | exten => s,8,EndWhile() 88 | exten => s,9,Hangup 89 | 90 | ; 91 | ; MRCPRecog() examples. 92 | ; 93 | 94 | ; This context demonstrates how to use the application MRCPRecog() with a pre-recorded prompt file and a built-in speech grammar. 95 | [mrcprecog-app1] 96 | exten => s,1,Answer 97 | exten => s,2,MRCPRecog(builtin:grammar/number,t=5000&b=1&ct=0.7&spl=en-US&f=hello-world) 98 | exten => s,3,Goto(mrcprecog-output,s,1) 99 | 100 | ; This context demonstrates how to use the application MRCPRecog() with a pre-recorded prompt file and a built-in DTMF grammar. 101 | [mrcprecog-app2] 102 | exten => s,1,Answer 103 | exten => s,2,MRCPRecog(builtin:dtmf/number,t=5000&b=1&ct=0.7&spl=en-US&f=hello-world) 104 | exten => s,3,Goto(mrcprecog-output,s,1) 105 | 106 | ; This macro outputs recognition results produced by the application SynthAndRecog() 107 | [mrcprecog-output] 108 | exten => s,1,Verbose(1, status: ${RECOGSTATUS}, completion-cause: ${RECOG_COMPLETION_CAUSE}, result: ${RECOG_RESULT}) 109 | exten => s,2,GotoIf($["${RECOGSTATUS}" = "OK"]?3:5) 110 | exten => s,3,GotoIf($["${RECOG_COMPLETION_CAUSE}" = "000"]?4:5) 111 | exten => s,4,Verbose(1, confidence: ${RECOG_CONFIDENCE(0)}, grammar: ${RECOG_GRAMMAR(0)}, input: ${RECOG_INPUT(0)}, instance: ${RECOG_INSTANCE(0/0)}) 112 | exten => s,5,Hangup 113 | 114 | ; 115 | ; MRCPSynth() examples. 116 | ; 117 | 118 | ; This context demonstrates how to use the application MRCPSynth() with a plain-text prompt. 119 | [mrcpsynth-app1] 120 | exten => s,1,Answer 121 | exten => s,n,MRCPSynth(Hello world!,l=en-US) 122 | exten => s,n,Verbose(1, ${SYNTHSTATUS}) 123 | exten => s,n,Hangup 124 | 125 | ; This context demonstrates how to use the application MRCPSynth() with an SSML prompt. 126 | [mrcpsynth-app2] 127 | exten => s,1,Answer 128 | exten => s,n,MRCPSynth(Hello world!) 129 | exten => s,n,Verbose(1, ${SYNTHSTATUS}) 130 | exten => s,n,Hangup 131 | 132 | ; 133 | ; Generic Speech Recognition API. 134 | ; 135 | 136 | ; This context makes use of the Generic Speech Recognition API of Asterisk implemented via the module res_speech_unimrcp.so. 137 | [say-digit] 138 | exten => s,1,Answer() 139 | exten => s,2,SpeechCreate() 140 | exten => s,3,SpeechLoadGrammar(digit,/usr/local/unimrcp/data/grammar.xml) 141 | exten => s,4,SpeechActivateGrammar(digit) 142 | exten => s,5,SpeechBackground(hello-world,20) 143 | exten => s,6,GotoIf($["${SPEECH(results)}" = "0"]?7:9) 144 | exten => s,7,Playback(vm-nonumber) 145 | exten => s,8,Goto(5) 146 | exten => s,9,Verbose(1, "Recognition result: ${SPEECH_TEXT(0)}, confidence score: ${SPEECH_SCORE(0)}, grammar-uri: ${SPEECH_GRAMMAR(0)}") 147 | exten => s,10,SpeechDeactivateGrammar(digit) 148 | exten => s,11,SpeechUnloadGrammar(digit) 149 | exten => s,12,SpeechDestroy() 150 | exten => s,13,Hangup() 151 | -------------------------------------------------------------------------------- /conf/res-speech-unimrcp.conf: -------------------------------------------------------------------------------- 1 | ; 2 | ; The configuration file of the res_speech_unimrcp module. 3 | ; 4 | 5 | ; 6 | ; General settings 7 | ; 8 | [general] 9 | ; UniMRCP named profile. Options are: 10 | unimrcp-profile = uni2 ; UniMRCP MRCPv2 Server 11 | ;unimrcp-profile = uni1 ; UniMRCP MRCPv1 Server 12 | ;unimrcp-profile = lv2 ; LumenVox MRCPv2 Server 13 | ;unimrcp-profile = lv1 ; LumenVox MRCPv1 Server 14 | ;unimrcp-profile = nss2 ; Nuance MRCPv2 Server 15 | ;unimrcp-profile = nss1 ; Nuance MRCPv1 Server 16 | 17 | ; UniMRCP logging level. Options are: 18 | ; EMERGENCY|ALERT|CRITICAL|ERROR|WARNING|NOTICE|INFO|DEBUG --> 19 | log-level = DEBUG 20 | 21 | ; 22 | ; Preloaded grammars 23 | ; 24 | [grammars] 25 | ;grammar-name = path-to-grammar-file 26 | 27 | ; 28 | ; MRCPv2 properties (recognizer and generic header fields) 29 | ; http://tools.ietf.org/html/draft-ietf-speechsc-mrcpv2-20#section-9.4 30 | ; 31 | [mrcpv2-properties] 32 | Recognition-Timeout = 20000 33 | No-Input-Timeout = 15000 34 | 35 | ; 36 | ; MRCPv1 properties (recognizer and generic header fields) 37 | ; http://tools.ietf.org/html/rfc4463#section-8.4 38 | ; 39 | [mrcpv1-properties] 40 | Recognition-Timeout = 20000 41 | No-Input-Timeout = 15000 42 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | dnl 2 | dnl Autoconf configuration file for the UniMRCP modules for Asterisk. 3 | dnl 4 | dnl Process this file with autoconf to produce a configure script. 5 | 6 | AC_PREREQ(2.59) 7 | 8 | AC_INIT([asterisk-unimrcp],[1.10.0]) 9 | 10 | AC_CONFIG_AUX_DIR([build]) 11 | AC_CONFIG_MACRO_DIR([build/acmacros]) 12 | 13 | dnl Set ac_macro_dir variable manually for autoconf 2.61 and above. 14 | ac_macro_dir="build/acmacros" 15 | 16 | AC_SUBST(ac_aux_dir) 17 | AC_SUBST(ac_macro_dir) 18 | 19 | dnl Generate ./config.nice to reuse ./configure command-line. 20 | APR_CONFIG_NICE(config.nice) 21 | 22 | dnl Include m4 macros for libtool. 23 | sinclude(build/acmacros/libtool.m4) 24 | sinclude(build/acmacros/ltoptions.m4) 25 | sinclude(build/acmacros/ltsugar.m4) 26 | sinclude(build/acmacros/ltversion.m4) 27 | sinclude(build/acmacros/lt~obsolete.m4) 28 | 29 | AM_INIT_AUTOMAKE([no-define nostdinc foreign]) 30 | 31 | dnl Enable silent build rules available since automake 1.11. 32 | m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) 33 | 34 | dnl Set default language. 35 | AC_LANG_C 36 | 37 | AC_PROG_CC 38 | AC_PROG_CXX 39 | AC_PROG_INSTALL 40 | 41 | dnl Skip detection of Fortran. 42 | m4_undefine([AC_PROG_F77]) 43 | m4_defun([AC_PROG_F77],[]) 44 | 45 | dnl Turn off static libraries. 46 | AC_DISABLE_STATIC 47 | 48 | dnl Add libtool support. 49 | AC_PROG_LIBTOOL 50 | 51 | dnl Do not use autoconf generated compiler DEFS. 52 | rm confdefs.h 53 | touch confdefs.h 54 | 55 | dnl Check for C compiler vendor. 56 | AX_COMPILER_VENDOR 57 | 58 | dnl Search for pkg-config. 59 | AC_PATH_PROG(PKG_CONFIG, pkg-config) 60 | 61 | FIND_UNIMRCP 62 | FIND_ASTERISK 63 | 64 | if test "x$prefix" = "xNONE"; then 65 | prefix="${asterisk_mod_dir}" 66 | fi 67 | 68 | moddir="${prefix}" 69 | 70 | AC_SUBST(moddir) 71 | 72 | dnl Enable maintainer mode. 73 | AC_ARG_ENABLE(maintainer-mode, 74 | [AC_HELP_STRING([--enable-maintainer-mode ],[turn on debugging and compile time warnings])], 75 | [enable_maintainer_mode="$enableval"], 76 | [enable_maintainer_mode="no"]) 77 | 78 | AC_MSG_NOTICE([enable maintainer mode: $enable_maintainer_mode]) 79 | if test "${enable_maintainer_mode}" != "no"; then 80 | APR_ADDTO(CFLAGS,-g) 81 | if test "x${ax_cv_c_compiler_vendor}" = "xgnu"; then 82 | APR_ADDTO(CFLAGS,-Wall -Werror) 83 | fi 84 | fi 85 | 86 | dnl Enable speech resource module (res-speech-unimrcp) 87 | AC_ARG_ENABLE(res-speech-unimrcp, 88 | [AC_HELP_STRING([--disable-res-speech-unimrcp ],[exclude UniMRCP speech resource module from build])], 89 | [enable_res_speech_unimrcp="$enableval"], 90 | [enable_res_speech_unimrcp="yes"]) 91 | 92 | case $asterisk_version in 93 | SVN-1.2*) 94 | enable_res_speech_unimrcp="no" 95 | ;; 96 | 1.2*) 97 | enable_res_speech_unimrcp="no" 98 | ;; 99 | esac 100 | 101 | AM_CONDITIONAL([RES_SPEECH_UNIMRCP],[test "${enable_res_speech_unimrcp}" = "yes"]) 102 | 103 | dnl Enable application module (app-unimrcp) 104 | AC_ARG_ENABLE(app-unimrcp, 105 | [AC_HELP_STRING([--disable-app-unimrcp ],[exclude UniMRCP application module from build])], 106 | [enable_app_unimrcp="$enableval"], 107 | [enable_app_unimrcp="yes"]) 108 | 109 | AM_CONDITIONAL([APP_UNIMRCP],[test "${enable_app_unimrcp}" = "yes"]) 110 | 111 | AM_CONDITIONAL(ISMAC, [test `uname -s` = Darwin]) 112 | 113 | AC_CONFIG_FILES([ 114 | Makefile 115 | res-speech-unimrcp/Makefile 116 | app-unimrcp/Makefile 117 | ]) 118 | 119 | AC_OUTPUT 120 | 121 | echo 122 | echo '****************************** REPORT ******************************' 123 | echo 124 | echo Asterisk version.............. : $asterisk_version 125 | echo UniMRCP version............... : $uni_version 126 | echo UniMRCP modules version....... : $PACKAGE_VERSION 127 | echo 128 | echo Compiler...................... : $CC 129 | echo Compiler flags................ : $CFLAGS 130 | echo Preprocessor definitions...... : $CPPFLAGS 131 | echo Linker flags.................. : $LDFLAGS 132 | echo 133 | echo Modules install path...........: $prefix 134 | echo Configuration install path.....: $asterisk_conf_dir 135 | echo XML doc install path...........: $asterisk_xmldoc_dir 136 | echo 137 | echo Speech resource module.........: $enable_res_speech_unimrcp 138 | echo Application module.............: $enable_app_unimrcp 139 | echo 140 | echo '********************************************************************' 141 | -------------------------------------------------------------------------------- /include/ast_compat_defs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Asterisk -- An open source telephony toolkit. 3 | * 4 | * See http://www.asterisk.org for more information about 5 | * the Asterisk project. Please do not directly contact 6 | * any of the maintainers of this project for assistance; 7 | * the project provides a web site, mailing lists and IRC 8 | * channels for your use. 9 | * 10 | * This program is free software, distributed under the terms of 11 | * the GNU General Public License Version 2. See the LICENSE file 12 | * at the top of the source tree. 13 | * 14 | * Please follow coding guidelines 15 | * http://svn.digium.com/view/asterisk/trunk/doc/CODING-GUIDELINES 16 | */ 17 | 18 | #ifndef AST_COMPAT_DEFS_H 19 | #define AST_COMPAT_DEFS_H 20 | 21 | /*! \file 22 | * 23 | * \brief Asterisk compatibility includes and definitions 24 | * 25 | * \author Arsen Chaloyan arsen.chaloyan@unimrcp.org 26 | * 27 | * \ingroup applications 28 | */ 29 | 30 | #include "uni_version.h" 31 | #include "asterisk.h" 32 | #include "asterisk/logger.h" 33 | #include "asterisk/channel.h" 34 | #include "apr.h" 35 | #include "apr_pools.h" 36 | 37 | /** 38 | * Check at compile time if the Asterisk version is at least a certain level. 39 | */ 40 | #define AST_VERSION_AT_LEAST(major,minor,patch) \ 41 | (((major) < ASTERISK_MAJOR_VERSION) \ 42 | || ((major) == ASTERISK_MAJOR_VERSION && (minor) < ASTERISK_MINOR_VERSION) \ 43 | || ((major) == ASTERISK_MAJOR_VERSION && (minor) == ASTERISK_MINOR_VERSION \ 44 | && (patch) <= ASTERISK_PATCH_VERSION)) 45 | 46 | /** 47 | * Check at compile time if the Asterisk version is equal to the specified 48 | * major.minor version. 49 | */ 50 | #define AST_VERSION_EQUAL(major,minor) \ 51 | ((major) == ASTERISK_MAJOR_VERSION && (minor) == ASTERISK_MINOR_VERSION) 52 | 53 | /** 54 | * Backward compatible type definition for application data parameter. 55 | */ 56 | #if AST_VERSION_AT_LEAST(1,8,0) 57 | typedef const char * ast_app_data; 58 | #else 59 | typedef void * ast_app_data; 60 | #endif 61 | 62 | /** 63 | * Channel accessors available since Asterisk 11. 64 | */ 65 | #if !AST_VERSION_AT_LEAST(11,0,0) 66 | static APR_INLINE enum ast_channel_state ast_channel_state(const struct ast_channel *chan) 67 | { 68 | return chan->_state; 69 | } 70 | static APR_INLINE const char *ast_channel_language(const struct ast_channel *chan) 71 | { 72 | return chan->language; 73 | } 74 | static APR_INLINE const char *ast_channel_name(const struct ast_channel *chan) 75 | { 76 | return chan->name; 77 | } 78 | #endif 79 | 80 | /** 81 | * Backward compatible media format definition and utility functions. 82 | */ 83 | #if AST_VERSION_AT_LEAST(10,0,0) 84 | #include "asterisk/format.h" 85 | typedef struct ast_format ast_format_compat; 86 | #if AST_VERSION_AT_LEAST(13,0,0) 87 | #include "asterisk/format_cache.h" 88 | #else /* < 13 */ 89 | static APR_INLINE unsigned int ast_format_get_sample_rate(const ast_format_compat *format) 90 | { 91 | return ast_format_rate(format); 92 | } 93 | static APR_INLINE const char *ast_format_get_name(const ast_format_compat *format) 94 | { 95 | return ast_getformatname(format); 96 | } 97 | #endif 98 | #else /* <= 1.8 */ 99 | struct ast_format_compat { 100 | #if AST_VERSION_AT_LEAST(1,8,0) 101 | format_t id; /* 1.8 */ 102 | #else 103 | int id; /* < 1.8 */ 104 | #endif 105 | }; 106 | typedef struct ast_format_compat ast_format_compat; 107 | 108 | static APR_INLINE void ast_format_clear(ast_format_compat *format) 109 | { 110 | format->id = 0; 111 | } 112 | static APR_INLINE unsigned int ast_format_get_sample_rate(const ast_format_compat *format) 113 | { 114 | return ast_format_rate(format->id); 115 | } 116 | static APR_INLINE const char *ast_format_get_name(const ast_format_compat *format) 117 | { 118 | return ast_getformatname(format->id); 119 | } 120 | #endif 121 | 122 | #if AST_VERSION_AT_LEAST(13,0,0) 123 | static APR_INLINE ast_format_compat* ast_get_speechformat(ast_format_compat *raw_format, apr_pool_t *pool) 124 | { 125 | if(raw_format == ast_format_ulaw || raw_format == ast_format_alaw) 126 | return raw_format; 127 | #if UNI_VERSION_AT_LEAST(1,8,0) 128 | if(raw_format == ast_format_g722) 129 | return raw_format; 130 | #endif 131 | 132 | int sample_rate = ast_format_get_sample_rate(raw_format); 133 | if(sample_rate != 8000 && sample_rate != 16000) 134 | sample_rate = 8000; 135 | return ast_format_cache_get_slin_by_rate(sample_rate); 136 | } 137 | static APR_INLINE const char* ast_format_get_unicodec(const ast_format_compat *format) 138 | { 139 | if(format == ast_format_ulaw) 140 | return "PCMU"; 141 | if(format == ast_format_alaw) 142 | return "PCMA"; 143 | #if UNI_VERSION_AT_LEAST(1,8,0) 144 | if(format == ast_format_g722) 145 | return "G722"; 146 | #endif 147 | /*! Use Raw 16-bit Signed Linear PCM for the rest */ 148 | return "LPCM"; 149 | } 150 | static APR_INLINE int ast_format_get_bits_per_sample(const ast_format_compat *format) 151 | { 152 | /*! Raw mu-law and A-law data (G.711) */ 153 | if(format == ast_format_ulaw || format == ast_format_alaw) 154 | return 8; 155 | #if UNI_VERSION_AT_LEAST(1,8,0) 156 | if(format == ast_format_g722) 157 | return 4; 158 | #endif 159 | /*! Use Raw 16-bit Signed Linear PCM for the rest */ 160 | return 16 * ast_format_get_sample_rate(format) / 8000; 161 | } 162 | #else 163 | static APR_INLINE ast_format_compat* ast_get_speechformat(ast_format_compat *raw_format, apr_pool_t *pool) 164 | { 165 | ast_format_compat *speech_format = apr_palloc(pool, sizeof(ast_format_compat)); 166 | ast_format_clear(speech_format); 167 | switch(raw_format->id) { 168 | /*! Raw mu-law and A-law data (G.711) */ 169 | case AST_FORMAT_ULAW: 170 | case AST_FORMAT_ALAW: 171 | #if UNI_VERSION_AT_LEAST(1,8,0) 172 | case AST_FORMAT_G722: 173 | #endif 174 | speech_format->id = raw_format->id; 175 | break; 176 | default: 177 | { 178 | int sample_rate = ast_format_get_sample_rate(raw_format); 179 | if(sample_rate == 16000) 180 | speech_format->id = AST_FORMAT_SLINEAR16; 181 | else 182 | speech_format->id = AST_FORMAT_SLINEAR; 183 | } 184 | } 185 | return speech_format; 186 | } 187 | static APR_INLINE const char* ast_format_get_unicodec(const ast_format_compat *format) 188 | { 189 | if(format->id == AST_FORMAT_ULAW) 190 | return "PCMU"; 191 | if(format->id == AST_FORMAT_ALAW) 192 | return "PCMA"; 193 | #if UNI_VERSION_AT_LEAST(1,8,0) 194 | if(format->id == AST_FORMAT_G722) 195 | return "G722"; 196 | #endif 197 | /*! Use Raw 16-bit Signed Linear PCM for the rest */ 198 | return "LPCM"; 199 | } 200 | static APR_INLINE int ast_format_get_bits_per_sample(const ast_format_compat *format) 201 | { 202 | /*! Raw mu-law and A-law data (G.711) */ 203 | if(format->id == AST_FORMAT_ULAW || format->id == AST_FORMAT_ALAW) 204 | return 8; 205 | #if UNI_VERSION_AT_LEAST(1,8,0) 206 | if(format->id == AST_FORMAT_G722) 207 | return 4; 208 | #endif 209 | /*! Use Raw 16-bit Signed Linear PCM for the rest */ 210 | return 16 * ast_format_get_sample_rate(format) / 8000; 211 | } 212 | #endif 213 | 214 | #if AST_VERSION_AT_LEAST(13,0,0) 215 | #elif AST_VERSION_AT_LEAST(10,0,0) 216 | static APR_INLINE void ast_channel_set_readformat(struct ast_channel *chan, ast_format_compat *format) 217 | { 218 | ast_set_read_format(chan, format); 219 | } 220 | static APR_INLINE void ast_channel_set_writeformat(struct ast_channel *chan, ast_format_compat *format) 221 | { 222 | ast_set_write_format(chan, format); 223 | } 224 | static APR_INLINE void ast_channel_set_rawreadformat(struct ast_channel *chan, ast_format_compat *format) 225 | { 226 | // Do nothing, defined for >= 13 only 227 | } 228 | static APR_INLINE void ast_channel_set_rawwriteformat(struct ast_channel *chan, ast_format_compat *format) 229 | { 230 | // Do nothing, defined for >= 13 only 231 | } 232 | #else /* <= 1.8 */ 233 | static APR_INLINE void ast_channel_set_readformat(struct ast_channel *chan, ast_format_compat *format) 234 | { 235 | ast_set_read_format(chan, format->id); 236 | } 237 | static APR_INLINE void ast_channel_set_writeformat(struct ast_channel *chan, ast_format_compat *format) 238 | { 239 | ast_set_write_format(chan, format->id); 240 | } 241 | static APR_INLINE void ast_channel_set_rawreadformat(struct ast_channel *chan, ast_format_compat *format) 242 | { 243 | // Do nothing, defined for >= 13 only 244 | } 245 | static APR_INLINE void ast_channel_set_rawwriteformat(struct ast_channel *chan, ast_format_compat *format) 246 | { 247 | // Do nothing, defined for >= 13 only 248 | } 249 | static APR_INLINE void ast_channel_readtrans_set(struct ast_channel *chan, struct ast_trans_pvt *value) 250 | { 251 | // Do nothing, defined for >= 11 only 252 | } 253 | static APR_INLINE void ast_channel_writetrans_set(struct ast_channel *chan, struct ast_trans_pvt *value) 254 | { 255 | // Do nothing, defined for >= 11 only 256 | } 257 | #endif 258 | 259 | #if AST_VERSION_AT_LEAST(11,0,0) 260 | static APR_INLINE ast_format_compat* ast_channel_get_speechreadformat(struct ast_channel *chan, apr_pool_t *pool) 261 | { 262 | ast_format_compat *raw_format = ast_channel_rawreadformat(chan); 263 | return ast_get_speechformat(raw_format, pool); 264 | } 265 | static APR_INLINE ast_format_compat* ast_channel_get_speechwriteformat(struct ast_channel *chan, apr_pool_t *pool) 266 | { 267 | ast_format_compat *raw_format = ast_channel_rawwriteformat(chan); 268 | return ast_get_speechformat(raw_format, pool); 269 | } 270 | static APR_INLINE ast_format_compat* ast_channel_get_readformat(struct ast_channel *chan, apr_pool_t *pool) 271 | { 272 | return ast_channel_readformat(chan); 273 | } 274 | static APR_INLINE ast_format_compat* ast_channel_get_writeformat(struct ast_channel *chan, apr_pool_t *pool) 275 | { 276 | return ast_channel_writeformat(chan); 277 | } 278 | static APR_INLINE ast_format_compat* ast_channel_get_rawreadformat(struct ast_channel *chan, apr_pool_t *pool) 279 | { 280 | return ast_channel_rawreadformat(chan); 281 | } 282 | static APR_INLINE ast_format_compat* ast_channel_get_rawwriteformat(struct ast_channel *chan, apr_pool_t *pool) 283 | { 284 | return ast_channel_rawwriteformat(chan); 285 | } 286 | #elif AST_VERSION_AT_LEAST(10,0,0) 287 | static APR_INLINE ast_format_compat* ast_channel_get_speechreadformat(struct ast_channel *chan, apr_pool_t *pool) 288 | { 289 | return ast_get_speechformat(chan->rawreadformat, pool); 290 | } 291 | static APR_INLINE ast_format_compat* ast_channel_get_speechwriteformat(struct ast_channel *chan, apr_pool_t *pool) 292 | { 293 | return ast_get_speechformat(chan->rawwriteformat, pool); 294 | } 295 | static APR_INLINE ast_format_compat* ast_channel_get_readformat(struct ast_channel *chan, apr_pool_t *pool) 296 | { 297 | return chan->readformat; 298 | } 299 | static APR_INLINE ast_format_compat* ast_channel_get_writeformat(struct ast_channel *chan, apr_pool_t *pool) 300 | { 301 | return chan->writeformat; 302 | } 303 | static APR_INLINE ast_format_compat* ast_channel_get_rawreadformat(struct ast_channel *chan, apr_pool_t *pool) 304 | { 305 | return chan->rawreadformat; 306 | } 307 | static APR_INLINE ast_format_compat* ast_channel_get_rawwriteformat(struct ast_channel *chan, apr_pool_t *pool) 308 | { 309 | return chan->rawwriteformat; 310 | } 311 | #else /* <= 1.8 */ 312 | static APR_INLINE ast_format_compat* ast_channel_get_speechreadformat(struct ast_channel *chan, apr_pool_t *pool) 313 | { 314 | ast_format_compat raw_format; 315 | ast_format_clear(&raw_format); 316 | raw_format.id = chan->rawreadformat; 317 | return ast_get_speechformat(&raw_format, pool); 318 | } 319 | static APR_INLINE ast_format_compat* ast_channel_get_speechwriteformat(struct ast_channel *chan, apr_pool_t *pool) 320 | { 321 | ast_format_compat raw_format; 322 | ast_format_clear(&raw_format); 323 | raw_format.id = chan->rawwriteformat; 324 | return ast_get_speechformat(&raw_format, pool); 325 | } 326 | static APR_INLINE ast_format_compat* ast_channel_get_readformat(struct ast_channel *chan, apr_pool_t *pool) 327 | { 328 | ast_format_compat *format = apr_palloc(pool, sizeof(ast_format_compat)); 329 | ast_format_clear(format); 330 | format->id = chan->readformat; 331 | return format; 332 | } 333 | static APR_INLINE ast_format_compat* ast_channel_get_writeformat(struct ast_channel *chan, apr_pool_t *pool) 334 | { 335 | ast_format_compat *format = apr_palloc(pool, sizeof(ast_format_compat)); 336 | ast_format_clear(format); 337 | format->id = chan->writeformat; 338 | return format; 339 | } 340 | static APR_INLINE ast_format_compat* ast_channel_get_rawreadformat(struct ast_channel *chan, apr_pool_t *pool) 341 | { 342 | ast_format_compat *format = apr_palloc(pool, sizeof(ast_format_compat)); 343 | ast_format_clear(format); 344 | format->id = chan->rawreadformat; 345 | return format; 346 | } 347 | static APR_INLINE ast_format_compat* ast_channel_get_rawwriteformat(struct ast_channel *chan, apr_pool_t *pool) 348 | { 349 | ast_format_compat *format = apr_palloc(pool, sizeof(ast_format_compat)); 350 | ast_format_clear(format); 351 | format->id = chan->rawwriteformat; 352 | return format; 353 | } 354 | #endif 355 | 356 | #if AST_VERSION_AT_LEAST(13,13,0) 357 | #else 358 | static APR_INLINE int ast_set_read_format_path(struct ast_channel *chan, struct ast_format *raw_format, struct ast_format *core_format) 359 | { 360 | ast_channel_readtrans_set(chan, NULL); 361 | ast_channel_set_readformat(chan, core_format); 362 | ast_channel_set_rawreadformat(chan, raw_format); 363 | return 0; 364 | } 365 | static APR_INLINE int ast_set_write_format_path(struct ast_channel *chan, struct ast_format *core_format, struct ast_format *raw_format) 366 | { 367 | ast_channel_writetrans_set(chan, NULL); 368 | ast_channel_set_writeformat(chan, core_format); 369 | ast_channel_set_rawwriteformat(chan, raw_format); 370 | return 0; 371 | } 372 | #endif 373 | 374 | /** 375 | * Backward compatible frame accessors. 376 | */ 377 | static APR_INLINE int ast_frame_get_dtmfkey(struct ast_frame *f) 378 | { 379 | #if AST_VERSION_AT_LEAST(1,8,0) 380 | return f->subclass.integer; 381 | #else 382 | return f->subclass; 383 | #endif 384 | } 385 | static APR_INLINE void* ast_frame_get_data(const struct ast_frame *f) 386 | { 387 | #if AST_VERSION_AT_LEAST(1,6,1) 388 | return (void *) (f->data.ptr); 389 | #else 390 | return (void *)(f->data); 391 | #endif 392 | } 393 | static APR_INLINE void ast_frame_set_data(struct ast_frame *f, void *data) 394 | { 395 | #if AST_VERSION_AT_LEAST(1,6,1) 396 | f->data.ptr = data; 397 | #else 398 | f->data = data; 399 | #endif 400 | } 401 | static APR_INLINE void ast_frame_set_format(struct ast_frame *f, ast_format_compat *format) 402 | { 403 | #if AST_VERSION_AT_LEAST(13,0,0) 404 | f->subclass.format = format; 405 | #elif AST_VERSION_AT_LEAST(10,0,0) 406 | ast_format_copy(&f->subclass.format, format); 407 | #elif AST_VERSION_AT_LEAST(1,8,0) 408 | f->subclass.codec = format->id; 409 | #else 410 | f->subclass = format->id; 411 | #endif 412 | } 413 | 414 | /** 415 | * Backward compatible URI encode function. 416 | */ 417 | static APR_INLINE char *ast_uri_encode_http(const char *string, char *outbuf, int buflen) 418 | { 419 | #if AST_VERSION_AT_LEAST(10,0,0) 420 | return ast_uri_encode(string, outbuf, buflen, ast_uri_http); 421 | #else 422 | return ast_uri_encode(string, outbuf, buflen, 1); 423 | #endif 424 | } 425 | 426 | /** 427 | * Backward compatible ASTERISK_REGISTER_FILE() macro. 428 | */ 429 | #if AST_VERSION_AT_LEAST(15,0,0) 430 | #define ASTERISK_REGISTER_FILE() 431 | #elif !AST_VERSION_AT_LEAST(14,0,0) 432 | #ifndef ASTERISK_REGISTER_FILE 433 | #define ASTERISK_REGISTER_FILE() ASTERISK_FILE_VERSION(__FILE__, "") 434 | #endif 435 | #endif 436 | 437 | /** 438 | * Asterisk JSON abstraction layer is available since Asterisk 12. 439 | */ 440 | #if AST_VERSION_AT_LEAST(12,0,0) 441 | #define WITH_AST_JSON 442 | #endif 443 | 444 | #endif /* AST_COMPAT_DEFS_H */ 445 | -------------------------------------------------------------------------------- /res-speech-unimrcp/Makefile.am: -------------------------------------------------------------------------------- 1 | MAINTAINERCLEANFILES = Makefile.in 2 | 3 | AM_CPPFLAGS = -I$(top_srcdir)/include $(UNIMRCP_INCLUDES) $(ASTERISK_INCLUDES) 4 | AM_CFLAGS = -DAST_MODULE_SELF_SYM="__internal_res_speech_unimrcp" 5 | 6 | mod_LTLIBRARIES = res_speech_unimrcp.la 7 | 8 | res_speech_unimrcp_la_SOURCES = res_speech_unimrcp.c 9 | 10 | res_speech_unimrcp_la_LDFLAGS = -avoid-version -no-undefined -module 11 | 12 | res_speech_unimrcp_la_LIBADD = $(UNIMRCP_LIBS) 13 | 14 | install-data-local: 15 | test -d $(DESTDIR)$(asterisk_conf_dir) || $(mkinstalldirs) $(DESTDIR)$(asterisk_conf_dir) 16 | test -f $(DESTDIR)$(asterisk_conf_dir)/res-speech-unimrcp.conf || $(INSTALL) -m 644 $(top_srcdir)/conf/res-speech-unimrcp.conf $(DESTDIR)$(asterisk_conf_dir) 17 | 18 | load: 19 | asterisk -rx "module load res_speech_unimrcp.so" 20 | 21 | unload: 22 | asterisk -rx "module unload res_speech_unimrcp.so" 23 | --------------------------------------------------------------------------------