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: 411314 $");
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 datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
00293 if (datastore == NULL) {
00294 return NULL;
00295 }
00296 speech = datastore->data;
00297
00298 return speech;
00299 }
00300
00301
00302 static struct ast_speech_result *find_result(struct ast_speech_result *results, char *result_num)
00303 {
00304 struct ast_speech_result *result = results;
00305 char *tmp = NULL;
00306 int nbest_num = 0, wanted_num = 0, i = 0;
00307
00308 if (!result) {
00309 return NULL;
00310 }
00311
00312 if ((tmp = strchr(result_num, '/'))) {
00313 *tmp++ = '\0';
00314 nbest_num = atoi(result_num);
00315 wanted_num = atoi(tmp);
00316 } else {
00317 wanted_num = atoi(result_num);
00318 }
00319
00320 do {
00321 if (result->nbest_num != nbest_num)
00322 continue;
00323 if (i == wanted_num)
00324 break;
00325 i++;
00326 } while ((result = AST_LIST_NEXT(result, list)));
00327
00328 return result;
00329 }
00330
00331
00332 static int speech_score(struct ast_channel *chan, const char *cmd, char *data,
00333 char *buf, size_t len)
00334 {
00335 struct ast_speech_result *result = NULL;
00336 struct ast_speech *speech = find_speech(chan);
00337 char tmp[128] = "";
00338
00339 if (data == NULL || speech == NULL || !(result = find_result(speech->results, data))) {
00340 return -1;
00341 }
00342
00343 snprintf(tmp, sizeof(tmp), "%d", result->score);
00344
00345 ast_copy_string(buf, tmp, len);
00346
00347 return 0;
00348 }
00349
00350 static struct ast_custom_function speech_score_function = {
00351 .name = "SPEECH_SCORE",
00352 .read = speech_score,
00353 .write = NULL,
00354 };
00355
00356
00357 static int speech_text(struct ast_channel *chan, const char *cmd, char *data,
00358 char *buf, size_t len)
00359 {
00360 struct ast_speech_result *result = NULL;
00361 struct ast_speech *speech = find_speech(chan);
00362
00363 if (data == NULL || speech == NULL || !(result = find_result(speech->results, data))) {
00364 return -1;
00365 }
00366
00367 if (result->text != NULL) {
00368 ast_copy_string(buf, result->text, len);
00369 } else {
00370 buf[0] = '\0';
00371 }
00372
00373 return 0;
00374 }
00375
00376 static struct ast_custom_function speech_text_function = {
00377 .name = "SPEECH_TEXT",
00378 .read = speech_text,
00379 .write = NULL,
00380 };
00381
00382
00383 static int speech_grammar(struct ast_channel *chan, const char *cmd, char *data,
00384 char *buf, size_t len)
00385 {
00386 struct ast_speech_result *result = NULL;
00387 struct ast_speech *speech = find_speech(chan);
00388
00389 if (data == NULL || speech == NULL || !(result = find_result(speech->results, data))) {
00390 return -1;
00391 }
00392
00393 if (result->grammar != NULL) {
00394 ast_copy_string(buf, result->grammar, len);
00395 } else {
00396 buf[0] = '\0';
00397 }
00398
00399 return 0;
00400 }
00401
00402 static struct ast_custom_function speech_grammar_function = {
00403 .name = "SPEECH_GRAMMAR",
00404 .read = speech_grammar,
00405 .write = NULL,
00406 };
00407
00408
00409 static int speech_engine_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
00410 {
00411 struct ast_speech *speech = find_speech(chan);
00412
00413 if (data == NULL || speech == NULL) {
00414 return -1;
00415 }
00416
00417 ast_speech_change(speech, data, value);
00418
00419 return 0;
00420 }
00421
00422 static struct ast_custom_function speech_engine_function = {
00423 .name = "SPEECH_ENGINE",
00424 .read = NULL,
00425 .write = speech_engine_write,
00426 };
00427
00428
00429 static int speech_results_type_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
00430 {
00431 struct ast_speech *speech = find_speech(chan);
00432
00433 if (data == NULL || speech == NULL)
00434 return -1;
00435
00436 if (!strcasecmp(value, "normal"))
00437 ast_speech_change_results_type(speech, AST_SPEECH_RESULTS_TYPE_NORMAL);
00438 else if (!strcasecmp(value, "nbest"))
00439 ast_speech_change_results_type(speech, AST_SPEECH_RESULTS_TYPE_NBEST);
00440
00441 return 0;
00442 }
00443
00444 static struct ast_custom_function speech_results_type_function = {
00445 .name = "SPEECH_RESULTS_TYPE",
00446 .read = NULL,
00447 .write = speech_results_type_write,
00448 };
00449
00450
00451 static int speech_read(struct ast_channel *chan, const char *cmd, char *data,
00452 char *buf, size_t len)
00453 {
00454 int results = 0;
00455 struct ast_speech_result *result = NULL;
00456 struct ast_speech *speech = find_speech(chan);
00457 char tmp[128] = "";
00458
00459
00460 if (!strcasecmp(data, "status")) {
00461 if (speech != NULL)
00462 ast_copy_string(buf, "1", len);
00463 else
00464 ast_copy_string(buf, "0", len);
00465 return 0;
00466 }
00467
00468
00469 if (speech == NULL) {
00470 return -1;
00471 }
00472
00473
00474 if (!strcasecmp(data, "spoke")) {
00475 if (ast_test_flag(speech, AST_SPEECH_SPOKE))
00476 ast_copy_string(buf, "1", len);
00477 else
00478 ast_copy_string(buf, "0", len);
00479 } else if (!strcasecmp(data, "results")) {
00480
00481 for (result = speech->results; result; result = AST_LIST_NEXT(result, list))
00482 results++;
00483 snprintf(tmp, sizeof(tmp), "%d", results);
00484 ast_copy_string(buf, tmp, len);
00485 } else {
00486 buf[0] = '\0';
00487 }
00488
00489 return 0;
00490 }
00491
00492 static struct ast_custom_function speech_function = {
00493 .name = "SPEECH",
00494 .read = speech_read,
00495 .write = NULL,
00496 };
00497
00498
00499
00500
00501 static int speech_create(struct ast_channel *chan, const char *data)
00502 {
00503 struct ast_speech *speech = NULL;
00504 struct ast_datastore *datastore = NULL;
00505
00506
00507 speech = ast_speech_new(data, ast_channel_nativeformats(chan));
00508 if (speech == NULL) {
00509
00510 pbx_builtin_setvar_helper(chan, "ERROR", "1");
00511 return 0;
00512 }
00513
00514 datastore = ast_datastore_alloc(&speech_datastore, NULL);
00515 if (datastore == NULL) {
00516 ast_speech_destroy(speech);
00517 pbx_builtin_setvar_helper(chan, "ERROR", "1");
00518 return 0;
00519 }
00520 pbx_builtin_setvar_helper(chan, "ERROR", NULL);
00521 datastore->data = speech;
00522 ast_channel_datastore_add(chan, datastore);
00523
00524 return 0;
00525 }
00526
00527
00528 static int speech_load(struct ast_channel *chan, const char *vdata)
00529 {
00530 int res = 0;
00531 struct ast_speech *speech = find_speech(chan);
00532 char *data;
00533 AST_DECLARE_APP_ARGS(args,
00534 AST_APP_ARG(grammar);
00535 AST_APP_ARG(path);
00536 );
00537
00538 data = ast_strdupa(vdata);
00539 AST_STANDARD_APP_ARGS(args, data);
00540
00541 if (speech == NULL)
00542 return -1;
00543
00544 if (args.argc != 2)
00545 return -1;
00546
00547
00548 res = ast_speech_grammar_load(speech, args.grammar, args.path);
00549
00550 return res;
00551 }
00552
00553
00554 static int speech_unload(struct ast_channel *chan, const char *data)
00555 {
00556 int res = 0;
00557 struct ast_speech *speech = find_speech(chan);
00558
00559 if (speech == NULL)
00560 return -1;
00561
00562
00563 res = ast_speech_grammar_unload(speech, data);
00564
00565 return res;
00566 }
00567
00568
00569 static int speech_deactivate(struct ast_channel *chan, const char *data)
00570 {
00571 int res = 0;
00572 struct ast_speech *speech = find_speech(chan);
00573
00574 if (speech == NULL)
00575 return -1;
00576
00577
00578 res = ast_speech_grammar_deactivate(speech, data);
00579
00580 return res;
00581 }
00582
00583
00584 static int speech_activate(struct ast_channel *chan, const char *data)
00585 {
00586 int res = 0;
00587 struct ast_speech *speech = find_speech(chan);
00588
00589 if (speech == NULL)
00590 return -1;
00591
00592
00593 res = ast_speech_grammar_activate(speech, data);
00594
00595 return res;
00596 }
00597
00598
00599 static int speech_start(struct ast_channel *chan, const char *data)
00600 {
00601 int res = 0;
00602 struct ast_speech *speech = find_speech(chan);
00603
00604 if (speech == NULL)
00605 return -1;
00606
00607 ast_speech_start(speech);
00608
00609 return res;
00610 }
00611
00612
00613 static int speech_processing_sound(struct ast_channel *chan, const char *data)
00614 {
00615 int res = 0;
00616 struct ast_speech *speech = find_speech(chan);
00617
00618 if (speech == NULL)
00619 return -1;
00620
00621 if (speech->processing_sound != NULL) {
00622 ast_free(speech->processing_sound);
00623 speech->processing_sound = NULL;
00624 }
00625
00626 speech->processing_sound = ast_strdup(data);
00627
00628 return res;
00629 }
00630
00631
00632 static int speech_streamfile(struct ast_channel *chan, const char *filename, const char *preflang)
00633 {
00634 struct ast_filestream *fs = NULL;
00635
00636 if (!(fs = ast_openstream(chan, filename, preflang)))
00637 return -1;
00638
00639 if (ast_applystream(chan, fs))
00640 return -1;
00641
00642 ast_playstream(fs);
00643
00644 return 0;
00645 }
00646
00647 enum {
00648 SB_OPT_NOANSWER = (1 << 0),
00649 };
00650
00651 AST_APP_OPTIONS(speech_background_options, BEGIN_OPTIONS
00652 AST_APP_OPTION('n', SB_OPT_NOANSWER),
00653 END_OPTIONS );
00654
00655
00656 static int speech_background(struct ast_channel *chan, const char *data)
00657 {
00658 unsigned int timeout = 0;
00659 int res = 0, done = 0, started = 0, quieted = 0, max_dtmf_len = 0;
00660 struct ast_speech *speech = find_speech(chan);
00661 struct ast_frame *f = NULL;
00662 struct ast_format oldreadformat;
00663 char dtmf[AST_MAX_EXTENSION] = "";
00664 struct timeval start = { 0, 0 }, current;
00665 struct ast_datastore *datastore = NULL;
00666 char *parse, *filename_tmp = NULL, *filename = NULL, tmp[2] = "", dtmf_terminator = '#';
00667 const char *tmp2 = NULL;
00668 struct ast_flags options = { 0 };
00669 AST_DECLARE_APP_ARGS(args,
00670 AST_APP_ARG(soundfile);
00671 AST_APP_ARG(timeout);
00672 AST_APP_ARG(options);
00673 );
00674
00675 parse = ast_strdupa(data);
00676 AST_STANDARD_APP_ARGS(args, parse);
00677
00678 ast_format_clear(&oldreadformat);
00679 if (speech == NULL)
00680 return -1;
00681
00682 if (!ast_strlen_zero(args.options)) {
00683 char *options_buf = ast_strdupa(args.options);
00684 ast_app_parse_options(speech_background_options, &options, NULL, options_buf);
00685 }
00686
00687
00688 if (ast_channel_state(chan) != AST_STATE_UP && !ast_test_flag(&options, SB_OPT_NOANSWER)
00689 && ast_answer(chan)) {
00690 return -1;
00691 }
00692
00693
00694 ast_format_copy(&oldreadformat, ast_channel_readformat(chan));
00695
00696
00697 if (ast_set_read_format(chan, &speech->format))
00698 return -1;
00699
00700 if (!ast_strlen_zero(args.soundfile)) {
00701
00702 filename_tmp = ast_strdupa(args.soundfile);
00703 if (!ast_strlen_zero(args.timeout)) {
00704 if ((timeout = atof(args.timeout) * 1000.0) == 0)
00705 timeout = -1;
00706 } else
00707 timeout = 0;
00708 }
00709
00710
00711 ast_channel_lock(chan);
00712 if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_MAXLEN")) && !ast_strlen_zero(tmp2)) {
00713 max_dtmf_len = atoi(tmp2);
00714 }
00715
00716
00717 if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_TERMINATOR"))) {
00718 if (ast_strlen_zero(tmp2))
00719 dtmf_terminator = '\0';
00720 else
00721 dtmf_terminator = tmp2[0];
00722 }
00723 ast_channel_unlock(chan);
00724
00725
00726 if (speech->state == AST_SPEECH_STATE_NOT_READY || speech->state == AST_SPEECH_STATE_DONE) {
00727 ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
00728 ast_speech_start(speech);
00729 }
00730
00731
00732 ast_stopstream(chan);
00733
00734
00735 while (done == 0) {
00736
00737 if (!quieted && (ast_channel_streamid(chan) == -1 && ast_channel_timingfunc(chan) == NULL) && (filename = strsep(&filename_tmp, "&"))) {
00738
00739 ast_stopstream(chan);
00740
00741 speech_streamfile(chan, filename, ast_channel_language(chan));
00742 }
00743
00744
00745 ast_sched_runq(ast_channel_sched(chan));
00746
00747
00748 res = ast_sched_wait(ast_channel_sched(chan));
00749 if (res < 0)
00750 res = 1000;
00751
00752
00753 if (ast_waitfor(chan, res) > 0) {
00754 f = ast_read(chan);
00755 if (f == NULL) {
00756
00757 done = 3;
00758 break;
00759 }
00760 }
00761
00762
00763 if ((!quieted || strlen(dtmf)) && started == 1) {
00764 current = ast_tvnow();
00765 if ((ast_tvdiff_ms(current, start)) >= timeout) {
00766 done = 1;
00767 if (f)
00768 ast_frfree(f);
00769 break;
00770 }
00771 }
00772
00773
00774 ast_mutex_lock(&speech->lock);
00775 if (ast_test_flag(speech, AST_SPEECH_QUIET)) {
00776 if (ast_channel_stream(chan))
00777 ast_stopstream(chan);
00778 ast_clear_flag(speech, AST_SPEECH_QUIET);
00779 quieted = 1;
00780 }
00781
00782 switch (speech->state) {
00783 case AST_SPEECH_STATE_READY:
00784
00785 if (ast_channel_streamid(chan) == -1 && ast_channel_timingfunc(chan) == NULL)
00786 ast_stopstream(chan);
00787 if (!quieted && ast_channel_stream(chan) == NULL && timeout && started == 0 && !filename_tmp) {
00788 if (timeout == -1) {
00789 done = 1;
00790 if (f)
00791 ast_frfree(f);
00792 break;
00793 }
00794 start = ast_tvnow();
00795 started = 1;
00796 }
00797
00798 if (!strlen(dtmf) && f != NULL && f->frametype == AST_FRAME_VOICE) {
00799 ast_speech_write(speech, f->data.ptr, f->datalen);
00800 }
00801 break;
00802 case AST_SPEECH_STATE_WAIT:
00803
00804 if (!strlen(dtmf)) {
00805 if (ast_channel_stream(chan) == NULL) {
00806 if (speech->processing_sound != NULL) {
00807 if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound, "none")) {
00808 speech_streamfile(chan, speech->processing_sound, ast_channel_language(chan));
00809 }
00810 }
00811 } else if (ast_channel_streamid(chan) == -1 && ast_channel_timingfunc(chan) == NULL) {
00812 ast_stopstream(chan);
00813 if (speech->processing_sound != NULL) {
00814 if (strlen(speech->processing_sound) > 0 && strcasecmp(speech->processing_sound, "none")) {
00815 speech_streamfile(chan, speech->processing_sound, ast_channel_language(chan));
00816 }
00817 }
00818 }
00819 }
00820 break;
00821 case AST_SPEECH_STATE_DONE:
00822
00823 ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
00824 if (!strlen(dtmf)) {
00825
00826 speech->results = ast_speech_results_get(speech);
00827
00828 done = 1;
00829
00830 if (ast_channel_stream(chan) != NULL) {
00831 ast_stopstream(chan);
00832 }
00833 }
00834 break;
00835 default:
00836 break;
00837 }
00838 ast_mutex_unlock(&speech->lock);
00839
00840
00841 if (f != NULL) {
00842
00843 switch (f->frametype) {
00844 case AST_FRAME_DTMF:
00845 if (dtmf_terminator != '\0' && f->subclass.integer == dtmf_terminator) {
00846 done = 1;
00847 } else {
00848 quieted = 1;
00849 if (ast_channel_stream(chan) != NULL) {
00850 ast_stopstream(chan);
00851 }
00852 if (!started) {
00853
00854 timeout = (ast_channel_pbx(chan) && ast_channel_pbx(chan)->dtimeoutms) ? ast_channel_pbx(chan)->dtimeoutms : 5000;
00855 started = 1;
00856 }
00857 start = ast_tvnow();
00858 snprintf(tmp, sizeof(tmp), "%c", f->subclass.integer);
00859 strncat(dtmf, tmp, sizeof(dtmf) - strlen(dtmf) - 1);
00860
00861 if (max_dtmf_len && strlen(dtmf) == max_dtmf_len)
00862 done = 1;
00863 }
00864 break;
00865 case AST_FRAME_CONTROL:
00866 switch (f->subclass.integer) {
00867 case AST_CONTROL_HANGUP:
00868
00869 done = 3;
00870 default:
00871 break;
00872 }
00873 default:
00874 break;
00875 }
00876 ast_frfree(f);
00877 f = NULL;
00878 }
00879 }
00880
00881 if (!ast_strlen_zero(dtmf)) {
00882
00883 speech->results = ast_calloc(1, sizeof(*speech->results));
00884 if (speech->results != NULL) {
00885 ast_speech_dtmf(speech, dtmf);
00886 speech->results->score = 1000;
00887 speech->results->text = ast_strdup(dtmf);
00888 speech->results->grammar = ast_strdup("dtmf");
00889 }
00890 ast_speech_change_state(speech, AST_SPEECH_STATE_NOT_READY);
00891 }
00892
00893
00894 if (done == 3) {
00895
00896 ast_speech_destroy(speech);
00897 datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
00898 if (datastore != NULL)
00899 ast_channel_datastore_remove(chan, datastore);
00900 } else {
00901
00902 ast_set_read_format(chan, &oldreadformat);
00903 }
00904
00905 return 0;
00906 }
00907
00908
00909
00910 static int speech_destroy(struct ast_channel *chan, const char *data)
00911 {
00912 int res = 0;
00913 struct ast_speech *speech = find_speech(chan);
00914 struct ast_datastore *datastore = NULL;
00915
00916 if (speech == NULL)
00917 return -1;
00918
00919
00920 ast_speech_destroy(speech);
00921
00922 datastore = ast_channel_datastore_find(chan, &speech_datastore, NULL);
00923 if (datastore != NULL) {
00924 ast_channel_datastore_remove(chan, datastore);
00925 }
00926
00927 return res;
00928 }
00929
00930 static int unload_module(void)
00931 {
00932 int res = 0;
00933
00934 res = ast_unregister_application("SpeechCreate");
00935 res |= ast_unregister_application("SpeechLoadGrammar");
00936 res |= ast_unregister_application("SpeechUnloadGrammar");
00937 res |= ast_unregister_application("SpeechActivateGrammar");
00938 res |= ast_unregister_application("SpeechDeactivateGrammar");
00939 res |= ast_unregister_application("SpeechStart");
00940 res |= ast_unregister_application("SpeechBackground");
00941 res |= ast_unregister_application("SpeechDestroy");
00942 res |= ast_unregister_application("SpeechProcessingSound");
00943 res |= ast_custom_function_unregister(&speech_function);
00944 res |= ast_custom_function_unregister(&speech_score_function);
00945 res |= ast_custom_function_unregister(&speech_text_function);
00946 res |= ast_custom_function_unregister(&speech_grammar_function);
00947 res |= ast_custom_function_unregister(&speech_engine_function);
00948 res |= ast_custom_function_unregister(&speech_results_type_function);
00949
00950 return res;
00951 }
00952
00953 static int load_module(void)
00954 {
00955 int res = 0;
00956
00957 res = ast_register_application_xml("SpeechCreate", speech_create);
00958 res |= ast_register_application_xml("SpeechLoadGrammar", speech_load);
00959 res |= ast_register_application_xml("SpeechUnloadGrammar", speech_unload);
00960 res |= ast_register_application_xml("SpeechActivateGrammar", speech_activate);
00961 res |= ast_register_application_xml("SpeechDeactivateGrammar", speech_deactivate);
00962 res |= ast_register_application_xml("SpeechStart", speech_start);
00963 res |= ast_register_application_xml("SpeechBackground", speech_background);
00964 res |= ast_register_application_xml("SpeechDestroy", speech_destroy);
00965 res |= ast_register_application_xml("SpeechProcessingSound", speech_processing_sound);
00966 res |= ast_custom_function_register(&speech_function);
00967 res |= ast_custom_function_register(&speech_score_function);
00968 res |= ast_custom_function_register(&speech_text_function);
00969 res |= ast_custom_function_register(&speech_grammar_function);
00970 res |= ast_custom_function_register(&speech_engine_function);
00971 res |= ast_custom_function_register(&speech_results_type_function);
00972
00973 return res;
00974 }
00975
00976 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Dialplan Speech Applications",
00977 .load = load_module,
00978 .unload = unload_module,
00979 .nonoptreq = "res_speech",
00980 );