mirror of
https://github.com/asterisk/asterisk.git
synced 2025-10-06 13:07:21 +00:00
ARI: The bridges play and record APIs now handle sample rates > 8K correctly.
The bridge play and record APIs were forcing the Announcer/Recorder channel to slin8 which meant that if you played or recorded audio with a sample rate > 8K, it was downsampled to 8K limiting the bandwidth. * The /bridges/play REST APIs have a new "announcer_format" parameter that allows the caller to explicitly set the format on the "Announcer" channel through which the audio is played into the bridge. If not specified, the default depends on how many channels are currently in the bridge. If a single channel is in the bridge, then the Announcer channel's format will be set to the same as that channel's. If multiple channels are in the bridge, the channels will be scanned to find the one with the highest sample rate and the Announcer channel's format will be set to the slin format that has an equal to or greater than sample rate. * The /bridges/record REST API has a new "recorder_format" parameter that allows the caller to explicitly set the format on the "Recorder" channel from which audio is retrieved to write to the file. If not specified, the Recorder channel's format will be set to the format that was requested to save the audio in. Resolves: #1479 DeveloperNote: The ARI /bridges/play and /bridges/record REST APIs have new parameters that allow the caller to specify the format to be used on the "Announcer" and "Recorder" channels respecitvely.
This commit is contained in:
@@ -311,7 +311,8 @@ static void *bridge_channel_control_thread(void *data)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct ast_channel *prepare_bridge_media_channel(const char *type)
|
static struct ast_channel *prepare_bridge_media_channel(const char *type,
|
||||||
|
struct ast_format *channel_format)
|
||||||
{
|
{
|
||||||
RAII_VAR(struct ast_format_cap *, cap, NULL, ao2_cleanup);
|
RAII_VAR(struct ast_format_cap *, cap, NULL, ao2_cleanup);
|
||||||
struct ast_channel *chan;
|
struct ast_channel *chan;
|
||||||
@@ -321,7 +322,8 @@ static struct ast_channel *prepare_bridge_media_channel(const char *type)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ast_format_cap_append(cap, ast_format_slin, 0);
|
/* This bumps the format's refcount */
|
||||||
|
ast_format_cap_append(cap, channel_format, 0);
|
||||||
|
|
||||||
chan = ast_request(type, cap, NULL, NULL, "ARI", NULL);
|
chan = ast_request(type, cap, NULL, NULL, "ARI", NULL);
|
||||||
if (!chan) {
|
if (!chan) {
|
||||||
@@ -407,6 +409,7 @@ static int ari_bridges_play_helper(const char **args_media,
|
|||||||
|
|
||||||
static void ari_bridges_play_new(const char **args_media,
|
static void ari_bridges_play_new(const char **args_media,
|
||||||
size_t args_media_count,
|
size_t args_media_count,
|
||||||
|
const char *args_format,
|
||||||
const char *args_lang,
|
const char *args_lang,
|
||||||
int args_offset_ms,
|
int args_offset_ms,
|
||||||
int args_skipms,
|
int args_skipms,
|
||||||
@@ -424,14 +427,64 @@ static void ari_bridges_play_new(const char **args_media,
|
|||||||
struct stasis_topic *bridge_topic;
|
struct stasis_topic *bridge_topic;
|
||||||
struct bridge_channel_control_thread_data *thread_data;
|
struct bridge_channel_control_thread_data *thread_data;
|
||||||
pthread_t threadid;
|
pthread_t threadid;
|
||||||
|
struct ast_format *channel_format = NULL;
|
||||||
|
|
||||||
struct ast_frame prog = {
|
struct ast_frame prog = {
|
||||||
.frametype = AST_FRAME_CONTROL,
|
.frametype = AST_FRAME_CONTROL,
|
||||||
.subclass.integer = AST_CONTROL_PROGRESS,
|
.subclass.integer = AST_CONTROL_PROGRESS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determine the format for the playback channel.
|
||||||
|
* If a format was specified, use that if it's valid.
|
||||||
|
* Otherwise, if the bridge is empty, use slin.
|
||||||
|
* If the bridge has one channel, use that channel's raw write format.
|
||||||
|
* If the bridge has multiple channels, use the slin format that
|
||||||
|
* will handle the highest sample rate of the raw write format of all the channels.
|
||||||
|
*/
|
||||||
|
if (!ast_strlen_zero(args_format)) {
|
||||||
|
channel_format = ast_format_cache_get(args_format);
|
||||||
|
if (!channel_format) {
|
||||||
|
ast_ari_response_error(
|
||||||
|
response, 422, "Unprocessable Entity",
|
||||||
|
"specified announcer_format is unknown on this system");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* ast_format_cache_get() bumps the refcount but the other calls
|
||||||
|
* to retrieve formats don't so we'll drop this reference.
|
||||||
|
* It'll be bumped again in the prepare_bridge_media_channel() call below.
|
||||||
|
*/
|
||||||
|
ao2_ref(channel_format, -1);
|
||||||
|
} else {
|
||||||
|
ast_bridge_lock(bridge);
|
||||||
|
if (bridge->num_channels == 0) {
|
||||||
|
channel_format = ast_format_slin;
|
||||||
|
} else if (bridge->num_channels == 1) {
|
||||||
|
struct ast_bridge_channel *bc = NULL;
|
||||||
|
bc = AST_LIST_FIRST(&bridge->channels);
|
||||||
|
if (bc) {
|
||||||
|
channel_format = ast_channel_rawwriteformat(bc->chan);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
struct ast_bridge_channel *bc = NULL;
|
||||||
|
unsigned int max_sample_rate = 0;
|
||||||
|
AST_LIST_TRAVERSE(&bridge->channels, bc, entry) {
|
||||||
|
struct ast_format *fmt = ast_channel_rawwriteformat(bc->chan);
|
||||||
|
max_sample_rate = MAX(ast_format_get_sample_rate(fmt), max_sample_rate);
|
||||||
|
}
|
||||||
|
channel_format = ast_format_cache_get_slin_by_rate(max_sample_rate);
|
||||||
|
}
|
||||||
|
ast_bridge_unlock(bridge);
|
||||||
|
}
|
||||||
|
|
||||||
if (!(play_channel = prepare_bridge_media_channel("Announcer"))) {
|
if (!channel_format) {
|
||||||
|
channel_format = ast_format_slin;
|
||||||
|
}
|
||||||
|
|
||||||
|
play_channel = prepare_bridge_media_channel("Announcer", channel_format);
|
||||||
|
ao2_cleanup(channel_format);
|
||||||
|
if (!play_channel) {
|
||||||
ast_ari_response_error(
|
ast_ari_response_error(
|
||||||
response, 500, "Internal Error", "Could not create playback channel");
|
response, 500, "Internal Error", "Could not create playback channel");
|
||||||
return;
|
return;
|
||||||
@@ -578,6 +631,7 @@ static void ari_bridges_handle_play(
|
|||||||
const char *args_bridge_id,
|
const char *args_bridge_id,
|
||||||
const char **args_media,
|
const char **args_media,
|
||||||
size_t args_media_count,
|
size_t args_media_count,
|
||||||
|
const char *args_format,
|
||||||
const char *args_lang,
|
const char *args_lang,
|
||||||
int args_offset_ms,
|
int args_offset_ms,
|
||||||
int args_skipms,
|
int args_skipms,
|
||||||
@@ -608,7 +662,7 @@ static void ari_bridges_handle_play(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ari_bridges_play_new(args_media, args_media_count, args_lang, args_offset_ms,
|
ari_bridges_play_new(args_media, args_media_count, args_format, args_lang, args_offset_ms,
|
||||||
args_skipms, args_playback_id, response, bridge);
|
args_skipms, args_playback_id, response, bridge);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,6 +674,7 @@ void ast_ari_bridges_play(struct ast_variable *headers,
|
|||||||
ari_bridges_handle_play(args->bridge_id,
|
ari_bridges_handle_play(args->bridge_id,
|
||||||
args->media,
|
args->media,
|
||||||
args->media_count,
|
args->media_count,
|
||||||
|
args->announcer_format,
|
||||||
args->lang,
|
args->lang,
|
||||||
args->offsetms,
|
args->offsetms,
|
||||||
args->skipms,
|
args->skipms,
|
||||||
@@ -634,6 +689,7 @@ void ast_ari_bridges_play_with_id(struct ast_variable *headers,
|
|||||||
ari_bridges_handle_play(args->bridge_id,
|
ari_bridges_handle_play(args->bridge_id,
|
||||||
args->media,
|
args->media,
|
||||||
args->media_count,
|
args->media_count,
|
||||||
|
args->announcer_format,
|
||||||
args->lang,
|
args->lang,
|
||||||
args->offsetms,
|
args->offsetms,
|
||||||
args->skipms,
|
args->skipms,
|
||||||
@@ -660,6 +716,8 @@ void ast_ari_bridges_record(struct ast_variable *headers,
|
|||||||
size_t uri_name_maxlen;
|
size_t uri_name_maxlen;
|
||||||
struct bridge_channel_control_thread_data *thread_data;
|
struct bridge_channel_control_thread_data *thread_data;
|
||||||
pthread_t threadid;
|
pthread_t threadid;
|
||||||
|
struct ast_format *file_format = NULL;
|
||||||
|
struct ast_format *channel_format = NULL;
|
||||||
|
|
||||||
ast_assert(response != NULL);
|
ast_assert(response != NULL);
|
||||||
|
|
||||||
@@ -667,7 +725,34 @@ void ast_ari_bridges_record(struct ast_variable *headers,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(record_channel = prepare_bridge_media_channel("Recorder"))) {
|
file_format = ast_get_format_for_file_ext(args->format);
|
||||||
|
if (!file_format) {
|
||||||
|
ast_ari_response_error(
|
||||||
|
response, 422, "Unprocessable Entity",
|
||||||
|
"specified format is unknown on this system");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ast_strlen_zero(args->recorder_format)) {
|
||||||
|
channel_format = ast_format_cache_get(args->recorder_format);
|
||||||
|
if (!channel_format) {
|
||||||
|
ast_ari_response_error(
|
||||||
|
response, 422, "Unprocessable Entity",
|
||||||
|
"specified recorder_format is unknown on this system");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* ast_format_cache_get() bumps the refcount but the other calls
|
||||||
|
* to retrieve formats don't so we'll drop this reference.
|
||||||
|
* It'll be bumped again in the prepare_bridge_media_channel() call below.
|
||||||
|
*/
|
||||||
|
ao2_ref(channel_format, -1);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
channel_format = file_format;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(record_channel = prepare_bridge_media_channel("Recorder", channel_format))) {
|
||||||
ast_ari_response_error(
|
ast_ari_response_error(
|
||||||
response, 500, "Internal Server Error", "Failed to create recording channel");
|
response, 500, "Internal Server Error", "Failed to create recording channel");
|
||||||
return;
|
return;
|
||||||
@@ -728,13 +813,6 @@ void ast_ari_bridges_record(struct ast_variable *headers,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ast_get_format_for_file_ext(options->format)) {
|
|
||||||
ast_ari_response_error(
|
|
||||||
response, 422, "Unprocessable Entity",
|
|
||||||
"specified format is unknown on this system");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
recording = stasis_app_control_record(control, options);
|
recording = stasis_app_control_record(control, options);
|
||||||
if (recording == NULL) {
|
if (recording == NULL) {
|
||||||
switch(errno) {
|
switch(errno) {
|
||||||
|
@@ -285,6 +285,8 @@ struct ast_ari_bridges_play_args {
|
|||||||
size_t media_count;
|
size_t media_count;
|
||||||
/*! Parsing context for media. */
|
/*! Parsing context for media. */
|
||||||
char *media_parse;
|
char *media_parse;
|
||||||
|
/*! Format of the 'Anouncer' channel attached to the bridge. Defaults to the format of the channel in the bridge with the highest sampe rate. */
|
||||||
|
const char *announcer_format;
|
||||||
/*! For sounds, selects language for sound. */
|
/*! For sounds, selects language for sound. */
|
||||||
const char *lang;
|
const char *lang;
|
||||||
/*! Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified. */
|
/*! Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified. */
|
||||||
@@ -327,6 +329,8 @@ struct ast_ari_bridges_play_with_id_args {
|
|||||||
size_t media_count;
|
size_t media_count;
|
||||||
/*! Parsing context for media. */
|
/*! Parsing context for media. */
|
||||||
char *media_parse;
|
char *media_parse;
|
||||||
|
/*! Format of the 'Anouncer' channel attached to the bridge. Defaults to the format of the channel in the bridge with the highest sampe rate. */
|
||||||
|
const char *announcer_format;
|
||||||
/*! For sounds, selects language for sound. */
|
/*! For sounds, selects language for sound. */
|
||||||
const char *lang;
|
const char *lang;
|
||||||
/*! Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified. */
|
/*! Number of milliseconds to skip before playing. Only applies to the first URI if multiple media URIs are specified. */
|
||||||
@@ -363,6 +367,8 @@ struct ast_ari_bridges_record_args {
|
|||||||
const char *name;
|
const char *name;
|
||||||
/*! Format to encode audio in */
|
/*! Format to encode audio in */
|
||||||
const char *format;
|
const char *format;
|
||||||
|
/*! Format of the 'Recorder' channel attached to the bridge. Defaults to the same format as the 'format' parameter. */
|
||||||
|
const char *recorder_format;
|
||||||
/*! Maximum duration of the recording, in seconds. 0 for no limit. */
|
/*! Maximum duration of the recording, in seconds. 0 for no limit. */
|
||||||
int max_duration_seconds;
|
int max_duration_seconds;
|
||||||
/*! Maximum duration of silence, in seconds. 0 for no limit. */
|
/*! Maximum duration of silence, in seconds. 0 for no limit. */
|
||||||
|
@@ -1044,6 +1044,10 @@ int ast_ari_bridges_play_parse_body(
|
|||||||
args->media[0] = ast_json_string_get(field);
|
args->media[0] = ast_json_string_get(field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
field = ast_json_object_get(body, "announcer_format");
|
||||||
|
if (field) {
|
||||||
|
args->announcer_format = ast_json_string_get(field);
|
||||||
|
}
|
||||||
field = ast_json_object_get(body, "lang");
|
field = ast_json_object_get(body, "lang");
|
||||||
if (field) {
|
if (field) {
|
||||||
args->lang = ast_json_string_get(field);
|
args->lang = ast_json_string_get(field);
|
||||||
@@ -1128,6 +1132,9 @@ static void ast_ari_bridges_play_cb(
|
|||||||
args.media[j] = (vals[j]);
|
args.media[j] = (vals[j]);
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
|
if (strcmp(i->name, "announcer_format") == 0) {
|
||||||
|
args.announcer_format = (i->value);
|
||||||
|
} else
|
||||||
if (strcmp(i->name, "lang") == 0) {
|
if (strcmp(i->name, "lang") == 0) {
|
||||||
args.lang = (i->value);
|
args.lang = (i->value);
|
||||||
} else
|
} else
|
||||||
@@ -1164,6 +1171,7 @@ static void ast_ari_bridges_play_cb(
|
|||||||
case 501: /* Not Implemented */
|
case 501: /* Not Implemented */
|
||||||
case 404: /* Bridge not found */
|
case 404: /* Bridge not found */
|
||||||
case 409: /* Bridge not in a Stasis application */
|
case 409: /* Bridge not in a Stasis application */
|
||||||
|
case 422: /* The format specified is unknown on this system */
|
||||||
is_valid = 1;
|
is_valid = 1;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -1223,6 +1231,10 @@ int ast_ari_bridges_play_with_id_parse_body(
|
|||||||
args->media[0] = ast_json_string_get(field);
|
args->media[0] = ast_json_string_get(field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
field = ast_json_object_get(body, "announcer_format");
|
||||||
|
if (field) {
|
||||||
|
args->announcer_format = ast_json_string_get(field);
|
||||||
|
}
|
||||||
field = ast_json_object_get(body, "lang");
|
field = ast_json_object_get(body, "lang");
|
||||||
if (field) {
|
if (field) {
|
||||||
args->lang = ast_json_string_get(field);
|
args->lang = ast_json_string_get(field);
|
||||||
@@ -1303,6 +1315,9 @@ static void ast_ari_bridges_play_with_id_cb(
|
|||||||
args.media[j] = (vals[j]);
|
args.media[j] = (vals[j]);
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
|
if (strcmp(i->name, "announcer_format") == 0) {
|
||||||
|
args.announcer_format = (i->value);
|
||||||
|
} else
|
||||||
if (strcmp(i->name, "lang") == 0) {
|
if (strcmp(i->name, "lang") == 0) {
|
||||||
args.lang = (i->value);
|
args.lang = (i->value);
|
||||||
} else
|
} else
|
||||||
@@ -1339,6 +1354,7 @@ static void ast_ari_bridges_play_with_id_cb(
|
|||||||
case 501: /* Not Implemented */
|
case 501: /* Not Implemented */
|
||||||
case 404: /* Bridge not found */
|
case 404: /* Bridge not found */
|
||||||
case 409: /* Bridge not in a Stasis application */
|
case 409: /* Bridge not in a Stasis application */
|
||||||
|
case 422: /* The format specified is unknown on this system */
|
||||||
is_valid = 1;
|
is_valid = 1;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -1377,6 +1393,10 @@ int ast_ari_bridges_record_parse_body(
|
|||||||
if (field) {
|
if (field) {
|
||||||
args->format = ast_json_string_get(field);
|
args->format = ast_json_string_get(field);
|
||||||
}
|
}
|
||||||
|
field = ast_json_object_get(body, "recorder_format");
|
||||||
|
if (field) {
|
||||||
|
args->recorder_format = ast_json_string_get(field);
|
||||||
|
}
|
||||||
field = ast_json_object_get(body, "maxDurationSeconds");
|
field = ast_json_object_get(body, "maxDurationSeconds");
|
||||||
if (field) {
|
if (field) {
|
||||||
args->max_duration_seconds = ast_json_integer_get(field);
|
args->max_duration_seconds = ast_json_integer_get(field);
|
||||||
@@ -1428,6 +1448,9 @@ static void ast_ari_bridges_record_cb(
|
|||||||
if (strcmp(i->name, "format") == 0) {
|
if (strcmp(i->name, "format") == 0) {
|
||||||
args.format = (i->value);
|
args.format = (i->value);
|
||||||
} else
|
} else
|
||||||
|
if (strcmp(i->name, "recorder_format") == 0) {
|
||||||
|
args.recorder_format = (i->value);
|
||||||
|
} else
|
||||||
if (strcmp(i->name, "maxDurationSeconds") == 0) {
|
if (strcmp(i->name, "maxDurationSeconds") == 0) {
|
||||||
args.max_duration_seconds = atoi(i->value);
|
args.max_duration_seconds = atoi(i->value);
|
||||||
} else
|
} else
|
||||||
|
@@ -490,6 +490,14 @@
|
|||||||
"allowMultiple": true,
|
"allowMultiple": true,
|
||||||
"dataType": "string"
|
"dataType": "string"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "announcer_format",
|
||||||
|
"description": "Format of the 'Anouncer' channel attached to the bridge. Defaults to the format of the channel in the bridge with the highest sampe rate.",
|
||||||
|
"paramType": "query",
|
||||||
|
"required": false,
|
||||||
|
"allowMultiple": false,
|
||||||
|
"dataType": "string"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "lang",
|
"name": "lang",
|
||||||
"description": "For sounds, selects language for sound.",
|
"description": "For sounds, selects language for sound.",
|
||||||
@@ -510,7 +518,6 @@
|
|||||||
"valueType": "RANGE",
|
"valueType": "RANGE",
|
||||||
"min": 0
|
"min": 0
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "skipms",
|
"name": "skipms",
|
||||||
@@ -542,6 +549,10 @@
|
|||||||
{
|
{
|
||||||
"code": 409,
|
"code": 409,
|
||||||
"reason": "Bridge not in a Stasis application"
|
"reason": "Bridge not in a Stasis application"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 422,
|
||||||
|
"reason": "The format specified is unknown on this system"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -585,6 +596,14 @@
|
|||||||
"allowMultiple": true,
|
"allowMultiple": true,
|
||||||
"dataType": "string"
|
"dataType": "string"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "announcer_format",
|
||||||
|
"description": "Format of the 'Anouncer' channel attached to the bridge. Defaults to the format of the channel in the bridge with the highest sampe rate.",
|
||||||
|
"paramType": "query",
|
||||||
|
"required": false,
|
||||||
|
"allowMultiple": false,
|
||||||
|
"dataType": "string"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "lang",
|
"name": "lang",
|
||||||
"description": "For sounds, selects language for sound.",
|
"description": "For sounds, selects language for sound.",
|
||||||
@@ -628,6 +647,10 @@
|
|||||||
{
|
{
|
||||||
"code": 409,
|
"code": 409,
|
||||||
"reason": "Bridge not in a Stasis application"
|
"reason": "Bridge not in a Stasis application"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 422,
|
||||||
|
"reason": "The format specified is unknown on this system"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -672,6 +695,14 @@
|
|||||||
"allowMultiple": false,
|
"allowMultiple": false,
|
||||||
"dataType": "string"
|
"dataType": "string"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "recorder_format",
|
||||||
|
"description": "Format of the 'Recorder' channel attached to the bridge. Defaults to the same format as the 'format' parameter.",
|
||||||
|
"paramType": "query",
|
||||||
|
"required": false,
|
||||||
|
"allowMultiple": false,
|
||||||
|
"dataType": "string"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "maxDurationSeconds",
|
"name": "maxDurationSeconds",
|
||||||
"description": "Maximum duration of the recording, in seconds. 0 for no limit.",
|
"description": "Maximum duration of the recording, in seconds. 0 for no limit.",
|
||||||
|
Reference in New Issue
Block a user