Skip to content

Instantly share code, notes, and snippets.

@Jared-Prime
Created January 16, 2014 06:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jared-Prime/8450657 to your computer and use it in GitHub Desktop.
Save Jared-Prime/8450657 to your computer and use it in GitHub Desktop.
static int handle_recordfile(struct ast_channel *chan, AGI *agi, int argc, const char * const argv[])
{
struct ast_filestream *fs;
struct ast_frame *f;
struct timeval start;
long sample_offset = 0;
int res = 0;
int ms;
struct ast_dsp *sildet=NULL; /* silence detector dsp */
int totalsilence = 0;
int dspsilence = 0;
int silence = 0; /* amount of silence to allow */
int gotsilence = 0; /* did we timeout for silence? */
char *silencestr = NULL;
struct ast_format rfmt;
ast_format_clear(&rfmt);
/* XXX EAGI FIXME XXX */
if (argc < 6)
return RESULT_SHOWUSAGE;
if (sscanf(argv[5], "%30d", &ms) != 1)
return RESULT_SHOWUSAGE;
if (argc > 6)
silencestr = strchr(argv[6],'s');
if ((argc > 7) && (!silencestr))
silencestr = strchr(argv[7],'s');
if ((argc > 8) && (!silencestr))
silencestr = strchr(argv[8],'s');
if (silencestr) {
if (strlen(silencestr) > 2) {
if ((silencestr[0] == 's') && (silencestr[1] == '=')) {
silencestr++;
silencestr++;
if (silencestr)
silence = atoi(silencestr);
if (silence > 0)
silence *= 1000;
}
}
}
if (silence > 0) {
ast_format_copy(&rfmt, ast_channel_readformat(chan));
res = ast_set_read_format_by_id(chan, AST_FORMAT_SLINEAR);
if (res < 0) {
ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
ast_agi_send(agi->fd, chan, "200 result=%d\n", res);
return RESULT_FAILURE;
}
sildet = ast_dsp_new();
if (!sildet) {
ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
ast_agi_send(agi->fd, chan, "200 result=-1\n");
return RESULT_FAILURE;
}
ast_dsp_set_threshold(sildet, ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE));
}
/* backward compatibility, if no offset given, arg[6] would have been
* caught below and taken to be a beep, else if it is a digit then it is a
* offset */
if ((argc >6) && (sscanf(argv[6], "%30ld", &sample_offset) != 1) && (!strchr(argv[6], '=')))
res = ast_streamfile(chan, "beep", ast_channel_language(chan));
if ((argc > 7) && (!strchr(argv[7], '=')))
res = ast_streamfile(chan, "beep", ast_channel_language(chan));
if (!res)
res = ast_waitstream(chan, argv[4]);
if (res) {
ast_agi_send(agi->fd, chan, "200 result=%d (randomerror) endpos=%ld\n", res, sample_offset);
} else {
fs = ast_writefile(argv[2], argv[3], NULL, O_CREAT | O_WRONLY | (sample_offset ? O_APPEND : 0), 0, AST_FILE_MODE);
if (!fs) {
res = -1;
ast_agi_send(agi->fd, chan, "200 result=%d (writefile)\n", res);
if (sildet)
ast_dsp_free(sildet);
return RESULT_FAILURE;
}
/* Request a video update */
ast_indicate(chan, AST_CONTROL_VIDUPDATE);
ast_channel_stream_set(chan, fs);
ast_applystream(chan,fs);
/* really should have checks */
ast_seekstream(fs, sample_offset, SEEK_SET);
ast_truncstream(fs);
start = ast_tvnow();
while ((ms < 0) || ast_tvdiff_ms(ast_tvnow(), start) < ms) {
res = ast_waitfor(chan, ms - ast_tvdiff_ms(ast_tvnow(), start));
if (res < 0) {
ast_closestream(fs);
ast_agi_send(agi->fd, chan, "200 result=%d (waitfor) endpos=%ld\n", res,sample_offset);
if (sildet)
ast_dsp_free(sildet);
return RESULT_FAILURE;
}
f = ast_read(chan);
if (!f) {
ast_agi_send(agi->fd, chan, "200 result=%d (hangup) endpos=%ld\n", -1, sample_offset);
ast_closestream(fs);
if (sildet)
ast_dsp_free(sildet);
return RESULT_FAILURE;
}
switch(f->frametype) {
case AST_FRAME_DTMF:
if (strchr(argv[4], f->subclass.integer)) {
/* This is an interrupting chracter, so rewind to chop off any small
amount of DTMF that may have been recorded
*/
ast_stream_rewind(fs, 200);
ast_truncstream(fs);
sample_offset = ast_tellstream(fs);
ast_agi_send(agi->fd, chan, "200 result=%d (dtmf) endpos=%ld\n", f->subclass.integer, sample_offset);
ast_closestream(fs);
ast_frfree(f);
if (sildet)
ast_dsp_free(sildet);
return RESULT_SUCCESS;
}
break;
case AST_FRAME_VOICE:
ast_writestream(fs, f);
/* this is a safe place to check progress since we know that fs
* is valid after a write, and it will then have our current
* location */
sample_offset = ast_tellstream(fs);
if (silence > 0) {
dspsilence = 0;
ast_dsp_silence(sildet, f, &dspsilence);
if (dspsilence) {
totalsilence = dspsilence;
} else {
totalsilence = 0;
}
if (totalsilence > silence) {
/* Ended happily with silence */
gotsilence = 1;
break;
}
}
break;
case AST_FRAME_VIDEO:
ast_writestream(fs, f);
default:
/* Ignore all other frames */
break;
}
ast_frfree(f);
if (gotsilence)
break;
}
if (gotsilence) {
ast_stream_rewind(fs, silence-1000);
ast_truncstream(fs);
sample_offset = ast_tellstream(fs);
}
ast_agi_send(agi->fd, chan, "200 result=%d (timeout) endpos=%ld\n", res, sample_offset);
ast_closestream(fs);
}
if (silence > 0) {
res = ast_set_read_format(chan, &rfmt);
if (res)
ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", ast_channel_name(chan));
ast_dsp_free(sildet);
}
return RESULT_SUCCESS;
}
static int record_exec(struct ast_channel *chan, const char *data)
{
int res = 0;
int count = 0;
char *ext = NULL, *opts[0];
char *parse, *dir, *file;
int i = 0;
char tmp[256];
struct ast_filestream *s = NULL;
struct ast_frame *f = NULL;
struct ast_dsp *sildet = NULL; /* silence detector dsp */
int totalsilence = 0;
int dspsilence = 0;
int silence = 0; /* amount of silence to allow */
int gotsilence = 0; /* did we timeout for silence? */
int maxduration = 0; /* max duration of recording in milliseconds */
int gottimeout = 0; /* did we timeout for maxduration exceeded? */
int terminator = '#';
struct ast_format rfmt;
int ioflags;
struct ast_silence_generator *silgen = NULL;
struct ast_flags flags = { 0, };
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(filename);
AST_APP_ARG(silence);
AST_APP_ARG(maxduration);
AST_APP_ARG(options);
);
int ms;
struct timeval start;
ast_format_clear(&rfmt);
/* The next few lines of code parse out the filename and header from the input string */
if (ast_strlen_zero(data)) { /* no data implies no filename or anything is present */
ast_log(LOG_WARNING, "Record requires an argument (filename)\n");
pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
return -1;
}
parse = ast_strdupa(data);
AST_STANDARD_APP_ARGS(args, parse);
if (args.argc == 4)
ast_app_parse_options(app_opts, &flags, opts, args.options);
if (!ast_strlen_zero(args.filename)) {
if (strstr(args.filename, "%d"))
ast_set_flag(&flags, FLAG_HAS_PERCENT);
ext = strrchr(args.filename, '.'); /* to support filename with a . in the filename, not format */
if (!ext)
ext = strchr(args.filename, ':');
if (ext) {
*ext = '\0';
ext++;
}
}
if (!ext) {
ast_log(LOG_WARNING, "No extension specified to filename!\n");
pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
return -1;
}
if (args.silence) {
if ((sscanf(args.silence, "%30d", &i) == 1) && (i > -1)) {
silence = i * 1000;
} else if (!ast_strlen_zero(args.silence)) {
ast_log(LOG_WARNING, "'%s' is not a valid silence duration\n", args.silence);
}
}
if (args.maxduration) {
if ((sscanf(args.maxduration, "%30d", &i) == 1) && (i > -1))
/* Convert duration to milliseconds */
maxduration = i * 1000;
else if (!ast_strlen_zero(args.maxduration))
ast_log(LOG_WARNING, "'%s' is not a valid maximum duration\n", args.maxduration);
}
if (ast_test_flag(&flags, OPTION_STAR_TERMINATE))
terminator = '*';
if (ast_test_flag(&flags, OPTION_IGNORE_TERMINATE))
terminator = '\0';
/* done parsing */
/* these are to allow the use of the %d in the config file for a wild card of sort to
create a new file with the inputed name scheme */
if (ast_test_flag(&flags, FLAG_HAS_PERCENT)) {
AST_DECLARE_APP_ARGS(fname,
AST_APP_ARG(piece)[100];
);
char *tmp2 = ast_strdupa(args.filename);
char countstring[15];
int idx;
/* Separate each piece out by the format specifier */
AST_NONSTANDARD_APP_ARGS(fname, tmp2, '%');
do {
int tmplen;
/* First piece has no leading percent, so it's copied verbatim */
ast_copy_string(tmp, fname.piece[0], sizeof(tmp));
tmplen = strlen(tmp);
for (idx = 1; idx < fname.argc; idx++) {
if (fname.piece[idx][0] == 'd') {
/* Substitute the count */
snprintf(countstring, sizeof(countstring), "%d", count);
ast_copy_string(tmp + tmplen, countstring, sizeof(tmp) - tmplen);
tmplen += strlen(countstring);
} else if (tmplen + 2 < sizeof(tmp)) {
/* Unknown format specifier - just copy it verbatim */
tmp[tmplen++] = '%';
tmp[tmplen++] = fname.piece[idx][0];
}
/* Copy the remaining portion of the piece */
ast_copy_string(tmp + tmplen, &(fname.piece[idx][1]), sizeof(tmp) - tmplen);
}
count++;
} while (ast_fileexists(tmp, ext, ast_channel_language(chan)) > 0);
pbx_builtin_setvar_helper(chan, "RECORDED_FILE", tmp);
} else
ast_copy_string(tmp, args.filename, sizeof(tmp));
/* end of routine mentioned */
if (ast_channel_state(chan) != AST_STATE_UP) {
if (ast_test_flag(&flags, OPTION_SKIP)) {
/* At the user's option, skip if the line is not up */
pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "SKIP");
return 0;
} else if (!ast_test_flag(&flags, OPTION_NOANSWER)) {
/* Otherwise answer unless we're supposed to record while on-hook */
res = ast_answer(chan);
}
}
if (res) {
ast_log(LOG_WARNING, "Could not answer channel '%s'\n", ast_channel_name(chan));
pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
goto out;
}
if (!ast_test_flag(&flags, OPTION_QUIET)) {
/* Some code to play a nice little beep to signify the start of the record operation */
res = ast_streamfile(chan, "beep", ast_channel_language(chan));
if (!res) {
res = ast_waitstream(chan, "");
} else {
ast_log(LOG_WARNING, "ast_streamfile failed on %s\n", ast_channel_name(chan));
}
ast_stopstream(chan);
}
/* The end of beep code. Now the recording starts */
if (silence > 0) {
ast_format_copy(&rfmt, ast_channel_readformat(chan));
res = ast_set_read_format_by_id(chan, AST_FORMAT_SLINEAR);
if (res < 0) {
ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
return -1;
}
sildet = ast_dsp_new();
if (!sildet) {
ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
return -1;
}
ast_dsp_set_threshold(sildet, ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE));
}
/* Create the directory if it does not exist. */
dir = ast_strdupa(tmp);
if ((file = strrchr(dir, '/')))
*file++ = '\0';
ast_mkdir (dir, 0777);
ioflags = ast_test_flag(&flags, OPTION_APPEND) ? O_CREAT|O_APPEND|O_WRONLY : O_CREAT|O_TRUNC|O_WRONLY;
s = ast_writefile(tmp, ext, NULL, ioflags, 0, AST_FILE_MODE);
if (!s) {
ast_log(LOG_WARNING, "Could not create file %s\n", args.filename);
pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
goto out;
}
if (ast_opt_transmit_silence)
silgen = ast_channel_start_silence_generator(chan);
/* Request a video update */
ast_indicate(chan, AST_CONTROL_VIDUPDATE);
if (maxduration <= 0)
maxduration = -1;
start = ast_tvnow();
while ((ms = ast_remaining_ms(start, maxduration))) {
ms = ast_waitfor(chan, ms);
if (ms < 0) {
break;
}
if (maxduration > 0 && ms == 0) {
break;
}
f = ast_read(chan);
if (!f) {
res = -1;
break;
}
if (f->frametype == AST_FRAME_VOICE) {
res = ast_writestream(s, f);
if (res) {
ast_log(LOG_WARNING, "Problem writing frame\n");
ast_frfree(f);
pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
break;
}
if (silence > 0) {
dspsilence = 0;
ast_dsp_silence(sildet, f, &dspsilence);
if (dspsilence) {
totalsilence = dspsilence;
} else {
totalsilence = 0;
}
if (totalsilence > silence) {
/* Ended happily with silence */
ast_frfree(f);
gotsilence = 1;
pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "SILENCE");
break;
}
}
} else if (f->frametype == AST_FRAME_VIDEO) {
res = ast_writestream(s, f);
if (res) {
ast_log(LOG_WARNING, "Problem writing frame\n");
pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
ast_frfree(f);
break;
}
} else if ((f->frametype == AST_FRAME_DTMF) &&
((f->subclass.integer == terminator) ||
(ast_test_flag(&flags, OPTION_ANY_TERMINATE)))) {
ast_frfree(f);
pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "DTMF");
break;
}
ast_frfree(f);
}
if (maxduration > 0 && !ms) {
gottimeout = 1;
pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "TIMEOUT");
}
if (!f) {
ast_debug(1, "Got hangup\n");
res = -1;
pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "HANGUP");
if (!ast_test_flag(&flags, OPTION_KEEP)) {
ast_filedelete(args.filename, NULL);
}
}
if (gotsilence) {
ast_stream_rewind(s, silence - 1000);
ast_truncstream(s);
} else if (!gottimeout) {
/* Strip off the last 1/4 second of it */
ast_stream_rewind(s, 250);
ast_truncstream(s);
}
ast_closestream(s);
if (silgen)
ast_channel_stop_silence_generator(chan, silgen);
out:
if ((silence > 0) && rfmt.id) {
res = ast_set_read_format(chan, &rfmt);
if (res) {
ast_log(LOG_WARNING, "Unable to restore read format on '%s'\n", ast_channel_name(chan));
}
}
if (sildet) {
ast_dsp_free(sildet);
}
return res;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment