Asterisk.org: Developer Documentation (28 Oct 2009)

Wed Oct 28 15:47:48 2009

## Asterisk developer's documentation

# app_record.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  * Matthew Fredrickson <creslin@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 record a sound file
    00022  *
    00023  * \ingroup applications
    00024  */
    00025
    00026 #include <stdlib.h>
    00027 #include <stdio.h>
    00028 #include <string.h>
    00029
    00030 #include "asterisk.h"
    00031
    00032 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 211526 $")
    00033
    00034 #include "asterisk/lock.h"
    00035 #include "asterisk/file.h"
    00036 #include "asterisk/logger.h"
    00037 #include "asterisk/channel.h"
    00038 #include "asterisk/pbx.h"
    00039 #include "asterisk/module.h"
    00040 #include "asterisk/translate.h"
    00041 #include "asterisk/dsp.h"
    00042 #include "asterisk/utils.h"
    00043 #include "asterisk/options.h"
    00044 #include "asterisk/app.h"
    00045
    00046 static char *tdesc = "Trivial Record Application";
    00047
    00048 static char *app = "Record";
    00049
    00050 static char *synopsis = "Record to a file";
    00051
    00052 static char *descrip =
    00053 "  Record(filename.format|silence[|maxduration][|options])\n\n"
    00054 "Records from the channel into a given filename. If the file exists it will\n"
    00055 "be overwritten.\n"
    00056 "- 'format' is the format of the file type to be recorded (wav, gsm, etc).\n"
    00057 "- 'silence' is the number of seconds of silence to allow before returning.\n"
    00058 "- 'maxduration' is the maximum recording duration in seconds. If missing\n"
    00059 "or 0 there is no maximum.\n"
    00060 "- 'options' may contain any of the following letters:\n"
    00061 "     'a' : append to existing recording rather than replacing\n"
    00062 "     'n' : do not answer, but record anyway if line not yet answered\n"
    00063 "     'q' : quiet (do not play a beep tone)\n"
    00064 "     's' : skip recording if the line is not yet answered\n"
    00065 "     't' : use alternate '*' terminator key instead of default '#'\n"
    00066 "\n"
    00067 "If filename contains '%d', these characters will be replaced with a number\n"
    00068 "incremented by one each time the file is recorded. A channel variable\n"
    00069 "named RECORDED_FILE will also be set, which contains the final filemname.\n\n"
    00070 "Use 'show file formats' to see the available formats on your system\n\n"
    00071 "User can press '#' to terminate the recording and continue to the next priority.\n\n"
    00072 "If the user should hangup during a recording, all data will be lost and the\n"
    00073 "application will teminate. \n";
    00074
    00075 STANDARD_LOCAL_USER;
    00076
    00077 LOCAL_USER_DECL;
    00078
    00079 static int record_exec(struct ast_channel *chan, void *data)
    00080 {
    00081    int res = 0;
    00082    int count = 0;
    00083    int percentflag = 0;
    00084    char *filename, *ext = NULL, *silstr, *maxstr, *options;
    00085    char *vdata, *p;
    00086    int i = 0;
    00087    char tmp[256];
    00088
    00089    struct ast_filestream *s = '\0';
    00090    struct localuser *u;
    00091    struct ast_frame *f = NULL;
    00092
    00093    struct ast_dsp *sildet = NULL;      /* silence detector dsp */
    00094    int totalsilence = 0;
    00095    int dspsilence = 0;
    00096    int silence = 0;     /* amount of silence to allow */
    00097    int gotsilence = 0;     /* did we timeout for silence? */
    00098    int maxduration = 0;    /* max duration of recording in milliseconds */
    00099    int gottimeout = 0;     /* did we timeout for maxduration exceeded? */
    00100    int option_skip = 0;
    00101    int option_noanswer = 0;
    00102    int option_append = 0;
    00103    int terminator = '#';
    00104    int option_quiet = 0;
    00105    int rfmt = 0;
    00106    int flags;
    00107    int waitres;
    00108    struct ast_silence_generator *silgen = NULL;
    00109
    00110    /* The next few lines of code parse out the filename and header from the input string */
    00111    if (ast_strlen_zero(data)) { /* no data implies no filename or anything is present */
    00112       ast_log(LOG_WARNING, "Record requires an argument (filename)\n");
    00113       return -1;
    00114    }
    00115
    00116    LOCAL_USER_ADD(u);
    00117
    00118    /* Yay for strsep being easy */
    00119    vdata = ast_strdupa(data);
    00120    if (!vdata) {
    00121       ast_log(LOG_ERROR, "Out of memory\n");
    00122       LOCAL_USER_REMOVE(u);
    00123       return -1;
    00124    }
    00125
    00126    p = vdata;
    00127    filename = strsep(&p, "|");
    00128    silstr = strsep(&p, "|");
    00129    maxstr = strsep(&p, "|");
    00130    options = strsep(&p, "|");
    00131
    00132    if (filename) {
    00133       if (strstr(filename, "%d"))
    00134          percentflag = 1;
    00135       ext = strrchr(filename, '.'); /* to support filename with a . in the filename, not format */
    00136       if (!ext)
    00137          ext = strchr(filename, ':');
    00138       if (ext) {
    00139          *ext = '\0';
    00140          ext++;
    00141       }
    00142    }
    00143    if (!ext) {
    00144       ast_log(LOG_WARNING, "No extension specified to filename!\n");
    00145       LOCAL_USER_REMOVE(u);
    00146       return -1;
    00147    }
    00148    if (silstr) {
    00149       if ((sscanf(silstr, "%30d", &i) == 1) && (i > -1)) {
    00150          silence = i * 1000;
    00151       } else if (!ast_strlen_zero(silstr)) {
    00152          ast_log(LOG_WARNING, "'%s' is not a valid silence duration\n", silstr);
    00153       }
    00154    }
    00155
    00156    if (maxstr) {
    00157       if ((sscanf(maxstr, "%30d", &i) == 1) && (i > -1))
    00158          /* Convert duration to milliseconds */
    00159          maxduration = i * 1000;
    00160       else if (!ast_strlen_zero(maxstr))
    00161          ast_log(LOG_WARNING, "'%s' is not a valid maximum duration\n", maxstr);
    00162    }
    00163    if (options) {
    00164       /* Retain backwards compatibility with old style options */
    00165       if (!strcasecmp(options, "skip"))
    00166          option_skip = 1;
    00167       else if (!strcasecmp(options, "noanswer"))
    00168          option_noanswer = 1;
    00169       else {
    00170          if (strchr(options, 's'))
    00171             option_skip = 1;
    00172          if (strchr(options, 'n'))
    00173             option_noanswer = 1;
    00174          if (strchr(options, 'a'))
    00175             option_append = 1;
    00176          if (strchr(options, 't'))
    00177             terminator = '*';
    00178          if (strchr(options, 'q'))
    00179             option_quiet = 1;
    00180       }
    00181    }
    00182
    00183    /* done parsing */
    00184
    00185    /* these are to allow the use of the %d in the config file for a wild card of sort to
    00186      create a new file with the inputed name scheme */
    00187    if (percentflag) {
    00188       AST_DECLARE_APP_ARGS(fname,
    00189          AST_APP_ARG(piece)[100];
    00190       );
    00191       char *tmp2 = ast_strdupa(filename);
    00192       char countstring[15];
    00193       int i;
    00194
    00195       /* Separate each piece out by the format specifier */
    00196       /* AST_NONSTANDARD_APP_ARGS(fname, tmp2, '%'); */
    00197       fname.argc = ast_app_separate_args(tmp2, '%', fname.argv, (sizeof(fname) - sizeof(fname.argc)) / sizeof(fname.argv[0]));
    00198       do {
    00199          int tmplen;
    00200          /* First piece has no leading percent, so it's copied verbatim */
    00201          ast_copy_string(tmp, fname.piece[0], sizeof(tmp));
    00202          tmplen = strlen(tmp);
    00203          for (i = 1; i < fname.argc; i++) {
    00204             if (fname.piece[i][0] == 'd') {
    00205                /* Substitute the count */
    00206                snprintf(countstring, sizeof(countstring), "%d", count);
    00207                ast_copy_string(tmp + tmplen, countstring, sizeof(tmp) - tmplen);
    00208                tmplen += strlen(countstring);
    00209             } else if (tmplen + 2 < sizeof(tmp)) {
    00210                /* Unknown format specifier - just copy it verbatim */
    00211                tmp[tmplen++] = '%';
    00212                tmp[tmplen++] = fname.piece[i][0];
    00213             }
    00214             /* Copy the remaining portion of the piece */
    00215             ast_copy_string(tmp + tmplen, &(fname.piece[i][1]), sizeof(tmp) - tmplen);
    00216          }
    00217          count++;
    00218       } while ( ast_fileexists(tmp, ext, chan->language) != -1 );
    00219       pbx_builtin_setvar_helper(chan, "RECORDED_FILE", tmp);
    00220    } else
    00221       strncpy(tmp, filename, sizeof(tmp)-1);
    00222    /* end of routine mentioned */
    00223
    00224
    00225
    00226    if (chan->_state != AST_STATE_UP) {
    00227       if (option_skip) {
    00228          /* At the user's option, skip if the line is not up */
    00229          LOCAL_USER_REMOVE(u);
    00230          return 0;
    00231       } else if (!option_noanswer) {
    00232          /* Otherwise answer unless we're supposed to record while on-hook */
    00233          res = ast_answer(chan);
    00234       }
    00235    }
    00236
    00237    if (res) {
    00238       ast_log(LOG_WARNING, "Could not answer channel '%s'\n", chan->name);
    00239       goto out;
    00240    }
    00241
    00242    if (!option_quiet) {
    00243       /* Some code to play a nice little beep to signify the start of the record operation */
    00244       res = ast_streamfile(chan, "beep", chan->language);
    00245       if (!res) {
    00246          res = ast_waitstream(chan, "");
    00247       } else {
    00248          ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", chan->name);
    00249       }
    00250       ast_stopstream(chan);
    00251    }
    00252
    00253    /* The end of beep code.  Now the recording starts */
    00254
    00255    if (silence > 0) {
    00256       rfmt = chan->readformat;
    00257       res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
    00258       if (res < 0) {
    00259          ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
    00260          LOCAL_USER_REMOVE(u);
    00261          return -1;
    00262       }
    00263       sildet = ast_dsp_new();
    00264       if (!sildet) {
    00265          ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
    00266          LOCAL_USER_REMOVE(u);
    00267          return -1;
    00268       }
    00269       ast_dsp_set_threshold(sildet, 256);
    00270    }
    00271
    00272
    00273    flags = option_append ? O_CREAT|O_APPEND|O_WRONLY : O_CREAT|O_TRUNC|O_WRONLY;
    00274    s = ast_writefile( tmp, ext, NULL, flags , 0, 0644);
    00275
    00276    if (!s) {
    00277       ast_log(LOG_WARNING, "Could not create file %s\n", filename);
    00278       goto out;
    00279    }
    00280
    00281    if (option_transmit_silence_during_record)
    00282       silgen = ast_channel_start_silence_generator(chan);
    00283
    00284    /* Request a video update */
    00285    ast_indicate(chan, AST_CONTROL_VIDUPDATE);
    00286
    00287    if (maxduration <= 0)
    00288       maxduration = -1;
    00289
    00290    while ((waitres = ast_waitfor(chan, maxduration)) > -1) {
    00291       if (maxduration > 0) {
    00292          if (waitres == 0) {
    00293             gottimeout = 1;
    00294             break;
    00295          }
    00296          maxduration = waitres;
    00297       }
    00298
    00299       f = ast_read(chan);
    00300       if (!f) {
    00301          res = -1;
    00302          break;
    00303       }
    00304       if (f->frametype == AST_FRAME_VOICE) {
    00305          res = ast_writestream(s, f);
    00306
    00307          if (res) {
    00308             ast_log(LOG_WARNING, "Problem writing frame\n");
    00309             ast_frfree(f);
    00310             break;
    00311          }
    00312
    00313          if (silence > 0) {
    00314             dspsilence = 0;
    00315             ast_dsp_silence(sildet, f, &dspsilence);
    00316             if (dspsilence) {
    00317                totalsilence = dspsilence;
    00318             } else {
    00319                totalsilence = 0;
    00320             }
    00321             if (totalsilence > silence) {
    00322                /* Ended happily with silence */
    00323                ast_frfree(f);
    00324                gotsilence = 1;
    00325                break;
    00326             }
    00327          }
    00328       } else if (f->frametype == AST_FRAME_VIDEO) {
    00329          res = ast_writestream(s, f);
    00330
    00331          if (res) {
    00332             ast_log(LOG_WARNING, "Problem writing frame\n");
    00333             ast_frfree(f);
    00334             break;
    00335          }
    00336       } else if ((f->frametype == AST_FRAME_DTMF) &&
    00337           (f->subclass == terminator)) {
    00338          ast_frfree(f);
    00339          break;
    00340       }
    00341       ast_frfree(f);
    00342    }
    00343    if (!f) {
    00344       ast_log(LOG_DEBUG, "Got hangup\n");
    00345       res = -1;
    00346    }
    00347
    00348    if (gotsilence) {
    00349       ast_stream_rewind(s, silence-1000);
    00350       ast_truncstream(s);
    00351    } else if (!gottimeout) {
    00352       /* Strip off the last 1/4 second of it */
    00353       ast_stream_rewind(s, 250);
    00354       ast_truncstream(s);
    00355    }
    00356    ast_closestream(s);
    00357
    00358    if (silgen)
    00359       ast_channel_stop_silence_generator(chan, silgen);
    00360
    00361  out:
    00362    if ((silence > 0) && rfmt) {
    00363       res = ast_set_read_format(chan, rfmt);
    00364       if (res)
    00365          ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", chan->name);
    00366       if (sildet)
    00367          ast_dsp_free(sildet);
    00368    }
    00369
    00370    LOCAL_USER_REMOVE(u);
    00371
    00372    return res;
    00373 }
    00374
    00375 int unload_module(void)
    00376 {
    00377    int res;
    00378
    00379    res = ast_unregister_application(app);
    00380
    00381    STANDARD_HANGUP_LOCALUSERS;
    00382
    00383    return res;
    00384 }
    00385
    00386 int load_module(void)
    00387 {
    00388    return ast_register_application(app, record_exec, synopsis, descrip);
    00389 }
    00390
    00391 char *description(void)
    00392 {
    00393    return tdesc;
    00394 }
    00395
    00396 int usecount(void)
    00397 {
    00398    int res;
    00399    STANDARD_USECOUNT(res);
    00400    return res;
    00401 }
    00402
    00403 char *key()
    00404 {
    00405    return ASTERISK_GPL_KEY;
    00406 }