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

Generated on Thu Apr 16 06:27:11 2015 for Asterisk - The Open Source Telephony Project by  doxygen 1.5.6