From 18d591cb48730b8eeff0daa5946fbc6684285088 Mon Sep 17 00:00:00 2001 From: David Vossel <dvossel@digium.com> Date: Wed, 20 Apr 2011 20:52:15 +0000 Subject: [PATCH] Introduction of the JITTERBUFFER dialplan function. Review: https://reviewboard.asterisk.org/r/1157/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@314509 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- CHANGES | 4 + funcs/func_jitterbuffer.c | 369 +++++++++++++++++++++++++++++++++ include/asterisk/abstract_jb.h | 52 ++++- include/asterisk/channel.h | 3 +- main/abstract_jb.c | 83 +++----- main/channel.c | 8 +- 6 files changed, 455 insertions(+), 64 deletions(-) create mode 100644 funcs/func_jitterbuffer.c diff --git a/CHANGES b/CHANGES index 1a511502aa..7460a9280a 100644 --- a/CHANGES +++ b/CHANGES @@ -66,6 +66,10 @@ Dialplan Variables Dialplan Functions ------------------ + * Addition of the JITTERBUFFER dialplan function. This function allows + for jitterbuffering to occur on the read side of a channel. By using + this function conference applications such as ConfBridge and MeetMe can + have the rx streams jitterbuffered before conference mixing occurs. * Added DB_KEYS, which lists the next set of keys in the Asterisk database hierarchy. diff --git a/funcs/func_jitterbuffer.c b/funcs/func_jitterbuffer.c new file mode 100644 index 0000000000..e613e6a23b --- /dev/null +++ b/funcs/func_jitterbuffer.c @@ -0,0 +1,369 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2011, Digium, Inc. + * + * David Vossel <dvossel@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 Put a jitterbuffer on the read side of a channel + * + * \author David Vossel <dvossel@digium.com> + * + * \ingroup functions + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/channel.h" +#include "asterisk/framehook.h" +#include "asterisk/pbx.h" +#include "asterisk/abstract_jb.h" +#include "asterisk/timing.h" +#include "asterisk/app.h" + +/*** DOCUMENTATION + <function name="JITTERBUFFER" language="en_US"> + <synopsis> + Add a Jitterbuffer to the Read side of the channel. This dejitters the audio stream before it reaches the Asterisk core. This is a write only function. + </synopsis> + <syntax> + <parameter name="jitterbuffer type" required="true"> + <para>Jitterbuffer type can be either <literal>fixed</literal> or <literal>adaptive</literal>.</para> + <para>Used as follows. </para> + <para>Set(JITTERBUFFER(type)=max_size[,resync_threshold[,target_extra]])</para> + <para>Set(JITTERBUFFER(type)=default) </para> + </parameter> + </syntax> + <description> + <para>max_size: Defaults to 200 ms</para> + <para>Length in milliseconds of buffer.</para> + <para> </para> + <para>resync_threshold: Defaults to 1000ms </para> + <para>The length in milliseconds over which a timestamp difference will result in resyncing the jitterbuffer. </para> + <para> </para> + <para>target_extra: Defaults to 40ms</para> + <para>This option only affects the adaptive jitterbuffer. It represents the amount time in milliseconds by which the new jitter buffer will pad its size.</para> + <para> </para> + <para>Examples:</para> + <para>exten => 1,1,Set(JITTERBUFFER(fixed)=default);Fixed with defaults. </para> + <para>exten => 1,1,Set(JITTERBUFFER(fixed)=200);Fixed with max size 200ms, default resync threshold and target extra. </para> + <para>exten => 1,1,Set(JITTERBUFFER(fixed)=200,1500);Fixed with max size 200ms resync threshold 1500. </para> + <para>exten => 1,1,Set(JITTERBUFFER(adaptive)=default);Adaptive with defaults. </para> + <para>exten => 1,1,Set(JITTERBUFFER(adaptive)=200,,60);Adaptive with max size 200ms, default resync threshold and 40ms target extra. </para> + </description> + </function> + ***/ + +#define DEFAULT_TIMER_INTERVAL 20 +#define DEFAULT_SIZE 200 +#define DEFAULT_TARGET_EXTRA 40 +#define DEFAULT_RESYNC 1000 +#define DEFAULT_TYPE AST_JB_FIXED + +struct jb_framedata { + const struct ast_jb_impl *jb_impl; + struct ast_jb_conf jb_conf; + struct timeval start_tv; + struct ast_format last_format; + struct ast_timer *timer; + int timer_interval; /* ms between deliveries */ + int timer_fd; + int first; + void *jb_obj; +}; + +static void jb_framedata_destroy(struct jb_framedata *framedata) +{ + if (framedata->timer) { + ast_timer_close(framedata->timer); + framedata->timer = NULL; + } + if (framedata->jb_impl && framedata->jb_obj) { + struct ast_frame *f; + while (framedata->jb_impl->remove(framedata->jb_obj, &f) == AST_JB_IMPL_OK) { + ast_frfree(f); + } + framedata->jb_impl->destroy(framedata->jb_obj); + framedata->jb_obj = NULL; + } + ast_free(framedata); +} + +static void jb_conf_default(struct ast_jb_conf *conf) +{ + conf->max_size = DEFAULT_SIZE; + conf->resync_threshold = DEFAULT_RESYNC; + ast_copy_string(conf->impl, "fixed", sizeof(conf->impl)); + conf->target_extra = DEFAULT_TARGET_EXTRA; +} + +/* set defaults */ +static int jb_framedata_init(struct jb_framedata *framedata, const char *data, const char *value) +{ + int jb_impl_type = DEFAULT_TYPE; + + /* Initialize defaults */ + framedata->timer_fd = -1; + jb_conf_default(&framedata->jb_conf); + if (!(framedata->jb_impl = ast_jb_get_impl(jb_impl_type))) { + return -1; + } + if (!(framedata->timer = ast_timer_open())) { + return -1; + } + framedata->timer_fd = ast_timer_fd(framedata->timer); + framedata->timer_interval = DEFAULT_TIMER_INTERVAL; + ast_timer_set_rate(framedata->timer, 1000 / framedata->timer_interval); + framedata->start_tv = ast_tvnow(); + + + + /* Now check user options to see if any of the defaults need to change. */ + if (!ast_strlen_zero(data)) { + if (!strcasecmp(data, "fixed")) { + jb_impl_type = AST_JB_FIXED; + } else if (!strcasecmp(data, "adaptive")) { + jb_impl_type = AST_JB_ADAPTIVE; + } else { + ast_log(LOG_WARNING, "Unknown Jitterbuffer type %s. Failed to create jitterbuffer.\n", data); + return -1; + } + ast_copy_string(framedata->jb_conf.impl, data, sizeof(framedata->jb_conf.impl)); + } + + if (!ast_strlen_zero(value) && strcasecmp(value, "default")) { + char *parse = ast_strdupa(value); + int res = 0; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(max_size); + AST_APP_ARG(resync_threshold); + AST_APP_ARG(target_extra); + ); + + AST_STANDARD_APP_ARGS(args, parse); + if (!ast_strlen_zero(args.max_size)) { + res |= ast_jb_read_conf(&framedata->jb_conf, + "jbmaxsize", + args.max_size); + } + if (!ast_strlen_zero(args.resync_threshold)) { + res |= ast_jb_read_conf(&framedata->jb_conf, + "jbresyncthreshold", + args.resync_threshold); + } + if (!ast_strlen_zero(args.target_extra)) { + res |= ast_jb_read_conf(&framedata->jb_conf, + "jbtargetextra", + args.target_extra); + } + if (res) { + ast_log(LOG_WARNING, "Invalid jitterbuffer parameters %s\n", value); + } + } + + /* now that all the user parsing is done and nothing will change, create the jb obj */ + framedata->jb_obj = framedata->jb_impl->create(&framedata->jb_conf, framedata->jb_conf.resync_threshold); + return 0; +} + +static void datastore_destroy_cb(void *data) { + ast_free(data); + ast_debug(1, "JITTERBUFFER datastore destroyed\n"); +} + +static const struct ast_datastore_info jb_datastore = { + .type = "jitterbuffer", + .destroy = datastore_destroy_cb +}; + +static void hook_destroy_cb(void *framedata) +{ + ast_debug(1, "JITTERBUFFER hook destroyed\n"); + jb_framedata_destroy((struct jb_framedata *) framedata); +} + +static struct ast_frame *hook_event_cb(struct ast_channel *chan, struct ast_frame *frame, enum ast_framehook_event event, void *data) +{ + struct jb_framedata *framedata = data; + struct timeval now_tv; + unsigned long now; + + switch (event) { + case AST_FRAMEHOOK_EVENT_READ: + break; + case AST_FRAMEHOOK_EVENT_ATTACHED: + case AST_FRAMEHOOK_EVENT_DETACHED: + case AST_FRAMEHOOK_EVENT_WRITE: + return frame; + } + + if (chan->fdno == AST_JITTERBUFFER_FD && framedata->timer) { + ast_timer_ack(framedata->timer, 1); + } + + if (!frame) { + return frame; + } + + now_tv = ast_tvnow(); + now = ast_tvdiff_ms(now_tv, framedata->start_tv); + + if (frame->frametype == AST_FRAME_VOICE) { + int res; + struct ast_frame *jbframe; + + if (!ast_test_flag(frame, AST_FRFLAG_HAS_TIMING_INFO) || frame->len < 2 || frame->ts < 0) { + /* only frames with timing info can enter the jitterbuffer */ + return frame; + } + + jbframe = ast_frisolate(frame); + ast_format_copy(&framedata->last_format, &frame->subclass.format); + + if (frame->len && (frame->len != framedata->timer_interval)) { + framedata->timer_interval = frame->len; + ast_timer_set_rate(framedata->timer, 1000 / framedata->timer_interval); + } + if (!framedata->first) { + framedata->first = 1; + res = framedata->jb_impl->put_first(framedata->jb_obj, jbframe, now); + } else { + res = framedata->jb_impl->put(framedata->jb_obj, jbframe, now); + } + if (res == AST_JB_IMPL_OK) { + frame = &ast_null_frame; + } + } + + if (frame->frametype == AST_FRAME_NULL) { + int res; + long next = framedata->jb_impl->next(framedata->jb_obj); + + if (now < next) { + return frame; + } + res = framedata->jb_impl->get(framedata->jb_obj, &frame, now, framedata->timer_interval); + switch (res) { + case AST_JB_IMPL_OK: + /* got it, and pass it through */ + break; + case AST_JB_IMPL_DROP: + ast_frfree(frame); + frame = &ast_null_frame; + break; + case AST_JB_IMPL_INTERP: + if (framedata->last_format.id) { + struct ast_frame tmp = { 0, }; + tmp.frametype = AST_FRAME_VOICE; + ast_format_copy(&tmp.subclass.format, &framedata->last_format); + /* example: 8000hz / (1000 / 20ms) = 160 samples */ + tmp.samples = ast_format_rate(&framedata->last_format) / (1000 / framedata->timer_interval); + tmp.delivery = ast_tvadd(framedata->start_tv, ast_samp2tv(next, 1000)); + tmp.offset = AST_FRIENDLY_OFFSET; + tmp.src = "func_jitterbuffer interpolation"; + frame = ast_frdup(&tmp); + break; + } + /* else fall through */ + case AST_JB_IMPL_NOFRAME: + frame = &ast_null_frame; + break; + } + } + + return frame; +} + +static int jb_helper(struct ast_channel *chan, const char *cmd, char *data, const char *value) +{ + struct jb_framedata *framedata; + struct ast_datastore *datastore = NULL; + struct ast_framehook_interface interface = { + .version = AST_FRAMEHOOK_INTERFACE_VERSION, + .event_cb = hook_event_cb, + .destroy_cb = hook_destroy_cb, + }; + int i = 0; + + if (!(framedata = ast_calloc(1, sizeof(*framedata)))) { + return 0; + } + + if (jb_framedata_init(framedata, data, value)) { + jb_framedata_destroy(framedata); + return 0; + } + + interface.data = framedata; + + ast_channel_lock(chan); + i = ast_framehook_attach(chan, &interface); + if (i >= 0) { + int *id; + if ((datastore = ast_channel_datastore_find(chan, &jb_datastore, NULL))) { + id = datastore->data; + ast_framehook_detach(chan, *id); + ast_channel_datastore_remove(chan, datastore); + } + + if (!(datastore = ast_datastore_alloc(&jb_datastore, NULL))) { + ast_framehook_detach(chan, i); + ast_channel_unlock(chan); + return 0; + } + + if (!(id = ast_calloc(1, sizeof(int)))) { + ast_datastore_free(datastore); + ast_framehook_detach(chan, i); + ast_channel_unlock(chan); + return 0; + } + + *id = i; /* Store off the id. The channel is still locked so it is safe to access this ptr. */ + datastore->data = id; + ast_channel_datastore_add(chan, datastore); + + ast_channel_set_fd(chan, AST_JITTERBUFFER_FD, framedata->timer_fd); + } else { + jb_framedata_destroy(framedata); + framedata = NULL; + } + ast_channel_unlock(chan); + + return 0; +} + +static struct ast_custom_function jb_function = { + .name = "JITTERBUFFER", + .write = jb_helper, +}; + +static int unload_module(void) +{ + return ast_custom_function_unregister(&jb_function); +} + +static int load_module(void) +{ + int res = ast_custom_function_register(&jb_function); + return res ? AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Jitter buffer for read side of channel."); + diff --git a/include/asterisk/abstract_jb.h b/include/asterisk/abstract_jb.h index d51554b663..d1121cc5e4 100644 --- a/include/asterisk/abstract_jb.h +++ b/include/asterisk/abstract_jb.h @@ -47,6 +47,19 @@ enum { AST_JB_LOG = (1 << 2) }; +enum ast_jb_type { + AST_JB_FIXED, + AST_JB_ADAPTIVE, +}; + +/*! Abstract return codes */ +enum { + AST_JB_IMPL_OK, + AST_JB_IMPL_DROP, + AST_JB_IMPL_INTERP, + AST_JB_IMPL_NOFRAME +}; + #define AST_JB_IMPL_NAME_SIZE 12 /*! @@ -77,9 +90,44 @@ struct ast_jb_conf #define AST_JB_CONF_IMPL "impl" #define AST_JB_CONF_LOG "log" +/* Hooks for the abstract jb implementation */ +/*! \brief Create */ +typedef void * (*jb_create_impl)(struct ast_jb_conf *general_config, long resynch_threshold); +/*! \brief Destroy */ +typedef void (*jb_destroy_impl)(void *jb); +/*! \brief Put first frame */ +typedef int (*jb_put_first_impl)(void *jb, struct ast_frame *fin, long now); +/*! \brief Put frame */ +typedef int (*jb_put_impl)(void *jb, struct ast_frame *fin, long now); +/*! \brief Get frame for now */ +typedef int (*jb_get_impl)(void *jb, struct ast_frame **fout, long now, long interpl); +/*! \brief Get next */ +typedef long (*jb_next_impl)(void *jb); +/*! \brief Remove first frame */ +typedef int (*jb_remove_impl)(void *jb, struct ast_frame **fout); +/*! \brief Force resynch */ +typedef void (*jb_force_resynch_impl)(void *jb); +/*! \brief Empty and reset jb */ +typedef void (*jb_empty_and_reset_impl)(void *jb); -struct ast_jb_impl; +/*! + * \brief Jitterbuffer implementation struct. + */ +struct ast_jb_impl +{ + char name[AST_JB_IMPL_NAME_SIZE]; + enum ast_jb_type type; + jb_create_impl create; + jb_destroy_impl destroy; + jb_put_first_impl put_first; + jb_put_impl put; + jb_get_impl get; + jb_next_impl next; + jb_remove_impl remove; + jb_force_resynch_impl force_resync; + jb_empty_and_reset_impl empty_and_reset; +}; /*! * \brief General jitterbuffer state. @@ -224,6 +272,8 @@ void ast_jb_get_config(const struct ast_channel *chan, struct ast_jb_conf *conf) */ void ast_jb_empty_and_reset(struct ast_channel *c0, struct ast_channel *c1); +const struct ast_jb_impl *ast_jb_get_impl(enum ast_jb_type type); + #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 2959aa980c..88e9db4bb9 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -154,7 +154,7 @@ extern "C" { #define DATASTORE_INHERIT_FOREVER INT_MAX -#define AST_MAX_FDS 10 +#define AST_MAX_FDS 11 /* * We have AST_MAX_FDS file descriptors in a channel. * Some of them have a fixed use: @@ -163,6 +163,7 @@ extern "C" { #define AST_TIMING_FD (AST_MAX_FDS-2) /*!< used for timingfd */ #define AST_AGENT_FD (AST_MAX_FDS-3) /*!< used by agents for pass through */ #define AST_GENERATOR_FD (AST_MAX_FDS-4) /*!< used by generator */ +#define AST_JITTERBUFFER_FD (AST_MAX_FDS-5) /*!< used by generator */ enum ast_bridge_result { AST_BRIDGE_COMPLETE = 0, diff --git a/main/abstract_jb.c b/main/abstract_jb.c index 5ff1c4b2a8..5ecce8c40f 100644 --- a/main/abstract_jb.c +++ b/main/abstract_jb.c @@ -49,43 +49,6 @@ enum { JB_CREATED = (1 << 2) }; -/* Hooks for the abstract jb implementation */ - -/*! \brief Create */ -typedef void * (*jb_create_impl)(struct ast_jb_conf *general_config, long resynch_threshold); -/*! \brief Destroy */ -typedef void (*jb_destroy_impl)(void *jb); -/*! \brief Put first frame */ -typedef int (*jb_put_first_impl)(void *jb, struct ast_frame *fin, long now); -/*! \brief Put frame */ -typedef int (*jb_put_impl)(void *jb, struct ast_frame *fin, long now); -/*! \brief Get frame for now */ -typedef int (*jb_get_impl)(void *jb, struct ast_frame **fout, long now, long interpl); -/*! \brief Get next */ -typedef long (*jb_next_impl)(void *jb); -/*! \brief Remove first frame */ -typedef int (*jb_remove_impl)(void *jb, struct ast_frame **fout); -/*! \brief Force resynch */ -typedef void (*jb_force_resynch_impl)(void *jb); -/*! \brief Empty and reset jb */ -typedef void (*jb_empty_and_reset_impl)(void *jb); - -/*! - * \brief Jitterbuffer implementation private struct. - */ -struct ast_jb_impl -{ - char name[AST_JB_IMPL_NAME_SIZE]; - jb_create_impl create; - jb_destroy_impl destroy; - jb_put_first_impl put_first; - jb_put_impl put; - jb_get_impl get; - jb_next_impl next; - jb_remove_impl remove; - jb_force_resynch_impl force_resync; - jb_empty_and_reset_impl empty_and_reset; -}; /* Implementation functions */ /* fixed */ @@ -113,6 +76,7 @@ static void jb_empty_and_reset_adaptive(void *jb); static const struct ast_jb_impl avail_impl[] = { { .name = "fixed", + .type = AST_JB_FIXED, .create = jb_create_fixed, .destroy = jb_destroy_fixed, .put_first = jb_put_first_fixed, @@ -125,6 +89,7 @@ static const struct ast_jb_impl avail_impl[] = { }, { .name = "adaptive", + .type = AST_JB_ADAPTIVE, .create = jb_create_adaptive, .destroy = jb_destroy_adaptive, .put_first = jb_put_first_adaptive, @@ -139,20 +104,11 @@ static const struct ast_jb_impl avail_impl[] = { static int default_impl = 0; - -/*! Abstract return codes */ -enum { - JB_IMPL_OK, - JB_IMPL_DROP, - JB_IMPL_INTERP, - JB_IMPL_NOFRAME -}; - /* Translations between impl and abstract return codes */ static const int fixed_to_abstract_code[] = - {JB_IMPL_OK, JB_IMPL_DROP, JB_IMPL_INTERP, JB_IMPL_NOFRAME}; + {AST_JB_IMPL_OK, AST_JB_IMPL_DROP, AST_JB_IMPL_INTERP, AST_JB_IMPL_NOFRAME}; static const int adaptive_to_abstract_code[] = - {JB_IMPL_OK, JB_IMPL_NOFRAME, JB_IMPL_NOFRAME, JB_IMPL_INTERP, JB_IMPL_DROP, JB_IMPL_OK}; + {AST_JB_IMPL_OK, AST_JB_IMPL_NOFRAME, AST_JB_IMPL_NOFRAME, AST_JB_IMPL_INTERP, AST_JB_IMPL_DROP, AST_JB_IMPL_OK}; /* JB_GET actions (used only for the frames log) */ static const char * const jb_get_actions[] = {"Delivered", "Dropped", "Interpolated", "No"}; @@ -346,7 +302,7 @@ int ast_jb_put(struct ast_channel *chan, struct ast_frame *f) return 0; } else { now = get_now(jb, NULL); - if (jbimpl->put(jbobj, frr, now) != JB_IMPL_OK) { + if (jbimpl->put(jbobj, frr, now) != AST_JB_IMPL_OK) { jb_framelog("JB_PUT {now=%ld}: Dropped frame with ts=%ld and len=%ld\n", now, frr->ts, frr->len); ast_frfree(frr); /*return -1;*/ @@ -403,16 +359,16 @@ static void jb_get_and_deliver(struct ast_channel *chan) res = jbimpl->get(jbobj, &f, now, interpolation_len); switch (res) { - case JB_IMPL_OK: + case AST_JB_IMPL_OK: /* deliver the frame */ ast_write(chan, f); - case JB_IMPL_DROP: + case AST_JB_IMPL_DROP: jb_framelog("\tJB_GET {now=%ld}: %s frame with ts=%ld and len=%ld\n", now, jb_get_actions[res], f->ts, f->len); ast_format_copy(&jb->last_format, &f->subclass.format); ast_frfree(f); break; - case JB_IMPL_INTERP: + case AST_JB_IMPL_INTERP: /* interpolate a frame */ f = &finterp; ast_format_copy(&f->subclass.format, &jb->last_format); @@ -424,9 +380,9 @@ static void jb_get_and_deliver(struct ast_channel *chan) ast_write(chan, f); jb_framelog("\tJB_GET {now=%ld}: Interpolated frame with len=%d\n", now, interpolation_len); break; - case JB_IMPL_NOFRAME: + case AST_JB_IMPL_NOFRAME: ast_log(LOG_WARNING, - "JB_IMPL_NOFRAME is returned from the %s jb when now=%ld >= next=%ld, jbnext=%ld!\n", + "AST_JB_IMPL_NOFRAME is returned from the %s jb when now=%ld >= next=%ld, jbnext=%ld!\n", jbimpl->name, now, jb->next, jbimpl->next(jbobj)); jb_framelog("\tJB_GET {now=%ld}: No frame for now!?\n", now); return; @@ -464,7 +420,7 @@ static int create_jb(struct ast_channel *chan, struct ast_frame *frr) /* The result of putting the first frame should not differ from OK. However, its possible some implementations (i.e. adaptive's when resynch_threshold is specified) to drop it. */ - if (res != JB_IMPL_OK) { + if (res != AST_JB_IMPL_OK) { ast_log(LOG_WARNING, "Failed to put first frame in the jitterbuffer on channel '%s'\n", chan->name); /* jbimpl->destroy(jbobj); @@ -508,7 +464,7 @@ static int create_jb(struct ast_channel *chan, struct ast_frame *frr) } } - if (res == JB_IMPL_OK) { + if (res == AST_JB_IMPL_OK) { jb_framelog("JB_PUT_FIRST {now=%ld}: Queued frame with ts=%ld and len=%ld\n", now, frr->ts, frr->len); } else { @@ -520,7 +476,7 @@ static int create_jb(struct ast_channel *chan, struct ast_frame *frr) ast_verb(3, "%s jitterbuffer created on channel %s\n", jbimpl->name, chan->name); /* Free the frame if it has not been queued in the jb */ - if (res != JB_IMPL_OK) { + if (res != AST_JB_IMPL_OK) { ast_frfree(frr); } @@ -542,7 +498,7 @@ void ast_jb_destroy(struct ast_channel *chan) if (ast_test_flag(jb, JB_CREATED)) { /* Remove and free all frames still queued in jb */ - while (jbimpl->remove(jbobj, &f) == JB_IMPL_OK) { + while (jbimpl->remove(jbobj, &f) == AST_JB_IMPL_OK) { ast_frfree(f); } @@ -831,3 +787,14 @@ static void jb_empty_and_reset_adaptive(void *jb) jb_reset(adaptivejb); } + +const struct ast_jb_impl *ast_jb_get_impl(enum ast_jb_type type) +{ + int i; + for (i = 0; i < ARRAY_LEN(avail_impl); i++) { + if (avail_impl[i].type == type) { + return &avail_impl[i]; + } + } + return NULL; +} diff --git a/main/channel.c b/main/channel.c index 6a422673a3..bb815e728e 100644 --- a/main/channel.c +++ b/main/channel.c @@ -3878,16 +3878,16 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio) ast_log(LOG_WARNING, "No read routine on channel %s\n", chan->name); } + /* Perform the framehook read event here. After the frame enters the framehook list + * there is no telling what will happen, <insert mad scientist laugh here>!!! */ + f = ast_framehook_list_read_event(chan->framehooks, f); + /* * Reset the recorded file descriptor that triggered this read so that we can * easily detect when ast_read() is called without properly using ast_waitfor(). */ chan->fdno = -1; - /* Perform the framehook read event here. After the frame enters the framehook list - * there is no telling what will happen, <insert mad scientist laugh here>!!! */ - f = ast_framehook_list_read_event(chan->framehooks, f); - if (f) { struct ast_frame *readq_tail = AST_LIST_LAST(&chan->readq); struct ast_control_read_action_payload *read_action_payload; -- GitLab