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