dect
/
asterisk
Archived
13
0
Fork 0

Introduction of the JITTERBUFFER dialplan function.

Review: https://reviewboard.asterisk.org/r/1157/


git-svn-id: http://svn.digium.com/svn/asterisk/trunk@314509 f38db490-d61c-443f-a65b-d21fe96a405b
This commit is contained in:
dvossel 2011-04-20 20:52:15 +00:00
parent a14ce25e4a
commit c9a36282b5
6 changed files with 455 additions and 64 deletions

View File

@ -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.

369
funcs/func_jitterbuffer.c Normal file
View File

@ -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.");

View File

@ -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

View File

@ -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,

View File

@ -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;
}

View File

@ -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;