diff options
author | Benny Prijono <bennylp@teluu.com> | 2008-01-04 18:19:40 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2008-01-04 18:19:40 +0000 |
commit | c828a3b26763bdfae04032c4962098e8343b8d92 (patch) | |
tree | 51e49c3389c48e965be396d41e902a21c1fb0738 /pjmedia/src | |
parent | 16ada6b2083d10fac21bdf022fe4231dc9009421 (diff) |
Ticket #438 (Workaround for frame bursts from audio devices): added delay buffer implementation in pjmedia
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1664 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia/src')
-rw-r--r-- | pjmedia/src/pjmedia/delaybuf.c | 231 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sound_port.c | 30 |
2 files changed, 261 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia/delaybuf.c b/pjmedia/src/pjmedia/delaybuf.c new file mode 100644 index 00000000..8ea95cb6 --- /dev/null +++ b/pjmedia/src/pjmedia/delaybuf.c @@ -0,0 +1,231 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 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/errno.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/pool.h> + +enum state +{ + STATE_WAITING, + STATE_LEARNING, + STATE_RUNNING +}; + +enum OP +{ + OP_PUT, + OP_GET, + OP_UNDEFINED +}; + +/* The following macros represent cycles of test. */ +/* Since there are two operations performed (get & put), */ +/* these macros value must be minimum 2 and should be even number */ +#define WAITING_COUNT 8 +#define LEARN_COUNT 8 + +struct pjmedia_delay_buf +{ + char obj_name[PJ_MAX_OBJ_NAME]; + + pj_int16_t *frame_buf; + unsigned put_pos; + unsigned get_pos; + unsigned buf_cnt; + + unsigned samples_per_frame; + unsigned max_cnt; + enum state state; + + struct { + unsigned level; + } op[2]; + + enum OP last_op; + unsigned max_level; + unsigned state_count; +}; + +PJ_DEF(pj_status_t) pjmedia_delay_buf_create( pj_pool_t *pool, + const char *name, + unsigned samples_per_frame, + unsigned max_cnt, + int delay, + pjmedia_delay_buf **p_b) +{ + pjmedia_delay_buf *b; + + PJ_ASSERT_RETURN(pool && samples_per_frame && max_cnt && 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->frame_buf = pj_pool_zalloc(pool, samples_per_frame * max_cnt * + sizeof(pj_int16_t)); + b->samples_per_frame = samples_per_frame; + b->max_cnt = max_cnt; + + if (delay >= 0) { + PJ_ASSERT_RETURN(delay <= (int)max_cnt, PJ_EINVAL); + b->max_level = delay; + b->state = STATE_RUNNING; + } else { + b->last_op = OP_UNDEFINED; + b->state = STATE_WAITING; + b->buf_cnt = 0; + } + + *p_b = b; + + PJ_LOG(5,(b->obj_name,"Delay buffer created")); + + return PJ_SUCCESS; +} + +static void update(pjmedia_delay_buf *b, enum OP op) +{ + enum OP other = !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 ONE frame compensation */ + b->max_level += 1; + + PJ_LOG(5,(b->obj_name,"Delay buffer start running, level=%u", + b->max_level)); + + /* buffer not enough! */ + if (b->max_level > b->max_cnt) { + b->max_level = b->max_cnt; + PJ_LOG(2,(b->obj_name,"Delay buffer %s learning result " \ + "exceeds the maximum delay allowed", + b->max_level)); + } + + b->state = STATE_RUNNING; + } + } + b->last_op = op; + break; + } + + +} + +PJ_DEF(pj_status_t) pjmedia_delay_buf_put(pjmedia_delay_buf *b, + pj_int16_t frame[]) +{ + update(b, OP_PUT); + + if (b->state != STATE_RUNNING) + return PJ_EPENDING; + + pj_memcpy(&b->frame_buf[b->put_pos * b->samples_per_frame], frame, + b->samples_per_frame*sizeof(pj_int16_t)); + + /* overflow case */ + if (b->put_pos == b->get_pos && b->buf_cnt) { + if (++b->get_pos == b->max_level) + b->get_pos = 0; + + b->put_pos = b->get_pos; + + PJ_LOG(5,(b->obj_name,"Warning: buffer overflow")); + + return PJ_ETOOMANY; + } + + ++b->buf_cnt; + + if (++b->put_pos == b->max_level) + b->put_pos = 0; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_delay_buf_get(pjmedia_delay_buf *b, + pj_int16_t frame[]) +{ + update(b, OP_GET); + + if (b->state != STATE_RUNNING || !b->buf_cnt) { + if (b->state == STATE_RUNNING) + PJ_LOG(5,(b->obj_name,"Warning: delay buffer empty")); + + pj_bzero(frame, b->samples_per_frame*sizeof(pj_int16_t)); + return PJ_EPENDING; + } + + pj_memcpy(frame, &b->frame_buf[b->get_pos * b->samples_per_frame], + b->samples_per_frame*sizeof(pj_int16_t)); + + if (++b->get_pos == b->max_level) + b->get_pos = 0; + + --b->buf_cnt; + + return PJ_SUCCESS; +} + +PJ_DECL(pj_status_t) pjmedia_delay_buf_learn(pjmedia_delay_buf *b) +{ + PJ_ASSERT_RETURN(b, PJ_EINVAL); + + 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; + + /* clean up buffer */ + b->buf_cnt = 0; + b->put_pos = b->get_pos = 0; + + PJ_LOG(5,(b->obj_name,"Delay buffer start learning")); + + return PJ_SUCCESS; +} diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c index 5fbbaab5..50ea18c6 100644 --- a/pjmedia/src/pjmedia/sound_port.c +++ b/pjmedia/src/pjmedia/sound_port.c @@ -17,6 +17,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <pjmedia/sound_port.h> +#include <pjmedia/delaybuf.h> #include <pjmedia/echo.h> #include <pjmedia/errno.h> #include <pjmedia/plc.h> @@ -62,6 +63,10 @@ struct pjmedia_snd_port unsigned channel_count; unsigned samples_per_frame; unsigned bits_per_sample; + +#if PJMEDIA_SOUND_USE_DELAYBUF + pjmedia_delay_buf *delay_buf; +#endif }; /* @@ -93,6 +98,15 @@ static pj_status_t play_cb(/* in */ void *user_data, frame.timestamp.u32.hi = 0; frame.timestamp.u32.lo = timestamp; +#if PJMEDIA_SOUND_USE_DELAYBUF + status = pjmedia_delay_buf_get(snd_port->delay_buf, (pj_int16_t*)output); + if (status != PJ_SUCCESS) { + pj_bzero(output, size); + } + + pjmedia_port_put_frame(port, &frame); +#endif + status = pjmedia_port_get_frame(port, &frame); if (status != PJ_SUCCESS) goto no_frame; @@ -185,12 +199,19 @@ static pj_status_t rec_cb(/* in */ void *user_data, pjmedia_echo_capture(snd_port->ec_state, (pj_int16_t*) input, 0); } +#if PJMEDIA_SOUND_USE_DELAYBUF + PJ_UNUSED_ARG(size); + PJ_UNUSED_ARG(timestamp); + PJ_UNUSED_ARG(frame); + pjmedia_delay_buf_put(snd_port->delay_buf, (pj_int16_t*)input); +#else frame.buf = (void*)input; frame.size = size; frame.type = PJMEDIA_FRAME_TYPE_AUDIO; frame.timestamp.u32.lo = timestamp; pjmedia_port_put_frame(port, &frame); +#endif return PJ_SUCCESS; } @@ -324,6 +345,7 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool, pjmedia_snd_port **p_port) { pjmedia_snd_port *snd_port; + pj_status_t status; PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL); @@ -338,6 +360,14 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool, snd_port->channel_count = channel_count; snd_port->samples_per_frame = samples_per_frame; snd_port->bits_per_sample = bits_per_sample; + +#if PJMEDIA_SOUND_USE_DELAYBUF + status = pjmedia_delay_buf_create(pool, "snd_buff", samples_per_frame, + 16, &snd_port->delay_buf); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); +#else + PJ_UNUSED_ARG(status); +#endif *p_port = snd_port; |