| /* $Id$ */ |
| /* |
| * 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; |
| } |
| |