Wed Oct 28 11:50:53 2009

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

Generated on Wed Oct 28 11:50:53 2009 for Asterisk - the Open Source PBX by  doxygen 1.5.6