mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-03 03:20:57 +00:00
Merge in the bridge_construction branch to make the system use the Bridging API.
Breaks many things until they can be reworked. A partial list: chan_agent chan_dahdi, chan_misdn, chan_iax2 native bridging app_queue COLP updates DTMF attended transfers Protocol attended transfers git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389378 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
@@ -41,6 +41,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
#include "asterisk/channel.h"
|
||||
#include "asterisk/term.h"
|
||||
#include "asterisk/utils.h"
|
||||
#include "asterisk/pbx.h"
|
||||
#include "asterisk/timing.h"
|
||||
|
||||
#include "asterisk/abstract_jb.h"
|
||||
#include "fixedjitterbuf.h"
|
||||
@@ -567,6 +569,13 @@ int ast_jb_read_conf(struct ast_jb_conf *conf, const char *varname, const char *
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ast_jb_enable_for_channel(struct ast_channel *chan)
|
||||
{
|
||||
struct ast_jb_conf conf = ast_channel_jb(chan)->conf;
|
||||
if (ast_test_flag(&conf, AST_JB_ENABLED)) {
|
||||
ast_jb_create_framehook(chan, &conf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void ast_jb_configure(struct ast_channel *chan, const struct ast_jb_conf *conf)
|
||||
{
|
||||
@@ -800,3 +809,303 @@ const struct ast_jb_impl *ast_jb_get_impl(enum ast_jb_type type)
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
void ast_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;
|
||||
}
|
||||
|
||||
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;
|
||||
int putframe = 0; /* signifies if audio frame was placed into the buffer or not */
|
||||
|
||||
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 (ast_channel_fdno(chan) == AST_JITTERBUFFER_FD && framedata->timer) {
|
||||
if (ast_timer_ack(framedata->timer, 1) < 0) {
|
||||
ast_log(LOG_ERROR, "Failed to acknowledge timer in jitter buffer\n");
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
putframe = 1;
|
||||
}
|
||||
|
||||
if (frame->frametype == AST_FRAME_NULL) {
|
||||
int res;
|
||||
long next = framedata->jb_impl->next(framedata->jb_obj);
|
||||
|
||||
/* If now is earlier than the next expected output frame
|
||||
* from the jitterbuffer we may choose to pass on retrieving
|
||||
* a frame during this read iteration. The only exception
|
||||
* to this rule is when an audio frame is placed into the buffer
|
||||
* and the time for the next frame to come out of the buffer is
|
||||
* at least within the timer_interval of the next output frame. By
|
||||
* doing this we are able to feed off the timing of the input frames
|
||||
* and only rely on our jitterbuffer timer when frames are dropped.
|
||||
* During testing, this hybrid form of timing gave more reliable results. */
|
||||
if (now < next) {
|
||||
long int diff = next - now;
|
||||
if (!putframe) {
|
||||
return frame;
|
||||
} else if (diff >= framedata->timer_interval) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (frame->frametype == AST_FRAME_CONTROL) {
|
||||
switch(frame->subclass.integer) {
|
||||
case AST_CONTROL_SRCUPDATE:
|
||||
case AST_CONTROL_SRCCHANGE:
|
||||
framedata->jb_impl->force_resync(framedata->jb_obj);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
/* set defaults */
|
||||
static int jb_framedata_init(struct jb_framedata *framedata, struct ast_jb_conf *jb_conf)
|
||||
{
|
||||
int jb_impl_type = DEFAULT_TYPE;
|
||||
/* Initialize defaults */
|
||||
framedata->timer_fd = -1;
|
||||
memcpy(&framedata->jb_conf, jb_conf, sizeof(*jb_conf));
|
||||
|
||||
/* Figure out implementation type from the configuration implementation string */
|
||||
if (!ast_strlen_zero(jb_conf->impl)) {
|
||||
if (!strcasecmp(jb_conf->impl, "fixed")) {
|
||||
jb_impl_type = AST_JB_FIXED;
|
||||
} else if (!strcasecmp(jb_conf->impl, "adaptive")) {
|
||||
jb_impl_type = AST_JB_ADAPTIVE;
|
||||
} else {
|
||||
ast_log(LOG_WARNING, "Unknown Jitterbuffer type %s. Failed to create jitterbuffer.\n", jb_conf->impl);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
framedata->jb_obj = framedata->jb_impl->create(&framedata->jb_conf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void ast_jb_create_framehook(struct ast_channel *chan, struct ast_jb_conf *jb_conf, int prefer_existing)
|
||||
{
|
||||
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 disabled, strip any existing jitterbuffer and don't replace it. */
|
||||
if (!strcasecmp(jb_conf->impl, "disabled")) {
|
||||
int *id;
|
||||
ast_channel_lock(chan);
|
||||
if ((datastore = ast_channel_datastore_find(chan, &jb_datastore, NULL))) {
|
||||
id = datastore->data;
|
||||
ast_framehook_detach(chan, *id);
|
||||
ast_channel_datastore_remove(chan, datastore);
|
||||
}
|
||||
ast_channel_unlock(chan);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(framedata = ast_calloc(1, sizeof(*framedata)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (jb_framedata_init(framedata, jb_conf)) {
|
||||
jb_framedata_destroy(framedata);
|
||||
return;
|
||||
}
|
||||
|
||||
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))) {
|
||||
/* There is already a jitterbuffer on the channel. */
|
||||
if (prefer_existing) {
|
||||
/* We prefer the existing jitterbuffer, so remove the new one and keep the old one. */
|
||||
ast_framehook_detach(chan, i);
|
||||
ast_channel_unlock(chan);
|
||||
return;
|
||||
}
|
||||
/* We prefer the new jitterbuffer, so strip the old one. */
|
||||
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;
|
||||
}
|
||||
|
||||
if (!(id = ast_calloc(1, sizeof(int)))) {
|
||||
ast_datastore_free(datastore);
|
||||
ast_framehook_detach(chan, i);
|
||||
ast_channel_unlock(chan);
|
||||
return;
|
||||
}
|
||||
|
||||
*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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user