diff options
Diffstat (limited to 'pjmedia/src/pjmedia/delaybuf.c')
-rw-r--r-- | pjmedia/src/pjmedia/delaybuf.c | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia/delaybuf.c b/pjmedia/src/pjmedia/delaybuf.c new file mode 100644 index 0000000..5acacaa --- /dev/null +++ b/pjmedia/src/pjmedia/delaybuf.c @@ -0,0 +1,405 @@ +/* $Id: delaybuf.c 3841 2011-10-24 09:28:13Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <pjmedia/delaybuf.h> +#include <pjmedia/circbuf.h> +#include <pjmedia/errno.h> +#include <pjmedia/frame.h> +#include <pjmedia/wsola.h> +#include <pj/assert.h> +#include <pj/lock.h> +#include <pj/log.h> +#include <pj/math.h> +#include <pj/pool.h> + + +#if 0 +# define TRACE__(x) PJ_LOG(3,x) +#else +# define TRACE__(x) +#endif + +/* Operation types of delay buffer */ +enum OP +{ + OP_PUT, + OP_GET +}; + +/* Specify time for delaybuf to recalculate effective delay, in ms. + */ +#define RECALC_TIME 2000 + +/* Default value of maximum delay, in ms, this value is used when + * maximum delay requested is less than ptime (one frame length). + */ +#define DEFAULT_MAX_DELAY 400 + +/* 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. */ + unsigned samples_per_frame; /**< Number of samples in one frame */ + unsigned ptime; /**< Frame time, in ms */ + unsigned channel_count; /**< Channel count, in ms */ + pjmedia_circ_buf *circ_buf; /**< Circular buffer to store audio + samples */ + unsigned max_cnt; /**< Maximum samples to be buffered */ + 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*/ + 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 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 && clock_rate && channel_count && + p_b, PJ_EINVAL); + + if (!name) { + name = "delaybuf"; + } + + 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->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; + + /* Create circular buffer */ + status = pjmedia_circ_buf_create(pool, b->max_cnt, &b->circ_buf); + if (status != PJ_SUCCESS) + return status; + + if (!(options & PJMEDIA_DELAY_BUF_SIMPLE_FIFO)) { + /* Create WSOLA */ + status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame, 1, + PJMEDIA_WSOLA_NO_FADING, &b->wsola); + if (status != PJ_SUCCESS) + return status; + PJ_LOG(5, (b->obj_name, "Using delay buffer with WSOLA.")); + } else { + PJ_LOG(5, (b->obj_name, "Using simple FIFO delay buffer.")); + } + + /* Finally, create mutex */ + status = pj_lock_create_recursive_mutex(pool, b->obj_name, + &b->lock); + if (status != PJ_SUCCESS) + return status; + + *p_b = b; + + TRACE__((b->obj_name,"Delay buffer created")); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_delay_buf_destroy(pjmedia_delay_buf *b) +{ + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(b, PJ_EINVAL); + + pj_lock_acquire(b->lock); + + if (b->wsola) { + status = pjmedia_wsola_destroy(b->wsola); + if (status == PJ_SUCCESS) + b->wsola = NULL; + } + + pj_lock_release(b->lock); + + pj_lock_destroy(b->lock); + b->lock = NULL; + + return status; +} + +/* This function will erase samples from delay buffer. + * The number of erased samples is guaranteed to be >= erase_cnt. + */ +static void shrink_buffer(pjmedia_delay_buf *b, unsigned erase_cnt) +{ + pj_int16_t *buf1, *buf2; + unsigned buf1len; + unsigned buf2len; + pj_status_t status; + + pj_assert(b && erase_cnt && pjmedia_circ_buf_get_len(b->circ_buf)); + + pjmedia_circ_buf_get_read_regions(b->circ_buf, &buf1, &buf1len, + &buf2, &buf2len); + status = pjmedia_wsola_discard(b->wsola, buf1, buf1len, buf2, buf2len, + &erase_cnt); + + if ((status == PJ_SUCCESS) && (erase_cnt > 0)) { + /* WSOLA discard will manage the first buffer to be full, unless + * erase_cnt is greater than second buffer length. So it is safe + * to just set the circular buffer length. + */ + + pjmedia_circ_buf_set_len(b->circ_buf, + pjmedia_circ_buf_get_len(b->circ_buf) - + erase_cnt); + + PJ_LOG(5,(b->obj_name,"%d samples reduced, buf_cnt=%d", + erase_cnt, pjmedia_circ_buf_get_len(b->circ_buf))); + } +} + +/* 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) + +static void update(pjmedia_delay_buf *b, enum OP op) +{ + /* Sequential operation */ + if (op == b->last_op) { + ++b->level; + return; + } + + /* Switching operation */ + if (b->level > b->max_level) + b->max_level = b->level; + + b->recalc_timer -= (b->level * b->ptime) >> 1; + + b->last_op = op; + b->level = 1; + + /* 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; + + /* 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); + + TRACE__((b->obj_name,"Cur eff_cnt=%d", b->eff_cnt)); + + b->max_level = 0; + b->recalc_timer = RECALC_TIME; + } + + /* See if we need to shrink the buffer to reduce delay */ + if (op == OP_PUT && pjmedia_circ_buf_get_len(b->circ_buf) > + b->samples_per_frame + b->eff_cnt) + { + unsigned erase_cnt = b->samples_per_frame >> 1; + unsigned old_buf_cnt = pjmedia_circ_buf_get_len(b->circ_buf); + + shrink_buffer(b, erase_cnt); + PJ_LOG(4,(b->obj_name,"Buffer size adjusted from %d to %d (eff_cnt=%d)", + old_buf_cnt, + pjmedia_circ_buf_get_len(b->circ_buf), + b->eff_cnt)); + } +} + +PJ_DEF(pj_status_t) pjmedia_delay_buf_put(pjmedia_delay_buf *b, + pj_int16_t frame[]) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(b && frame, PJ_EINVAL); + + pj_lock_acquire(b->lock); + + if (b->wsola) { + update(b, OP_PUT); + + status = pjmedia_wsola_save(b->wsola, frame, PJ_FALSE); + if (status != PJ_SUCCESS) { + pj_lock_release(b->lock); + return status; + } + } + + /* Overflow checking */ + if (pjmedia_circ_buf_get_len(b->circ_buf) + b->samples_per_frame > + b->max_cnt) + { + unsigned erase_cnt; + + if (b->wsola) { + /* shrink one frame or just the diff? */ + //erase_cnt = b->samples_per_frame; + erase_cnt = pjmedia_circ_buf_get_len(b->circ_buf) + + b->samples_per_frame - b->max_cnt; + + shrink_buffer(b, erase_cnt); + } + + /* Check if shrinking failed or erased count is less than requested, + * delaybuf needs to drop eldest samples, this is bad since the voice + * samples get rough transition which may produce tick noise. + */ + if (pjmedia_circ_buf_get_len(b->circ_buf) + b->samples_per_frame > + b->max_cnt) + { + erase_cnt = pjmedia_circ_buf_get_len(b->circ_buf) + + b->samples_per_frame - b->max_cnt; + + pjmedia_circ_buf_adv_read_ptr(b->circ_buf, erase_cnt); + + PJ_LOG(4,(b->obj_name,"%sDropping %d eldest samples, buf_cnt=%d", + (b->wsola? "Shrinking failed or insufficient. ": ""), + erase_cnt, pjmedia_circ_buf_get_len(b->circ_buf))); + } + } + + pjmedia_circ_buf_write(b->circ_buf, frame, b->samples_per_frame); + + pj_lock_release(b->lock); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_delay_buf_get( pjmedia_delay_buf *b, + pj_int16_t frame[]) +{ + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(b && frame, PJ_EINVAL); + + pj_lock_acquire(b->lock); + + if (b->wsola) + update(b, OP_GET); + + /* Starvation checking */ + if (pjmedia_circ_buf_get_len(b->circ_buf) < b->samples_per_frame) { + + PJ_LOG(4,(b->obj_name,"Underflow, buf_cnt=%d, will generate 1 frame", + pjmedia_circ_buf_get_len(b->circ_buf))); + + if (b->wsola) { + status = pjmedia_wsola_generate(b->wsola, frame); + + if (status == PJ_SUCCESS) { + TRACE__((b->obj_name,"Successfully generate 1 frame")); + if (pjmedia_circ_buf_get_len(b->circ_buf) == 0) { + pj_lock_release(b->lock); + return PJ_SUCCESS; + } + + /* Put generated frame into buffer */ + pjmedia_circ_buf_write(b->circ_buf, frame, + b->samples_per_frame); + } + } + + if (!b->wsola || status != PJ_SUCCESS) { + unsigned buf_len = pjmedia_circ_buf_get_len(b->circ_buf); + + /* Give all what delay buffer has, then pad with zeroes */ + if (b->wsola) + PJ_LOG(4,(b->obj_name,"Error generating frame, status=%d", + status)); + + pjmedia_circ_buf_read(b->circ_buf, frame, buf_len); + pjmedia_zero_samples(&frame[buf_len], + b->samples_per_frame - buf_len); + + /* The buffer is empty now, reset it */ + pjmedia_circ_buf_reset(b->circ_buf); + + pj_lock_release(b->lock); + + return PJ_SUCCESS; + } + } + + pjmedia_circ_buf_read(b->circ_buf, frame, b->samples_per_frame); + + pj_lock_release(b->lock); + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_delay_buf_reset(pjmedia_delay_buf *b) +{ + PJ_ASSERT_RETURN(b, PJ_EINVAL); + + pj_lock_acquire(b->lock); + + b->recalc_timer = RECALC_TIME; + + /* Reset buffer */ + pjmedia_circ_buf_reset(b->circ_buf); + + /* Reset WSOLA */ + if (b->wsola) + pjmedia_wsola_reset(b->wsola, 0); + + pj_lock_release(b->lock); + + PJ_LOG(5,(b->obj_name,"Delay buffer is reset")); + + return PJ_SUCCESS; +} + |