Thu Apr 28 2011 16:56:40

Asterisk developer's documentation


app_playback.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2005, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  *
00021  * \brief Trivial application to playback a sound file
00022  *
00023  * \author Mark Spencer <markster@digium.com>
00024  * 
00025  * \ingroup applications
00026  */
00027  
00028 #include "asterisk.h"
00029 
00030 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 220292 $")
00031 
00032 #include "asterisk/file.h"
00033 #include "asterisk/pbx.h"
00034 #include "asterisk/module.h"
00035 #include "asterisk/app.h"
00036 /* This file provides config-file based 'say' functions, and implenents
00037  * some CLI commands.
00038  */
00039 #include "asterisk/say.h"  /* provides config-file based 'say' functions */
00040 #include "asterisk/cli.h"
00041 
00042 /*** DOCUMENTATION
00043    <application name="Playback" language="en_US">
00044       <synopsis>
00045          Play a file.
00046       </synopsis>
00047       <syntax>
00048          <parameter name="filenames" required="true" argsep="&amp;">
00049             <argument name="filename" required="true" />
00050             <argument name="filename2" multiple="true" />
00051          </parameter>
00052          <parameter name="options">
00053             <para>Comma separated list of options</para>
00054             <optionlist>
00055                <option name="skip">
00056                   <para>Do not play if not answered</para>
00057                </option>
00058                <option name="noanswer">
00059                   <para>Playback without answering, otherwise the channel will
00060                   be answered before the sound is played.</para>
00061                   <note><para>Not all channel types support playing messages while still on hook.</para></note>
00062                </option>
00063             </optionlist>
00064          </parameter>
00065       </syntax>
00066       <description>
00067          <para>Plays back given filenames (do not put extension of wav/alaw etc).
00068          The playback command answer the channel if no options are specified.
00069          If the file is non-existant it will fail</para>
00070          <para>This application sets the following channel variable upon completion:</para>
00071          <variablelist>
00072             <variable name="PLAYBACKSTATUS">
00073                <para>The status of the playback attempt as a text string.</para>
00074                <value name="SUCCESS"/>
00075                <value name="FAILED"/>
00076             </variable>
00077          </variablelist>
00078          <para>See Also: Background (application) -- for playing sound files that are interruptible</para>
00079          <para>WaitExten (application) -- wait for digits from caller, optionally play music on hold</para>
00080       </description>
00081    </application>
00082  ***/
00083 
00084 static char *app = "Playback";
00085 
00086 static struct ast_config *say_cfg = NULL;
00087 /* save the say' api calls.
00088  * The first entry is NULL if we have the standard source,
00089  * otherwise we are sourcing from here.
00090  * 'say load [new|old]' will enable the new or old method, or report status
00091  */
00092 static const void *say_api_buf[40];
00093 static const char *say_old = "old";
00094 static const char *say_new = "new";
00095 
00096 static void save_say_mode(const void *arg)
00097 {
00098    int i = 0;
00099    say_api_buf[i++] = arg;
00100 
00101    say_api_buf[i++] = ast_say_number_full;
00102    say_api_buf[i++] = ast_say_enumeration_full;
00103    say_api_buf[i++] = ast_say_digit_str_full;
00104    say_api_buf[i++] = ast_say_character_str_full;
00105    say_api_buf[i++] = ast_say_phonetic_str_full;
00106    say_api_buf[i++] = ast_say_datetime;
00107    say_api_buf[i++] = ast_say_time;
00108    say_api_buf[i++] = ast_say_date;
00109    say_api_buf[i++] = ast_say_datetime_from_now;
00110    say_api_buf[i++] = ast_say_date_with_format;
00111 }
00112 
00113 static void restore_say_mode(void *arg)
00114 {
00115    int i = 0;
00116    say_api_buf[i++] = arg;
00117 
00118    ast_say_number_full = say_api_buf[i++];
00119    ast_say_enumeration_full = say_api_buf[i++];
00120    ast_say_digit_str_full = say_api_buf[i++];
00121    ast_say_character_str_full = say_api_buf[i++];
00122    ast_say_phonetic_str_full = say_api_buf[i++];
00123    ast_say_datetime = say_api_buf[i++];
00124    ast_say_time = say_api_buf[i++];
00125    ast_say_date = say_api_buf[i++];
00126    ast_say_datetime_from_now = say_api_buf[i++];
00127    ast_say_date_with_format = say_api_buf[i++];
00128 }
00129 
00130 /* 
00131  * Typical 'say' arguments in addition to the date or number or string
00132  * to say. We do not include 'options' because they may be different
00133  * in recursive calls, and so they are better left as an external
00134  * parameter.
00135  */
00136 typedef struct {
00137    struct ast_channel *chan;
00138    const char *ints;
00139    const char *language;
00140    int audiofd;
00141    int ctrlfd;
00142 } say_args_t;
00143 
00144 static int s_streamwait3(const say_args_t *a, const char *fn)
00145 {
00146    int res = ast_streamfile(a->chan, fn, a->language);
00147    if (res) {
00148       ast_log(LOG_WARNING, "Unable to play message %s\n", fn);
00149       return res;
00150    }
00151    res = (a->audiofd  > -1 && a->ctrlfd > -1) ?
00152    ast_waitstream_full(a->chan, a->ints, a->audiofd, a->ctrlfd) :
00153    ast_waitstream(a->chan, a->ints);
00154    ast_stopstream(a->chan);
00155    return res;  
00156 }
00157 
00158 /*
00159  * the string is 'prefix:data' or prefix:fmt:data'
00160  * with ':' being invalid in strings.
00161  */
00162 static int do_say(say_args_t *a, const char *s, const char *options, int depth)
00163 {
00164    struct ast_variable *v;
00165    char *lang, *x, *rule = NULL;
00166    int ret = 0;   
00167    struct varshead head = { .first = NULL, .last = NULL };
00168    struct ast_var_t *n;
00169 
00170    ast_debug(2, "string <%s> depth <%d>\n", s, depth);
00171    if (depth++ > 10) {
00172       ast_log(LOG_WARNING, "recursion too deep, exiting\n");
00173       return -1;
00174    } else if (!say_cfg) {
00175       ast_log(LOG_WARNING, "no say.conf, cannot spell '%s'\n", s);
00176       return -1;
00177    }
00178 
00179    /* scan languages same as in file.c */
00180    if (a->language == NULL)
00181       a->language = "en";     /* default */
00182    ast_debug(2, "try <%s> in <%s>\n", s, a->language);
00183    lang = ast_strdupa(a->language);
00184    for (;;) {
00185       for (v = ast_variable_browse(say_cfg, lang); v ; v = v->next) {
00186          if (ast_extension_match(v->name, s)) {
00187             rule = ast_strdupa(v->value);
00188             break;
00189          }
00190       }
00191       if (rule)
00192          break;
00193       if ( (x = strchr(lang, '_')) )
00194          *x = '\0';      /* try without suffix */
00195       else if (strcmp(lang, "en"))
00196          lang = "en";    /* last resort, try 'en' if not done yet */
00197       else
00198          break;
00199    }
00200    if (!rule)
00201       return 0;
00202 
00203    /* skip up to two prefixes to get the value */
00204    if ( (x = strchr(s, ':')) )
00205       s = x + 1;
00206    if ( (x = strchr(s, ':')) )
00207       s = x + 1;
00208    ast_debug(2, "value is <%s>\n", s);
00209    n = ast_var_assign("SAY", s);
00210    AST_LIST_INSERT_HEAD(&head, n, entries);
00211 
00212    /* scan the body, one piece at a time */
00213    while ( !ret && (x = strsep(&rule, ",")) ) { /* exit on key */
00214       char fn[128];
00215       const char *p, *fmt, *data; /* format and data pointers */
00216 
00217       /* prepare a decent file name */
00218       x = ast_skip_blanks(x);
00219       ast_trim_blanks(x);
00220 
00221       /* replace variables */
00222       pbx_substitute_variables_varshead(&head, x, fn, sizeof(fn));
00223       ast_debug(2, "doing [%s]\n", fn);
00224 
00225       /* locate prefix and data, if any */
00226       fmt = strchr(fn, ':');
00227       if (!fmt || fmt == fn)  {  /* regular filename */
00228          ret = s_streamwait3(a, fn);
00229          continue;
00230       }
00231       fmt++;
00232       data = strchr(fmt, ':');   /* colon before data */
00233       if (!data || data == fmt) {   /* simple prefix-fmt */
00234          ret = do_say(a, fn, options, depth);
00235          continue;
00236       }
00237       /* prefix:fmt:data */
00238       for (p = fmt; p < data && ret <= 0; p++) {
00239          char fn2[sizeof(fn)];
00240          if (*p == ' ' || *p == '\t')  /* skip blanks */
00241             continue;
00242          if (*p == '\'') {/* file name - we trim them */
00243             char *y;
00244             strcpy(fn2, ast_skip_blanks(p+1));  /* make a full copy */
00245             y = strchr(fn2, '\'');
00246             if (!y) {
00247                p = data;   /* invalid. prepare to end */
00248                break;
00249             }
00250             *y = '\0';
00251             ast_trim_blanks(fn2);
00252             p = strchr(p+1, '\'');
00253             ret = s_streamwait3(a, fn2);
00254          } else {
00255             int l = fmt-fn;
00256             strcpy(fn2, fn); /* copy everything */
00257             /* after prefix, append the format */
00258             fn2[l++] = *p;
00259             strcpy(fn2 + l, data);
00260             ret = do_say(a, fn2, options, depth);
00261          }
00262          
00263          if (ret) {
00264             break;
00265          }
00266       }
00267    }
00268    ast_var_delete(n);
00269    return ret;
00270 }
00271 
00272 static int say_full(struct ast_channel *chan, const char *string,
00273    const char *ints, const char *lang, const char *options,
00274    int audiofd, int ctrlfd)
00275 {
00276    say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
00277    return do_say(&a, string, options, 0);
00278 }
00279 
00280 static int say_number_full(struct ast_channel *chan, int num,
00281    const char *ints, const char *lang, const char *options,
00282    int audiofd, int ctrlfd)
00283 {
00284    char buf[64];
00285    say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
00286    snprintf(buf, sizeof(buf), "num:%d", num);
00287    return do_say(&a, buf, options, 0);
00288 }
00289 
00290 static int say_enumeration_full(struct ast_channel *chan, int num,
00291    const char *ints, const char *lang, const char *options,
00292    int audiofd, int ctrlfd)
00293 {
00294    char buf[64];
00295    say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
00296    snprintf(buf, sizeof(buf), "enum:%d", num);
00297    return do_say(&a, buf, options, 0);
00298 }
00299 
00300 static int say_date_generic(struct ast_channel *chan, time_t t,
00301    const char *ints, const char *lang, const char *format, const char *timezonename, const char *prefix)
00302 {
00303    char buf[128];
00304    struct ast_tm tm;
00305    struct timeval when = { t, 0 };
00306    say_args_t a = { chan, ints, lang, -1, -1 };
00307    if (format == NULL)
00308       format = "";
00309 
00310    ast_localtime(&when, &tm, NULL);
00311    snprintf(buf, sizeof(buf), "%s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d",
00312       prefix,
00313       format,
00314       tm.tm_year+1900,
00315       tm.tm_mon+1,
00316       tm.tm_mday,
00317       tm.tm_hour,
00318       tm.tm_min,
00319       tm.tm_sec,
00320       tm.tm_wday,
00321       tm.tm_yday);
00322    return do_say(&a, buf, NULL, 0);
00323 }
00324 
00325 static int say_date_with_format(struct ast_channel *chan, time_t t,
00326    const char *ints, const char *lang, const char *format, const char *timezonename)
00327 {
00328    return say_date_generic(chan, t, ints, lang, format, timezonename, "datetime");
00329 }
00330 
00331 static int say_date(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
00332 {
00333    return say_date_generic(chan, t, ints, lang, "", NULL, "date");
00334 }
00335 
00336 static int say_time(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
00337 {
00338    return say_date_generic(chan, t, ints, lang, "", NULL, "time");
00339 }
00340 
00341 static int say_datetime(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
00342 {
00343    return say_date_generic(chan, t, ints, lang, "", NULL, "datetime");
00344 }
00345 
00346 /*
00347  * remap the 'say' functions to use those in this file
00348  */
00349 static int say_init_mode(const char *mode) {
00350    if (!strcmp(mode, say_new)) {
00351       if (say_cfg == NULL) {
00352          ast_log(LOG_ERROR, "There is no say.conf file to use new mode\n");
00353          return -1;
00354       }
00355       save_say_mode(say_new);
00356       ast_say_number_full = say_number_full;
00357 
00358       ast_say_enumeration_full = say_enumeration_full;
00359 #if 0
00360       ast_say_digits_full = say_digits_full;
00361       ast_say_digit_str_full = say_digit_str_full;
00362       ast_say_character_str_full = say_character_str_full;
00363       ast_say_phonetic_str_full = say_phonetic_str_full;
00364       ast_say_datetime_from_now = say_datetime_from_now;
00365 #endif
00366       ast_say_datetime = say_datetime;
00367       ast_say_time = say_time;
00368       ast_say_date = say_date;
00369       ast_say_date_with_format = say_date_with_format;
00370    } else if (!strcmp(mode, say_old) && say_api_buf[0] == say_new) {
00371       restore_say_mode(NULL);
00372    } else if (strcmp(mode, say_old)) {
00373       ast_log(LOG_WARNING, "unrecognized mode %s\n", mode);
00374       return -1;
00375    }
00376    
00377    return 0;
00378 }
00379 
00380 static char *__say_cli_init(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00381 {
00382    const char *old_mode = say_api_buf[0] ? say_new : say_old;
00383    char *mode;
00384    switch (cmd) {
00385    case CLI_INIT:
00386       e->command = "say load [new|old]";
00387       e->usage = 
00388          "Usage: say load [new|old]\n"
00389          "       say load\n"
00390          "           Report status of current say mode\n"
00391          "       say load new\n"
00392          "           Set say method, configured in say.conf\n"
00393          "       say load old\n"
00394          "           Set old say method, coded in asterisk core\n";
00395       return NULL;
00396    case CLI_GENERATE:
00397       return NULL;
00398    }
00399    if (a->argc == 2) {
00400       ast_cli(a->fd, "say mode is [%s]\n", old_mode);
00401       return CLI_SUCCESS;
00402    } else if (a->argc != e->args)
00403       return CLI_SHOWUSAGE;
00404    mode = a->argv[2];
00405    if (!strcmp(mode, old_mode))
00406       ast_cli(a->fd, "say mode is %s already\n", mode);
00407    else
00408       if (say_init_mode(mode) == 0)
00409          ast_cli(a->fd, "setting say mode from %s to %s\n", old_mode, mode);
00410 
00411    return CLI_SUCCESS;
00412 }
00413 
00414 static struct ast_cli_entry cli_playback[] = {
00415    AST_CLI_DEFINE(__say_cli_init, "Set or show the say mode"),
00416 };
00417 
00418 static int playback_exec(struct ast_channel *chan, void *data)
00419 {
00420    int res = 0;
00421    int mres = 0;
00422    char *tmp;
00423    int option_skip=0;
00424    int option_say=0;
00425    int option_noanswer = 0;
00426 
00427    AST_DECLARE_APP_ARGS(args,
00428       AST_APP_ARG(filenames);
00429       AST_APP_ARG(options);
00430    );
00431    
00432    if (ast_strlen_zero(data)) {
00433       ast_log(LOG_WARNING, "Playback requires an argument (filename)\n");
00434       return -1;
00435    }
00436 
00437    tmp = ast_strdupa(data);
00438    AST_STANDARD_APP_ARGS(args, tmp);
00439 
00440    if (args.options) {
00441       if (strcasestr(args.options, "skip"))
00442          option_skip = 1;
00443       if (strcasestr(args.options, "say"))
00444          option_say = 1;
00445       if (strcasestr(args.options, "noanswer"))
00446          option_noanswer = 1;
00447    } 
00448    if (chan->_state != AST_STATE_UP) {
00449       if (option_skip) {
00450          /* At the user's option, skip if the line is not up */
00451          goto done;
00452       } else if (!option_noanswer) {
00453          /* Otherwise answer unless we're supposed to send this while on-hook */
00454          res = ast_answer(chan);
00455       }
00456    }
00457    if (!res) {
00458       char *back = args.filenames;
00459       char *front;
00460 
00461       ast_stopstream(chan);
00462       while (!res && (front = strsep(&back, "&"))) {
00463          if (option_say)
00464             res = say_full(chan, front, "", chan->language, NULL, -1, -1);
00465          else
00466             res = ast_streamfile(chan, front, chan->language);
00467          if (!res) { 
00468             res = ast_waitstream(chan, "");  
00469             ast_stopstream(chan);
00470          } else {
00471             ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", chan->name, (char *)data);
00472             res = 0;
00473             mres = 1;
00474          }
00475       }
00476    }
00477 done:
00478    pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", mres ? "FAILED" : "SUCCESS");
00479    return res;
00480 }
00481 
00482 static int reload(void)
00483 {
00484    struct ast_variable *v;
00485    struct ast_flags config_flags = { CONFIG_FLAG_FILEUNCHANGED };
00486    struct ast_config *newcfg;
00487 
00488    if ((newcfg = ast_config_load("say.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) {
00489       return 0;
00490    } else if (newcfg == CONFIG_STATUS_FILEINVALID) {
00491       ast_log(LOG_ERROR, "Config file say.conf is in an invalid format.  Aborting.\n");
00492       return 0;
00493    }
00494 
00495    if (say_cfg) {
00496       ast_config_destroy(say_cfg);
00497       ast_log(LOG_NOTICE, "Reloading say.conf\n");
00498       say_cfg = newcfg;
00499    }
00500 
00501    if (say_cfg) {
00502       for (v = ast_variable_browse(say_cfg, "general"); v ; v = v->next) {
00503             if (ast_extension_match(v->name, "mode")) {
00504             say_init_mode(v->value);
00505             break;
00506          }
00507       }
00508    }
00509    
00510    /*
00511     * XXX here we should sort rules according to the same order
00512     * we have in pbx.c so we have the same matching behaviour.
00513     */
00514    return 0;
00515 }
00516 
00517 static int unload_module(void)
00518 {
00519    int res;
00520 
00521    res = ast_unregister_application(app);
00522 
00523    ast_cli_unregister_multiple(cli_playback, ARRAY_LEN(cli_playback));
00524 
00525    if (say_cfg)
00526       ast_config_destroy(say_cfg);
00527 
00528    return res; 
00529 }
00530 
00531 static int load_module(void)
00532 {
00533    struct ast_variable *v;
00534    struct ast_flags config_flags = { 0 };
00535 
00536    say_cfg = ast_config_load("say.conf", config_flags);
00537    if (say_cfg && say_cfg != CONFIG_STATUS_FILEINVALID) {
00538       for (v = ast_variable_browse(say_cfg, "general"); v ; v = v->next) {
00539             if (ast_extension_match(v->name, "mode")) {
00540             say_init_mode(v->value);
00541             break;
00542          }
00543       }
00544    }
00545 
00546    ast_cli_register_multiple(cli_playback, ARRAY_LEN(cli_playback));
00547    return ast_register_application_xml(app, playback_exec);
00548 }
00549 
00550 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Sound File Playback Application",
00551       .load = load_module,
00552       .unload = unload_module,
00553       .reload = reload,
00554           );