app_record.c File Reference

Trivial application to record a sound file. More...

#include "asterisk.h"
#include "asterisk/file.h"
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/app.h"
#include "asterisk/channel.h"
#include "asterisk/dsp.h"
#include "asterisk/format_cache.h"

Include dependency graph for app_record.c:

Go to the source code of this file.

Defines

#define OPERATOR_KEY   '0'

Enumerations

enum  {
  OPTION_APPEND = (1 << 0), OPTION_NOANSWER = (1 << 1), OPTION_QUIET = (1 << 2), OPTION_SKIP = (1 << 3),
  OPTION_STAR_TERMINATE = (1 << 4), OPTION_IGNORE_TERMINATE = (1 << 5), OPTION_KEEP = (1 << 6), FLAG_HAS_PERCENT = (1 << 7),
  OPTION_ANY_TERMINATE = (1 << 8), OPTION_OPERATOR_EXIT = (1 << 9)
}

Functions

static void __reg_module (void)
static void __unreg_module (void)
static int load_module (void)
static int record_dtmf_response (struct ast_channel *chan, struct ast_flags *flags, int dtmf_integer, int terminator)
static int record_exec (struct ast_channel *chan, const char *data)
static int unload_module (void)

Variables

static struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "Trivial Record Application" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_DEFAULT, .support_level = AST_MODULE_SUPPORT_CORE, }
static char * app = "Record"
static struct ast_app_option app_opts [128] = { [ 'a' ] = { .flag = OPTION_APPEND }, [ 'k' ] = { .flag = OPTION_KEEP }, [ 'n' ] = { .flag = OPTION_NOANSWER }, [ 'o' ] = { .flag = OPTION_OPERATOR_EXIT }, [ 'q' ] = { .flag = OPTION_QUIET }, [ 's' ] = { .flag = OPTION_SKIP }, [ 't' ] = { .flag = OPTION_STAR_TERMINATE }, [ 'y' ] = { .flag = OPTION_ANY_TERMINATE }, [ 'x' ] = { .flag = OPTION_IGNORE_TERMINATE },}
static struct ast_module_infoast_module_info = &__mod_info


Detailed Description

Trivial application to record a sound file.

Author:
Matthew Fredrickson <creslin@digium.com>

Definition in file app_record.c.


Define Documentation

#define OPERATOR_KEY   '0'

Definition at line 121 of file app_record.c.

Referenced by record_dtmf_response().


Enumeration Type Documentation

anonymous enum

Enumerator:
OPTION_APPEND 
OPTION_NOANSWER 
OPTION_QUIET 
OPTION_SKIP 
OPTION_STAR_TERMINATE 
OPTION_IGNORE_TERMINATE 
OPTION_KEEP 
FLAG_HAS_PERCENT 
OPTION_ANY_TERMINATE 
OPTION_OPERATOR_EXIT 

Definition at line 125 of file app_record.c.

00125      {
00126    OPTION_APPEND = (1 << 0),
00127    OPTION_NOANSWER = (1 << 1),
00128    OPTION_QUIET = (1 << 2),
00129    OPTION_SKIP = (1 << 3),
00130    OPTION_STAR_TERMINATE = (1 << 4),
00131    OPTION_IGNORE_TERMINATE = (1 << 5),
00132    OPTION_KEEP = (1 << 6),
00133    FLAG_HAS_PERCENT = (1 << 7),
00134    OPTION_ANY_TERMINATE = (1 << 8),
00135    OPTION_OPERATOR_EXIT = (1 << 9),
00136 };


Function Documentation

static void __reg_module ( void   )  [static]

Definition at line 489 of file app_record.c.

static void __unreg_module ( void   )  [static]

Definition at line 489 of file app_record.c.

static int load_module ( void   )  [static]

Definition at line 484 of file app_record.c.

References ast_register_application_xml, and record_exec().

00485 {
00486    return ast_register_application_xml(app, record_exec);
00487 }

static int record_dtmf_response ( struct ast_channel chan,
struct ast_flags flags,
int  dtmf_integer,
int  terminator 
) [static]

Definition at line 163 of file app_record.c.

References ast_test_flag, OPERATOR_KEY, OPTION_ANY_TERMINATE, OPTION_OPERATOR_EXIT, and pbx_builtin_setvar_helper().

Referenced by record_exec().

00164 {
00165    if ((dtmf_integer == OPERATOR_KEY) &&
00166       (ast_test_flag(flags, OPTION_OPERATOR_EXIT))) {
00167       pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "OPERATOR");
00168       return -1;
00169    }
00170 
00171    if ((dtmf_integer == terminator) ||
00172       (ast_test_flag(flags, OPTION_ANY_TERMINATE))) {
00173       pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "DTMF");
00174       return -1;
00175    }
00176 
00177    return 0;
00178 }

static int record_exec ( struct ast_channel chan,
const char *  data 
) [static]

Definition at line 180 of file app_record.c.

References ao2_bump, ao2_cleanup, app_opts, args, ast_answer(), AST_APP_ARG, ast_app_parse_options(), ast_channel_language(), ast_channel_name(), ast_channel_readformat(), ast_channel_start_silence_generator(), ast_channel_stop_silence_generator(), ast_closestream(), AST_CONTROL_VIDUPDATE, ast_copy_string(), ast_debug, AST_DECLARE_APP_ARGS, ast_dsp_free(), ast_dsp_get_threshold_from_settings(), ast_dsp_new(), ast_dsp_set_threshold(), ast_dsp_silence(), AST_FILE_MODE, ast_filedelete(), ast_fileexists(), ast_format_slin, AST_FRAME_DTMF, AST_FRAME_VIDEO, AST_FRAME_VOICE, ast_frfree, ast_indicate(), ast_log, ast_mkdir(), AST_NONSTANDARD_APP_ARGS, ast_opt_transmit_silence, ast_read(), ast_remaining_ms(), ast_set_flag, ast_set_read_format(), AST_STANDARD_APP_ARGS, AST_STATE_UP, ast_stopstream(), ast_strdupa, ast_stream_rewind(), ast_streamfile(), ast_strlen_zero, ast_test_flag, ast_truncstream(), ast_tvnow(), ast_waitfor(), ast_waitstream(), ast_writefile(), ast_writestream(), ext, f, FLAG_HAS_PERCENT, ast_frame::frametype, ast_frame_subclass::integer, LOG_WARNING, NULL, OPTION_APPEND, OPTION_IGNORE_TERMINATE, OPTION_KEEP, OPTION_NOANSWER, OPTION_QUIET, OPTION_SKIP, OPTION_STAR_TERMINATE, out, parse(), pbx_builtin_setvar_helper(), RAII_VAR, record_dtmf_response(), ast_frame::subclass, THRESHOLD_SILENCE, tmp(), and ast_dsp::totalsilence.

Referenced by load_module().

00181 {
00182    int res = 0;
00183    int count = 0;
00184    char *ext = NULL, *opts[0];
00185    char *parse, *dir, *file;
00186    int i = 0;
00187    char tmp[256];
00188 
00189    struct ast_filestream *s = NULL;
00190    struct ast_frame *f = NULL;
00191    
00192    struct ast_dsp *sildet = NULL;      /* silence detector dsp */
00193    int totalsilence = 0;
00194    int dspsilence = 0;
00195    int silence = 0;     /* amount of silence to allow */
00196    int gotsilence = 0;     /* did we timeout for silence? */
00197    int maxduration = 0;    /* max duration of recording in milliseconds */
00198    int gottimeout = 0;     /* did we timeout for maxduration exceeded? */
00199    int terminator = '#';
00200    RAII_VAR(struct ast_format *, rfmt, NULL, ao2_cleanup);
00201    int ioflags;
00202    struct ast_silence_generator *silgen = NULL;
00203    struct ast_flags flags = { 0, };
00204    AST_DECLARE_APP_ARGS(args,
00205       AST_APP_ARG(filename);
00206       AST_APP_ARG(silence);
00207       AST_APP_ARG(maxduration);
00208       AST_APP_ARG(options);
00209    );
00210    int ms;
00211    struct timeval start;
00212 
00213    /* The next few lines of code parse out the filename and header from the input string */
00214    if (ast_strlen_zero(data)) { /* no data implies no filename or anything is present */
00215       ast_log(LOG_WARNING, "Record requires an argument (filename)\n");
00216       pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
00217       return -1;
00218    }
00219 
00220    parse = ast_strdupa(data);
00221    AST_STANDARD_APP_ARGS(args, parse);
00222    if (args.argc == 4)
00223       ast_app_parse_options(app_opts, &flags, opts, args.options);
00224 
00225    if (!ast_strlen_zero(args.filename)) {
00226       if (strstr(args.filename, "%d"))
00227          ast_set_flag(&flags, FLAG_HAS_PERCENT);
00228       ext = strrchr(args.filename, '.'); /* to support filename with a . in the filename, not format */
00229       if (!ext)
00230          ext = strchr(args.filename, ':');
00231       if (ext) {
00232          *ext = '\0';
00233          ext++;
00234       }
00235    }
00236    if (!ext) {
00237       ast_log(LOG_WARNING, "No extension specified to filename!\n");
00238       pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
00239       return -1;
00240    }
00241    if (args.silence) {
00242       if ((sscanf(args.silence, "%30d", &i) == 1) && (i > -1)) {
00243          silence = i * 1000;
00244       } else if (!ast_strlen_zero(args.silence)) {
00245          ast_log(LOG_WARNING, "'%s' is not a valid silence duration\n", args.silence);
00246       }
00247    }
00248    
00249    if (args.maxduration) {
00250       if ((sscanf(args.maxduration, "%30d", &i) == 1) && (i > -1))
00251          /* Convert duration to milliseconds */
00252          maxduration = i * 1000;
00253       else if (!ast_strlen_zero(args.maxduration))
00254          ast_log(LOG_WARNING, "'%s' is not a valid maximum duration\n", args.maxduration);
00255    }
00256 
00257    if (ast_test_flag(&flags, OPTION_STAR_TERMINATE))
00258       terminator = '*';
00259    if (ast_test_flag(&flags, OPTION_IGNORE_TERMINATE))
00260       terminator = '\0';
00261 
00262    /* done parsing */
00263 
00264    /* these are to allow the use of the %d in the config file for a wild card of sort to
00265      create a new file with the inputed name scheme */
00266    if (ast_test_flag(&flags, FLAG_HAS_PERCENT)) {
00267       AST_DECLARE_APP_ARGS(fname,
00268          AST_APP_ARG(piece)[100];
00269       );
00270       char *tmp2 = ast_strdupa(args.filename);
00271       char countstring[15];
00272       int idx;
00273 
00274       /* Separate each piece out by the format specifier */
00275       AST_NONSTANDARD_APP_ARGS(fname, tmp2, '%');
00276       do {
00277          int tmplen;
00278          /* First piece has no leading percent, so it's copied verbatim */
00279          ast_copy_string(tmp, fname.piece[0], sizeof(tmp));
00280          tmplen = strlen(tmp);
00281          for (idx = 1; idx < fname.argc; idx++) {
00282             if (fname.piece[idx][0] == 'd') {
00283                /* Substitute the count */
00284                snprintf(countstring, sizeof(countstring), "%d", count);
00285                ast_copy_string(tmp + tmplen, countstring, sizeof(tmp) - tmplen);
00286                tmplen += strlen(countstring);
00287             } else if (tmplen + 2 < sizeof(tmp)) {
00288                /* Unknown format specifier - just copy it verbatim */
00289                tmp[tmplen++] = '%';
00290                tmp[tmplen++] = fname.piece[idx][0];
00291             }
00292             /* Copy the remaining portion of the piece */
00293             ast_copy_string(tmp + tmplen, &(fname.piece[idx][1]), sizeof(tmp) - tmplen);
00294          }
00295          count++;
00296       } while (ast_fileexists(tmp, ext, ast_channel_language(chan)) > 0);
00297       pbx_builtin_setvar_helper(chan, "RECORDED_FILE", tmp);
00298    } else
00299       ast_copy_string(tmp, args.filename, sizeof(tmp));
00300    /* end of routine mentioned */
00301 
00302    if (ast_channel_state(chan) != AST_STATE_UP) {
00303       if (ast_test_flag(&flags, OPTION_SKIP)) {
00304          /* At the user's option, skip if the line is not up */
00305          pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "SKIP");
00306          return 0;
00307       } else if (!ast_test_flag(&flags, OPTION_NOANSWER)) {
00308          /* Otherwise answer unless we're supposed to record while on-hook */
00309          res = ast_answer(chan);
00310       }
00311    }
00312 
00313    if (res) {
00314       ast_log(LOG_WARNING, "Could not answer channel '%s'\n", ast_channel_name(chan));
00315       pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
00316       goto out;
00317    }
00318 
00319    if (!ast_test_flag(&flags, OPTION_QUIET)) {
00320       /* Some code to play a nice little beep to signify the start of the record operation */
00321       res = ast_streamfile(chan, "beep", ast_channel_language(chan));
00322       if (!res) {
00323          res = ast_waitstream(chan, "");
00324       } else {
00325          ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", ast_channel_name(chan));
00326       }
00327       ast_stopstream(chan);
00328    }
00329 
00330    /* The end of beep code.  Now the recording starts */
00331 
00332    if (silence > 0) {
00333       rfmt = ao2_bump(ast_channel_readformat(chan));
00334       res = ast_set_read_format(chan, ast_format_slin);
00335       if (res < 0) {
00336          ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
00337          pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
00338          return -1;
00339       }
00340       sildet = ast_dsp_new();
00341       if (!sildet) {
00342          ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
00343          pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
00344          return -1;
00345       }
00346       ast_dsp_set_threshold(sildet, ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE));
00347    } 
00348 
00349    /* Create the directory if it does not exist. */
00350    dir = ast_strdupa(tmp);
00351    if ((file = strrchr(dir, '/')))
00352       *file++ = '\0';
00353    ast_mkdir (dir, 0777);
00354 
00355    ioflags = ast_test_flag(&flags, OPTION_APPEND) ? O_CREAT|O_APPEND|O_WRONLY : O_CREAT|O_TRUNC|O_WRONLY;
00356    s = ast_writefile(tmp, ext, NULL, ioflags, 0, AST_FILE_MODE);
00357 
00358    if (!s) {
00359       ast_log(LOG_WARNING, "Could not create file %s\n", args.filename);
00360       pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
00361       goto out;
00362    }
00363 
00364    if (ast_opt_transmit_silence)
00365       silgen = ast_channel_start_silence_generator(chan);
00366 
00367    /* Request a video update */
00368    ast_indicate(chan, AST_CONTROL_VIDUPDATE);
00369 
00370    if (maxduration <= 0)
00371       maxduration = -1;
00372 
00373    start = ast_tvnow();
00374    while ((ms = ast_remaining_ms(start, maxduration))) {
00375       ms = ast_waitfor(chan, ms);
00376       if (ms < 0) {
00377          break;
00378       }
00379 
00380       if (maxduration > 0 && ms == 0) {
00381          break;
00382       }
00383 
00384       f = ast_read(chan);
00385       if (!f) {
00386          res = -1;
00387          break;
00388       }
00389       if (f->frametype == AST_FRAME_VOICE) {
00390          res = ast_writestream(s, f);
00391 
00392          if (res) {
00393             ast_log(LOG_WARNING, "Problem writing frame\n");
00394             ast_frfree(f);
00395             pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
00396             break;
00397          }
00398 
00399          if (silence > 0) {
00400             dspsilence = 0;
00401             ast_dsp_silence(sildet, f, &dspsilence);
00402             if (dspsilence) {
00403                totalsilence = dspsilence;
00404             } else {
00405                totalsilence = 0;
00406             }
00407             if (totalsilence > silence) {
00408                /* Ended happily with silence */
00409                ast_frfree(f);
00410                gotsilence = 1;
00411                pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "SILENCE");
00412                break;
00413             }
00414          }
00415       } else if (f->frametype == AST_FRAME_VIDEO) {
00416          res = ast_writestream(s, f);
00417 
00418          if (res) {
00419             ast_log(LOG_WARNING, "Problem writing frame\n");
00420             pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
00421             ast_frfree(f);
00422             break;
00423          }
00424       } else if (f->frametype == AST_FRAME_DTMF) {
00425          if (record_dtmf_response(chan, &flags, f->subclass.integer, terminator)) {
00426             ast_frfree(f);
00427             break;
00428          }
00429       }
00430       ast_frfree(f);
00431    }
00432 
00433    if (maxduration > 0 && !ms) {
00434       gottimeout = 1;
00435       pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "TIMEOUT");
00436    }
00437 
00438    if (!f) {
00439       ast_debug(1, "Got hangup\n");
00440       res = -1;
00441       pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "HANGUP");
00442       if (!ast_test_flag(&flags, OPTION_KEEP)) {
00443          ast_filedelete(args.filename, NULL);
00444       }
00445    }
00446 
00447    if (gotsilence) {
00448       ast_stream_rewind(s, silence - 1000);
00449       ast_truncstream(s);
00450    } else if (!gottimeout && f) {
00451       /*
00452        * Strip off the last 1/4 second of it, if we didn't end because of a timeout,
00453        * or a hangup.  This must mean we ended because of a DTMF tone and while this
00454        * 1/4 second stripping is very old code the most likely explanation is that it
00455        * relates to stripping a partial DTMF tone.
00456        */
00457       ast_stream_rewind(s, 250);
00458       ast_truncstream(s);
00459    }
00460    ast_closestream(s);
00461 
00462    if (silgen)
00463       ast_channel_stop_silence_generator(chan, silgen);
00464 
00465 out:
00466    if ((silence > 0) && rfmt) {
00467       res = ast_set_read_format(chan, rfmt);
00468       if (res) {
00469          ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", ast_channel_name(chan));
00470       }
00471    }
00472 
00473    if (sildet) {
00474       ast_dsp_free(sildet);
00475    }
00476    return res;
00477 }

static int unload_module ( void   )  [static]

Definition at line 479 of file app_record.c.

References ast_unregister_application().

00480 {
00481    return ast_unregister_application(app);
00482 }


Variable Documentation

struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "Trivial Record Application" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_DEFAULT, .support_level = AST_MODULE_SUPPORT_CORE, } [static]

Definition at line 489 of file app_record.c.

char* app = "Record" [static]

Definition at line 123 of file app_record.c.

struct ast_app_option app_opts[128] = { [ 'a' ] = { .flag = OPTION_APPEND }, [ 'k' ] = { .flag = OPTION_KEEP }, [ 'n' ] = { .flag = OPTION_NOANSWER }, [ 'o' ] = { .flag = OPTION_OPERATOR_EXIT }, [ 'q' ] = { .flag = OPTION_QUIET }, [ 's' ] = { .flag = OPTION_SKIP }, [ 't' ] = { .flag = OPTION_STAR_TERMINATE }, [ 'y' ] = { .flag = OPTION_ANY_TERMINATE }, [ 'x' ] = { .flag = OPTION_IGNORE_TERMINATE },} [static]

Definition at line 148 of file app_record.c.

Definition at line 489 of file app_record.c.


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