diff options
author | Benny Prijono <bennylp@teluu.com> | 2008-03-04 15:37:45 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2008-03-04 15:37:45 +0000 |
commit | 23b72a335dc2dfbec1145ffa6b886c5af925e588 (patch) | |
tree | eba320d470d821069bbd2604fd398923c50d6435 /pjmedia | |
parent | 93f91b36381a56608b372cc5bc99b17fe8d6174d (diff) |
More ticket #438: improve docs, added channel_count in wsola, etc.
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1844 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia')
-rw-r--r-- | pjmedia/include/pjmedia/wsola.h | 2 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/delaybuf.c | 2 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/splitcomb.c | 125 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/wsola.c | 108 | ||||
-rw-r--r-- | pjmedia/src/test/wsola_test.c | 17 |
5 files changed, 172 insertions, 82 deletions
diff --git a/pjmedia/include/pjmedia/wsola.h b/pjmedia/include/pjmedia/wsola.h index dbeab186..891753a9 100644 --- a/pjmedia/include/pjmedia/wsola.h +++ b/pjmedia/include/pjmedia/wsola.h @@ -75,6 +75,7 @@ enum pjmedia_wsola_option * @param pool Pool to allocate memory for WSOLA. * @param clock_rate Sampling rate of audio playback. * @param samples_per_frame Number of samples per frame. + * @param channel_count Number of channels. * @param options Option flags, bitmask combination of * #pjmedia_wsola_option. * @param p_wsola Pointer to receive WSOLA structure. @@ -84,6 +85,7 @@ enum pjmedia_wsola_option PJ_DECL(pj_status_t) pjmedia_wsola_create(pj_pool_t *pool, unsigned clock_rate, unsigned samples_per_frame, + unsigned channel_count, unsigned options, pjmedia_wsola **p_wsola); diff --git a/pjmedia/src/pjmedia/delaybuf.c b/pjmedia/src/pjmedia/delaybuf.c index 70e27a5d..09903511 100644 --- a/pjmedia/src/pjmedia/delaybuf.c +++ b/pjmedia/src/pjmedia/delaybuf.c @@ -157,7 +157,7 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_create( pj_pool_t *pool, b->state = STATE_WAITING; } - status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame, + status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame, 1, PJMEDIA_WSOLA_NO_PLC, &b->wsola); if (status != PJ_SUCCESS) return status; diff --git a/pjmedia/src/pjmedia/splitcomb.c b/pjmedia/src/pjmedia/splitcomb.c index 84286c9d..6bd59de4 100644 --- a/pjmedia/src/pjmedia/splitcomb.c +++ b/pjmedia/src/pjmedia/splitcomb.c @@ -48,31 +48,35 @@ #define OP_PUT (1) #define OP_GET (-1) -/* Media flow directions */ + +/* + * Media flow directions: + * + * put_frame() +-----+ + * UPSTREAM ------------>|split|<--> DOWNSTREAM + * <------------|comb | + * get_frame() +-----+ + * + */ enum sc_dir { - /* This is the direction from the splitcomb to the downstream - * port(s), or when put_frame() is called to the splitcomb. + /* This is the media direction from the splitcomb to the + * downstream port(s), which happens when: + * - put_frame() is called to the splitcomb + * - get_frame() is called to the reverse channel port. */ DIR_DOWNSTREAM, - /* This is the direction from the downstream port to the splitcomb, - * or when get_frame() is called to the splitcomb. + /* This is the media direction from the downstream port to + * the splitcomb, which happens when: + * - get_frame() is called to the splitcomb + * - put_frame() is called to the reverse channel port. */ DIR_UPSTREAM }; -#if 0 -# define TRACE_UP_(x) PJ_LOG(5,x) -# define TRACE_DN_(x) PJ_LOG(5,x) -#else -# define TRACE_UP_(x) -# define TRACE_DN_(x) -#endif - - /* * This structure describes the splitter/combiner. */ @@ -106,13 +110,37 @@ struct reverse_port struct splitcomb*parent; unsigned ch_num; - /* Maximum burst before media flow is suspended */ + /* Maximum burst before media flow is suspended. + * With reverse port, it's possible that either end of the + * port doesn't actually process the media flow (meaning, it + * stops calling get_frame()/put_frame()). When this happens, + * the other end will encounter excessive underflow or overflow, + * depending on which direction is not actively processed by + * the stopping end. + * + * To avoid excessive underflow/overflow, the media flow will + * be suspended once underflow/overflow goes over this max_burst + * limit. + */ int max_burst; - /* Maximum NULL frames received before media flow is suspended. */ + /* When the media interface port of the splitcomb or the reverse + * channel port is registered to conference bridge, the bridge + * will transmit NULL frames to the media port when the media + * port is not receiving any audio from other slots (for example, + * when no other slots are connected to the media port). + * + * When this happens, we will generate zero frame to our buffer, + * to avoid underflow/overflow. But after too many NULL frames + * are received, we will pause the media flow instead, to save + * some processing. + * + * This value controls how many NULL frames can be received + * before we suspend media flow for a particular direction. + */ unsigned max_null_frames; - /* A reverse port need a temporary buffer to store frame + /* A reverse port need a temporary buffer to store frames * (because of the different phase, see splitcomb.h for details). * Since we can not expect get_frame() and put_frame() to be * called evenly one after another, we use delay buffers to @@ -313,7 +341,7 @@ PJ_DEF(pj_status_t) pjmedia_splitcomb_create_rev_channel( pj_pool_t *pool, port->get_frame = &rport_get_frame; port->on_destroy = &rport_on_destroy; - + /* Buffer settings */ buf_cnt = options & 0xFF; if (buf_cnt == 0) buf_cnt = MAX_BUF_CNT; @@ -322,7 +350,7 @@ PJ_DEF(pj_status_t) pjmedia_splitcomb_create_rev_channel( pj_pool_t *pool, rport->max_null_frames = MAX_NULL_FRAMES; /* Create downstream/put buffers */ - status = pjmedia_delay_buf_create(pool, "scomb-down", + status = pjmedia_delay_buf_create(pool, "scombdb-dn", port->info.clock_rate, port->info.samples_per_frame, buf_cnt, -1, 0, @@ -332,7 +360,7 @@ PJ_DEF(pj_status_t) pjmedia_splitcomb_create_rev_channel( pj_pool_t *pool, } /* Create upstream/get buffers */ - status = pjmedia_delay_buf_create(pool, "scomb-up", + status = pjmedia_delay_buf_create(pool, "scombdb-up", port->info.clock_rate, port->info.samples_per_frame, buf_cnt, -1, 0, @@ -445,8 +473,9 @@ static void op_update(struct reverse_port *rport, int dir, int op) /* - * "Write" a multichannel frame. This would split the multichannel frame - * into individual mono channel, and write it to the appropriate port. + * "Write" a multichannel frame downstream. This would split + * the multichannel frame into individual mono channel, and write + * it to the appropriate port. */ static pj_status_t put_frame(pjmedia_port *this_port, const pjmedia_frame *frame) @@ -466,13 +495,6 @@ static pj_status_t put_frame(pjmedia_port *this_port, } else { struct reverse_port *rport = (struct reverse_port*)port; - /* Write zero port to delaybuf so that it doesn't underflow. - * If we don't do this, get_frame() on this direction will - * cause delaybuf to generate missing frame and the last - * frame transmitted to delaybuf will be replayed multiple - * times, which doesn't sound good. - */ - /* Update the number of NULL frames received. Once we have too * many of this, we'll stop calling op_update() to let the * media be suspended. @@ -489,6 +511,13 @@ static pj_status_t put_frame(pjmedia_port *this_port, continue; } + /* Write zero port to delaybuf so that it doesn't underflow. + * If we don't do this, get_frame() on this direction will + * cause delaybuf to generate missing frame and the last + * frame transmitted to delaybuf will be replayed multiple + * times, which doesn't sound good. + */ + /* Update rport state. */ op_update(rport, DIR_DOWNSTREAM, OP_PUT); @@ -497,12 +526,12 @@ static pj_status_t put_frame(pjmedia_port *this_port, continue; /* Generate zero frame. */ - pjmedia_zero_samples(rport->tmp_up_buf, + pjmedia_zero_samples(sc->put_buf, this_port->info.samples_per_frame); /* Put frame to delay buffer */ pjmedia_delay_buf_put(rport->buf[DIR_DOWNSTREAM].dbuf, - rport->tmp_up_buf); + sc->put_buf); } } @@ -565,7 +594,7 @@ static pj_status_t put_frame(pjmedia_port *this_port, /* - * Get a multichannel frame. + * Get a multichannel frame upstream. * This will get mono channel frame from each port and put the * mono frame into the multichannel frame. */ @@ -616,7 +645,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, } else { pjmedia_zero_samples(sc->get_buf, - rport->base.info.samples_per_frame); + port->info.samples_per_frame); } frame->timestamp.u64 = rport->buf[DIR_UPSTREAM].ts.u64; @@ -628,8 +657,6 @@ static pj_status_t get_frame(pjmedia_port *this_port, this_port->info.channel_count, this_port->info.samples_per_frame); - - has_frame = PJ_TRUE; } @@ -646,7 +673,9 @@ static pj_status_t get_frame(pjmedia_port *this_port, static pj_status_t on_destroy(pjmedia_port *this_port) { - /* Nothing to do */ + /* Nothing to do for the splitcomb + * Reverse ports must be destroyed separately. + */ PJ_UNUSED_ARG(this_port); return PJ_SUCCESS; @@ -654,7 +683,8 @@ static pj_status_t on_destroy(pjmedia_port *this_port) /* - * Get a mono frame from a reversed phase channel. + * Put a frame in the reverse port (upstream direction). This frame + * will be picked up by get_frame() above. */ static pj_status_t rport_put_frame(pjmedia_port *this_port, const pjmedia_frame *frame) @@ -665,16 +695,6 @@ static pj_status_t rport_put_frame(pjmedia_port *this_port, /* Handle NULL frame */ if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) { - TRACE_UP_((THIS_FILE, "Upstream write %d null samples at buf pos %d", - this_port->info.samples_per_frame, rport->up_write_pos)); - - /* Write zero port to delaybuf so that it doesn't underflow. - * If we don't do this, get_frame() on this direction will - * cause delaybuf to generate missing frame and the last - * frame transmitted to delaybuf will be replayed multiple - * times, which doesn't sound good. - */ - /* Update the number of NULL frames received. Once we have too * many of this, we'll stop calling op_update() to let the * media be suspended. @@ -687,6 +707,13 @@ static pj_status_t rport_put_frame(pjmedia_port *this_port, return PJ_SUCCESS; } + /* Write zero port to delaybuf so that it doesn't underflow. + * If we don't do this, get_frame() on this direction will + * cause delaybuf to generate missing frame and the last + * frame transmitted to delaybuf will be replayed multiple + * times, which doesn't sound good. + */ + /* Update rport state. */ op_update(rport, DIR_UPSTREAM, OP_PUT); @@ -729,8 +756,8 @@ static pj_status_t rport_put_frame(pjmedia_port *this_port, } -/* - * Get a mono frame from a reversed phase channel. +/* Get a mono frame from a reversed phase channel (downstream direction). + * The frame is put by put_frame() call to the splitcomb. */ static pj_status_t rport_get_frame(pjmedia_port *this_port, pjmedia_frame *frame) diff --git a/pjmedia/src/pjmedia/wsola.c b/pjmedia/src/pjmedia/wsola.c index 54c819e6..2be14ea7 100644 --- a/pjmedia/src/pjmedia/wsola.c +++ b/pjmedia/src/pjmedia/wsola.c @@ -36,7 +36,7 @@ #define TEMPLATE_PTIME (5) /* Generate extra samples, in msec */ -#define GEN_EXTRA_PTIME (0.0) +#define GEN_EXTRA_PTIME (5) /* Number of frames in erase buffer */ #define ERASE_CNT ((unsigned)3) @@ -53,17 +53,43 @@ #endif +/* Buffer content: + * + * t0 time tn + * ----------------> + * + * +---------+-------+-------+--- --+ + * | history | frame | extra | ...empty... | + * +---------+-------+-------+---- ---+ + * ^ ^ ^ ^ + * buf hist_cnt cur_cnt buf_cnt + * or + * frm pointer + * + * History count (hist_cnt) is a constant value, initialized upon + * creation. + * + * At any particular time, the buffer will contain at least + * (hist_cnt+samples_per_frame) samples. + * + * A "save" operation will append the frame to the end of the + * buffer, return the samples right after history (the frm pointer), + * and shift the buffer by one frame. + */ + +/* WSOLA structure */ struct pjmedia_wsola { unsigned clock_rate; /* Sampling rate. */ - pj_uint16_t samples_per_frame;/* Samples per frame. */ + pj_uint16_t samples_per_frame;/* Samples per frame (const) */ + pj_uint16_t channel_count; /* Samples per frame (const) */ pj_uint16_t options; /* Options. */ - pj_uint16_t hist_cnt; /* # of history samples. */ - pj_uint16_t buf_cnt; /* Total buffer capacity */ + pj_uint16_t hist_cnt; /* # of history samples (const) */ + pj_uint16_t buf_cnt; /* Total buffer capacity (const) */ pj_uint16_t cur_cnt; /* Cur # of samples, inc. history */ - pj_uint16_t template_size; /* Template size. */ - pj_uint16_t min_extra; /* Min extra samples for merging. */ - pj_uint16_t gen_extra; /* Generate extra samples. */ + pj_uint16_t template_size; /* Template size (const) */ + pj_uint16_t min_extra; /* Min extra samples for merging (const)*/ + pj_uint16_t gen_extra; /* Generate extra samples (const) */ pj_uint16_t expand_cnt; /* Number of expansion currently done */ short *buf; /* The buffer. */ @@ -123,7 +149,7 @@ static short *find_pitch(short *frm, short *beg, short *end, } } - TRACE_((THIS_FILE, "found pitch at %u", best-beg)); + /*TRACE_((THIS_FILE, "found pitch at %u", best-beg));*/ return best; } @@ -207,7 +233,7 @@ static short *find_pitch(short *frm, short *beg, short *end, } } - TRACE_((THIS_FILE, "found pitch at %u", best-beg)); + /*TRACE_((THIS_FILE, "found pitch at %u", best-beg));*/ return best; } @@ -292,6 +318,7 @@ static void create_win(pj_pool_t *pool, pj_uint16_t **pw, unsigned count) PJ_DEF(pj_status_t) pjmedia_wsola_create( pj_pool_t *pool, unsigned clock_rate, unsigned samples_per_frame, + unsigned channel_count, unsigned options, pjmedia_wsola **p_wsola) { @@ -301,11 +328,13 @@ PJ_DEF(pj_status_t) pjmedia_wsola_create( pj_pool_t *pool, PJ_EINVAL); PJ_ASSERT_RETURN(clock_rate <= 65535, PJ_EINVAL); PJ_ASSERT_RETURN(samples_per_frame < clock_rate, PJ_EINVAL); + PJ_ASSERT_RETURN(channel_count > 0, PJ_EINVAL); wsola = PJ_POOL_ZALLOC_T(pool, pjmedia_wsola); wsola->clock_rate= (pj_uint16_t) clock_rate; wsola->samples_per_frame = (pj_uint16_t) samples_per_frame; + wsola->channel_count = (pj_uint16_t) channel_count; wsola->options = (pj_uint16_t) options; wsola->hist_cnt = (pj_uint16_t)(samples_per_frame * HISTSZ); wsola->buf_cnt = (pj_uint16_t)(wsola->hist_cnt + @@ -359,9 +388,9 @@ PJ_DEF(pj_status_t) pjmedia_wsola_reset( pjmedia_wsola *wsola, PJ_ASSERT_RETURN(wsola && options==0, PJ_EINVAL); PJ_UNUSED_ARG(options); - pjmedia_zero_samples(wsola->buf, wsola->cur_cnt); wsola->cur_cnt = (pj_uint16_t)(wsola->hist_cnt + wsola->samples_per_frame); + pjmedia_zero_samples(wsola->buf, wsola->cur_cnt); return PJ_SUCCESS; } @@ -373,32 +402,47 @@ static void expand(pjmedia_wsola *wsola, unsigned needed) unsigned rep; for (rep=1;; ++rep) { - short *start; - unsigned dist; + short *start, *frm; + unsigned min_dist, max_dist, dist; + + frm = wsola->buf + wsola->cur_cnt - frmsz; + pj_assert(frm - wsola->buf >= wsola->hist_cnt); + + max_dist = wsola->hist_cnt; + min_dist = frmsz >> 1; - start = find_pitch(wsola->frm, wsola->buf, - wsola->frm - (wsola->samples_per_frame >> 1), + start = find_pitch(frm, frm - max_dist, frm - min_dist, wsola->template_size, 1); + /* Should we make sure that "start" is really aligned to + * channel #0, in case of stereo? Probably not necessary, as + * find_pitch() should have found the best match anyway. + */ + if (wsola->options & PJMEDIA_WSOLA_NO_HANNING) { - overlapp_add_simple(wsola->mergebuf, wsola->samples_per_frame, - wsola->frm, start); + overlapp_add_simple(wsola->mergebuf, frmsz,frm, start); } else { - overlapp_add(wsola->mergebuf, wsola->samples_per_frame, - wsola->frm, start, wsola->hanning); + overlapp_add(wsola->mergebuf, frmsz, frm, start, wsola->hanning); } - dist = wsola->frm - start; - pjmedia_move_samples(wsola->frm + frmsz, start + frmsz, + /* How many new samples do we have */ + dist = frm - start; + + /* Copy the "tail" (excess frame) to the end */ + pjmedia_move_samples(frm + frmsz, start + frmsz, wsola->buf+wsola->cur_cnt - (start+frmsz)); - pjmedia_copy_samples(wsola->frm, wsola->mergebuf, frmsz); + /* Copy the merged frame */ + pjmedia_copy_samples(frm, wsola->mergebuf, frmsz); + /* We have new samples */ wsola->cur_cnt = (pj_uint16_t)(wsola->cur_cnt + dist); + + pj_assert(wsola->cur_cnt <= wsola->buf_cnt); + generated += dist; if (generated >= needed) { - assert(wsola->cur_cnt <= wsola->buf_cnt); TRACE_((THIS_FILE, "WSOLA frame expanded after %d iterations", rep)); break; @@ -468,17 +512,24 @@ PJ_DEF(pj_status_t) pjmedia_wsola_save( pjmedia_wsola *wsola, if (prev_lost && extra >= wsola->min_extra) { short *dst = wsola->buf + wsola->hist_cnt + wsola->samples_per_frame; - unsigned i; + /* Smoothen the transition. This will also erase the excess + * samples + */ overlapp_add_simple(dst, extra, dst, frm); - for (i=extra; i<wsola->samples_per_frame; ++i) - dst[i] = frm[i]; + /* Copy remaining samples from the frame */ + pjmedia_copy_samples(dst+extra, frm+extra, + wsola->samples_per_frame-extra); - + /* Retrieve frame */ pjmedia_copy_samples(frm, wsola->frm, wsola->samples_per_frame); + + /* Remove excess samples */ wsola->cur_cnt = (pj_uint16_t)(wsola->hist_cnt + wsola->samples_per_frame); + + /* Shift buffer */ pjmedia_move_samples(wsola->buf, wsola->buf+wsola->samples_per_frame, wsola->cur_cnt); @@ -489,10 +540,15 @@ PJ_DEF(pj_status_t) pjmedia_wsola_save( pjmedia_wsola *wsola, "Appending new frame without interpolation")); } + /* Append frame */ pjmedia_copy_samples(wsola->buf + wsola->cur_cnt, frm, wsola->samples_per_frame); + + /* Retrieve frame */ pjmedia_copy_samples(frm, wsola->frm, wsola->samples_per_frame); + + /* Shift buffer */ pjmedia_move_samples(wsola->buf, wsola->buf+wsola->samples_per_frame, wsola->cur_cnt); } diff --git a/pjmedia/src/test/wsola_test.c b/pjmedia/src/test/wsola_test.c index 1258ff77..8c489fc8 100644 --- a/pjmedia/src/test/wsola_test.c +++ b/pjmedia/src/test/wsola_test.c @@ -5,7 +5,7 @@ #include <stdio.h> #include <assert.h> -#define CLOCK_RATE 44100 +#define CLOCK_RATE 16000 #define SAMPLES_PER_FRAME (10 * CLOCK_RATE / 1000) #define RESET() memset(buf1, 0, sizeof(buf1)), \ @@ -85,7 +85,7 @@ int expand(pj_pool_t *pool, const char *filein, const char *fileout, out = fopen(fileout, "wb"); if (!out) return 1; - pjmedia_wsola_create(pool, CLOCK_RATE, SAMPLES_PER_FRAME, 0, &wsola); + pjmedia_wsola_create(pool, CLOCK_RATE, SAMPLES_PER_FRAME, 1, 0, &wsola); samples = 0; elapsed.u64 = 0; @@ -200,7 +200,7 @@ int compress(pj_pool_t *pool, out = fopen(fileout, "wb"); if (!out) return 1; - pjmedia_wsola_create(pool, CLOCK_RATE, SAMPLES_PER_FRAME, 0, &wsola); + pjmedia_wsola_create(pool, CLOCK_RATE, SAMPLES_PER_FRAME, 1, 0, &wsola); elapsed.u64 = 0; @@ -319,7 +319,7 @@ int main() { pj_caching_pool cp; pj_pool_t *pool; - int rc; + int i, rc; //test_find_pitch(); @@ -329,8 +329,13 @@ int main() srand(2); - rc = expand(pool, "beet44.pcm", "output.pcm", 0, 0, 0); - //rc = compress(pool, "beet44.pcm", "output.pcm", 2); + rc = expand(pool, "galileo16.pcm", "temp1.pcm", 20, 0, 0); + rc = compress(pool, "temp1.pcm", "output.pcm", 1); + + for (i=0; i<2; ++i) { + rc = expand(pool, "output.pcm", "temp1.pcm", 20, 0, 0); + rc = compress(pool, "temp1.pcm", "output.pcm", 1); + } if (rc != 0) { puts("Error"); |