00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032 #include "asterisk.h"
00033
00034 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 419684 $");
00035
00036 #include "asterisk/file.h"
00037 #include "asterisk/channel.h"
00038 #include "asterisk/pbx.h"
00039 #include "asterisk/module.h"
00040 #include "asterisk/lock.h"
00041 #include "asterisk/app.h"
00042 #include "asterisk/speech.h"
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140
00141
00142
00143
00144
00145
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160
00161
00162
00163
00164
00165
00166
00167
00168
00169
00170
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206
00207
00208
00209
00210
00211
00212
00213
00214
00215
00216
00217
00218
00219
00220
00221
00222
00223
00224
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234
00235
00236
00237
00238
00239
00240
00241
00242
00243
00244
00245
00246
00247
00248
00249
00250
00251
00252
00253
00254
00255
00256
00257
00258
00259
00260
00261
00262 static void destroy_callback(void *data)
00263 {
00264 struct ast_speech *speech = (struct ast_speech*)data;
00265
00266 if (speech == NULL) {
00267 return;
00268 }
00269
00270
00271 ast_speech_destroy(speech);
00272
00273 return;
00274 }
00275
00276
00277 static const struct ast_datastore_info speech_datastore = {
00278 .type = "speech",
00279 .destroy = destroy_callback
00280 };
00281
00282
00283 static struct ast_speech *find_speech(struct ast_channel *chan)
00284 {
00285 struct ast_speech *speech = NULL;
00286 struct ast_datastore *datastore = NULL;
00287
00288 if (!chan) {
00289 return NULL;
00290 }
00291
00292 ast_channel_lock(chan);
00293 datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
00294 ast_channel_unlock(chan);
00295 if (datastore == NULL) {
00296 return NULL;
00297 }
00298 speech = datastore->data;
00299
00300 return speech;
00301 }
00302
00303
00304
00305
00306
00307
00308
00309
00310
00311
00312 static int speech_datastore_destroy(struct ast_channel *chan)
00313 {
00314 struct ast_datastore *datastore;
00315 int res;
00316
00317 ast_channel_lock(chan);
00318 datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
00319 if (datastore) {
00320 ast_channel_datastore_remove(chan, datastore);
00321 }
00322 ast_channel_unlock(chan);
00323 if (datastore) {
00324 ast_datastore_free(datastore);
00325 res = 0;
00326 } else {
00327 res = -1;
00328 }
00329 return res;
00330 }
00331
00332
00333 static struct ast_speech_result *find_result(struct ast_speech_result *results, char *result_num)
00334 {
00335 struct ast_speech_result *result = results;
00336 char *tmp = NULL;
00337 int nbest_num = 0, wanted_num = 0, i = 0;
00338
00339 if (!result) {
00340 return NULL;
00341 }
00342
00343 if ((tmp = strchr(result_num, '/'))) {
00344 *tmp++ = '\0';
00345 nbest_num = atoi(result_num);
00346 wanted_num = atoi(tmp);
00347 } else {
00348 wanted_num = atoi(result_num);
00349 }
00350
00351 do {
00352 if (result->nbest_num != nbest_num)
00353 continue;
00354 if (i == wanted_num)
00355 break;
00356 i++;
00357 } while ((result = AST_LIST_NEXT(result, list)));
00358
00359 return result;
00360 }
00361
00362
00363 static int speech_score(struct ast_channel *chan, const char *cmd, char *data,
00364 char *buf, size_t len)
00365 {
00366 struct ast_speech_result *result = NULL;
00367 struct ast_speech *speech = find_speech(chan);
00368 char tmp[128] = "";
00369
00370 if (data == NULL || speech == NULL || !(result = find_result(speech->results, data))) {
00371 return -1;
00372 }
00373
00374 snprintf(tmp, sizeof(tmp), "%d", result->score);
00375
00376 ast_copy_string(buf, tmp, len);
00377
00378 return 0;
00379 }
00380
00381 static struct ast_custom_function speech_score_function = {
00382 .name = "SPEECH_SCORE",
00383 .read = speech_score,
00384 .write = NULL,
00385 };
00386
00387
00388 static int speech_text(struct ast_channel *chan, const char *cmd, char *data,
00389 char *buf, size_t len)
00390 {
00391 struct ast_speech_result *result = NULL;
00392 struct ast_speech *speech = find_speech(chan);
00393
00394 if (data == NULL || speech == NULL || !(result = find_result(speech->results, data))) {
00395 return -1;
00396 }
00397
00398 if (result->text != NULL) {
00399 ast_copy_string(buf, result->text, len);
00400 } else {
00401 buf[0] = '\0';
00402 }
00403
00404 return 0;
00405 }
00406
00407 static struct ast_custom_function speech_text_function = {
00408 .name = "SPEECH_TEXT",
00409 .read = speech_text,
00410 .write = NULL,
00411 };
00412
00413
00414 static int speech_grammar(struct ast_channel *chan, const char *cmd, char *data,
00415 char *buf, size_t len)
00416 {
00417 struct ast_speech_result *result = NULL;
00418 struct ast_speech *speech = find_speech(chan);
00419
00420 if (data == NULL || speech == NULL || !(result = find_result(speech->results, data))) {
00421 return -1;
00422 }
00423
00424 if (result->grammar != NULL) {
00425 ast_copy_string(buf, result->grammar, len);
00426 } else {
00427 buf[0] = '\0';
00428 }
00429
00430 return 0;
00431 }
00432
00433 static struct ast_custom_function speech_grammar_function = {
00434 .name = "SPEECH_GRAMMAR",
00435 .read = speech_grammar,
00436 .write = NULL,
00437 };
00438
00439
00440 static int speech_engine_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
00441 {
00442 struct ast_speech *speech = find_speech(chan);
00443
00444 if (data == NULL || speech == NULL) {
00445 return -1;
00446 }
00447
00448 ast_speech_change(speech, data, value);
00449
00450 return 0;
00451 }
00452
00453 static struct ast_custom_function speech_engine_function = {
00454 .name = "SPEECH_ENGINE",
00455 .read = NULL,
00456 .write = speech_engine_write,
00457 };
00458
00459
00460 static int speech_results_type_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
00461 {
00462 struct ast_speech *speech = find_speech(chan);
00463
00464 if (data == NULL || speech == NULL)
00465 return -1;
00466
00467 if (!strcasecmp(value, "normal"))
00468 ast_speech_change_results_type(speech, AST_SPEECH_RESULTS_TYPE_NORMAL);
00469 else if (!strcasecmp(value, "nbest"))
00470 ast_speech_change_results_type(speech, AST_SPEECH_RESULTS_TYPE_NBEST);
00471
00472 return 0;
00473 }
00474
00475 static struct ast_custom_function speech_results_type_function = {
00476 .name = "SPEECH_RESULTS_TYPE",
00477 .read = NULL,
00478 .write = speech_results_type_write,
00479 };
00480
00481
00482 static int speech_read(struct ast_channel *chan, const char *cmd, char *data,
00483 char *buf, size_t len)
00484 {
00485 int results = 0;
00486 struct ast_speech_result *result = NULL;
00487 struct ast_speech *speech = find_speech(chan);
00488 char tmp[128] = "";
00489
00490
00491 if (!strcasecmp(data, "status")) {
00492 if (speech != NULL)
00493 ast_copy_string(buf, "1", len);
00494 else
00495 ast_copy_string(buf, "0", len);
00496 return 0;
00497 }
00498
00499
00500 if (speech == NULL) {
00501 return -1;
00502 }
00503
00504
00505 if (!strcasecmp(data, "spoke")) {
00506 if (ast_test_flag(speech, AST_SPEECH_SPOKE))
00507 ast_copy_string(buf, "1", len);
00508 else
00509 ast_copy_string(buf, "0", len);
00510 } else if (!strcasecmp(data, "results")) {
00511
00512 for (result = speech->results; result; result = AST_LIST_NEXT(result, list))
00513 results++;
00514 snprintf(tmp, sizeof(tmp), "%d", results);
00515 ast_copy_string(buf, tmp, len);
00516 } else {
00517 buf[0] = '\0';
00518 }
00519
00520 return 0;
00521 }
00522
00523 static struct ast_custom_function speech_function = {
00524 .name = "SPEECH",
00525 .read = speech_read,
00526 .write = NULL,
00527 };
00528
00529
00530
00531
00532 static int speech_create(struct ast_channel *chan, const char *data)
00533 {
00534 struct ast_speech *speech = NULL;
00535 struct ast_datastore *datastore = NULL;
00536
00537
00538 speech = ast_speech_new(data, chan->nativeformats);
00539 if (speech == NULL) {
00540
00541 pbx_builtin_setvar_helper(chan, "ERROR", "1");
00542 return 0;
00543 }
00544
00545 datastore = ast_datastore_alloc(&speech_datastore, NULL);
00546 if (datastore == NULL) {
00547 ast_speech_destroy(speech);
00548 pbx_builtin_setvar_helper(chan, "ERROR", "1");
00549 return 0;
00550 }
00551 pbx_builtin_setvar_helper(chan, "ERROR", NULL);
00552 datastore->data = speech;
00553 ast_channel_lock(chan);
00554 ast_channel_datastore_add(chan, datastore);
00555 ast_channel_unlock(chan);
00556
00557 return 0;
00558 }
00559
00560
00561 static int speech_load(struct ast_channel *chan, const char *vdata)
00562 {
00563 int res = 0;
00564 struct ast_speech *speech = find_speech(chan);
00565 char *data;
00566 AST_DECLARE_APP_ARGS(args,
00567 AST_APP_ARG(grammar);
00568 AST_APP_ARG(path);
00569 );
00570
00571 data = ast_strdupa(vdata);
00572 AST_STANDARD_APP_ARGS(args, data);
00573
00574 if (speech == NULL)
00575 return -1;
00576
00577 if (args.argc != 2)
00578 return -1;
00579
00580
00581 res = ast_speech_grammar_load(speech, args.grammar, args.path);
00582
00583 return res;
00584 }
00585
00586
00587 static int speech_unload(struct ast_channel *chan, const char *data)
00588 {
00589 int res = 0;
00590 struct ast_speech *speech = find_speech(chan);
00591
00592 if (speech == NULL)
00593 return -1;
00594
00595
00596 res = ast_speech_grammar_unload(speech, data);
00597
00598 return res;
00599 }
00600
00601
00602 static int speech_deactivate(struct ast_channel *chan, const char *data)
00603 {
00604 int res = 0;
00605 struct ast_speech *speech = find_speech(chan);
00606
00607 if (speech == NULL)
00608 return -1;
00609
00610
00611 res = ast_speech_grammar_deactivate(speech, data);
00612
00613 return res;
00614 }
00615
00616
00617 static int speech_activate(struct ast_channel *chan, const char *data)
00618 {
00619 int res = 0;
00620 struct ast_speech *speech = find_speech(chan);
00621
00622 if (speech == NULL)
00623 return -1;
00624
00625
00626 res = ast_speech_grammar_activate(speech, data);
00627
00628 return res;
00629 }
00630
00631
00632 static int speech_start(struct ast_channel *chan, const char *data)
00633 {
00634 int res = 0;
00635 struct ast_speech *speech = find_speech(chan);
00636
00637 if (speech == NULL)
00638 return -1;
00639
00640 ast_speech_start(speech);
00641
00642 return res;
00643 }
00644
00645
00646 static int speech_processing_sound(struct ast_channel *chan, const char *data)
00647 {
00648 int res = 0;
00649 struct ast_speech *speech = find_speech(chan);
00650
00651 if (speech == NULL)
00652 return -1;
00653
00654 if (speech->processing_sound != NULL) {
00655 ast_free(speech->processing_sound);
00656 speech->processing_sound = NULL;
00657 }
00658
00659 speech->processing_sound = ast_strdup(data);
00660
00661 return res;
00662 }
00663
00664
00665 static int speech_streamfile(struct ast_channel *chan, const char *filename, const char *preflang)
00666 {
00667 struct ast_filestream *fs = NULL;
00668
00669 if (!(fs = ast_openstream(chan, filename, preflang)))
00670 return -1;
00671
00672 if (ast_applystream(chan, fs))
00673 return -1;
00674
00675 ast_playstream(fs);
00676
00677 return 0;
00678 }
00679
00680 enum {
00681 SB_OPT_NOANSWER = (1 << 0),
00682 };
00683
00684 AST_APP_OPTIONS(speech_background_options, BEGIN_OPTIONS
00685 AST_APP_OPTION('n', SB_OPT_NOANSWER),
00686 END_OPTIONS );
00687
00688
00689 static int speech_background(struct ast_channel *chan, const char *data)
00690 {
00691 unsigned int timeout = 0;
00692 int res = 0, done = 0, started = 0, quieted = 0, max_dtmf_len = 0;
00693 struct ast_speech *speech = find_speech(chan);
00694 struct ast_frame *f = NULL;
00695 int oldreadformat = AST_FORMAT_SLINEAR;
00696 char dtmf[AST_MAX_EXTENSION] = "";
00697 struct timeval start = { 0, 0 }, current;
00698 char *parse, *filename_tmp = NULL, *filename = NULL, tmp[2] = "", dtmf_terminator = '#';
00699 const char *tmp2 = NULL;
00700 struct ast_flags options = { 0 };
00701 AST_DECLARE_APP_ARGS(args,
00702 AST_APP_ARG(soundfile);
00703 AST_APP_ARG(timeout);
00704 AST_APP_ARG(options);
00705 );
00706
00707 parse = ast_strdupa(data);
00708 AST_STANDARD_APP_ARGS(args, parse);
00709
00710 if (speech == NULL)
00711 return -1;
00712
00713 if (!ast_strlen_zero(args.options)) {
00714 char *options_buf = ast_strdupa(args.options);
00715 ast_app_parse_options(speech_background_options, &options, NULL, options_buf);
00716 }
00717
00718
00719 if (chan->_state != AST_STATE_UP && !ast_test_flag(&options, SB_OPT_NOANSWER)
00720 && ast_answer(chan)) {
00721 return -1;
00722 }
00723
00724
00725 oldreadformat = chan->readformat;
00726
00727
00728 if (ast_set_read_format(chan, speech->format))
00729 return -1;
00730
00731 if (!ast_strlen_zero(args.soundfile)) {
00732
00733 filename_tmp = ast_strdupa(args.soundfile);
00734 if (!ast_strlen_zero(args.timeout)) {
00735 if ((timeout = atof(args.timeout) * 1000.0) == 0)
00736 timeout = -1;
00737 } else
00738 timeout = 0;
00739 }
00740
00741
00742 ast_channel_lock(chan);
00743 if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_MAXLEN")) && !ast_strlen_zero(tmp2)) {
00744 max_dtmf_len = atoi(tmp2);
00745 }
00746
00747
00748 if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_TERMINATOR"))) {
00749 if (ast_strlen_zero(tmp2))
00750 dtmf_terminator = '\0';
00751 else
00752 dtmf_terminator = tmp2[0];
00753 }
00754 ast_channel_unlock(chan);
00755
00756
00757 if (speech->state == AST_SPEECH_STATE_NOT_READY || speech->state == AST_SPEECH_STATE_DONE) {
00758 ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
00759 ast_speech_start(speech);
00760 }
00761
00762
00763 ast_stopstream(chan);
00764
00765
00766 while (done == 0) {
00767
00768 if (!quieted && (chan->streamid == -1 && chan->timingfunc == NULL) && (filename = strsep(&filename_tmp, "&"))) {
00769
00770 ast_stopstream(chan);
00771
00772 speech_streamfile(chan, filename, chan->language);
00773 }
00774
00775
00776 ast_sched_runq(chan->sched);
00777
00778
00779 res = ast_sched_wait(chan->sched);
00780 if (res < 0)
00781 res = 1000;
00782
00783
00784 if (ast_waitfor(chan, res) > 0) {
00785 f = ast_read(chan);
00786 if (f == NULL) {
00787
00788 done = 3;
00789 break;
00790 }
00791 }
00792
00793
00794 if ((!quieted || strlen(dtmf)) && started == 1) {
00795 current = ast_tvnow();
00796 if ((ast_tvdiff_ms(current, start)) >= timeout) {
00797 done = 1;
00798 if (f)
00799 ast_frfree(f);
00800 break;
00801 }
00802 }
00803
00804
00805 ast_mutex_lock(&speech->lock);
00806 if (ast_test_flag(speech, AST_SPEECH_QUIET)) {
00807 if (chan->stream)
00808 ast_stopstream(chan);
00809 ast_clear_flag(speech, AST_SPEECH_QUIET);
00810 quieted = 1;
00811 }
00812
00813 switch (speech->state) {
00814 case AST_SPEECH_STATE_READY:
00815
00816 if (chan->streamid == -1 && chan->timingfunc == NULL)
00817 ast_stopstream(chan);
00818 if (!quieted && chan->stream == NULL && timeout && started == 0 && !filename_tmp) {
00819 if (timeout == -1) {
00820 done = 1;
00821 if (f)
00822 ast_frfree(f);
00823 break;
00824 }
00825 start = ast_tvnow();
00826 started = 1;
00827 }
00828
00829 if (!strlen(dtmf) && f != NULL && f->frametype == AST_FRAME_VOICE) {
00830 ast_speech_write(speech, f->data.ptr, f->datalen);
00831 }
00832 break;
00833 case AST_SPEECH_STATE_WAIT:
00834
00835 if (!strlen(dtmf)) {
00836 if (chan->stream == NULL) {
00837 if (speech->processing_sound != NULL) {
00838 if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound, "none")) {
00839 speech_streamfile(chan, speech->processing_sound, chan->language);
00840 }
00841 }
00842 } else if (chan->streamid == -1 && chan->timingfunc == NULL) {
00843 ast_stopstream(chan);
00844 if (speech->processing_sound != NULL) {
00845 if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound, "none")) {
00846 speech_streamfile(chan, speech->processing_sound, chan->language);
00847 }
00848 }
00849 }
00850 }
00851 break;
00852 case AST_SPEECH_STATE_DONE:
00853
00854 ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
00855 if (!strlen(dtmf)) {
00856
00857 speech->results = ast_speech_results_get(speech);
00858
00859 done = 1;
00860
00861 if (chan->stream != NULL) {
00862 ast_stopstream(chan);
00863 }
00864 }
00865 break;
00866 default:
00867 break;
00868 }
00869 ast_mutex_unlock(&speech->lock);
00870
00871
00872 if (f != NULL) {
00873
00874 switch (f->frametype) {
00875 case AST_FRAME_DTMF:
00876 if (dtmf_terminator != '\0' && f->subclass.integer == dtmf_terminator) {
00877 done = 1;
00878 } else {
00879 quieted = 1;
00880 if (chan->stream != NULL) {
00881 ast_stopstream(chan);
00882 }
00883 if (!started) {
00884
00885 timeout = (chan->pbx && chan->pbx->dtimeoutms) ? chan->pbx->dtimeoutms : 5000;
00886 started = 1;
00887 }
00888 start = ast_tvnow();
00889 snprintf(tmp, sizeof(tmp), "%c", f->subclass.integer);
00890 strncat(dtmf, tmp, sizeof(dtmf) - strlen(dtmf) - 1);
00891
00892 if (max_dtmf_len && strlen(dtmf) == max_dtmf_len)
00893 done = 1;
00894 }
00895 break;
00896 case AST_FRAME_CONTROL:
00897 switch (f->subclass.integer) {
00898 case AST_CONTROL_HANGUP:
00899
00900 done = 3;
00901 default:
00902 break;
00903 }
00904 default:
00905 break;
00906 }
00907 ast_frfree(f);
00908 f = NULL;
00909 }
00910 }
00911
00912 if (!ast_strlen_zero(dtmf)) {
00913
00914 speech->results = ast_calloc(1, sizeof(*speech->results));
00915 if (speech->results != NULL) {
00916 ast_speech_dtmf(speech, dtmf);
00917 speech->results->score = 1000;
00918 speech->results->text = ast_strdup(dtmf);
00919 speech->results->grammar = ast_strdup("dtmf");
00920 }
00921 ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
00922 }
00923
00924
00925 if (done == 3) {
00926 speech_datastore_destroy(chan);
00927 } else {
00928
00929 ast_set_read_format(chan, oldreadformat);
00930 }
00931
00932 return 0;
00933 }
00934
00935
00936
00937 static int speech_destroy(struct ast_channel *chan, const char *data)
00938 {
00939 if (!chan) {
00940 return -1;
00941 }
00942 return speech_datastore_destroy(chan);
00943 }
00944
00945 static int unload_module(void)
00946 {
00947 int res = 0;
00948
00949 res = ast_unregister_application("SpeechCreate");
00950 res |= ast_unregister_application("SpeechLoadGrammar");
00951 res |= ast_unregister_application("SpeechUnloadGrammar");
00952 res |= ast_unregister_application("SpeechActivateGrammar");
00953 res |= ast_unregister_application("SpeechDeactivateGrammar");
00954 res |= ast_unregister_application("SpeechStart");
00955 res |= ast_unregister_application("SpeechBackground");
00956 res |= ast_unregister_application("SpeechDestroy");
00957 res |= ast_unregister_application("SpeechProcessingSound");
00958 res |= ast_custom_function_unregister(&speech_function);
00959 res |= ast_custom_function_unregister(&speech_score_function);
00960 res |= ast_custom_function_unregister(&speech_text_function);
00961 res |= ast_custom_function_unregister(&speech_grammar_function);
00962 res |= ast_custom_function_unregister(&speech_engine_function);
00963 res |= ast_custom_function_unregister(&speech_results_type_function);
00964
00965 return res;
00966 }
00967
00968 static int load_module(void)
00969 {
00970 int res = 0;
00971
00972 res = ast_register_application_xml("SpeechCreate", speech_create);
00973 res |= ast_register_application_xml("SpeechLoadGrammar", speech_load);
00974 res |= ast_register_application_xml("SpeechUnloadGrammar", speech_unload);
00975 res |= ast_register_application_xml("SpeechActivateGrammar", speech_activate);
00976 res |= ast_register_application_xml("SpeechDeactivateGrammar", speech_deactivate);
00977 res |= ast_register_application_xml("SpeechStart", speech_start);
00978 res |= ast_register_application_xml("SpeechBackground", speech_background);
00979 res |= ast_register_application_xml("SpeechDestroy", speech_destroy);
00980 res |= ast_register_application_xml("SpeechProcessingSound", speech_processing_sound);
00981 res |= ast_custom_function_register(&speech_function);
00982 res |= ast_custom_function_register(&speech_score_function);
00983 res |= ast_custom_function_register(&speech_text_function);
00984 res |= ast_custom_function_register(&speech_grammar_function);
00985 res |= ast_custom_function_register(&speech_engine_function);
00986 res |= ast_custom_function_register(&speech_results_type_function);
00987
00988 return res;
00989 }
00990
00991 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Dialplan Speech Applications",
00992 .load = load_module,
00993 .unload = unload_module,
00994 .nonoptreq = "res_speech",
00995 );