Files
asterisk/res/res_stasis_recording.c
David M. Lee 987fdfb444 ARI: allow other operations to happen while bridged
This patch changes ARI bridging to allow other channel operations to
happen while the channel is bridged.

ARI channel operations are designed to queue up and execute
sequentially. This meant, though, that while a channel was bridged,
any other channel operations would queue up and execute only after the
channel left the bridge.

This patch changes ARI bridging so that channel commands can execute
while the channel is bridged. For most operations, things simply work
as expected. The one thing that ended up being a bit odd is recording.

The current recording implementation will fail when one attempts to
record a channel that's in a bridge. Note that the bridge itself may
be recording; it's recording a specific channel in the bridge that
fails. While this is an annoying limitation, channel recording is
still very useful for use cases such as voice mail, and bridge
recording makes up much of the difference for other use cases.

(closes issue ASTERISK-22084)
Review: https://reviewboard.asterisk.org/r/2726/


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@396568 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2013-08-13 15:27:32 +00:00

571 lines
15 KiB
C

/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2013, Digium, Inc.
*
* David M. Lee, II <dlee@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*! \file
*
* \brief res_stasis recording support.
*
* \author David M. Lee, II <dlee@digium.com>
*/
/*** MODULEINFO
<depend type="module">res_stasis</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/dsp.h"
#include "asterisk/file.h"
#include "asterisk/module.h"
#include "asterisk/paths.h"
#include "asterisk/stasis_app_impl.h"
#include "asterisk/stasis_app_recording.h"
#include "asterisk/stasis_channels.h"
/*! Number of hash buckets for recording container. Keep it prime! */
#define RECORDING_BUCKETS 127
/*! Comment is ignored by most formats, so we will ignore it, too. */
#define RECORDING_COMMENT NULL
/*! Recording check is unimplemented. le sigh */
#define RECORDING_CHECK 0
STASIS_MESSAGE_TYPE_DEFN(stasis_app_recording_snapshot_type);
/*! Container of all current recordings */
static struct ao2_container *recordings;
struct stasis_app_recording {
/*! Recording options. */
struct stasis_app_recording_options *options;
/*! Absolute path (minus extension) of the recording */
char *absolute_name;
/*! Control object for the channel we're recording */
struct stasis_app_control *control;
/*! Current state of the recording. */
enum stasis_app_recording_state state;
/*! Indicates whether the recording is currently muted */
int muted:1;
};
static int recording_hash(const void *obj, int flags)
{
const struct stasis_app_recording *recording = obj;
const char *id = flags & OBJ_KEY ? obj : recording->options->name;
return ast_str_hash(id);
}
static int recording_cmp(void *obj, void *arg, int flags)
{
struct stasis_app_recording *lhs = obj;
struct stasis_app_recording *rhs = arg;
const char *rhs_id = flags & OBJ_KEY ? arg : rhs->options->name;
if (strcmp(lhs->options->name, rhs_id) == 0) {
return CMP_MATCH | CMP_STOP;
} else {
return 0;
}
}
static const char *state_to_string(enum stasis_app_recording_state state)
{
switch (state) {
case STASIS_APP_RECORDING_STATE_QUEUED:
return "queued";
case STASIS_APP_RECORDING_STATE_RECORDING:
return "recording";
case STASIS_APP_RECORDING_STATE_PAUSED:
return "paused";
case STASIS_APP_RECORDING_STATE_COMPLETE:
return "done";
case STASIS_APP_RECORDING_STATE_FAILED:
return "failed";
case STASIS_APP_RECORDING_STATE_CANCELED:
return "canceled";
case STASIS_APP_RECORDING_STATE_MAX:
return "?";
}
return "?";
}
static void recording_options_dtor(void *obj)
{
struct stasis_app_recording_options *options = obj;
ast_string_field_free_memory(options);
}
struct stasis_app_recording_options *stasis_app_recording_options_create(
const char *name, const char *format)
{
RAII_VAR(struct stasis_app_recording_options *, options, NULL,
ao2_cleanup);
options = ao2_alloc(sizeof(*options), recording_options_dtor);
if (!options || ast_string_field_init(options, 128)) {
return NULL;
}
ast_string_field_set(options, name, name);
ast_string_field_set(options, format, format);
ao2_ref(options, +1);
return options;
}
char stasis_app_recording_termination_parse(const char *str)
{
if (ast_strlen_zero(str)) {
return STASIS_APP_RECORDING_TERMINATE_NONE;
}
if (strcasecmp(str, "none") == 0) {
return STASIS_APP_RECORDING_TERMINATE_NONE;
}
if (strcasecmp(str, "any") == 0) {
return STASIS_APP_RECORDING_TERMINATE_ANY;
}
if (strcasecmp(str, "#") == 0) {
return '#';
}
if (strcasecmp(str, "*") == 0) {
return '*';
}
return STASIS_APP_RECORDING_TERMINATE_INVALID;
}
enum ast_record_if_exists stasis_app_recording_if_exists_parse(
const char *str)
{
if (ast_strlen_zero(str)) {
/* Default value */
return AST_RECORD_IF_EXISTS_FAIL;
}
if (strcasecmp(str, "fail") == 0) {
return AST_RECORD_IF_EXISTS_FAIL;
}
if (strcasecmp(str, "overwrite") == 0) {
return AST_RECORD_IF_EXISTS_OVERWRITE;
}
if (strcasecmp(str, "append") == 0) {
return AST_RECORD_IF_EXISTS_APPEND;
}
return -1;
}
static void recording_publish(struct stasis_app_recording *recording)
{
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
ast_assert(recording != NULL);
json = stasis_app_recording_to_json(recording);
if (json == NULL) {
return;
}
message = ast_channel_blob_create_from_cache(
stasis_app_control_get_channel_id(recording->control),
stasis_app_recording_snapshot_type(), json);
if (message == NULL) {
return;
}
stasis_app_control_publish(recording->control, message);
}
static void recording_fail(struct stasis_app_recording *recording)
{
SCOPED_AO2LOCK(lock, recording);
recording->state = STASIS_APP_RECORDING_STATE_FAILED;
recording_publish(recording);
}
static void recording_cleanup(struct stasis_app_recording *recording)
{
ao2_unlink_flags(recordings, recording,
OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
}
static void *record_file(struct stasis_app_control *control,
struct ast_channel *chan, void *data)
{
RAII_VAR(struct stasis_app_recording *, recording,
NULL, recording_cleanup);
char *acceptdtmf;
int res;
int duration = 0;
recording = data;
ast_assert(recording != NULL);
if (stasis_app_get_bridge(control)) {
ast_log(LOG_ERROR, "Cannot record channel while in bridge\n");
recording_fail(recording);
return NULL;
}
switch (recording->options->terminate_on) {
case STASIS_APP_RECORDING_TERMINATE_NONE:
case STASIS_APP_RECORDING_TERMINATE_INVALID:
acceptdtmf = "";
break;
case STASIS_APP_RECORDING_TERMINATE_ANY:
acceptdtmf = "#*0123456789abcd";
break;
default:
acceptdtmf = ast_alloca(2);
acceptdtmf[0] = recording->options->terminate_on;
acceptdtmf[1] = '\0';
}
res = ast_auto_answer(chan);
if (res != 0) {
ast_debug(3, "%s: Failed to answer\n",
ast_channel_uniqueid(chan));
recording_fail(recording);
return NULL;
}
ao2_lock(recording);
recording->state = STASIS_APP_RECORDING_STATE_RECORDING;
recording_publish(recording);
ao2_unlock(recording);
ast_play_and_record_full(chan,
NULL, /* playfile */
recording->absolute_name,
recording->options->max_duration_seconds,
recording->options->format,
&duration,
NULL, /* sound_duration */
recording->options->beep,
-1, /* silencethreshold */
recording->options->max_silence_seconds * 1000,
NULL, /* path */
acceptdtmf,
NULL, /* canceldtmf */
1, /* skip_confirmation_sound */
recording->options->if_exists);
ast_debug(3, "%s: Recording complete\n", ast_channel_uniqueid(chan));
ao2_lock(recording);
recording->state = STASIS_APP_RECORDING_STATE_COMPLETE;
recording_publish(recording);
ao2_unlock(recording);
return NULL;
}
static void recording_dtor(void *obj)
{
struct stasis_app_recording *recording = obj;
ao2_cleanup(recording->options);
}
struct stasis_app_recording *stasis_app_control_record(
struct stasis_app_control *control,
struct stasis_app_recording_options *options)
{
RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
char *last_slash;
errno = 0;
if (options == NULL ||
ast_strlen_zero(options->name) ||
ast_strlen_zero(options->format) ||
options->max_silence_seconds < 0 ||
options->max_duration_seconds < 0) {
errno = EINVAL;
return NULL;
}
ast_debug(3, "%s: Sending record(%s.%s) command\n",
stasis_app_control_get_channel_id(control), options->name,
options->format);
recording = ao2_alloc(sizeof(*recording), recording_dtor);
if (!recording) {
errno = ENOMEM;
return NULL;
}
ast_asprintf(&recording->absolute_name, "%s/%s",
ast_config_AST_RECORDING_DIR, options->name);
if (recording->absolute_name == NULL) {
errno = ENOMEM;
return NULL;
}
if ((last_slash = strrchr(recording->absolute_name, '/'))) {
*last_slash = '\0';
if (ast_safe_mkdir(ast_config_AST_RECORDING_DIR,
recording->absolute_name, 0777) != 0) {
/* errno set by ast_mkdir */
return NULL;
}
*last_slash = '/';
}
ao2_ref(options, +1);
recording->options = options;
recording->control = control;
recording->state = STASIS_APP_RECORDING_STATE_QUEUED;
{
RAII_VAR(struct stasis_app_recording *, old_recording, NULL,
ao2_cleanup);
SCOPED_AO2LOCK(lock, recordings);
old_recording = ao2_find(recordings, options->name,
OBJ_KEY | OBJ_NOLOCK);
if (old_recording) {
ast_log(LOG_WARNING,
"Recording %s already in progress\n",
recording->options->name);
errno = EEXIST;
return NULL;
}
ao2_link(recordings, recording);
}
/* A ref is kept in the recordings container; no need to bump */
stasis_app_send_command_async(control, record_file, recording);
/* Although this should be bumped for the caller */
ao2_ref(recording, +1);
return recording;
}
enum stasis_app_recording_state stasis_app_recording_get_state(
struct stasis_app_recording *recording)
{
return recording->state;
}
const char *stasis_app_recording_get_name(
struct stasis_app_recording *recording)
{
return recording->options->name;
}
struct stasis_app_recording *stasis_app_recording_find_by_name(const char *name)
{
RAII_VAR(struct stasis_app_recording *, recording, NULL, ao2_cleanup);
recording = ao2_find(recordings, name, OBJ_KEY);
if (recording == NULL) {
return NULL;
}
ao2_ref(recording, +1);
return recording;
}
struct ast_json *stasis_app_recording_to_json(
const struct stasis_app_recording *recording)
{
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
if (recording == NULL) {
return NULL;
}
json = ast_json_pack("{s: s, s: s, s: s}",
"name", recording->options->name,
"format", recording->options->format,
"state", state_to_string(recording->state));
return ast_json_ref(json);
}
typedef int (*recording_operation_cb)(struct stasis_app_recording *recording);
static int recording_noop(struct stasis_app_recording *recording)
{
return 0;
}
static int recording_disregard(struct stasis_app_recording *recording)
{
recording->state = STASIS_APP_RECORDING_STATE_CANCELED;
return 0;
}
static int recording_cancel(struct stasis_app_recording *recording)
{
int res = 0;
recording->state = STASIS_APP_RECORDING_STATE_CANCELED;
res |= stasis_app_control_queue_control(recording->control,
AST_CONTROL_RECORD_CANCEL);
res |= ast_filedelete(recording->absolute_name, NULL);
return res;
}
static int recording_stop(struct stasis_app_recording *recording)
{
recording->state = STASIS_APP_RECORDING_STATE_COMPLETE;
return stasis_app_control_queue_control(recording->control,
AST_CONTROL_RECORD_STOP);
}
static int recording_pause(struct stasis_app_recording *recording)
{
recording->state = STASIS_APP_RECORDING_STATE_PAUSED;
return stasis_app_control_queue_control(recording->control,
AST_CONTROL_RECORD_SUSPEND);
}
static int recording_unpause(struct stasis_app_recording *recording)
{
recording->state = STASIS_APP_RECORDING_STATE_RECORDING;
return stasis_app_control_queue_control(recording->control,
AST_CONTROL_RECORD_SUSPEND);
}
static int recording_mute(struct stasis_app_recording *recording)
{
if (recording->muted) {
/* already muted */
return 0;
}
recording->muted = 1;
return stasis_app_control_queue_control(recording->control,
AST_CONTROL_RECORD_MUTE);
}
static int recording_unmute(struct stasis_app_recording *recording)
{
if (!recording->muted) {
/* already unmuted */
return 0;
}
return stasis_app_control_queue_control(recording->control,
AST_CONTROL_RECORD_MUTE);
}
recording_operation_cb operations[STASIS_APP_RECORDING_STATE_MAX][STASIS_APP_RECORDING_OPER_MAX] = {
[STASIS_APP_RECORDING_STATE_QUEUED][STASIS_APP_RECORDING_CANCEL] = recording_disregard,
[STASIS_APP_RECORDING_STATE_QUEUED][STASIS_APP_RECORDING_STOP] = recording_disregard,
[STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_CANCEL] = recording_cancel,
[STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_STOP] = recording_stop,
[STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_PAUSE] = recording_pause,
[STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_UNPAUSE] = recording_noop,
[STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_MUTE] = recording_mute,
[STASIS_APP_RECORDING_STATE_RECORDING][STASIS_APP_RECORDING_UNMUTE] = recording_unmute,
[STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_CANCEL] = recording_cancel,
[STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_STOP] = recording_stop,
[STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_PAUSE] = recording_noop,
[STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_UNPAUSE] = recording_unpause,
[STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_MUTE] = recording_mute,
[STASIS_APP_RECORDING_STATE_PAUSED][STASIS_APP_RECORDING_UNMUTE] = recording_unmute,
};
enum stasis_app_recording_oper_results stasis_app_recording_operation(
struct stasis_app_recording *recording,
enum stasis_app_recording_media_operation operation)
{
recording_operation_cb cb;
SCOPED_AO2LOCK(lock, recording);
if (recording->state < 0 || recording->state >= STASIS_APP_RECORDING_STATE_MAX) {
ast_log(LOG_WARNING, "Invalid recording state %d\n",
recording->state);
return -1;
}
if (operation < 0 || operation >= STASIS_APP_RECORDING_OPER_MAX) {
ast_log(LOG_WARNING, "Invalid recording operation %d\n",
operation);
return -1;
}
cb = operations[recording->state][operation];
if (!cb) {
if (recording->state != STASIS_APP_RECORDING_STATE_RECORDING) {
/* So we can be specific in our error message. */
return STASIS_APP_RECORDING_OPER_NOT_RECORDING;
} else {
/* And, really, all operations should be valid during
* recording */
ast_log(LOG_ERROR,
"Unhandled operation during recording: %d\n",
operation);
return STASIS_APP_RECORDING_OPER_FAILED;
}
}
return cb(recording) ?
STASIS_APP_RECORDING_OPER_FAILED : STASIS_APP_RECORDING_OPER_OK;
}
static int load_module(void)
{
int r;
r = STASIS_MESSAGE_TYPE_INIT(stasis_app_recording_snapshot_type);
if (r != 0) {
return AST_MODULE_LOAD_FAILURE;
}
recordings = ao2_container_alloc(RECORDING_BUCKETS, recording_hash,
recording_cmp);
if (!recordings) {
return AST_MODULE_LOAD_FAILURE;
}
return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
{
ao2_cleanup(recordings);
recordings = NULL;
STASIS_MESSAGE_TYPE_CLEANUP(stasis_app_recording_snapshot_type);
return 0;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application recording support",
.load = load_module,
.unload = unload_module,
.nonoptreq = "res_stasis");