diff options
Diffstat (limited to 'apps/app_jack.c')
-rw-r--r-- | apps/app_jack.c | 959 |
1 files changed, 959 insertions, 0 deletions
diff --git a/apps/app_jack.c b/apps/app_jack.c new file mode 100644 index 000000000..46d00f6d5 --- /dev/null +++ b/apps/app_jack.c @@ -0,0 +1,959 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2007 - 2008, Russell Bryant + * + * Russell Bryant <russell@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 Jack Application + * + * \author Russell Bryant <russell@digium.com> + * + * This is an application to connect an Asterisk channel to an input + * and output jack port so that the audio can be processed through + * another application, or to play audio from another application. + * + * \arg http://www.jackaudio.org/ + * + * \ingroup applications + */ + +/*** MODULEINFO + <depend>jack</depend> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <limits.h> + +#include <jack/jack.h> +#include <jack/ringbuffer.h> + +#include "asterisk/module.h" +#include "asterisk/channel.h" +#include "asterisk/strings.h" +#include "asterisk/lock.h" +#include "asterisk/libresample.h" +#include "asterisk/app.h" +#include "asterisk/pbx.h" +#include "asterisk/audiohook.h" + +#define RESAMPLE_QUALITY 0 + +#define RINGBUFFER_SIZE 16384 + +/*! \brief Common options between the Jack() app and JACK_HOOK() function */ +#define COMMON_OPTIONS \ +" s(<name>) - Connect to the specified jack server name.\n" \ +" i(<name>) - Connect the output port that gets created to the specified\n" \ +" jack input port.\n" \ +" o(<name>) - Connect the input port that gets created to the specified\n" \ +" jack output port.\n" + +static char *jack_app = "JACK"; +static char *jack_synopsis = +"JACK (Jack Audio Connection Kit) Application"; +static char *jack_desc = +"JACK([options])\n" +" When this application is executed, two jack ports will be created; one input\n" +"and one output. Other applications can be hooked up to these ports to access\n" +"the audio coming from, or being sent to the channel.\n" +" Valid options:\n" +COMMON_OPTIONS +""; + +struct jack_data { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(server_name); + AST_STRING_FIELD(connect_input_port); + AST_STRING_FIELD(connect_output_port); + ); + jack_client_t *client; + jack_port_t *input_port; + jack_port_t *output_port; + jack_ringbuffer_t *input_rb; + jack_ringbuffer_t *output_rb; + void *output_resampler; + double output_resample_factor; + void *input_resampler; + double input_resample_factor; + unsigned int stop:1; + unsigned int has_audiohook:1; + /*! Only used with JACK_HOOK */ + struct ast_audiohook audiohook; +}; + +static const struct { + jack_status_t status; + const char *str; +} jack_status_table[] = { + { JackFailure, "Failure" }, + { JackInvalidOption, "Invalid Option" }, + { JackNameNotUnique, "Name Not Unique" }, + { JackServerStarted, "Server Started" }, + { JackServerFailed, "Server Failed" }, + { JackServerError, "Server Error" }, + { JackNoSuchClient, "No Such Client" }, + { JackLoadFailure, "Load Failure" }, + { JackInitFailure, "Init Failure" }, + { JackShmFailure, "Shared Memory Access Failure" }, + { JackVersionError, "Version Mismatch" }, +}; + +static const char *jack_status_to_str(jack_status_t status) +{ + int i; + + for (i = 0; i < ARRAY_LEN(jack_status_table); i++) { + if (jack_status_table[i].status == status) + return jack_status_table[i].str; + } + + return "Unknown Error"; +} + +static void log_jack_status(const char *prefix, jack_status_t status) +{ + struct ast_str *str = ast_str_alloca(512); + int i, first = 0; + + for (i = 0; i < (sizeof(status) * 8); i++) { + if (!(status & (1 << i))) + continue; + + if (!first) { + ast_str_set(&str, 0, "%s", jack_status_to_str((1 << i))); + first = 1; + } else + ast_str_append(&str, 0, ", %s", jack_status_to_str((1 << i))); + } + + ast_log(LOG_NOTICE, "%s: %s\n", prefix, str->str); +} + +static int alloc_resampler(struct jack_data *jack_data, int input) +{ + double from_srate, to_srate, jack_srate; + void **resampler; + double *resample_factor; + + if (input && jack_data->input_resampler) + return 0; + + if (!input && jack_data->output_resampler) + return 0; + + jack_srate = jack_get_sample_rate(jack_data->client); + + /* XXX Hard coded 8 kHz */ + + to_srate = input ? 8000.0 : jack_srate; + from_srate = input ? jack_srate : 8000.0; + + resample_factor = input ? &jack_data->input_resample_factor : + &jack_data->output_resample_factor; + + if (from_srate == to_srate) { + /* Awesome! The jack sample rate is the same as ours. + * Resampling isn't needed. */ + *resample_factor = 1.0; + return 0; + } + + *resample_factor = to_srate / from_srate; + + resampler = input ? &jack_data->input_resampler : + &jack_data->output_resampler; + + if (!(*resampler = resample_open(RESAMPLE_QUALITY, + *resample_factor, *resample_factor))) { + ast_log(LOG_ERROR, "Failed to open %s resampler\n", + input ? "input" : "output"); + return -1; + } + + return 0; +} + +/*! + * \brief Handle jack input port + * + * Read nframes number of samples from the input buffer, resample it + * if necessary, and write it into the appropriate ringbuffer. + */ +static void handle_input(void *buf, jack_nframes_t nframes, + struct jack_data *jack_data) +{ + short s_buf[nframes]; + float *in_buf = buf; + size_t res; + int i; + size_t write_len = sizeof(s_buf); + + if (jack_data->input_resampler) { + int total_in_buf_used = 0; + int total_out_buf_used = 0; + float f_buf[nframes + 1]; + + memset(f_buf, 0, sizeof(f_buf)); + + while (total_in_buf_used < nframes) { + int in_buf_used; + int out_buf_used; + + out_buf_used = resample_process(jack_data->input_resampler, + jack_data->input_resample_factor, + &in_buf[total_in_buf_used], nframes - total_in_buf_used, + 0, &in_buf_used, + &f_buf[total_out_buf_used], ARRAY_LEN(f_buf) - total_out_buf_used); + + if (out_buf_used < 0) + break; + + total_out_buf_used += out_buf_used; + total_in_buf_used += in_buf_used; + + if (total_out_buf_used == ARRAY_LEN(f_buf)) { + ast_log(LOG_ERROR, "Output buffer filled ... need to increase its size, " + "nframes '%d', total_out_buf_used '%d'\n", nframes, total_out_buf_used); + break; + } + } + + for (i = 0; i < total_out_buf_used; i++) + s_buf[i] = f_buf[i] * (SHRT_MAX / 1.0); + + write_len = total_out_buf_used * sizeof(int16_t); + } else { + /* No resampling needed */ + + for (i = 0; i < nframes; i++) + s_buf[i] = in_buf[i] * (SHRT_MAX / 1.0); + } + + res = jack_ringbuffer_write(jack_data->input_rb, (const char *) s_buf, write_len); + if (res != write_len) { + ast_debug(2, "Tried to write %d bytes to the ringbuffer, but only wrote %d\n", + (int) sizeof(s_buf), (int) res); + } +} + +/*! + * \brief Handle jack output port + * + * Read nframes number of samples from the ringbuffer and write it out to the + * output port buffer. + */ +static void handle_output(void *buf, jack_nframes_t nframes, + struct jack_data *jack_data) +{ + size_t res, len; + + len = nframes * sizeof(float); + + res = jack_ringbuffer_read(jack_data->output_rb, buf, len); + + if (len != res) { + ast_debug(2, "Wanted %d bytes to send to the output port, " + "but only got %d\n", (int) len, (int) res); + } +} + +static int jack_process(jack_nframes_t nframes, void *arg) +{ + struct jack_data *jack_data = arg; + void *input_port_buf, *output_port_buf; + + if (!jack_data->input_resample_factor) + alloc_resampler(jack_data, 1); + + input_port_buf = jack_port_get_buffer(jack_data->input_port, nframes); + handle_input(input_port_buf, nframes, jack_data); + + output_port_buf = jack_port_get_buffer(jack_data->output_port, nframes); + handle_output(output_port_buf, nframes, jack_data); + + return 0; +} + +static void jack_shutdown(void *arg) +{ + struct jack_data *jack_data = arg; + + jack_data->stop = 1; +} + +static struct jack_data *destroy_jack_data(struct jack_data *jack_data) +{ + if (jack_data->input_port) { + jack_port_unregister(jack_data->client, jack_data->input_port); + jack_data->input_port = NULL; + } + + if (jack_data->output_port) { + jack_port_unregister(jack_data->client, jack_data->output_port); + jack_data->output_port = NULL; + } + + if (jack_data->client) { + jack_client_close(jack_data->client); + jack_data->client = NULL; + } + + if (jack_data->input_rb) { + jack_ringbuffer_free(jack_data->input_rb); + jack_data->input_rb = NULL; + } + + if (jack_data->output_rb) { + jack_ringbuffer_free(jack_data->output_rb); + jack_data->output_rb = NULL; + } + + if (jack_data->output_resampler) { + resample_close(jack_data->output_resampler); + jack_data->output_resampler = NULL; + } + + if (jack_data->input_resampler) { + resample_close(jack_data->input_resampler); + jack_data->input_resampler = NULL; + } + + if (jack_data->has_audiohook) + ast_audiohook_destroy(&jack_data->audiohook); + + ast_string_field_free_memory(jack_data); + + ast_free(jack_data); + + return NULL; +} + +static int init_jack_data(struct ast_channel *chan, struct jack_data *jack_data) +{ + const char *chan_name; + jack_status_t status = 0; + + ast_channel_lock(chan); + chan_name = ast_strdupa(chan->name); + ast_channel_unlock(chan); + + if (!(jack_data->output_rb = jack_ringbuffer_create(RINGBUFFER_SIZE))) + return -1; + + if (!(jack_data->input_rb = jack_ringbuffer_create(RINGBUFFER_SIZE))) + return -1; + + if (!ast_strlen_zero(jack_data->server_name)) { + jack_data->client = jack_client_open(chan_name, JackServerName, &status, + jack_data->server_name); + } else { + jack_data->client = jack_client_open(chan_name, JackNullOption, &status); + } + + if (status) + log_jack_status("Client Open Status", status); + + if (!jack_data->client) + return -1; + + jack_data->input_port = jack_port_register(jack_data->client, "input", + JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput | JackPortIsTerminal, 0); + if (!jack_data->input_port) { + ast_log(LOG_ERROR, "Failed to create input port for jack port\n"); + return -1; + } + + jack_data->output_port = jack_port_register(jack_data->client, "output", + JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsTerminal, 0); + if (!jack_data->output_port) { + ast_log(LOG_ERROR, "Failed to create output port for jack port\n"); + return -1; + } + + if (jack_set_process_callback(jack_data->client, jack_process, jack_data)) { + ast_log(LOG_ERROR, "Failed to register process callback with jack client\n"); + return -1; + } + + jack_on_shutdown(jack_data->client, jack_shutdown, jack_data); + + if (jack_activate(jack_data->client)) { + ast_log(LOG_ERROR, "Unable to activate jack client\n"); + return -1; + } + + while (!ast_strlen_zero(jack_data->connect_input_port)) { + const char **ports; + int i; + + ports = jack_get_ports(jack_data->client, jack_data->connect_input_port, + NULL, JackPortIsInput); + + if (!ports) { + ast_log(LOG_ERROR, "No input port matching '%s' was found\n", + jack_data->connect_input_port); + break; + } + + for (i = 0; ports[i]; i++) { + ast_debug(1, "Found port '%s' that matched specified input port '%s'\n", + ports[i], jack_data->connect_input_port); + } + + if (jack_connect(jack_data->client, jack_port_name(jack_data->output_port), ports[0])) { + ast_log(LOG_ERROR, "Failed to connect '%s' to '%s'\n", ports[0], + jack_port_name(jack_data->output_port)); + } else { + ast_debug(1, "Connected '%s' to '%s'\n", ports[0], + jack_port_name(jack_data->output_port)); + } + + free((void *) ports); + + break; + } + + while (!ast_strlen_zero(jack_data->connect_output_port)) { + const char **ports; + int i; + + ports = jack_get_ports(jack_data->client, jack_data->connect_output_port, + NULL, JackPortIsOutput); + + if (!ports) { + ast_log(LOG_ERROR, "No output port matching '%s' was found\n", + jack_data->connect_output_port); + break; + } + + for (i = 0; ports[i]; i++) { + ast_debug(1, "Found port '%s' that matched specified output port '%s'\n", + ports[i], jack_data->connect_output_port); + } + + if (jack_connect(jack_data->client, ports[0], jack_port_name(jack_data->input_port))) { + ast_log(LOG_ERROR, "Failed to connect '%s' to '%s'\n", ports[0], + jack_port_name(jack_data->input_port)); + } else { + ast_debug(1, "Connected '%s' to '%s'\n", ports[0], + jack_port_name(jack_data->input_port)); + } + + free((void *) ports); + + break; + } + + return 0; +} + +static int queue_voice_frame(struct jack_data *jack_data, struct ast_frame *f) +{ + float f_buf[f->samples * 8]; + size_t f_buf_used = 0; + int i; + int16_t *s_buf = f->data; + size_t res; + + memset(f_buf, 0, sizeof(f_buf)); + + if (!jack_data->output_resample_factor) + alloc_resampler(jack_data, 0); + + if (jack_data->output_resampler) { + float in_buf[f->samples]; + int total_in_buf_used = 0; + int total_out_buf_used = 0; + + memset(in_buf, 0, sizeof(in_buf)); + + for (i = 0; i < f->samples; i++) + in_buf[i] = s_buf[i] * (1.0 / SHRT_MAX); + + while (total_in_buf_used < ARRAY_LEN(in_buf)) { + int in_buf_used; + int out_buf_used; + + out_buf_used = resample_process(jack_data->output_resampler, + jack_data->output_resample_factor, + &in_buf[total_in_buf_used], ARRAY_LEN(in_buf) - total_in_buf_used, + 0, &in_buf_used, + &f_buf[total_out_buf_used], ARRAY_LEN(f_buf) - total_out_buf_used); + + if (out_buf_used < 0) + break; + + total_out_buf_used += out_buf_used; + total_in_buf_used += in_buf_used; + + if (total_out_buf_used == ARRAY_LEN(f_buf)) { + ast_log(LOG_ERROR, "Output buffer filled ... need to increase its size\n"); + break; + } + } + + f_buf_used = total_out_buf_used; + if (f_buf_used > ARRAY_LEN(f_buf)) + f_buf_used = ARRAY_LEN(f_buf); + } else { + /* No resampling needed */ + + for (i = 0; i < f->samples; i++) + f_buf[i] = s_buf[i] * (1.0 / SHRT_MAX); + + f_buf_used = f->samples; + } + + res = jack_ringbuffer_write(jack_data->output_rb, (const char *) f_buf, f_buf_used * sizeof(float)); + if (res != (f_buf_used * sizeof(float))) { + ast_debug(2, "Tried to write %d bytes to the ringbuffer, but only wrote %d\n", + (int) (f_buf_used * sizeof(float)), (int) res); + } + + return 0; +} + +/*! + * \brief handle jack audio + * + * \param[in] chan The Asterisk channel to write the frames to if no output frame + * is provided. + * \param[in] jack_data This is the jack_data struct that contains the input + * ringbuffer that audio will be read from. + * \param[out] out_frame If this argument is non-NULL, then assuming there is + * enough data avilable in the ringbuffer, the audio in this frame + * will get replaced with audio from the input buffer. If there is + * not enough data available to read at this time, then the frame + * data gets zeroed out. + * + * Read data from the input ringbuffer, which is the properly resampled audio + * that was read from the jack input port. Write it to the channel in 20 ms frames, + * or fill up an output frame instead if one is provided. + * + * \return Nothing. + */ +static void handle_jack_audio(struct ast_channel *chan, struct jack_data *jack_data, + struct ast_frame *out_frame) +{ + short buf[160]; + struct ast_frame f = { + .frametype = AST_FRAME_VOICE, + .subclass = AST_FORMAT_SLINEAR, + .src = "JACK", + .data = buf, + .datalen = sizeof(buf), + .samples = ARRAY_LEN(buf), + }; + + for (;;) { + size_t res, read_len; + char *read_buf; + + read_len = out_frame ? out_frame->datalen : sizeof(buf); + read_buf = out_frame ? out_frame->data : buf; + + res = jack_ringbuffer_read_space(jack_data->input_rb); + + if (res < read_len) { + /* Not enough data ready for another frame, move on ... */ + if (out_frame) { + ast_debug(1, "Sending an empty frame for the JACK_HOOK\n"); + memset(out_frame->data, 0, out_frame->datalen); + } + break; + } + + res = jack_ringbuffer_read(jack_data->input_rb, (char *) read_buf, read_len); + + if (res < read_len) { + ast_log(LOG_ERROR, "Error reading from ringbuffer, even though it said there was enough data\n"); + break; + } + + if (out_frame) { + /* If an output frame was provided, then we just want to fill up the + * buffer in that frame and return. */ + break; + } + + ast_write(chan, &f); + } +} + +enum { + OPT_SERVER_NAME = (1 << 0), + OPT_INPUT_PORT = (1 << 1), + OPT_OUTPUT_PORT = (1 << 2), +}; + +enum { + OPT_ARG_SERVER_NAME, + OPT_ARG_INPUT_PORT, + OPT_ARG_OUTPUT_PORT, + /* Must be the last element */ + OPT_ARG_ARRAY_SIZE, +}; + +AST_APP_OPTIONS(jack_exec_options, BEGIN_OPTIONS + AST_APP_OPTION_ARG('s', OPT_SERVER_NAME, OPT_ARG_SERVER_NAME), + AST_APP_OPTION_ARG('i', OPT_INPUT_PORT, OPT_ARG_INPUT_PORT), + AST_APP_OPTION_ARG('o', OPT_OUTPUT_PORT, OPT_ARG_OUTPUT_PORT), +END_OPTIONS ); + +static struct jack_data *jack_data_alloc(void) +{ + struct jack_data *jack_data; + + if (!(jack_data = ast_calloc(1, sizeof(*jack_data)))) + return NULL; + + if (ast_string_field_init(jack_data, 32)) { + ast_free(jack_data); + return NULL; + } + + return jack_data; +} + +/*! + * \note This must be done before calling init_jack_data(). + */ +static int handle_options(struct jack_data *jack_data, const char *__options_str) +{ + struct ast_flags options = { 0, }; + char *option_args[OPT_ARG_ARRAY_SIZE]; + char *options_str; + + options_str = ast_strdupa(__options_str); + + ast_app_parse_options(jack_exec_options, &options, option_args, options_str); + + if (ast_test_flag(&options, OPT_SERVER_NAME)) { + if (!ast_strlen_zero(option_args[OPT_ARG_SERVER_NAME])) + ast_string_field_set(jack_data, server_name, option_args[OPT_ARG_SERVER_NAME]); + else { + ast_log(LOG_ERROR, "A server name must be provided with the s() option\n"); + return -1; + } + } + + if (ast_test_flag(&options, OPT_INPUT_PORT)) { + if (!ast_strlen_zero(option_args[OPT_ARG_INPUT_PORT])) + ast_string_field_set(jack_data, connect_input_port, option_args[OPT_ARG_INPUT_PORT]); + else { + ast_log(LOG_ERROR, "A name must be provided with the i() option\n"); + return -1; + } + } + + if (ast_test_flag(&options, OPT_OUTPUT_PORT)) { + if (!ast_strlen_zero(option_args[OPT_ARG_OUTPUT_PORT])) + ast_string_field_set(jack_data, connect_output_port, option_args[OPT_ARG_OUTPUT_PORT]); + else { + ast_log(LOG_ERROR, "A name must be provided with the o() option\n"); + return -1; + } + } + + return 0; +} + +static int jack_exec(struct ast_channel *chan, void *data) +{ + struct jack_data *jack_data; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(options); + ); + + if (!(jack_data = jack_data_alloc())) + return -1; + + args.options = data; + + if (!ast_strlen_zero(args.options) && handle_options(jack_data, args.options)) { + destroy_jack_data(jack_data); + return -1; + } + + if (init_jack_data(chan, jack_data)) { + destroy_jack_data(jack_data); + return -1; + } + + if (ast_set_read_format(chan, AST_FORMAT_SLINEAR)) { + destroy_jack_data(jack_data); + return -1; + } + + if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) { + destroy_jack_data(jack_data); + return -1; + } + + while (!jack_data->stop) { + struct ast_frame *f; + + ast_waitfor(chan, -1); + + f = ast_read(chan); + if (!f) { + jack_data->stop = 1; + continue; + } + + switch (f->frametype) { + case AST_FRAME_CONTROL: + if (f->subclass == AST_CONTROL_HANGUP) + jack_data->stop = 1; + break; + case AST_FRAME_VOICE: + queue_voice_frame(jack_data, f); + default: + break; + } + + ast_frfree(f); + + handle_jack_audio(chan, jack_data, NULL); + } + + jack_data = destroy_jack_data(jack_data); + + return 0; +} + +static void jack_hook_ds_destroy(void *data) +{ + struct jack_data *jack_data = data; + + destroy_jack_data(jack_data); +} + +static const struct ast_datastore_info jack_hook_ds_info = { + .type = "JACK_HOOK", + .destroy = jack_hook_ds_destroy, +}; + +static int jack_hook_callback(struct ast_audiohook *audiohook, struct ast_channel *chan, + struct ast_frame *frame, enum ast_audiohook_direction direction) +{ + struct ast_datastore *datastore; + struct jack_data *jack_data; + + if (audiohook->status == AST_AUDIOHOOK_STATUS_DONE) + return 0; + + if (direction != AST_AUDIOHOOK_DIRECTION_READ) + return 0; + + if (frame->frametype != AST_FRAME_VOICE) + return 0; + + if (frame->subclass != AST_FORMAT_SLINEAR) { + ast_log(LOG_WARNING, "Expected frame in SLINEAR for the audiohook, but got format %d\n", + frame->subclass); + return 0; + } + + ast_channel_lock(chan); + + if (!(datastore = ast_channel_datastore_find(chan, &jack_hook_ds_info, NULL))) { + ast_log(LOG_ERROR, "JACK_HOOK datastore not found for '%s'\n", chan->name); + ast_channel_unlock(chan); + return -1; + } + + jack_data = datastore->data; + + queue_voice_frame(jack_data, frame); + + handle_jack_audio(chan, jack_data, frame); + + ast_channel_unlock(chan); + + return 0; +} + +static int enable_jack_hook(struct ast_channel *chan, char *data) +{ + struct ast_datastore *datastore; + struct jack_data *jack_data = NULL; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(mode); + AST_APP_ARG(options); + ); + + AST_STANDARD_APP_ARGS(args, data); + + ast_channel_lock(chan); + + if ((datastore = ast_channel_datastore_find(chan, &jack_hook_ds_info, NULL))) { + ast_log(LOG_ERROR, "JACK_HOOK already enabled for '%s'\n", chan->name); + goto return_error; + } + + if (ast_strlen_zero(args.mode) || strcasecmp(args.mode, "manipulate")) { + ast_log(LOG_ERROR, "'%s' is not a supported mode. Only manipulate is supported.\n", + S_OR(args.mode, "<none>")); + goto return_error; + } + + if (!(jack_data = jack_data_alloc())) + goto return_error; + + if (!ast_strlen_zero(args.options) && handle_options(jack_data, args.options)) + goto return_error; + + if (init_jack_data(chan, jack_data)) + goto return_error; + + if (!(datastore = ast_channel_datastore_alloc(&jack_hook_ds_info, NULL))) + goto return_error; + + jack_data->has_audiohook = 1; + ast_audiohook_init(&jack_data->audiohook, AST_AUDIOHOOK_TYPE_MANIPULATE, "JACK_HOOK"); + jack_data->audiohook.manipulate_callback = jack_hook_callback; + + datastore->data = jack_data; + + if (ast_audiohook_attach(chan, &jack_data->audiohook)) + goto return_error; + + if (ast_channel_datastore_add(chan, datastore)) + goto return_error; + + ast_channel_unlock(chan); + + return 0; + +return_error: + ast_channel_unlock(chan); + + if (jack_data) + destroy_jack_data(jack_data); + + return -1; +} + +static int disable_jack_hook(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + struct jack_data *jack_data; + + ast_channel_lock(chan); + + if (!(datastore = ast_channel_datastore_find(chan, &jack_hook_ds_info, NULL))) { + ast_channel_unlock(chan); + ast_log(LOG_WARNING, "No JACK_HOOK found to disable\n"); + return -1; + } + + ast_channel_datastore_remove(chan, datastore); + + jack_data = datastore->data; + ast_audiohook_detach(&jack_data->audiohook); + + /* Keep the channel locked while we destroy the datastore, so that we can + * ensure that all of the jack stuff is stopped just in case another frame + * tries to come through the audiohook callback. */ + ast_channel_datastore_free(datastore); + + ast_channel_unlock(chan); + + return 0; +} + +static int jack_hook_write(struct ast_channel *chan, const char *cmd, char *data, + const char *value) +{ + int res; + + if (!strcasecmp(value, "on")) + res = enable_jack_hook(chan, data); + else if (!strcasecmp(value, "off")) + res = disable_jack_hook(chan); + else { + ast_log(LOG_ERROR, "'%s' is not a valid value for JACK_HOOK()\n", value); + res = -1; + } + + return res; +} + +static struct ast_custom_function jack_hook_function = { + .name = "JACK_HOOK", + .synopsis = "Enable a jack hook on a channel", + .syntax = "JACK_HOOK(<mode>,[options])", + .desc = + " The JACK_HOOK allows turning on or off jack connectivity to this channel.\n" + "When the JACK_HOOK is turned on, jack ports will get created that allow\n" + "access to the audio stream for this channel. The mode specifies which mode\n" + "this hook should run in. A mode must be specified when turning the JACK_HOOK.\n" + "on. However, all arguments are optional when turning it off.\n" + "\n" + " Valid modes are:\n" +#if 0 + /* XXX TODO */ + " spy - Create a read-only audio hook. Only an output jack port will\n" + " get created.\n" + " whisper - Create a write-only audio hook. Only an input jack port will\n" + " get created.\n" +#endif + " manipulate - Create a read/write audio hook. Both an input and an output\n" + " jack port will get created. Audio from the channel will be\n" + " sent out the output port and will be replaced by the audio\n" + " coming in on the input port as it gets passed on.\n" + "\n" + " Valid options are:\n" + COMMON_OPTIONS + "\n" + " Examples:\n" + " To turn on the JACK_HOOK,\n" + " Set(JACK_HOOK(manipulate,i(pure_data_0:input0)o(pure_data_0:output0))=on)\n" + " To turn off the JACK_HOOK,\n" + " Set(JACK_HOOK()=off)\n" + "", + .write = jack_hook_write, +}; + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(jack_app); + res |= ast_custom_function_unregister(&jack_hook_function); + + return res; +} + +static int load_module(void) +{ + if (ast_register_application(jack_app, jack_exec, jack_synopsis, jack_desc)) + return AST_MODULE_LOAD_DECLINE; + + if (ast_custom_function_register(&jack_hook_function)) { + ast_unregister_application(jack_app); + return AST_MODULE_LOAD_DECLINE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "JACK Interface"); |