summaryrefslogtreecommitdiff
path: root/pjmedia
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2008-03-04 15:37:45 +0000
committerBenny Prijono <bennylp@teluu.com>2008-03-04 15:37:45 +0000
commit23b72a335dc2dfbec1145ffa6b886c5af925e588 (patch)
treeeba320d470d821069bbd2604fd398923c50d6435 /pjmedia
parent93f91b36381a56608b372cc5bc99b17fe8d6174d (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.h2
-rw-r--r--pjmedia/src/pjmedia/delaybuf.c2
-rw-r--r--pjmedia/src/pjmedia/splitcomb.c125
-rw-r--r--pjmedia/src/pjmedia/wsola.c108
-rw-r--r--pjmedia/src/test/wsola_test.c17
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");