app_chanspy: Add 'D' option for dual-channel audio

Adds the 'D' option to app chanspy that causes the input and output
frames of the spied channel to be interleaved in the spy output frame.
This allows the input and output of the spied channel to be decoded
separately by the receiver.

If the 'o' option is also set, the 'D' option is ignored as the
audio being spied is inherently one direction.

Fixes: #569

UserNote: The ChanSpy application now accepts the 'D' option which
will interleave the spied audio within the outgoing frames. The
purpose of this is to allow the audio to be read as a Dual channel
stream with separate incoming and outgoing audio. Setting both the
'o' option and the 'D' option and results in the 'D' option being
ignored.
This commit is contained in:
Mike Bradeen 2024-01-31 08:55:04 -07:00 committed by asterisk-org-access-app[bot]
parent de806580f3
commit d7583f12b6
1 changed files with 57 additions and 0 deletions

View File

@ -245,6 +245,11 @@
</enum>
</enumlist>
</option>
<option name="D">
<para>Interleave the audio coming from the channel and the audio coming to the channel in
the output audio as a dual channel stream, rather than mix it. Does nothing if 'o'
is also set.</para>
</option>
<option name="e">
<argument name="ext" required="true" />
<para>Enable <emphasis>enforced</emphasis> mode, so the spying channel can
@ -393,6 +398,7 @@ enum {
OPTION_EXITONHANGUP = (1 << 18), /* Hang up when the spied-on channel hangs up. */
OPTION_UNIQUEID = (1 << 19), /* The chanprefix is a channel uniqueid or fully specified channel name. */
OPTION_LONG_QUEUE = (1 << 20), /* Allow usage of a long queue to store audio frames. */
OPTION_INTERLEAVED = (1 << 21), /* Interleave the Read and Write frames in the output frame. */
};
enum {
@ -411,6 +417,7 @@ AST_APP_OPTIONS(spy_opts, {
AST_APP_OPTION('B', OPTION_BARGE),
AST_APP_OPTION_ARG('c', OPTION_DTMF_CYCLE, OPT_ARG_CYCLE),
AST_APP_OPTION('d', OPTION_DTMF_SWITCH_MODES),
AST_APP_OPTION('D', OPTION_INTERLEAVED),
AST_APP_OPTION_ARG('e', OPTION_ENFORCED, OPT_ARG_ENFORCED),
AST_APP_OPTION('E', OPTION_EXITONHANGUP),
AST_APP_OPTION_ARG('g', OPTION_GROUP, OPT_ARG_GROUP),
@ -471,6 +478,56 @@ static int spy_generate(struct ast_channel *chan, void *data, int len, int sampl
if (ast_test_flag(&csth->flags, OPTION_READONLY)) {
/* Option 'o' was set, so don't mix channel audio */
f = ast_audiohook_read_frame(&csth->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_READ, ast_format_slin);
} else if (ast_test_flag(&csth->flags, OPTION_INTERLEAVED)) {
/* Option 'D' was set, so mix the spy frame as an interleaved dual channel frame. */
int i;
struct ast_frame *fr_read = NULL;
struct ast_frame *fr_write = NULL;
short read_buf[samples];
short write_buf[samples];
short stereo_buf[samples * 2];
struct ast_frame stereo_frame = {
.frametype = AST_FRAME_VOICE,
.datalen = sizeof(stereo_buf),
.samples = samples,
};
f = ast_audiohook_read_frame_all(&csth->spy_audiohook, samples, ast_format_slin, &fr_read, &fr_write);
if (f) {
ast_frame_free(f, 0);
f = NULL;
}
if (fr_read) {
memcpy(read_buf, fr_read->data.ptr, sizeof(read_buf));
} else {
/* silent out the output frame if we can't read the input */
memset(read_buf, 0, sizeof(read_buf));
}
if (fr_write) {
memcpy(write_buf, fr_write->data.ptr, sizeof(write_buf));
} else {
memset(write_buf, 0, sizeof(write_buf));
}
for (i = 0; i < samples; i++) {
stereo_buf[i*2] = read_buf[i];
stereo_buf[i*2+1] = write_buf[i];
}
stereo_frame.data.ptr = stereo_buf;
stereo_frame.subclass.format = ast_format_cache_get_slin_by_rate(samples);
f = ast_frdup(&stereo_frame);
if (fr_read) {
ast_frame_free(fr_read, 0);
}
if (fr_write) {
ast_frame_free(fr_write, 0);
}
} else {
f = ast_audiohook_read_frame(&csth->spy_audiohook, samples, AST_AUDIOHOOK_DIRECTION_BOTH, ast_format_slin);
}