diff options
-rw-r--r-- | pjmedia/include/pjmedia/delaybuf.h | 29 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/conference.c | 123 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/delaybuf.c | 403 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sound_port.c | 24 | ||||
-rw-r--r-- | pjmedia/src/test/wsola_test.c | 5 |
5 files changed, 448 insertions, 136 deletions
diff --git a/pjmedia/include/pjmedia/delaybuf.h b/pjmedia/include/pjmedia/delaybuf.h index bdb0dd1d..2141c481 100644 --- a/pjmedia/include/pjmedia/delaybuf.h +++ b/pjmedia/include/pjmedia/delaybuf.h @@ -66,15 +66,17 @@ typedef struct pjmedia_delay_buf pjmedia_delay_buf; * from. * @param name Optional name for the buffer for log * identification. + * @param clock_rate Number of samples processed per second. * @param samples_per_frame Number of samples per frame. - * @param max_cnt Maximum number of delay to be accommodated, + * @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, the delay buffer will + * 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 option Option flags, must be zero for now. * @param p_b Pointer to receive the delay buffer instance. * * @return PJ_SUCCESS if the delay buffer has been @@ -83,9 +85,11 @@ typedef struct pjmedia_delay_buf pjmedia_delay_buf; */ 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_cnt, + unsigned max_frames, int delay, + unsigned options, pjmedia_delay_buf **p_b); /** @@ -131,6 +135,25 @@ PJ_DECL(pj_status_t) pjmedia_delay_buf_get(pjmedia_delay_buf *b, */ 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. + * + * @param b The delay buffer. + * + * @return PJ_SUCCESS on success or the appropriate error. + */ +PJ_DECL(pj_status_t) pjmedia_delay_buf_reset(pjmedia_delay_buf *b); + +/** + * Destroy delay buffer. + * + * @param b Delay buffer session. + * + * @return PJ_SUCCESS normally. + */ +PJ_DECL(pj_status_t) pjmedia_delay_buf_destroy(pjmedia_delay_buf *b); + PJ_END_DECL diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index 4999b7fe..185d08bc 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -18,6 +18,7 @@ */ #include <pjmedia/conference.h> #include <pjmedia/alaw_ulaw.h> +#include <pjmedia/delaybuf.h> #include <pjmedia/errno.h> #include <pjmedia/port.h> #include <pjmedia/resample.h> @@ -56,12 +57,7 @@ static FILE *fhnd_rec; #define THIS_FILE "conference.c" -/* When delay buffer is used, we only need 1 frame buffering */ -#if defined(PJMEDIA_SOUND_USE_DELAYBUF) && PJMEDIA_SOUND_USE_DELAYBUF!=0 -# define RX_BUF_COUNT 1 -#else -# define RX_BUF_COUNT PJMEDIA_SOUND_BUFFER_COUNT -#endif +#define RX_BUF_COUNT PJMEDIA_SOUND_BUFFER_COUNT #define BYTES_PER_SAMPLE 2 @@ -183,17 +179,22 @@ struct conf_port unsigned tx_buf_cap; /**< Max size, in samples. */ unsigned tx_buf_count; /**< # of samples in the buffer. */ - /* Snd buffers is a special buffer for sound device port (port 0, master - * port). It's not used by other ports. + /* Delay buffer is a special buffer for sound device port (port 0, master + * port) and other passive ports (sound device port is also passive port). + * + * We need the delay buffer because we can not expect the mic and speaker + * thread to run equally after one another. In most systems, each thread + * will run multiple times before the other thread gains execution time. + * For example, in my system, mic thread is called three times, then + * speaker thread is called three times, and so on. This we call burst. * - * There are multiple numbers of this buffer, because we can not expect - * the mic and speaker thread to run equally after one another. In most - * systems, each thread will run multiple times before the other thread - * gains execution time. For example, in my system, mic thread is called - * three times, then speaker thread is called three times, and so on. + * There is also possibility of drift, unbalanced rate between put_frame + * and get_frame operation, in passive ports. If drift happens, snd_buf + * needs to be expanded or shrinked. + * + * Burst and drift are handled by delay buffer. */ - int snd_write_pos, snd_read_pos; - pj_int16_t *snd_buf[RX_BUF_COUNT]; /**< Buffer */ + pjmedia_delay_buf *delay_buf; }; @@ -226,6 +227,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, static pj_status_t get_frame_pasv(pjmedia_port *this_port, pjmedia_frame *frame); static pj_status_t destroy_port(pjmedia_port *this_port); +static pj_status_t destroy_port_pasv(pjmedia_port *this_port); /* @@ -377,7 +379,6 @@ static pj_status_t create_pasv_port( pjmedia_conf *conf, struct conf_port **p_conf_port) { struct conf_port *conf_port; - unsigned i; pj_status_t status; /* Create port */ @@ -385,17 +386,16 @@ static pj_status_t create_pasv_port( pjmedia_conf *conf, if (status != PJ_SUCCESS) return status; - /* Passive port has rx buffers. */ - for (i=0; i<RX_BUF_COUNT; ++i) { - conf_port->snd_buf[i] = (pj_int16_t*) - pj_pool_zalloc(pool, conf->samples_per_frame * - sizeof(conf_port->snd_buf[0][0])); - if (conf_port->snd_buf[i] == NULL) { - return PJ_ENOMEM; - } - } - conf_port->snd_write_pos = 0; - conf_port->snd_read_pos = 0; + /* Passive port has delay buf. */ + status = pjmedia_delay_buf_create(pool, name->ptr, + conf->clock_rate, + conf->samples_per_frame, + RX_BUF_COUNT, /* max */ + -1, /* delay */ + 0, /* options */ + &conf_port->delay_buf); + if (status != PJ_SUCCESS) + return status; *p_conf_port = conf_port; @@ -444,6 +444,7 @@ static pj_status_t create_sound_port( pj_pool_t *pool, conf->bits_per_sample, 0, /* Options */ &conf->snd_dev_port); + } if (status != PJ_SUCCESS) @@ -606,6 +607,17 @@ static pj_status_t destroy_port(pjmedia_port *this_port) return pjmedia_conf_destroy(conf); } +static pj_status_t destroy_port_pasv(pjmedia_port *this_port) { + pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; + struct conf_port *port = conf->ports[this_port->port_data.ldata]; + pj_status_t status; + + status = pjmedia_delay_buf_destroy(port->delay_buf); + if (status == PJ_SUCCESS) + port->delay_buf = NULL; + + return status; +} /* * Get port zero interface. @@ -785,7 +797,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_passive_port( pjmedia_conf *conf, port->get_frame = &get_frame_pasv; port->put_frame = &put_frame; - port->on_destroy = NULL; + port->on_destroy = &destroy_port_pasv; /* Create conf port structure. */ @@ -954,7 +966,9 @@ PJ_DEF(pj_status_t) pjmedia_conf_disconnect_port( pjmedia_conf *conf, (int)dst_port->name.slen, dst_port->name.ptr)); - + /* if source port is passive port and has no listener, reset delaybuf */ + if (src_port->delay_buf && src_port->listener_cnt == 0) + pjmedia_delay_buf_reset(src_port->delay_buf); } pj_mutex_unlock(conf->mutex); @@ -1616,29 +1630,17 @@ static pj_status_t get_frame(pjmedia_port *this_port, continue; } - /* Get frame from this port. - * For port zero (sound port) and passive ports, get the frame from - * the rx_buffer instead. + /* Get frame from this port. + * For passive ports, get the frame from the delay_buf. + * For other ports, get the frame from the port. */ - if (conf_port->port == NULL) { - pj_int16_t *snd_buf; - - if (conf_port->snd_read_pos == conf_port->snd_write_pos) { - conf_port->snd_read_pos = - (conf_port->snd_write_pos+RX_BUF_COUNT-RX_BUF_COUNT/2) % - RX_BUF_COUNT; - } - - /* Skip if this port is muted/disabled. */ - if (conf_port->rx_setting != PJMEDIA_PORT_ENABLE) { - conf_port->rx_level = 0; + if (conf_port->delay_buf != NULL) { + pj_status_t status; + + status = pjmedia_delay_buf_get(conf_port->delay_buf, + (pj_int16_t*)frame->buf); + if (status != PJ_SUCCESS) continue; - } - - snd_buf = conf_port->snd_buf[conf_port->snd_read_pos]; - pjmedia_copy_samples((pj_int16_t*)frame->buf, snd_buf, - conf->samples_per_frame); - conf_port->snd_read_pos = (conf_port->snd_read_pos+1) % RX_BUF_COUNT; } else { @@ -1830,21 +1832,23 @@ static pj_status_t get_frame_pasv(pjmedia_port *this_port, /* - * Recorder callback. + * Recorder (or passive port) callback. */ static pj_status_t put_frame(pjmedia_port *this_port, const pjmedia_frame *frame) { pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; struct conf_port *port = conf->ports[this_port->port_data.ldata]; - const pj_int16_t *input = (const pj_int16_t*) frame->buf; - pj_int16_t *target_snd_buf; + pj_status_t status; /* Check for correct size. */ PJ_ASSERT_RETURN( frame->size == conf->samples_per_frame * conf->bits_per_sample / 8, PJMEDIA_ENCSAMPLESPFRAME); + /* Check existance of delay_buf instance */ + PJ_ASSERT_RETURN( port->delay_buf, PJ_EBUG ); + /* Skip if this port is muted/disabled. */ if (port->rx_setting != PJMEDIA_PORT_ENABLE) { return PJ_SUCCESS; @@ -1855,17 +1859,8 @@ static pj_status_t put_frame(pjmedia_port *this_port, return PJ_SUCCESS; } + status = pjmedia_delay_buf_put(port->delay_buf, (pj_int16_t*)frame->buf); - /* Determine which rx_buffer to fill in */ - target_snd_buf = port->snd_buf[port->snd_write_pos]; - - /* Copy samples from audio device to target rx_buffer */ - pjmedia_copy_samples(target_snd_buf, input, conf->samples_per_frame); - - /* Switch buffer */ - port->snd_write_pos = (port->snd_write_pos+1)%RX_BUF_COUNT; - - - return PJ_SUCCESS; + return status; } diff --git a/pjmedia/src/pjmedia/delaybuf.c b/pjmedia/src/pjmedia/delaybuf.c index 6133f7bf..69c1e45b 100644 --- a/pjmedia/src/pjmedia/delaybuf.c +++ b/pjmedia/src/pjmedia/delaybuf.c @@ -19,10 +19,21 @@ #include <pjmedia/delaybuf.h> #include <pjmedia/errno.h> +#include <pjmedia/wsola.h> #include <pj/assert.h> +#include <pj/lock.h> #include <pj/log.h> #include <pj/pool.h> + +#if 0 +# define TRACE__(x) PJ_LOG(3,x) +#else +# define TRACE__(x) +#endif + + +/* Type of states of delay buffer */ enum state { STATE_WAITING, @@ -30,6 +41,7 @@ enum state STATE_RUNNING }; +/* Type of operation of delay buffer */ enum OP { OP_PUT, @@ -37,13 +49,18 @@ enum OP 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 */ +/* 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. + */ #define WAITING_COUNT 4 #define LEARN_COUNT 16 -/* Number of buffers to add to learnt level for additional stability */ +/* 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. + */ #define SAFE_MARGIN 2 /* @@ -66,36 +83,48 @@ enum OP struct pjmedia_delay_buf { - char obj_name[PJ_MAX_OBJ_NAME]; + char obj_name[PJ_MAX_OBJ_NAME]; + + pj_lock_t *lock; /**< Lock object. */ - pj_int16_t *frame_buf; - unsigned put_pos; - unsigned get_pos; - unsigned buf_cnt; + 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 samples_per_frame; - unsigned max_cnt; - enum state state; + 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; + unsigned level; /**< Burst level storage on learning */ } op[2]; + enum OP last_op; /**< Last op (GET or PUT) of learning*/ + unsigned state_count; /**< Counter of op cycles of learning*/ - enum OP last_op; - unsigned max_level; - unsigned state_count; + unsigned max_level; /**< Learning result: burst level */ + + 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_cnt, + unsigned max_frames, int 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(options==0, PJ_EINVAL); - PJ_ASSERT_RETURN(pool && samples_per_frame && max_cnt && p_b, PJ_EINVAL); + PJ_UNUSED_ARG(options); if (!name) { name = "delaybuf"; @@ -104,29 +133,184 @@ 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->frame_buf = (pj_int16_t*) - 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; + b->max_frames = max_frames; + + status = pj_lock_create_recursive_mutex(pool, b->obj_name, + &b->lock); + if (status != PJ_SUCCESS) + return status; + + b->frame_buf = (pj_int16_t*) + pj_pool_zalloc(pool, samples_per_frame * max_frames * + sizeof(pj_int16_t)); if (delay >= 0) { - PJ_ASSERT_RETURN(delay <= (int)max_cnt, PJ_EINVAL); + 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; - b->buf_cnt = 0; } + status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame, + PJMEDIA_WSOLA_NO_PLC, &b->wsola); + if (status != PJ_SUCCESS) + return status; + *p_b = b; - PJ_LOG(5,(b->obj_name,"Delay buffer created")); + 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_ASSERT_RETURN(b, PJ_EINVAL); + + pj_lock_acquire(b->lock); + + 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) +{ + unsigned buf1len; + unsigned buf2len; + pj_status_t status; + + pj_assert(b && erase_cnt); + + if (b->get_pos < b->put_pos) { + /* sssss .. sssss + * ^ ^ + * G P + */ + buf1len = b->put_pos - b->get_pos; + buf2len = 0; + } else { + /* sssss .. sssss + * ^ ^ + * P G + */ + buf1len = b->max_cnt - b->get_pos; + buf2len = b->put_pos; + } + + /* Consistency checking */ + pj_assert((buf1len + buf2len) == b->buf_cnt); + + if (buf1len != 0) + status = pjmedia_wsola_discard(b->wsola, + &b->frame_buf[b->get_pos], buf1len, + b->frame_buf, buf2len, + &erase_cnt); + else + status = pjmedia_wsola_discard(b->wsola, + b->frame_buf, buf2len, + NULL, 0, + &erase_cnt); + + if ((status == PJ_SUCCESS) && (erase_cnt > 0)) { + /* WSOLA discard will shrink only the second buffer, but it may + * also shrink first buffer if second buffer is 'destroyed', so + * it is safe to just set the new put_pos. + */ + if (b->put_pos >= erase_cnt) + b->put_pos -= erase_cnt; + else + b->put_pos = b->max_cnt - (erase_cnt - b->put_pos); + + b->buf_cnt -= erase_cnt; + + PJ_LOG(5,(b->obj_name,"Successfully shrinking %d samples, " + "buf_cnt=%d", erase_cnt, b->buf_cnt)); + } + + /* Shrinking failed or erased count is less than requested, + * delaybuf needs to drop eldest samples, this is bad since the voice + * samples may not have smooth transition. + */ + if (b->buf_cnt + b->samples_per_frame > b->max_cnt) { + erase_cnt = b->buf_cnt + b->samples_per_frame - b->max_cnt; + + b->buf_cnt -= erase_cnt; + + /* Shift get_pos forward */ + b->get_pos = (b->get_pos + erase_cnt) % b->max_cnt; + + PJ_LOG(4,(b->obj_name,"Shrinking failed or insufficient, dropping" + " %d eldest samples, buf_cnt=%d", erase_cnt, b->buf_cnt)); + } +} + +static void set_max_cnt(pjmedia_delay_buf *b, unsigned new_max_cnt) +{ + unsigned old_max_cnt = b->max_cnt; + + /* nothing to adjust */ + if (old_max_cnt == new_max_cnt) + return; + + /* For now, only support shrinking */ + pj_assert(old_max_cnt > new_max_cnt); + + shrink_buffer(b, old_max_cnt - new_max_cnt); + + /* 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; + + /* Consistency checking */ + pj_assert((b->get_pos - b->put_pos) >= (old_max_cnt - new_max_cnt)); + + /* 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; + } + + b->max_cnt = new_max_cnt; +} + static void update(pjmedia_delay_buf *b, enum OP op) { enum OP other = (enum OP) !op; @@ -157,18 +341,23 @@ static void update(pjmedia_delay_buf *b, enum OP op) /* give SAFE_MARGIN compensation for added stability */ b->max_level += SAFE_MARGIN; - 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)); + 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; @@ -181,73 +370,163 @@ static void update(pjmedia_delay_buf *b, enum OP op) 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_status_t status; - pj_memcpy(&b->frame_buf[b->put_pos * b->samples_per_frame], frame, - b->samples_per_frame*sizeof(pj_int16_t)); + PJ_ASSERT_RETURN(b && frame, PJ_EINVAL); - /* overflow case */ - if (b->put_pos == b->get_pos && b->buf_cnt) { - if (++b->get_pos == b->max_level) - b->get_pos = 0; + pj_lock_acquire(b->lock); - b->put_pos = b->get_pos; + update(b, OP_PUT); + + status = pjmedia_wsola_save(b->wsola, frame, PJ_FALSE); + if (status != PJ_SUCCESS) { + pj_lock_release(b->lock); + return status; + } - PJ_LOG(5,(b->obj_name,"Warning: buffer overflow")); + /* Overflow checking */ + if (b->buf_cnt + b->samples_per_frame > b->max_cnt) + { + /* shrink one frame or just the diff? */ + //unsigned erase_cnt = b->samples_per_frame; + unsigned erase_cnt = b->buf_cnt + b->samples_per_frame - b->max_cnt; - return PJ_ETOOMANY; + shrink_buffer(b, erase_cnt); } - ++b->buf_cnt; + /* put the frame on put_pos */ + if (b->put_pos + b->samples_per_frame <= b->max_cnt) { + pjmedia_copy_samples(&b->frame_buf[b->put_pos], frame, + b->samples_per_frame); + } else { + int remainder = b->put_pos + b->samples_per_frame - b->max_cnt; - if (++b->put_pos == b->max_level) - b->put_pos = 0; + pjmedia_copy_samples(&b->frame_buf[b->put_pos], frame, + b->samples_per_frame - remainder); + pjmedia_copy_samples(&b->frame_buf[0], + &frame[b->samples_per_frame - remainder], + remainder); + } + /* Update put_pos & buf_cnt */ + b->put_pos = (b->put_pos + b->samples_per_frame) % b->max_cnt; + b->buf_cnt += 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_DEF(pj_status_t) pjmedia_delay_buf_get( pjmedia_delay_buf *b, pj_int16_t frame[]) { + pj_status_t status; + + PJ_ASSERT_RETURN(b && frame, PJ_EINVAL); + + pj_lock_acquire(b->lock); + 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")); + /* Starvation checking */ + if (b->buf_cnt < b->samples_per_frame) { + + PJ_LOG(5,(b->obj_name,"Underflow, buf_cnt=%d, will generate 1 frame", + b->buf_cnt)); + + status = pjmedia_wsola_generate(b->wsola, frame); + + if (status == PJ_SUCCESS) { + TRACE__((b->obj_name,"Successfully generate 1 frame")); + if (b->buf_cnt == 0) { + pj_lock_release(b->lock); + return PJ_SUCCESS; + } + + /* Put generated frame into buffer */ + if (b->put_pos + b->samples_per_frame <= b->max_cnt) { + pjmedia_copy_samples(&b->frame_buf[b->put_pos], frame, + b->samples_per_frame); + } else { + int remainder = b->put_pos + b->samples_per_frame - b->max_cnt; + + pjmedia_copy_samples(&b->frame_buf[b->put_pos], &frame[0], + b->samples_per_frame - remainder); + pjmedia_copy_samples(&b->frame_buf[0], + &frame[b->samples_per_frame - remainder], + remainder); + } + + b->put_pos = (b->put_pos + b->samples_per_frame) % b->max_cnt; + b->buf_cnt += b->samples_per_frame; + + } else { + /* Give all what delay buffer has, then pad zeroes */ + PJ_LOG(4,(b->obj_name,"Error generating frame, status=%d", + status)); + + pjmedia_copy_samples(frame, &b->frame_buf[b->get_pos], b->buf_cnt); + pjmedia_zero_samples(&frame[b->buf_cnt], + b->samples_per_frame - b->buf_cnt); + b->get_pos += b->buf_cnt; + b->buf_cnt = 0; + + /* Consistency checking */ + pj_assert(b->get_pos == b->put_pos); - pj_bzero(frame, b->samples_per_frame*sizeof(pj_int16_t)); - return PJ_EPENDING; + pj_lock_release(b->lock); + + return PJ_SUCCESS; + } } - pj_memcpy(frame, &b->frame_buf[b->get_pos * b->samples_per_frame], - b->samples_per_frame*sizeof(pj_int16_t)); + pjmedia_copy_samples(frame, &b->frame_buf[b->get_pos], + b->samples_per_frame); - if (++b->get_pos == b->max_level) - b->get_pos = 0; + b->get_pos = (b->get_pos + b->samples_per_frame) % b->max_cnt; + b->buf_cnt -= b->samples_per_frame; - --b->buf_cnt; + pj_lock_release(b->lock); return PJ_SUCCESS; } -PJ_DECL(pj_status_t) pjmedia_delay_buf_learn(pjmedia_delay_buf *b) +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) +{ + PJ_ASSERT_RETURN(b, PJ_EINVAL); + + pj_lock_acquire(b->lock); /* clean up buffer */ b->buf_cnt = 0; b->put_pos = b->get_pos = 0; + pjmedia_wsola_reset(b->wsola, 0); - PJ_LOG(5,(b->obj_name,"Delay buffer start learning")); + pj_lock_release(b->lock); + + PJ_LOG(5,(b->obj_name,"Delay buffer resetted")); return PJ_SUCCESS; } + diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c index 79dbd7cc..8d21e666 100644 --- a/pjmedia/src/pjmedia/sound_port.c +++ b/pjmedia/src/pjmedia/sound_port.c @@ -32,6 +32,8 @@ #define THIS_FILE "sound_port.c" +#define TEST_OVERFLOW_UNDERFLOW + enum { PJMEDIA_PLC_ENABLED = 1, @@ -101,12 +103,27 @@ static pj_status_t play_cb(/* in */ void *user_data, #if PJMEDIA_SOUND_USE_DELAYBUF if (snd_port->delay_buf) { status = pjmedia_delay_buf_get(snd_port->delay_buf, (pj_int16_t*)output); - if (status != PJ_SUCCESS) { + if (status != PJ_SUCCESS) pj_bzero(output, size); - } frame.type = PJMEDIA_FRAME_TYPE_AUDIO; pjmedia_port_put_frame(port, &frame); + +#ifdef TEST_OVERFLOW_UNDERFLOW + { + static int count = 1; + if (++count % 10 == 0) { + status = pjmedia_delay_buf_get(snd_port->delay_buf, + (pj_int16_t*)output); + if (status != PJ_SUCCESS) + pj_bzero(output, size); + + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + pjmedia_port_put_frame(port, &frame); + } + } +#endif + } #endif @@ -371,7 +388,8 @@ 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 - status = pjmedia_delay_buf_create(pool, "snd_buff", samples_per_frame, + status = pjmedia_delay_buf_create(pool, "snd_buff", + clock_rate, samples_per_frame, PJMEDIA_SOUND_BUFFER_COUNT, -1, &snd_port->delay_buf); PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); diff --git a/pjmedia/src/test/wsola_test.c b/pjmedia/src/test/wsola_test.c index 92b74322..1258ff77 100644 --- a/pjmedia/src/test/wsola_test.c +++ b/pjmedia/src/test/wsola_test.c @@ -327,12 +327,9 @@ int main() pj_caching_pool_init(&cp, NULL, 0); pool = pj_pool_create(&cp.factory, "", 1000, 1000, NULL); - mem_test(pool); - return 0; - srand(2); - rc = expand(pool, "beet44.pcm", "output.pcm", 1, 0, 0); + rc = expand(pool, "beet44.pcm", "output.pcm", 0, 0, 0); //rc = compress(pool, "beet44.pcm", "output.pcm", 2); if (rc != 0) { |