diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 94053eec29b3714aff1eba2573759d1bedefcae6..59145fc08bd3b25dfd3fdb59a08a2e727e07914a 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -4334,4 +4334,35 @@ const char *ast_channel_oldest_linkedid(const char *a, const char *b); */ void ast_channel_name_to_dial_string(char *channel_name); +#define AST_MUTE_DIRECTION_READ (1 << 0) +#define AST_MUTE_DIRECTION_WRITE (1 << 1) + +/*! + * \brief Suppress passing of a frame type on a channel + * + * \note The channel should be locked before calling this function. + * + * \param chan The channel to suppress + * \param direction The direction in which to suppress + * \param frametype The type of frame (AST_FRAME_VOICE, etc) to suppress + * + * \retval 0 Success + * \retval -1 Failure + */ +int ast_channel_suppress(struct ast_channel *chan, unsigned int direction, enum ast_frame_type frametype); + +/*! + * \brief Stop suppressing of a frame type on a channel + * + * \note The channel should be locked before calling this function. + * + * \param chan The channel to stop suppressing + * \param direction The direction in which to stop suppressing + * \param frametype The type of frame (AST_FRAME_VOICE, etc) to stop suppressing + * + * \retval 0 Success + * \retval -1 Failure + */ +int ast_channel_unsuppress(struct ast_channel *chan, unsigned int direction, enum ast_frame_type frametype); + #endif /* _ASTERISK_CHANNEL_H */ diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h index 33091409c7f1dae04f578fcc55673bd3878257f9..731133674dcc376d9c6ca3ea0c212c6e790c6ebb 100644 --- a/include/asterisk/stasis_app.h +++ b/include/asterisk/stasis_app.h @@ -165,6 +165,30 @@ int stasis_app_control_dial(struct stasis_app_control *control, const char *endp */ int stasis_app_control_continue(struct stasis_app_control *control, const char *context, const char *extension, int priority); +/*! + * \brief Mute the channel associated with this control. + * + * \param control Control for \c res_stasis. + * \param direction The direction in which the audio should be muted. + * \param frametype The type of stream that should be muted. + * + * \return 0 for success + * \return -1 for error. + */ +int stasis_app_control_mute(struct stasis_app_control *control, unsigned int direction, enum ast_frame_type frametype); + +/*! + * \brief Unmute the channel associated with this control. + * + * \param control Control for \c res_stasis. + * \param direction The direction in which the audio should be unmuted. + * \param frametype The type of stream that should be unmuted. + * + * \return 0 for success + * \return -1 for error. + */ +int stasis_app_control_unmute(struct stasis_app_control *control, unsigned int direction, enum ast_frame_type frametype); + /*! * \brief Answer the channel associated with this control. * \param control Control for \c res_stasis. diff --git a/main/channel.c b/main/channel.c index 9932642c3809cb175110f4d7fde2ed77e67f18aa..fba59d4df4adfa757e8ca0e1da51a313f5cfae1e 100644 --- a/main/channel.c +++ b/main/channel.c @@ -10248,3 +10248,159 @@ int ast_channel_move(struct ast_channel *dest, struct ast_channel *source) ast_do_masquerade(dest); return 0; } + +static void suppress_datastore_destroy_cb(void *data) +{ + ao2_cleanup(data); +} + +static const struct ast_datastore_info suppress_datastore_voice = { + .type = "suppressvoice", + .destroy = suppress_datastore_destroy_cb +}; + +static void suppress_framehook_destroy_cb(void *data) +{ + ao2_cleanup(data); +} + +struct suppress_data { + enum ast_frame_type frametype; + unsigned int direction; + int framehook_id; +}; + +static struct ast_frame *suppress_framehook_event_cb(struct ast_channel *chan, struct ast_frame *frame, enum ast_framehook_event event, void *data) +{ + struct suppress_data *suppress = data; + int suppress_frame = 0; + + if (!frame) { + return NULL; + } + + if (frame->frametype != suppress->frametype) { + return frame; + } + + if (event == AST_FRAMEHOOK_EVENT_READ && (suppress->direction & AST_MUTE_DIRECTION_READ)) { + suppress_frame = 1; + } else if (event == AST_FRAMEHOOK_EVENT_WRITE && (suppress->direction & AST_MUTE_DIRECTION_WRITE)) { + suppress_frame = 1; + } + + if (suppress_frame) { + switch (frame->frametype) { + case AST_FRAME_VOICE: + frame = &ast_null_frame; + break; + default: + break; + } + } + + return frame; +} + +static const struct ast_datastore_info *suppress_get_datastore_information(enum ast_frame_type frametype) +{ + switch (frametype) { + case AST_FRAME_VOICE: + return &suppress_datastore_voice; + default: + return NULL; + } +} + +int ast_channel_suppress(struct ast_channel *chan, unsigned int direction, enum ast_frame_type frametype) +{ + RAII_VAR(struct suppress_data *, suppress, NULL, ao2_cleanup); + const struct ast_datastore_info *datastore_info = NULL; + struct ast_datastore *datastore = NULL; + struct ast_framehook_interface interface = { + .version = AST_FRAMEHOOK_INTERFACE_VERSION, + .event_cb = suppress_framehook_event_cb, + .destroy_cb = suppress_framehook_destroy_cb, + }; + int framehook_id; + + if (!(datastore_info = suppress_get_datastore_information(frametype))) { + ast_log(LOG_WARNING, "Attempted to suppress an unsupported frame type (%d).\n", frametype); + return -1; + } + + if ((datastore = ast_channel_datastore_find(chan, datastore_info, NULL))) { + suppress = datastore->data; + ao2_ref(suppress, +1); + + suppress->direction |= direction; + + return 0; + } + + if (!(suppress = ao2_alloc(sizeof(*suppress), NULL))) { + ast_log(LOG_WARNING, "Failed to allocate data while attempting to suppress a stream.\n"); + return -1; + } + + suppress->frametype = frametype; + suppress->direction |= direction; + + interface.data = suppress; + + framehook_id = ast_framehook_attach(chan, &interface); + if (framehook_id < 0) { + /* Hook attach failed. Get rid of the evidence. */ + ast_log(LOG_WARNING, "Failed to attach framehook while attempting to suppress a stream.\n"); + return -1; + } + + /* One ref for the framehook */ + ao2_ref(suppress, +1); + + suppress->framehook_id = framehook_id; + + if (!(datastore = ast_datastore_alloc(datastore_info, NULL))) { + ast_log(LOG_WARNING, "Failed to allocate datastore while attempting to suppress a stream.\n"); + ast_framehook_detach(chan, framehook_id); + return -1; + } + + datastore->data = suppress; + + ast_channel_datastore_add(chan, datastore); + + /* and another ref for the datastore */ + ao2_ref(suppress, +1); + + return 0; +} + +int ast_channel_unsuppress(struct ast_channel *chan, unsigned int direction, enum ast_frame_type frametype) +{ + const struct ast_datastore_info *datastore_info = NULL; + struct ast_datastore *datastore = NULL; + struct suppress_data *suppress; + + if (!(datastore_info = suppress_get_datastore_information(frametype))) { + ast_log(LOG_WARNING, "Attempted to unsuppress an unsupported frame type (%d).\n", frametype); + return -1; + } + + if (!(datastore = ast_channel_datastore_find(chan, datastore_info, NULL))) { + /* Nothing to do! */ + return 0; + } + + suppress = datastore->data; + + suppress->direction &= ~(direction); + + if (suppress->direction == 0) { + /* Nothing left to suppress. Bye! */ + ast_framehook_detach(chan, suppress->framehook_id); + ast_channel_datastore_remove(chan, datastore); + } + + return 0; +} diff --git a/res/res_mutestream.c b/res/res_mutestream.c index f7032eacf79a1a066f1c1336c3901b6075d20ae7..b907fbe3a8d101c8083b0f551c718235ab69af80 100644 --- a/res/res_mutestream.c +++ b/res/res_mutestream.c @@ -123,149 +123,39 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") ***/ -/*! Our own datastore */ -struct mute_information { - struct ast_audiohook audiohook; - int mute_write; - int mute_read; -}; - - -/*! Datastore destroy audiohook callback */ -static void destroy_callback(void *data) +static int mute_channel(struct ast_channel *chan, const char *direction, int mute) { - struct mute_information *mute = data; - - /* Destroy the audiohook, and destroy ourselves */ - ast_audiohook_destroy(&mute->audiohook); - ast_free(mute); - ast_module_unref(ast_module_info->self); -} - -/*! \brief Static structure for datastore information */ -static const struct ast_datastore_info mute_datastore = { - .type = "mute", - .destroy = destroy_callback -}; - -/*! \brief The callback from the audiohook subsystem. We basically get a frame to have fun with */ -static int mute_callback(struct ast_audiohook *audiohook, struct ast_channel *chan, struct ast_frame *frame, enum ast_audiohook_direction direction) -{ - struct ast_datastore *datastore = NULL; - struct mute_information *mute = NULL; - - - /* If the audiohook is stopping it means the channel is shutting down.... but we let the datastore destroy take care of it */ - if (audiohook->status == AST_AUDIOHOOK_STATUS_DONE) { - return 0; + unsigned int mute_direction = 0; + enum ast_frame_type frametype = AST_FRAME_VOICE; + int ret = 0; + + if (!strcmp(direction, "in")) { + mute_direction = AST_MUTE_DIRECTION_READ; + } else if (!strcmp(direction, "out")) { + mute_direction = AST_MUTE_DIRECTION_WRITE; + } else if (!strcmp(direction, "all")) { + mute_direction = AST_MUTE_DIRECTION_READ | AST_MUTE_DIRECTION_WRITE; + } else { + return -1; } ast_channel_lock(chan); - /* Grab datastore which contains our mute information */ - if (!(datastore = ast_channel_datastore_find(chan, &mute_datastore, NULL))) { - ast_channel_unlock(chan); - ast_debug(2, "Can't find any datastore to use. Bad. \n"); - return 0; - } - - mute = datastore->data; - - /* If this is audio then allow them to increase/decrease the gains */ - if (frame->frametype == AST_FRAME_VOICE) { - ast_debug(2, "Audio frame - direction %s mute READ %s WRITE %s\n", direction == AST_AUDIOHOOK_DIRECTION_READ ? "read" : "write", mute->mute_read ? "on" : "off", mute->mute_write ? "on" : "off"); - - /* Based on direction of frame grab the gain, and confirm it is applicable */ - if ((direction == AST_AUDIOHOOK_DIRECTION_READ && mute->mute_read) || (direction == AST_AUDIOHOOK_DIRECTION_WRITE && mute->mute_write)) { - /* Ok, we just want to reset all audio in this frame. Keep NOTHING, thanks. */ - ast_frame_clear(frame); - } - } - ast_channel_unlock(chan); - - return 0; -} - -/*! \brief Initialize mute hook on channel, but don't activate it - \pre Assumes that the channel is locked -*/ -static struct ast_datastore *initialize_mutehook(struct ast_channel *chan) -{ - struct ast_datastore *datastore = NULL; - struct mute_information *mute = NULL; - - ast_debug(2, "Initializing new Mute Audiohook \n"); - - /* Allocate a new datastore to hold the reference to this mute_datastore and audiohook information */ - if (!(datastore = ast_datastore_alloc(&mute_datastore, NULL))) { - return NULL; + if (mute) { + ret = ast_channel_suppress(chan, mute_direction, frametype); + } else { + ret = ast_channel_unsuppress(chan, mute_direction, frametype); } - if (!(mute = ast_calloc(1, sizeof(*mute)))) { - ast_datastore_free(datastore); - return NULL; - } - ast_audiohook_init(&mute->audiohook, AST_AUDIOHOOK_TYPE_MANIPULATE, "Mute", AST_AUDIOHOOK_MANIPULATE_ALL_RATES); - mute->audiohook.manipulate_callback = mute_callback; - datastore->data = mute; - return datastore; -} + ast_channel_unlock(chan); -/*! \brief Add or activate mute audiohook on channel - Assumes channel is locked -*/ -static int mute_add_audiohook(struct ast_channel *chan, struct mute_information *mute, struct ast_datastore *datastore) -{ - /* Activate the settings */ - ast_channel_datastore_add(chan, datastore); - if (ast_audiohook_attach(chan, &mute->audiohook)) { - ast_log(LOG_ERROR, "Failed to attach audiohook for muting channel %s\n", ast_channel_name(chan)); - return -1; - } - ast_module_ref(ast_module_info->self); - ast_debug(2, "Initialized audiohook on channel %s\n", ast_channel_name(chan)); - return 0; + return ret; } /*! \brief Mute dialplan function */ static int func_mute_write(struct ast_channel *chan, const char *cmd, char *data, const char *value) { - struct ast_datastore *datastore = NULL; - struct mute_information *mute = NULL; - int is_new = 0; - int turnon; - - ast_channel_lock(chan); - if (!(datastore = ast_channel_datastore_find(chan, &mute_datastore, NULL))) { - if (!(datastore = initialize_mutehook(chan))) { - ast_channel_unlock(chan); - return 0; - } - is_new = 1; - } - mute = datastore->data; - - turnon = ast_true(value); - if (!strcasecmp(data, "out")) { - mute->mute_write = turnon; - ast_debug(1, "%s channel - outbound \n", turnon ? "Muting" : "Unmuting"); - } else if (!strcasecmp(data, "in")) { - mute->mute_read = turnon; - ast_debug(1, "%s channel - inbound \n", turnon ? "Muting" : "Unmuting"); - } else if (!strcasecmp(data,"all")) { - mute->mute_write = mute->mute_read = turnon; - } - - if (is_new) { - if (mute_add_audiohook(chan, mute, datastore)) { - /* Can't add audiohook - already printed error message */ - ast_datastore_free(datastore); - ast_free(mute); - } - } - ast_channel_unlock(chan); - - return 0; + return mute_channel(chan, data, ast_true(value)); } /* Function for debugging - might be useful */ @@ -282,10 +172,6 @@ static int manager_mutestream(struct mansession *s, const struct message *m) const char *direction = astman_get_header(m,"Direction"); char id_text[256]; struct ast_channel *c = NULL; - struct ast_datastore *datastore = NULL; - struct mute_information *mute = NULL; - int is_new = 0; - int turnon; if (ast_strlen_zero(channel)) { astman_send_error(s, m, "Channel not specified"); @@ -307,40 +193,12 @@ static int manager_mutestream(struct mansession *s, const struct message *m) return 0; } - ast_channel_lock(c); - - if (!(datastore = ast_channel_datastore_find(c, &mute_datastore, NULL))) { - if (!(datastore = initialize_mutehook(c))) { - ast_channel_unlock(c); - ast_channel_unref(c); - astman_send_error(s, m, "Memory allocation failure"); - return 0; - } - is_new = 1; - } - mute = datastore->data; - - turnon = ast_true(state); - if (!strcasecmp(direction, "in")) { - mute->mute_read = turnon; - } else if (!strcasecmp(direction, "out")) { - mute->mute_write = turnon; - } else if (!strcasecmp(direction, "all")) { - mute->mute_read = mute->mute_write = turnon; + if (mute_channel(c, direction, ast_true(state))) { + astman_send_error(s, m, "Failed to mute/unmute stream"); + ast_channel_unref(c); + return 0; } - if (is_new) { - if (mute_add_audiohook(c, mute, datastore)) { - /* Can't add audiohook */ - ast_datastore_free(datastore); - ast_free(mute); - ast_channel_unlock(c); - ast_channel_unref(c); - astman_send_error(s, m, "Couldn't add mute audiohook"); - return 0; - } - } - ast_channel_unlock(c); ast_channel_unref(c); if (!ast_strlen_zero(id)) { diff --git a/res/stasis/control.c b/res/stasis/control.c index 1fdcb8dedfbf7d1ae24b1952b3fc761d20aa3048..df57a90a7f4a1e23aa8362c9d36b59a7a2d49f8d 100644 --- a/res/stasis/control.c +++ b/res/stasis/control.c @@ -35,6 +35,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/bridging.h" #include "asterisk/bridging_basic.h" #include "asterisk/bridging_features.h" +#include "asterisk/frame.h" #include "asterisk/pbx.h" struct stasis_app_control { @@ -207,6 +208,65 @@ int stasis_app_control_continue(struct stasis_app_control *control, const char * return 0; } +struct stasis_app_control_mute_data { + enum ast_frame_type frametype; + unsigned int direction; +}; + +static void *app_control_mute(struct stasis_app_control *control, + struct ast_channel *chan, void *data) +{ + RAII_VAR(struct stasis_app_control_mute_data *, mute_data, data, ast_free); + SCOPED_CHANNELLOCK(lockvar, chan); + + ast_channel_suppress(control->channel, mute_data->direction, mute_data->frametype); + + return NULL; +} + +int stasis_app_control_mute(struct stasis_app_control *control, unsigned int direction, enum ast_frame_type frametype) +{ + struct stasis_app_control_mute_data *mute_data; + + if (!(mute_data = ast_calloc(1, sizeof(*mute_data)))) { + return -1; + } + + mute_data->direction = direction; + mute_data->frametype = frametype; + + stasis_app_send_command_async(control, app_control_mute, mute_data); + + return 0; +} + +static void *app_control_unmute(struct stasis_app_control *control, + struct ast_channel *chan, void *data) +{ + RAII_VAR(struct stasis_app_control_mute_data *, mute_data, data, ast_free); + SCOPED_CHANNELLOCK(lockvar, chan); + + ast_channel_unsuppress(control->channel, mute_data->direction, mute_data->frametype); + + return NULL; +} + +int stasis_app_control_unmute(struct stasis_app_control *control, unsigned int direction, enum ast_frame_type frametype) +{ + struct stasis_app_control_mute_data *mute_data; + + if (!(mute_data = ast_calloc(1, sizeof(*mute_data)))) { + return -1; + } + + mute_data->direction = direction; + mute_data->frametype = frametype; + + stasis_app_send_command_async(control, app_control_unmute, mute_data); + + return 0; +} + char *stasis_app_control_get_channel_var(struct stasis_app_control *control, const char *variable) { RAII_VAR(struct ast_str *, tmp, ast_str_create(32), ast_free); diff --git a/res/stasis_http/resource_channels.c b/res/stasis_http/resource_channels.c index 1700b86caeca6c645bbad97d608d65857b9cb31e..f0bbd4b1f4b97e8dfa0c8f8245d20035d4cfe99e 100644 --- a/res/stasis_http/resource_channels.c +++ b/res/stasis_http/resource_channels.c @@ -143,12 +143,62 @@ void stasis_http_answer_channel(struct ast_variable *headers, void stasis_http_mute_channel(struct ast_variable *headers, struct ast_mute_channel_args *args, struct stasis_http_response *response) { - ast_log(LOG_ERROR, "TODO: stasis_http_mute_channel\n"); + RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup); + unsigned int direction = 0; + enum ast_frame_type frametype = AST_FRAME_VOICE; + + control = find_control(response, args->channel_id); + if (control == NULL) { + return; + } + + if (!strcmp(args->direction, "in")) { + direction = AST_MUTE_DIRECTION_READ; + } else if (!strcmp(args->direction, "out")) { + direction = AST_MUTE_DIRECTION_WRITE; + } else if (!strcmp(args->direction, "both")) { + direction = AST_MUTE_DIRECTION_READ | AST_MUTE_DIRECTION_WRITE; + } else { + stasis_http_response_error( + response, 400, "Bad Request", + "Invalid direction specified"); + return; + } + + stasis_app_control_mute(control, direction, frametype); + + stasis_http_response_no_content(response); } + void stasis_http_unmute_channel(struct ast_variable *headers, struct ast_unmute_channel_args *args, struct stasis_http_response *response) { - ast_log(LOG_ERROR, "TODO: stasis_http_unmute_channel\n"); + RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup); + unsigned int direction = 0; + enum ast_frame_type frametype = AST_FRAME_VOICE; + + control = find_control(response, args->channel_id); + if (control == NULL) { + return; + } + + if (!strcmp(args->direction, "in")) { + direction = AST_MUTE_DIRECTION_READ; + } else if (!strcmp(args->direction, "out")) { + direction = AST_MUTE_DIRECTION_WRITE; + } else if (!strcmp(args->direction, "both")) { + direction = AST_MUTE_DIRECTION_READ | AST_MUTE_DIRECTION_WRITE; + } else { + stasis_http_response_error( + response, 400, "Bad Request", + "Invalid direction specified"); + return; + } + + stasis_app_control_unmute(control, direction, frametype); + + stasis_http_response_no_content(response); } + void stasis_http_hold_channel(struct ast_variable *headers, struct ast_hold_channel_args *args, struct stasis_http_response *response) { RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);