Mix Monitor: Now with r and t options.

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@310373 65c4cc65-6c06-0410-ace0-fbb531ad65f3
10-digiumphones
Jonathan Rose 14 years ago
parent 7cf70df999
commit 6e36042f64

@ -70,6 +70,13 @@ Calendaring
* Added setvar option to calendar.conf to allow setting channel variables on
notification channels.
MixMonitor
--------------------------
* Added two new options, r and t with file name arguments to record
single direction (unmixed) audio recording separate from the bidirectional
(mixed) recording. The mixed file name argument is optional now as long
as at least one recording option is used.
------------------------------------------------------------------------------
--- Functionality changes from Asterisk 1.6.2 to Asterisk 1.8 ----------------
------------------------------------------------------------------------------

@ -89,6 +89,19 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
of <replaceable>x</replaceable> (range <literal>-4</literal> to <literal>4</literal>)</para>
<argument name="x" required="true" />
</option>
<option name="r">
<argument name="file" required="true" />
<para>Use the specified file to record the <emphasis>receive</emphasis> audio feed.
Like with the basic filename argument, if an absolute path isn't given, it will create
the file in the configured monitoring directory.</para>
</option>
<option name="t">
<argument name="file" required="true" />
<para>Use the specified file to record the <emphasis>transmit</emphasis> audio feed.
Like with the basic filename argument, if an absolute path isn't given, it will create
the file in the configured monitoring directory.</para>
</option>
</optionlist>
</parameter>
<parameter name="command">
@ -159,6 +172,8 @@ static const char * const mixmonitor_spy_type = "MixMonitor";
struct mixmonitor {
struct ast_audiohook audiohook;
char *filename;
char *filename_read;
char *filename_write;
char *post_process;
char *name;
unsigned int flags;
@ -172,13 +187,18 @@ enum mixmonitor_flags {
MUXFLAG_VOLUME = (1 << 3),
MUXFLAG_READVOLUME = (1 << 4),
MUXFLAG_WRITEVOLUME = (1 << 5),
MUXFLAG_READ = (1 << 6),
MUXFLAG_WRITE = (1 << 7),
MUXFLAG_COMBINED = (1 << 8),
};
enum mixmonitor_args {
OPT_ARG_READVOLUME = 0,
OPT_ARG_WRITEVOLUME,
OPT_ARG_VOLUME,
OPT_ARG_ARRAY_SIZE,
OPT_ARG_WRITENAME,
OPT_ARG_READNAME,
OPT_ARG_ARRAY_SIZE, /* Always last element of the enum */
};
AST_APP_OPTIONS(mixmonitor_opts, {
@ -187,6 +207,8 @@ AST_APP_OPTIONS(mixmonitor_opts, {
AST_APP_OPTION_ARG('v', MUXFLAG_READVOLUME, OPT_ARG_READVOLUME),
AST_APP_OPTION_ARG('V', MUXFLAG_WRITEVOLUME, OPT_ARG_WRITEVOLUME),
AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
AST_APP_OPTION_ARG('r', MUXFLAG_READ, OPT_ARG_READNAME),
AST_APP_OPTION_ARG('t', MUXFLAG_WRITE, OPT_ARG_WRITENAME),
});
struct mixmonitor_ds {
@ -197,7 +219,11 @@ struct mixmonitor_ds {
/* The filestream is held in the datastore so it can be stopped
* immediately during stop_mixmonitor or channel destruction. */
int fs_quit;
struct ast_filestream *fs;
struct ast_filestream *fs_read;
struct ast_filestream *fs_write;
struct ast_audiohook *audiohook;
};
@ -207,11 +233,31 @@ struct mixmonitor_ds {
*/
static void mixmonitor_ds_close_fs(struct mixmonitor_ds *mixmonitor_ds)
{
unsigned char quitting = 0;
if (mixmonitor_ds->fs) {
quitting = 1;
ast_closestream(mixmonitor_ds->fs);
mixmonitor_ds->fs = NULL;
ast_verb(2, "MixMonitor close filestream (mixed)\n");
}
if (mixmonitor_ds->fs_read) {
quitting = 1;
ast_closestream(mixmonitor_ds->fs_read);
mixmonitor_ds->fs_read = NULL;
ast_verb(2, "MixMonitor close filestream (read)\n");
}
if (mixmonitor_ds->fs_write) {
quitting = 1;
ast_closestream(mixmonitor_ds->fs_write);
mixmonitor_ds->fs_write = NULL;
ast_verb(2, "MixMonitor close filestream (write)\n");
}
if (quitting) {
mixmonitor_ds->fs_quit = 1;
ast_verb(2, "MixMonitor close filestream\n");
}
}
@ -269,18 +315,51 @@ static void mixmonitor_free(struct mixmonitor *mixmonitor)
if (mixmonitor->mixmonitor_ds) {
ast_mutex_destroy(&mixmonitor->mixmonitor_ds->lock);
ast_cond_destroy(&mixmonitor->mixmonitor_ds->destruction_condition);
ast_free(mixmonitor->filename_write);
ast_free(mixmonitor->filename_read);
ast_free(mixmonitor->mixmonitor_ds);
ast_free(mixmonitor->name);
ast_free(mixmonitor->post_process);
}
ast_free(mixmonitor);
}
}
static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename, struct ast_filestream **fs, unsigned int *oflags, int *errflag)
{
/* Initialize the file if not already done so */
char *ext = NULL;
char *last_slash = NULL;
if (!ast_strlen_zero(filename)) {
if (!*fs && !*errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
*oflags = O_CREAT | O_WRONLY;
*oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
last_slash = strrchr(filename, '/');
if ((ext = strrchr(filename, '.')) && (ext > last_slash)) {
*(ext++) = '\0';
} else {
ext = "raw";
}
if (!(*fs = ast_writefile(filename, ext, NULL, *oflags, 0, 0666))) {
ast_log(LOG_ERROR, "Cannot open %s.%s\n", filename, ext);
*errflag = 1;
}
}
}
}
static void *mixmonitor_thread(void *obj)
{
struct mixmonitor *mixmonitor = obj;
struct ast_filestream **fs = NULL;
struct ast_filestream **fs_read = NULL;
struct ast_filestream **fs_write = NULL;
unsigned int oflags;
char *ext;
char *last_slash;
int errflag = 0;
struct ast_format format_slin;
@ -288,13 +367,18 @@ static void *mixmonitor_thread(void *obj)
ast_verb(2, "Begin MixMonitor Recording %s\n", mixmonitor->name);
fs = &mixmonitor->mixmonitor_ds->fs;
fs_read = &mixmonitor->mixmonitor_ds->fs_read;
fs_write = &mixmonitor->mixmonitor_ds->fs_write;
/* The audiohook must enter and exit the loop locked */
ast_audiohook_lock(&mixmonitor->audiohook);
while (mixmonitor->audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING && !mixmonitor->mixmonitor_ds->fs_quit) {
struct ast_frame *fr = NULL;
struct ast_frame *fr_read = NULL;
struct ast_frame *fr_write = NULL;
if (!(fr = ast_audiohook_read_frame(&mixmonitor->audiohook, SAMPLES_PER_FRAME, AST_AUDIOHOOK_DIRECTION_BOTH, &format_slin))) {
if (!(fr = ast_audiohook_read_frame_all(&mixmonitor->audiohook, SAMPLES_PER_FRAME, &format_slin,
&fr_read, &fr_write))) {
ast_audiohook_trigger_wait(&mixmonitor->audiohook);
if (mixmonitor->audiohook.status != AST_AUDIOHOOK_STATUS_RUNNING) {
@ -309,25 +393,28 @@ static void *mixmonitor_thread(void *obj)
if (!ast_test_flag(mixmonitor, MUXFLAG_BRIDGED) || (mixmonitor->autochan->chan && ast_bridged_channel(mixmonitor->autochan->chan))) {
ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
/* Initialize the file if not already done so */
if (!*fs && !errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
oflags = O_CREAT | O_WRONLY;
oflags |= ast_test_flag(mixmonitor, MUXFLAG_APPEND) ? O_APPEND : O_TRUNC;
last_slash = strrchr(mixmonitor->filename, '/');
if ((ext = strrchr(mixmonitor->filename, '.')) && (ext > last_slash))
*(ext++) = '\0';
else
ext = "raw";
if (!(*fs = ast_writefile(mixmonitor->filename, ext, NULL, oflags, 0, 0666))) {
ast_log(LOG_ERROR, "Cannot open %s.%s\n", mixmonitor->filename, ext);
errflag = 1;
mixmonitor_save_prep(mixmonitor, mixmonitor->filename, fs, &oflags, &errflag);
mixmonitor_save_prep(mixmonitor, mixmonitor->filename_read, fs_read, &oflags, &errflag);
mixmonitor_save_prep(mixmonitor, mixmonitor->filename_write, fs_write, &oflags, &errflag);
/* Write out the frame(s) */
if ((*fs_read) && (fr_read)) {
struct ast_frame *cur;
for (cur = fr_read; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
ast_writestream(*fs_read, cur);
}
}
/* Write out the frame(s) */
if (*fs) {
if ((*fs_write) && (fr_write)) {
struct ast_frame *cur;
for (cur = fr_write; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
ast_writestream(*fs_write, cur);
}
}
if ((*fs) && (fr)) {
struct ast_frame *cur;
for (cur = fr; cur; cur = AST_LIST_NEXT(cur, frame_list)) {
@ -337,7 +424,19 @@ static void *mixmonitor_thread(void *obj)
ast_mutex_unlock(&mixmonitor->mixmonitor_ds->lock);
}
/* All done! free it. */
ast_frame_free(fr, 0);
if (fr) {
ast_frame_free(fr, 0);
}
if (fr_read) {
ast_frame_free(fr_read, 0);
}
if (fr_write) {
ast_frame_free(fr_write, 0);
}
fr = NULL;
fr_write = NULL;
fr_read = NULL;
ast_audiohook_lock(&mixmonitor->audiohook);
}
@ -396,15 +495,14 @@ static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel
return 0;
}
static void launch_monitor_thread(struct ast_channel *chan, const char *filename, unsigned int flags,
int readvol, int writevol, const char *post_process)
static void launch_monitor_thread(struct ast_channel *chan, const char *filename,
unsigned int flags, int readvol, int writevol,
const char *post_process, const char *filename_write,
const char *filename_read)
{
pthread_t thread;
struct mixmonitor *mixmonitor;
char postprocess2[1024] = "";
size_t len;
len = sizeof(*mixmonitor) + strlen(chan->name) + strlen(filename) + 2;
postprocess2[0] = 0;
/* If a post process system command is given attach it to the structure */
@ -412,18 +510,16 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
char *p1, *p2;
p1 = ast_strdupa(post_process);
for (p2 = p1; *p2 ; p2++) {
for (p2 = p1; *p2; p2++) {
if (*p2 == '^' && *(p2+1) == '{') {
*p2 = '$';
}
}
pbx_substitute_variables_helper(chan, p1, postprocess2, sizeof(postprocess2) - 1);
if (!ast_strlen_zero(postprocess2))
len += strlen(postprocess2) + 1;
}
/* Pre-allocate mixmonitor structure and spy */
if (!(mixmonitor = ast_calloc(1, len))) {
if (!(mixmonitor = ast_calloc(1, sizeof(*mixmonitor)))) {
return;
}
@ -445,15 +541,24 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
mixmonitor_free(mixmonitor);
return;
}
mixmonitor->name = (char *) mixmonitor + sizeof(*mixmonitor);
strcpy(mixmonitor->name, chan->name);
mixmonitor->name = ast_strdup(chan->name);
if (!ast_strlen_zero(postprocess2)) {
mixmonitor->post_process = mixmonitor->name + strlen(mixmonitor->name) + strlen(filename) + 2;
strcpy(mixmonitor->post_process, postprocess2);
mixmonitor->post_process = ast_strdup(postprocess2);
}
if (!ast_strlen_zero(filename)) {
mixmonitor->filename = ast_strdup(filename);
}
if (!ast_strlen_zero(filename_write)) {
mixmonitor->filename_write = ast_strdup(filename_write);
}
mixmonitor->filename = (char *) mixmonitor + sizeof(*mixmonitor) + strlen(chan->name) + 1;
strcpy(mixmonitor->filename, filename);
if (!ast_strlen_zero(filename_read)) {
mixmonitor->filename_read = ast_strdup(filename_read);
}
ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
@ -473,11 +578,39 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
ast_pthread_create_detached_background(&thread, NULL, mixmonitor_thread, mixmonitor);
}
/* a note on filename_parse: creates directory structure and assigns absolute path from relative paths for filenames */
/* requires immediate copying of string from return to retain data since otherwise it will immediately lose scope */
static char *filename_parse(char *filename, char *buffer, size_t len)
{
char *slash;
if (ast_strlen_zero(filename)) {
ast_log(LOG_WARNING, "No file name was provided for a file save option.\n");
} else if (filename[0] != '/') {
char *build;
build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(filename) + 3);
sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, filename);
filename = build;
}
ast_copy_string(buffer, filename, len);
if ((slash = strrchr(filename, '/'))) {
*slash = '\0';
}
ast_mkdir(filename, 0777);
return buffer;
}
static int mixmonitor_exec(struct ast_channel *chan, const char *data)
{
int x, readvol = 0, writevol = 0;
struct ast_flags flags = {0};
char *parse, *tmp, *slash;
char *filename_read = NULL;
char *filename_write = NULL;
char filename_buffer[1024] = "";
struct ast_flags flags = { 0 };
char *parse;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(filename);
AST_APP_ARG(options);
@ -485,18 +618,13 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
);
if (ast_strlen_zero(data)) {
ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
ast_log(LOG_WARNING, "MixMonitor requires an argument (filename or ,t(filename) and/or r(filename)\n");
return -1;
}
parse = ast_strdupa(data);
AST_STANDARD_APP_ARGS(args, parse);
if (ast_strlen_zero(args.filename)) {
ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
return -1;
}
if (args.options) {
char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
@ -532,24 +660,30 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
readvol = writevol = get_volfactor(x);
}
}
if (ast_test_flag(&flags, MUXFLAG_WRITE)) {
filename_write = ast_strdupa(filename_parse(opts[OPT_ARG_WRITENAME], filename_buffer, sizeof(filename_buffer)));
}
if (ast_test_flag(&flags, MUXFLAG_READ)) {
filename_read = ast_strdupa(filename_parse(opts[OPT_ARG_READNAME], filename_buffer, sizeof(filename_buffer)));
}
}
/* if not provided an absolute path, use the system-configured monitoring directory */
if (args.filename[0] != '/') {
char *build;
/* If there are no file writing arguments/options for the mix monitor, send a warning message and return -1 */
build = alloca(strlen(ast_config_AST_MONITOR_DIR) + strlen(args.filename) + 3);
sprintf(build, "%s/%s", ast_config_AST_MONITOR_DIR, args.filename);
args.filename = build;
if (!ast_test_flag(&flags, MUXFLAG_WRITE) && !ast_test_flag(&flags, MUXFLAG_READ) && ast_strlen_zero(args.filename)) {
ast_log(LOG_WARNING, "MixMonitor requires an argument (filename)\n");
return -1;
}
tmp = ast_strdupa(args.filename);
if ((slash = strrchr(tmp, '/')))
*slash = '\0';
ast_mkdir(tmp, 0777);
/* If filename exists, try to create directories for it */
if (!(ast_strlen_zero(args.filename))) {
args.filename = ast_strdupa(filename_parse(args.filename, filename_buffer, sizeof(filename_buffer)));
}
pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process);
launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process, filename_write, filename_read);
return 0;
}

@ -151,6 +151,17 @@ int ast_audiohook_write_frame(struct ast_audiohook *audiohook, enum ast_audiohoo
*/
struct ast_frame *ast_audiohook_read_frame(struct ast_audiohook *audiohook, size_t samples, enum ast_audiohook_direction direction, struct ast_format *format);
/*! \brief Reads a frame in from the audiohook structure in mixed audio mode and copies read and write frame data to provided arguments.
* \param audiohook Audiohook structure
* \param samples Number of samples wanted
* \param direction Direction the audio frame came from
* \param format Format of frame remote side wants back
* \param ast_frame read_frame - if available, we'll copy the read buffer to this.
* \param ast_frame write_frame - if available, we'll copy the write buffer to this.
* \return Returns frame on success, NULL on failure
*/
struct ast_frame *ast_audiohook_read_frame_all(struct ast_audiohook *audiohook, size_t samples, struct ast_format *format, struct ast_frame **read_frame, struct ast_frame **write_frame);
/*! \brief Attach audiohook to channel
* \param chan Channel
* \param audiohook Audiohook structure

@ -238,7 +238,7 @@ static struct ast_frame *audiohook_read_frame_single(struct ast_audiohook *audio
return ast_frdup(&frame);
}
static struct ast_frame *audiohook_read_frame_both(struct ast_audiohook *audiohook, size_t samples)
static struct ast_frame *audiohook_read_frame_both(struct ast_audiohook *audiohook, size_t samples, struct ast_frame **read_reference, struct ast_frame **write_reference)
{
int i = 0, usable_read, usable_write;
short buf1[samples], buf2[samples], *read_buf = NULL, *write_buf = NULL, *final_buf = NULL, *data1 = NULL, *data2 = NULL;
@ -313,14 +313,24 @@ static struct ast_frame *audiohook_read_frame_both(struct ast_audiohook *audioho
/* Basically we figure out which buffer to use... and if mixing can be done here */
if (!read_buf && !write_buf)
return NULL;
else if (read_buf && write_buf) {
if (read_buf) {
final_buf = buf1;
frame.data.ptr = final_buf;
*read_reference = ast_frdup(&frame);
}
if (write_buf) {
final_buf = buf2;
frame.data.ptr = final_buf;
*write_reference = ast_frdup(&frame);
}
if (read_buf && write_buf) {
for (i = 0, data1 = read_buf, data2 = write_buf; i < samples; i++, data1++, data2++)
ast_slinear_saturated_add(data1, data2);
final_buf = buf1;
} else if (read_buf)
final_buf = buf1;
else if (write_buf)
final_buf = buf2;
}
/* Make the final buffer part of the frame, so it gets duplicated fine */
frame.data.ptr = final_buf;
@ -329,14 +339,7 @@ static struct ast_frame *audiohook_read_frame_both(struct ast_audiohook *audioho
return ast_frdup(&frame);
}
/*! \brief Reads a frame in from the audiohook structure
* \param audiohook Audiohook structure
* \param samples Number of samples wanted in requested output format
* \param direction Direction the audio frame came from
* \param format Format of frame remote side wants back
* \return Returns frame on success, NULL on failure
*/
struct ast_frame *ast_audiohook_read_frame(struct ast_audiohook *audiohook, size_t samples, enum ast_audiohook_direction direction, struct ast_format *format)
static struct ast_frame *audiohook_read_frame_helper(struct ast_audiohook *audiohook, size_t samples, enum ast_audiohook_direction direction, struct ast_format *format, struct ast_frame **read_reference, struct ast_frame **write_reference)
{
struct ast_frame *read_frame = NULL, *final_frame = NULL;
struct ast_format tmp_fmt;
@ -352,10 +355,10 @@ struct ast_frame *ast_audiohook_read_frame(struct ast_audiohook *audiohook, size
samples_converted = samples * (ast_format_rate(format) / (float) audiohook->hook_internal_samp_rate);
}
if (!(read_frame = (direction == AST_AUDIOHOOK_DIRECTION_BOTH ?
audiohook_read_frame_both(audiohook, samples_converted) :
audiohook_read_frame_single(audiohook, samples_converted, direction)))) {
return NULL;
if (!(read_frame = (direction == AST_AUDIOHOOK_DIRECTION_BOTH ?
audiohook_read_frame_both(audiohook, samples_converted, read_reference, write_reference) :
audiohook_read_frame_single(audiohook, samples_converted, direction)))) {
return NULL;
}
/* If they don't want signed linear back out, we'll have to send it through the translation path */
@ -383,6 +386,32 @@ struct ast_frame *ast_audiohook_read_frame(struct ast_audiohook *audiohook, size
return final_frame;
}
/*! \brief Reads a frame in from the audiohook structure
* \param audiohook Audiohook structure
* \param samples Number of samples wanted in requested output format
* \param direction Direction the audio frame came from
* \param format Format of frame remote side wants back
* \return Returns frame on success, NULL on failure
*/
struct ast_frame *ast_audiohook_read_frame(struct ast_audiohook *audiohook, size_t samples, enum ast_audiohook_direction direction, struct ast_format *format)
{
return audiohook_read_frame_helper(audiohook, samples, direction, format, NULL, NULL);
}
/*! \brief Reads a frame in from the audiohook structure
* \param audiohook Audiohook structure
* \param samples Number of samples wanted
* \param direction Direction the audio frame came from
* \param format Format of frame remote side wants back
* \param read_frame frame pointer for copying read frame data
* \param write_frame frame pointer for copying write frame data
* \return Returns frame on success, NULL on failure
*/
struct ast_frame *ast_audiohook_read_frame_all(struct ast_audiohook *audiohook, size_t samples, struct ast_format *format, struct ast_frame **read_frame, struct ast_frame **write_frame)
{
return audiohook_read_frame_helper(audiohook, samples, AST_AUDIOHOOK_DIRECTION_BOTH, format, read_frame, write_frame);
}
static void audiohook_list_set_samplerate_compatibility(struct ast_audiohook_list *audiohook_list)
{
struct ast_audiohook *ah = NULL;

Loading…
Cancel
Save