diff options
author | Nanang Izzuddin <nanang@teluu.com> | 2008-05-30 11:30:24 +0000 |
---|---|---|
committer | Nanang Izzuddin <nanang@teluu.com> | 2008-05-30 11:30:24 +0000 |
commit | f3c4d699b34f41157d87ddb5903ec19323531d94 (patch) | |
tree | dfa862cf87f1a3a36cd9507a4c4d2bf995d87ca5 | |
parent | 37c57cd786183e214ae07d461f3391a1d612a013 (diff) |
Updated delaybuf to learn burst level in realtime instead of only in the beginning, this can optimize the latency and increase adaptivity
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1972 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r-- | pjmedia/include/pjmedia/delaybuf.h | 26 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/conference.c | 7 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/delaybuf.c | 276 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sound_port.c | 9 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/splitcomb.c | 11 |
5 files changed, 113 insertions, 216 deletions
diff --git a/pjmedia/include/pjmedia/delaybuf.h b/pjmedia/include/pjmedia/delaybuf.h index 2141c481..1108ee23 100644 --- a/pjmedia/include/pjmedia/delaybuf.h +++ b/pjmedia/include/pjmedia/delaybuf.h @@ -68,14 +68,11 @@ typedef struct pjmedia_delay_buf pjmedia_delay_buf; * identification. * @param clock_rate Number of samples processed per second. * @param samples_per_frame Number of samples per frame. - * @param max_frames Maximum number of delay to be accommodated, - * in number of frames. - * @param delay The delay to be applied, in number of frames. - * If the value is -1 or 0, the delay buffer will - * learn about the delay automatically. If - * the value is greater than zero, then this - * value will be used and no learning will be - * performed. + * @param channel_count Number of channel per frame. + * @param max_delay Maximum number of delay to be accommodated, + * in ms, if this value is negative or less than + * one frame time, default maximum delay used is + * 400 ms. * @param option Option flags, must be zero for now. * @param p_b Pointer to receive the delay buffer instance. * @@ -87,8 +84,8 @@ PJ_DECL(pj_status_t) pjmedia_delay_buf_create(pj_pool_t *pool, const char *name, unsigned clock_rate, unsigned samples_per_frame, - unsigned max_frames, - int delay, + unsigned channel_count, + unsigned max_delay, unsigned options, pjmedia_delay_buf **p_b); @@ -127,15 +124,6 @@ PJ_DECL(pj_status_t) pjmedia_delay_buf_get(pjmedia_delay_buf *b, pj_int16_t frame[]); /** - * Reinitiate learning state. This will clear the buffer's content. - * - * @param b The delay buffer. - * - * @return PJ_SUCCESS on success or the appropriate error. - */ -PJ_DECL(pj_status_t) pjmedia_delay_buf_learn(pjmedia_delay_buf *b); - -/** * Reset delay buffer. This will clear the buffer's content. But keep * the learning result. * diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index c766cdf0..9e7cbab3 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -418,6 +418,7 @@ static pj_status_t create_pasv_port( pjmedia_conf *conf, { struct conf_port *conf_port; pj_status_t status; + unsigned ptime; /* Create port */ status = create_conf_port(pool, conf, port, name, &conf_port); @@ -425,11 +426,13 @@ static pj_status_t create_pasv_port( pjmedia_conf *conf, return status; /* Passive port has delay buf. */ + ptime = conf->samples_per_frame * 1000 / conf->clock_rate / + conf->channel_count; status = pjmedia_delay_buf_create(pool, name->ptr, conf->clock_rate, conf->samples_per_frame, - RX_BUF_COUNT, /* max */ - -1, /* delay */ + conf->channel_count, + RX_BUF_COUNT * ptime, /* max delay */ 0, /* options */ &conf_port->delay_buf); if (status != PJ_SUCCESS) diff --git a/pjmedia/src/pjmedia/delaybuf.c b/pjmedia/src/pjmedia/delaybuf.c index 2c6aa37b..37a50fe4 100644 --- a/pjmedia/src/pjmedia/delaybuf.c +++ b/pjmedia/src/pjmedia/delaybuf.c @@ -23,6 +23,7 @@ #include <pj/assert.h> #include <pj/lock.h> #include <pj/log.h> +#include <pj/math.h> #include <pj/pool.h> @@ -32,96 +33,74 @@ # define TRACE__(x) #endif - -/* Type of states of delay buffer */ -enum state -{ - STATE_WAITING, - STATE_LEARNING, - STATE_RUNNING -}; - -/* Type of operation of delay buffer */ +/* Operation types of delay buffer */ enum OP { OP_PUT, - OP_GET, - OP_UNDEFINED + OP_GET }; -/* The following macros represent cycles of test. Since there are two - * operations performed (get & put), these macros minimum value must be 2 - * and should be even number. +/* Specify time for delaybuf to recalculate effective delay, in ms. */ -#define WAITING_COUNT 4 -#define LEARN_COUNT 16 +#define RECALC_TIME 2000 -/* Number of buffers to add to learnt level for additional stability - * Please note that wsola_discard() needs minimum 3 frames, so max buffer - * count should be minimally 3, setting SAFE_MARGIN to 2 will guarantees - * this. +/* Default value of maximum delay, in ms, this value is used when + * maximum delay requested is less than ptime (one frame length). */ -#define SAFE_MARGIN 2 +#define DEFAULT_MAX_DELAY 400 -/* - * Some experimental data (with SAFE_MARGIN=1): - * - * System 1: - * - XP, WMME, 10ms ptime, - * - Sennheiser Headset+USB sound card - * - Stable delaybuf level: 6, on WAITING_COUNT=4 and LEARNING_COUNT=48 - * - * System 2: - * - XP, WMME, 10ms ptime - * - Onboard SoundMAX Digital Audio - * - Stable delaybuf level: 6, on WAITING_COUNT=4 and LEARNING_COUNT=48 - * - * System 3: - * - MacBook Core 2 Duo, OSX 10.5, 10ms ptime - * - Stable delaybuf level: 2, on WAITING_COUNT=4 and LEARNING_COUNT=8 +/* Number of frames to add to learnt level for additional stability. */ +#define SAFE_MARGIN 0 +/* This structure describes internal delaybuf settings and states. + */ struct pjmedia_delay_buf { + /* Properties and configuration */ char obj_name[PJ_MAX_OBJ_NAME]; - pj_lock_t *lock; /**< Lock object. */ - pj_int16_t *frame_buf; - enum state state; /**< State of delay buffer */ unsigned samples_per_frame; /**< Number of samples in one frame */ - unsigned max_frames; /**< Buffer allocated, in frames */ + unsigned ptime; /**< Frame time, in ms */ + unsigned channel_count; /**< Channel count, in ms */ + unsigned max_cnt; /**< Max buffered samples, in samples*/ + /* Buffer pointer and counter */ unsigned put_pos; /**< Position for put op, in samples */ unsigned get_pos; /**< Position for get op, in samples */ unsigned buf_cnt; /**< Number of buffered samples */ - unsigned max_cnt; /**< Max number of buffered samples */ - - struct { - unsigned level; /**< Burst level storage on learning */ - } op[2]; + unsigned eff_cnt; /**< Effective count of buffered + samples to keep the optimum + balance between delay and + stability. This is calculated + based on burst level. */ + + /* Learning vars */ + unsigned level; /**< Burst level counter */ enum OP last_op; /**< Last op (GET or PUT) of learning*/ - unsigned state_count; /**< Counter of op cycles of learning*/ - - unsigned max_level; /**< Learning result: burst level */ + int recalc_timer; /**< Timer for recalculating max_level*/ + unsigned max_level; /**< Current max burst level */ + /* Drift handler */ pjmedia_wsola *wsola; /**< Drift handler */ }; + PJ_DEF(pj_status_t) pjmedia_delay_buf_create( pj_pool_t *pool, const char *name, unsigned clock_rate, unsigned samples_per_frame, - unsigned max_frames, - int delay, + unsigned channel_count, + unsigned max_delay, unsigned options, pjmedia_delay_buf **p_b) { pjmedia_delay_buf *b; pj_status_t status; - PJ_ASSERT_RETURN(pool && samples_per_frame && max_frames && p_b, PJ_EINVAL); - PJ_ASSERT_RETURN((int)max_frames >= delay, PJ_EINVAL); + PJ_ASSERT_RETURN(pool && samples_per_frame && clock_rate && channel_count && + p_b, PJ_EINVAL); PJ_ASSERT_RETURN(options==0, PJ_EINVAL); PJ_UNUSED_ARG(options); @@ -133,8 +112,16 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_create( pj_pool_t *pool, b = PJ_POOL_ZALLOC_T(pool, pjmedia_delay_buf); pj_ansi_strncpy(b->obj_name, name, PJ_MAX_OBJ_NAME-1); + b->samples_per_frame = samples_per_frame; - b->max_frames = max_frames; + b->channel_count = channel_count; + b->ptime = samples_per_frame * 1000 / clock_rate / channel_count; + if (max_delay < b->ptime) + max_delay = PJ_MAX(DEFAULT_MAX_DELAY, b->ptime); + + b->max_cnt = samples_per_frame * max_delay / b->ptime; + b->eff_cnt = b->max_cnt >> 1; + b->recalc_timer = RECALC_TIME; status = pj_lock_create_recursive_mutex(pool, b->obj_name, &b->lock); @@ -142,20 +129,7 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_create( pj_pool_t *pool, return status; b->frame_buf = (pj_int16_t*) - pj_pool_zalloc(pool, samples_per_frame * max_frames * - sizeof(pj_int16_t)); - - if (delay >= 0) { - if (delay == 0) - delay = 1; - b->max_level = delay; - b->max_cnt = delay * samples_per_frame; - b->state = STATE_RUNNING; - } else { - b->max_cnt = max_frames * samples_per_frame; - b->last_op = OP_UNDEFINED; - b->state = STATE_WAITING; - } + pj_pool_zalloc(pool, b->max_cnt * sizeof(pj_int16_t)); status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame, 1, PJMEDIA_WSOLA_NO_PLC, &b->wsola); @@ -182,6 +156,7 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_destroy(pjmedia_delay_buf *b) b->wsola = NULL; pj_lock_release(b->lock); + pj_lock_destroy(b->lock); b->lock = NULL; @@ -262,118 +237,58 @@ static void shrink_buffer(pjmedia_delay_buf *b, unsigned erase_cnt) } } -static void set_max_cnt(pjmedia_delay_buf *b, unsigned new_max_cnt) -{ - unsigned old_max_cnt = b->max_cnt; +/* Fast increase, slow decrease */ +#define AGC_UP(cur, target) cur = (cur + target*3) >> 2 +#define AGC_DOWN(cur, target) cur = (cur*3 + target) >> 2 +#define AGC(cur, target) \ + if (cur < target) AGC_UP(cur, target); \ + else AGC_DOWN(cur, target) - /* nothing to adjust */ - if (old_max_cnt == new_max_cnt) +static void update(pjmedia_delay_buf *b, enum OP op) +{ + /* Sequential operation */ + if (op == b->last_op) { + ++b->level; return; + } - /* For now, only support shrinking */ - pj_assert(old_max_cnt > new_max_cnt); + /* Switching operation */ + if (b->level > b->max_level) + b->max_level = b->level; - /* Buffer empty, only need to reset pointers then set new max directly */ - if (b->buf_cnt == 0) { - b->put_pos = b->get_pos = 0; - b->max_cnt = new_max_cnt; - return; - } + b->recalc_timer -= (b->level * b->ptime) >> 1; - /* If samples number in the buffer > new_max_cnt, reduce samples first */ - if (b->buf_cnt > new_max_cnt) - shrink_buffer(b, b->buf_cnt - new_max_cnt); + b->last_op = op; + b->level = 1; - /* Adjust buffer to accomodate the new max_cnt so the samples is secured. - * This is done by make get_pos = 0 - */ - if (b->get_pos <= b->put_pos) { - /* sssss .. sssss - * ^ ^ - * G P - */ - /* Consistency checking */ - pj_assert((b->put_pos - b->get_pos) <= new_max_cnt); - pj_assert((b->put_pos - b->get_pos) == b->buf_cnt); - - pjmedia_move_samples(b->frame_buf, &b->frame_buf[b->get_pos], - b->get_pos); - b->put_pos -= b->get_pos; - b->get_pos = 0; - } else { - /* sssss .. sssss - * ^ ^ - * P G - */ - unsigned d = old_max_cnt - b->get_pos; + /* Recalculate effective count based on max_level */ + if (b->recalc_timer <= 0) { + unsigned new_eff_cnt = (b->max_level+SAFE_MARGIN)*b->samples_per_frame; - /* Consistency checking */ - pj_assert((b->get_pos - b->put_pos) >= (old_max_cnt - new_max_cnt)); + /* Smoothening effective count transition */ + AGC(b->eff_cnt, new_eff_cnt); + + /* Make sure the new effective count is multiplication of + * channel_count, so let's round it up. + */ + if (b->eff_cnt % b->channel_count) + b->eff_cnt += b->channel_count - (b->eff_cnt % b->channel_count); - /* Make get_pos = 0, shift right the leftmost block first */ - pjmedia_move_samples(&b->frame_buf[d], b->frame_buf, d); - pjmedia_copy_samples(b->frame_buf, &b->frame_buf[b->get_pos], d); - b->put_pos += d; - b->get_pos = 0; + TRACE__((b->obj_name,"Cur eff_cnt=%d", b->eff_cnt)); + + b->max_level = 0; + b->recalc_timer = RECALC_TIME; } - b->max_cnt = new_max_cnt; -} + /* See if we need to shrink the buffer to reduce delay */ + if (b->buf_cnt > b->samples_per_frame + b->eff_cnt) { + unsigned erase_cnt = b->samples_per_frame >> 1; + unsigned old_buf_cnt = b->buf_cnt; -static void update(pjmedia_delay_buf *b, enum OP op) -{ - enum OP other = (enum OP) !op; - - switch (b->state) { - case STATE_RUNNING: - break; - case STATE_WAITING: - ++b->op[op].level; - if (b->op[other].level != 0) { - ++b->state_count; - if (b->state_count == WAITING_COUNT) { - /* Start learning */ - pjmedia_delay_buf_learn(b); - } - } - b->last_op = op; - break; - case STATE_LEARNING: - ++b->op[op].level; - if (b->last_op == other) { - unsigned last_level = b->op[other].level; - if (last_level > b->max_level) - b->max_level = last_level; - b->op[other].level = 0; - b->state_count++; - if (b->state_count == LEARN_COUNT) { - /* give SAFE_MARGIN compensation for added stability */ - b->max_level += SAFE_MARGIN; - - /* buffer not enough! */ - if (b->max_level > b->max_frames) { - PJ_LOG(4,(b->obj_name,"Delay buffer learning result (%d)" - " exceeds the maximum delay allowed (%d)", - b->max_level, - b->max_frames)); - - b->max_level = b->max_frames; - } - - /* we need to set new max_cnt & adjust buffer */ - set_max_cnt(b, b->max_level * b->samples_per_frame); - - b->state = STATE_RUNNING; - - PJ_LOG(4,(b->obj_name,"Delay buffer start running, level=%u", - b->max_level)); - } - } - b->last_op = op; - break; + shrink_buffer(b, erase_cnt); + PJ_LOG(5,(b->obj_name,"Buffer size adjusted from %d to %d", + old_buf_cnt, b->buf_cnt)); } - - } PJ_DEF(pj_status_t) pjmedia_delay_buf_put(pjmedia_delay_buf *b, @@ -499,27 +414,6 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_get( pjmedia_delay_buf *b, return PJ_SUCCESS; } -PJ_DEF(pj_status_t) pjmedia_delay_buf_learn(pjmedia_delay_buf *b) -{ - PJ_ASSERT_RETURN(b, PJ_EINVAL); - - pj_lock_acquire(b->lock); - - b->last_op = OP_UNDEFINED; - b->op[OP_GET].level = b->op[OP_PUT].level = 0; - b->state = STATE_LEARNING; - b->state_count = 0; - b->max_level = 0; - b->max_cnt = b->max_frames * b->samples_per_frame; - - pjmedia_delay_buf_reset(b); - - pj_lock_release(b->lock); - - PJ_LOG(5,(b->obj_name,"Delay buffer start learning")); - - return PJ_SUCCESS; -} PJ_DEF(pj_status_t) pjmedia_delay_buf_reset(pjmedia_delay_buf *b) { @@ -527,6 +421,8 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_reset(pjmedia_delay_buf *b) pj_lock_acquire(b->lock); + b->recalc_timer = RECALC_TIME; + /* clean up buffer */ b->buf_cnt = 0; b->put_pos = b->get_pos = 0; diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c index 188ee28c..6682e4a4 100644 --- a/pjmedia/src/pjmedia/sound_port.c +++ b/pjmedia/src/pjmedia/sound_port.c @@ -372,6 +372,7 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool, { pjmedia_snd_port *snd_port; pj_status_t status; + unsigned ptime; PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL); @@ -388,13 +389,17 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool, snd_port->bits_per_sample = bits_per_sample; #if PJMEDIA_SOUND_USE_DELAYBUF + ptime = samples_per_frame * 1000 / (clock_rate * channel_count); + status = pjmedia_delay_buf_create(pool, "snd_buff", - clock_rate, samples_per_frame, - PJMEDIA_SOUND_BUFFER_COUNT, -1, + clock_rate, samples_per_frame, + channel_count, + PJMEDIA_SOUND_BUFFER_COUNT * ptime, 0, &snd_port->delay_buf); PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); #else PJ_UNUSED_ARG(status); + PJ_UNUSED_ARG(ptime); #endif *p_port = snd_port; diff --git a/pjmedia/src/pjmedia/splitcomb.c b/pjmedia/src/pjmedia/splitcomb.c index df1d1316..79dbcde7 100644 --- a/pjmedia/src/pjmedia/splitcomb.c +++ b/pjmedia/src/pjmedia/splitcomb.c @@ -307,7 +307,7 @@ PJ_DEF(pj_status_t) pjmedia_splitcomb_create_rev_channel( pj_pool_t *pool, const pj_str_t name = pj_str("scomb-rev"); struct splitcomb *sc = (struct splitcomb*) splitcomb; struct reverse_port *rport; - unsigned buf_cnt; + unsigned buf_cnt, ptime; pjmedia_port *port; pj_status_t status; @@ -346,6 +346,9 @@ PJ_DEF(pj_status_t) pjmedia_splitcomb_create_rev_channel( pj_pool_t *pool, if (buf_cnt == 0) buf_cnt = MAX_BUF_CNT; + ptime = port->info.samples_per_frame * 1000 / port->info.clock_rate / + port->info.channel_count; + rport->max_burst = MAX_BURST; rport->max_null_frames = MAX_NULL_FRAMES; @@ -353,7 +356,8 @@ PJ_DEF(pj_status_t) pjmedia_splitcomb_create_rev_channel( pj_pool_t *pool, status = pjmedia_delay_buf_create(pool, "scombdb-dn", port->info.clock_rate, port->info.samples_per_frame, - buf_cnt, -1, 0, + port->info.channel_count, + buf_cnt * ptime, 0, &rport->buf[DIR_DOWNSTREAM].dbuf); if (status != PJ_SUCCESS) { return status; @@ -363,7 +367,8 @@ PJ_DEF(pj_status_t) pjmedia_splitcomb_create_rev_channel( pj_pool_t *pool, status = pjmedia_delay_buf_create(pool, "scombdb-up", port->info.clock_rate, port->info.samples_per_frame, - buf_cnt, -1, 0, + port->info.channel_count, + buf_cnt * ptime, 0, &rport->buf[DIR_UPSTREAM].dbuf); if (status != PJ_SUCCESS) { pjmedia_delay_buf_destroy(rport->buf[DIR_DOWNSTREAM].dbuf); |