/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * 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 #include #include #include #include #include #include /* * This file contains implementation of WSOLA using PJMEDIA_WSOLA_IMP_WSOLA * or PJMEDIA_WSOLA_IMP_NULL */ #define THIS_FILE "wsola.c" /* * http://trac.pjsip.org/repos/ticket/683: * Workaround for segfault problem in the fixed point version of create_win() * on ARM9 platform, possibly due to gcc optimization bug. * * For now, we will use linear window when floating point is disabled. */ #ifndef PJMEDIA_WSOLA_LINEAR_WIN # define PJMEDIA_WSOLA_LINEAR_WIN (!PJ_HAS_FLOATING_POINT) #endif #if 0 # define TRACE_(x) PJ_LOG(4,x) #else # define TRACE_(x) #endif #if 0 # define CHECK_(x) pj_assert(x) #else # define CHECK_(x) #endif #if (PJMEDIA_WSOLA_IMP==PJMEDIA_WSOLA_IMP_WSOLA) || \ (PJMEDIA_WSOLA_IMP==PJMEDIA_WSOLA_IMP_WSOLA_LITE) /* * WSOLA implementation using WSOLA */ /* Buffer size including history, in frames */ #define FRAME_CNT 6 /* Number of history frames in buffer */ #define HIST_CNT 1.5 /* Template size, in msec */ #define TEMPLATE_PTIME PJMEDIA_WSOLA_TEMPLATE_LENGTH_MSEC /* Hanning window size, in msec */ #define HANNING_PTIME PJMEDIA_WSOLA_DELAY_MSEC /* Number of frames in erase buffer */ #define ERASE_CNT ((unsigned)3) /* Minimum distance from template for find_pitch() of expansion, in frames */ #define EXP_MIN_DIST 0.5 /* Maximum distance from template for find_pitch() of expansion, in frames */ #define EXP_MAX_DIST HIST_CNT /* Duration of a continuous synthetic frames after which the volume * of the synthetic frame will be set to zero with fading-out effect. */ #define MAX_EXPAND_MSEC PJMEDIA_WSOLA_MAX_EXPAND_MSEC /* Buffer content: * * +---------+-----------+--------------------+ * | history | min_extra | more extra / empty | * +---------+-----------+--------------------+ * ^ ^ ^ ^ * buf hist_size min_extra buf_size * * History size (hist_size) is a constant value, initialized upon creation. * * min_extra size is equal to HANNING_PTIME, this samples is useful for * smoothening samples transition between generated frame & history * (when PLC is invoked), or between generated samples & normal frame * (after lost/PLC). Since min_extra samples need to be available at * any time, this will introduce delay of HANNING_PTIME ms. * * More extra is excess samples produced by PLC (PLC frame generation may * produce more than exact one frame). * * At any particular time, the buffer will contain at least (hist_size + * min_extra) samples. * * A "save" operation will append the new frame to the end of the buffer, * return the frame from samples right after history 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 (const) */ pj_uint16_t channel_count; /* Channel countt (const) */ pj_uint16_t options; /* Options. */ pjmedia_circ_buf *buf; /* The buffer. */ pj_int16_t *erase_buf; /* Temporary erase buffer. */ pj_int16_t *merge_buf; /* Temporary merge buffer. */ pj_uint16_t buf_size; /* Total buffer size (const) */ pj_uint16_t hanning_size; /* Hanning window size (const) */ pj_uint16_t templ_size; /* Template size (const) */ pj_uint16_t hist_size; /* History size (const) */ pj_uint16_t min_extra; /* Minimum extra (const) */ unsigned max_expand_cnt; /* Max # of synthetic samples */ unsigned fade_out_pos; /* Last fade-out position */ pj_uint16_t expand_sr_min_dist;/* Minimum distance from template for find_pitch() on expansion (const) */ pj_uint16_t expand_sr_max_dist;/* Maximum distance from template for find_pitch() on expansion (const) */ #if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0 float *hanning; /* Hanning window. */ #else pj_uint16_t *hanning; /* Hanning window. */ #endif pj_timestamp ts; /* Running timestamp. */ }; #if (PJMEDIA_WSOLA_IMP==PJMEDIA_WSOLA_IMP_WSOLA_LITE) /* In this implementation, waveform similarity comparison is done by calculating * the difference of total level between template frame and the target buffer * for each template_cnt samples. The smallest difference value assumed to be * the most similar block. This seems to be naive, however some tests show * acceptable results and the processing speed is amazing. * * diff level = (template[1]+..+template[n]) - (target[1]+..+target[n]) */ static pj_int16_t *find_pitch(pj_int16_t *frm, pj_int16_t *beg, pj_int16_t *end, unsigned template_cnt, int first) { pj_int16_t *sr, *best=beg; int best_corr = 0x7FFFFFFF; int frm_sum = 0; unsigned i; for (i = 0; i 0? corr : -corr; if (first) { if (abs_corr < best_corr) { best_corr = abs_corr; best = sr; } } else { if (abs_corr <= best_corr) { best_corr = abs_corr; best = sr; } } } /*TRACE_((THIS_FILE, "found pitch at %u", best-beg));*/ return best; } #endif #if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0 /* * Floating point version. */ #if (PJMEDIA_WSOLA_IMP==PJMEDIA_WSOLA_IMP_WSOLA) static pj_int16_t *find_pitch(pj_int16_t *frm, pj_int16_t *beg, pj_int16_t *end, unsigned template_cnt, int first) { pj_int16_t *sr, *best=beg; double best_corr = 0; for (sr=beg; sr!=end; ++sr) { double corr = 0; unsigned i; /* Do calculation on 8 samples at once */ for (i=0; i best_corr) { best_corr = corr; best = sr; } } else { if (corr >= best_corr) { best_corr = corr; best = sr; } } } /*TRACE_((THIS_FILE, "found pitch at %u", best-beg));*/ return best; } #endif static void overlapp_add(pj_int16_t dst[], unsigned count, pj_int16_t l[], pj_int16_t r[], float w[]) { unsigned i; for (i=0; i best_corr) { best_corr = corr; best = sr; } } else { if (corr >= best_corr) { best_corr = corr; best = sr; } } } /*TRACE_((THIS_FILE, "found pitch at %u", best-beg));*/ return best; } #endif static void overlapp_add(pj_int16_t dst[], unsigned count, pj_int16_t l[], pj_int16_t r[], pj_uint16_t w[]) { unsigned i; for (i=0; i> WINDOW_BITS); } } static void overlapp_add_simple(pj_int16_t dst[], unsigned count, pj_int16_t l[], pj_int16_t r[]) { int step = ((WINDOW_MAX_VAL+1) / count), stepdown = WINDOW_MAX_VAL; unsigned i; for (i=0; i> WINDOW_BITS); stepdown -= step; } } #if PJ_HAS_INT64 && !PJMEDIA_WSOLA_LINEAR_WIN /* approx_cos(): * see: http://www.audiomulch.com/~rossb/code/sinusoids/ */ static pj_uint32_t approx_cos( pj_uint32_t x ) { pj_uint32_t i,j,k; if( x == 0 ) return 0xFFFFFFFF; i = x << 1; k = ((x + 0xBFFFFFFD) & 0x80000000) >> 30; j = i - i * ((i & 0x80000000)>>30); j = j >> 15; j = (j * j + j) >> 1; j = j - j * k; return j; } #endif /* PJ_HAS_INT64 && .. */ static void create_win(pj_pool_t *pool, pj_uint16_t **pw, unsigned count) { unsigned i; pj_uint16_t *w = (pj_uint16_t*)pj_pool_calloc(pool, count, sizeof(pj_uint16_t)); *pw = w; for (i=0; imax_expand_cnt; #if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0 float fade_pos = (float)wsola->fade_out_pos; #else int fade_pos = wsola->fade_out_pos; #endif if (wsola->fade_out_pos == 0) { pjmedia_zero_samples(buf, count); } else if (fade_pos < count) { for (; fade_pos; --fade_pos, ++buf) { *buf = (pj_int16_t)(*buf * fade_pos / fade_cnt); } if (buf != end) pjmedia_zero_samples(buf, (unsigned)(end - buf)); wsola->fade_out_pos = 0; } else { for (; buf != end; --fade_pos, ++buf) { *buf = (pj_int16_t)(*buf * fade_pos / fade_cnt); } wsola->fade_out_pos -= 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) { pjmedia_wsola *wsola; pj_status_t status; PJ_ASSERT_RETURN(pool && clock_rate && samples_per_frame && p_wsola, 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); /* Allocate wsola and initialize vars */ 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->max_expand_cnt = clock_rate * MAX_EXPAND_MSEC / 1000; wsola->fade_out_pos = wsola->max_expand_cnt; /* Create circular buffer */ wsola->buf_size = (pj_uint16_t) (samples_per_frame * FRAME_CNT); status = pjmedia_circ_buf_create(pool, wsola->buf_size, &wsola->buf); if (status != PJ_SUCCESS) { PJ_PERROR(3, (THIS_FILE, status, "Failed to create circular buf")); return status; } /* Calculate history size */ wsola->hist_size = (pj_uint16_t)(HIST_CNT * samples_per_frame); /* Calculate template size */ wsola->templ_size = (pj_uint16_t)(TEMPLATE_PTIME * clock_rate * channel_count / 1000); if (wsola->templ_size > samples_per_frame) wsola->templ_size = wsola->samples_per_frame; /* Calculate hanning window size */ wsola->hanning_size = (pj_uint16_t)(HANNING_PTIME * clock_rate * channel_count / 1000); if (wsola->hanning_size > wsola->samples_per_frame) wsola->hanning_size = wsola->samples_per_frame; pj_assert(wsola->templ_size <= wsola->hanning_size); /* Create merge buffer */ wsola->merge_buf = (pj_int16_t*) pj_pool_calloc(pool, wsola->hanning_size, sizeof(pj_int16_t)); /* Setup with PLC */ if ((options & PJMEDIA_WSOLA_NO_PLC) == 0) { wsola->min_extra = wsola->hanning_size; wsola->expand_sr_min_dist = (pj_uint16_t) (EXP_MIN_DIST * wsola->samples_per_frame); wsola->expand_sr_max_dist = (pj_uint16_t) (EXP_MAX_DIST * wsola->samples_per_frame); } /* Setup with hanning */ if ((options & PJMEDIA_WSOLA_NO_HANNING) == 0) { create_win(pool, &wsola->hanning, wsola->hanning_size); } /* Setup with discard */ if ((options & PJMEDIA_WSOLA_NO_DISCARD) == 0) { wsola->erase_buf = (pj_int16_t*)pj_pool_calloc(pool, samples_per_frame * ERASE_CNT, sizeof(pj_int16_t)); } /* Generate dummy extra */ pjmedia_circ_buf_set_len(wsola->buf, wsola->hist_size + wsola->min_extra); *p_wsola = wsola; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_wsola_destroy(pjmedia_wsola *wsola) { /* Nothing to do */ PJ_UNUSED_ARG(wsola); return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_wsola_set_max_expand(pjmedia_wsola *wsola, unsigned msec) { PJ_ASSERT_RETURN(wsola, PJ_EINVAL); wsola->max_expand_cnt = msec * wsola->clock_rate / 1000; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_wsola_reset( pjmedia_wsola *wsola, unsigned options) { PJ_ASSERT_RETURN(wsola && options==0, PJ_EINVAL); PJ_UNUSED_ARG(options); pjmedia_circ_buf_reset(wsola->buf); pjmedia_circ_buf_set_len(wsola->buf, wsola->hist_size + wsola->min_extra); pjmedia_zero_samples(wsola->buf->start, wsola->buf->len); wsola->fade_out_pos = wsola->max_expand_cnt; return PJ_SUCCESS; } static void expand(pjmedia_wsola *wsola, unsigned needed) { unsigned generated = 0; unsigned rep; pj_int16_t *reg1, *reg2; unsigned reg1_len, reg2_len; pjmedia_circ_buf_pack_buffer(wsola->buf); pjmedia_circ_buf_get_read_regions(wsola->buf, ®1, ®1_len, ®2, ®2_len); CHECK_(reg2_len == 0); for (rep=1;; ++rep) { pj_int16_t *start, *templ; unsigned dist; templ = reg1 + reg1_len - wsola->hanning_size; CHECK_(templ - reg1 >= wsola->hist_size); start = find_pitch(templ, templ - wsola->expand_sr_max_dist, templ - wsola->expand_sr_min_dist, wsola->templ_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->merge_buf, wsola->hanning_size, templ, start); } else { /* Check if pointers are in the valid range */ CHECK_(templ >= wsola->buf->buf && templ + wsola->hanning_size <= wsola->buf->buf + wsola->buf->capacity); CHECK_(start >= wsola->buf->buf && start + wsola->hanning_size <= wsola->buf->buf + wsola->buf->capacity); overlapp_add(wsola->merge_buf, wsola->hanning_size, templ, start, wsola->hanning); } /* How many new samples do we have */ dist = (unsigned)(templ - start); /* Not enough buffer to hold the result */ if (reg1_len + dist > wsola->buf_size) { pj_assert(!"WSOLA buffer size may be to small!"); break; } /* Copy the "tail" (excess frame) to the end */ pjmedia_move_samples(templ + wsola->hanning_size, start + wsola->hanning_size, dist); /* Copy the merged frame */ pjmedia_copy_samples(templ, wsola->merge_buf, wsola->hanning_size); /* We have new samples */ reg1_len += dist; pjmedia_circ_buf_set_len(wsola->buf, reg1_len); generated += dist; if (generated >= needed) { TRACE_((THIS_FILE, "WSOLA frame expanded after %d iterations", rep)); break; } } } static unsigned compress(pjmedia_wsola *wsola, pj_int16_t *buf, unsigned count, unsigned del_cnt) { unsigned samples_del = 0, rep; for (rep=1; ; ++rep) { pj_int16_t *start, *end; unsigned dist; if (count <= wsola->hanning_size + del_cnt) { TRACE_((THIS_FILE, "Not enough samples to compress!")); return samples_del; } // Make start distance to del_cnt, so discard will be performed in // only one iteration. //start = buf + (frmsz >> 1); start = buf + del_cnt - samples_del; end = start + wsola->samples_per_frame; if (end + wsola->hanning_size > buf + count) { end = buf+count-wsola->hanning_size; } CHECK_(start < end); start = find_pitch(buf, start, end, wsola->templ_size, 0); dist = (unsigned)(start - buf); if (wsola->options & PJMEDIA_WSOLA_NO_HANNING) { overlapp_add_simple(buf, wsola->hanning_size, buf, start); } else { overlapp_add(buf, wsola->hanning_size, buf, start, wsola->hanning); } pjmedia_move_samples(buf + wsola->hanning_size, buf + wsola->hanning_size + dist, count - wsola->hanning_size - dist); count -= dist; samples_del += dist; if (samples_del >= del_cnt) { TRACE_((THIS_FILE, "Erased %d of %d requested after %d iteration(s)", samples_del, del_cnt, rep)); break; } } return samples_del; } PJ_DEF(pj_status_t) pjmedia_wsola_save( pjmedia_wsola *wsola, pj_int16_t frm[], pj_bool_t prev_lost) { unsigned buf_len; pj_status_t status; buf_len = pjmedia_circ_buf_get_len(wsola->buf); /* Update vars */ wsola->ts.u64 += wsola->samples_per_frame; /* If previous frame was lost, smoothen this frame with the generated one */ if (prev_lost) { pj_int16_t *reg1, *reg2; unsigned reg1_len, reg2_len; pj_int16_t *ola_left; /* Trim excessive len */ if ((int)buf_len > wsola->hist_size + (wsola->min_extra<<1)) { buf_len = wsola->hist_size + (wsola->min_extra<<1); pjmedia_circ_buf_set_len(wsola->buf, buf_len); } pjmedia_circ_buf_get_read_regions(wsola->buf, ®1, ®1_len, ®2, ®2_len); CHECK_(pjmedia_circ_buf_get_len(wsola->buf) >= (unsigned)(wsola->hist_size + (wsola->min_extra<<1))); /* Continue applying fade out to the extra samples */ if ((wsola->options & PJMEDIA_WSOLA_NO_FADING)==0) { if (reg2_len == 0) { wsola_fade_out(wsola, reg1 + reg1_len - (wsola->min_extra<<1), (wsola->min_extra<<1)); } else if ((int)reg2_len >= (wsola->min_extra<<1)) { wsola_fade_out(wsola, reg2 + reg2_len - (wsola->min_extra<<1), (wsola->min_extra<<1)); } else { unsigned tmp = (wsola->min_extra<<1) - reg2_len; wsola_fade_out(wsola, reg1 + reg1_len - tmp, tmp); wsola_fade_out(wsola, reg2, reg2_len); } } /* Get the region in buffer to be merged with the frame */ if (reg2_len == 0) { ola_left = reg1 + reg1_len - wsola->min_extra; } else if (reg2_len >= wsola->min_extra) { ola_left = reg2 + reg2_len - wsola->min_extra; } else { unsigned tmp; tmp = wsola->min_extra - reg2_len; pjmedia_copy_samples(wsola->merge_buf, reg1 + reg1_len - tmp, tmp); pjmedia_copy_samples(wsola->merge_buf + tmp, reg2, reg2_len); ola_left = wsola->merge_buf; } /* Apply fade-in to the frame before merging */ if ((wsola->options & PJMEDIA_WSOLA_NO_FADING)==0) { unsigned count = wsola->min_extra; int fade_in_pos; /* Scale fade_in position based on last fade-out */ fade_in_pos = wsola->fade_out_pos * count / wsola->max_expand_cnt; /* Fade-in it */ fade_in(frm, wsola->samples_per_frame, fade_in_pos, count); } /* Merge it */ overlapp_add_simple(frm, wsola->min_extra, ola_left, frm); /* Trim len */ buf_len -= wsola->min_extra; pjmedia_circ_buf_set_len(wsola->buf, buf_len); } else if ((wsola->options & PJMEDIA_WSOLA_NO_FADING)==0 && wsola->fade_out_pos != wsola->max_expand_cnt) { unsigned count = wsola->min_extra; int fade_in_pos; /* Fade out the remaining synthetic samples */ if (buf_len > wsola->hist_size) { pj_int16_t *reg1, *reg2; unsigned reg1_len, reg2_len; /* Number of samples to fade out */ count = buf_len - wsola->hist_size; pjmedia_circ_buf_get_read_regions(wsola->buf, ®1, ®1_len, ®2, ®2_len); CHECK_(pjmedia_circ_buf_get_len(wsola->buf) >= (unsigned)(wsola->hist_size + (wsola->min_extra<<1))); /* Continue applying fade out to the extra samples */ if (reg2_len == 0) { wsola_fade_out(wsola, reg1 + reg1_len - count, count); } else if (reg2_len >= count) { wsola_fade_out(wsola, reg2 + reg2_len - count, count); } else { unsigned tmp = count - reg2_len; wsola_fade_out(wsola, reg1 + reg1_len - tmp, tmp); wsola_fade_out(wsola, reg2, reg2_len); } } /* Apply fade-in to the frame */ count = wsola->min_extra; /* Scale fade_in position based on last fade-out */ fade_in_pos = wsola->fade_out_pos * count / wsola->max_expand_cnt; /* Fade it in */ fade_in(frm, wsola->samples_per_frame, fade_in_pos, count); } wsola->fade_out_pos = wsola->max_expand_cnt; status = pjmedia_circ_buf_write(wsola->buf, frm, wsola->samples_per_frame); if (status != PJ_SUCCESS) { TRACE_((THIS_FILE, "Failed writing to circbuf [err=%d]", status)); return status; } status = pjmedia_circ_buf_copy(wsola->buf, wsola->hist_size, frm, wsola->samples_per_frame); if (status != PJ_SUCCESS) { TRACE_((THIS_FILE, "Failed copying from circbuf [err=%d]", status)); return status; } return pjmedia_circ_buf_adv_read_ptr(wsola->buf, wsola->samples_per_frame); } PJ_DEF(pj_status_t) pjmedia_wsola_generate( pjmedia_wsola *wsola, pj_int16_t frm[]) { unsigned samples_len, samples_req; pj_status_t status = PJ_SUCCESS; CHECK_(pjmedia_circ_buf_get_len(wsola->buf) >= wsola->hist_size + wsola->min_extra); /* Calculate how many samples in the buffer */ samples_len = pjmedia_circ_buf_get_len(wsola->buf) - wsola->hist_size; /* Calculate how many samples are required to be available in the buffer */ samples_req = wsola->samples_per_frame + (wsola->min_extra << 1); wsola->ts.u64 += wsola->samples_per_frame; if (samples_len < samples_req) { /* Expand buffer */ expand(wsola, samples_req - samples_len); TRACE_((THIS_FILE, "Buf size after expanded = %d", pjmedia_circ_buf_get_len(wsola->buf))); } status = pjmedia_circ_buf_copy(wsola->buf, wsola->hist_size, frm, wsola->samples_per_frame); if (status != PJ_SUCCESS) { TRACE_((THIS_FILE, "Failed copying from circbuf [err=%d]", status)); return status; } pjmedia_circ_buf_adv_read_ptr(wsola->buf, wsola->samples_per_frame); /* Apply fade-out to the frame */ if ((wsola->options & PJMEDIA_WSOLA_NO_FADING)==0) { wsola_fade_out(wsola, frm, wsola->samples_per_frame); } return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_wsola_discard( pjmedia_wsola *wsola, pj_int16_t buf1[], unsigned buf1_cnt, pj_int16_t buf2[], unsigned buf2_cnt, unsigned *del_cnt) { PJ_ASSERT_RETURN(wsola && buf1 && buf1_cnt && del_cnt, PJ_EINVAL); PJ_ASSERT_RETURN(*del_cnt, PJ_EINVAL); if (buf2_cnt == 0) { /* The whole buffer is contiguous space, straight away. */ *del_cnt = compress(wsola, buf1, buf1_cnt, *del_cnt); } else { PJ_ASSERT_RETURN(buf2, PJ_EINVAL); if (buf1_cnt < ERASE_CNT * wsola->samples_per_frame && buf2_cnt < ERASE_CNT * wsola->samples_per_frame && wsola->erase_buf == NULL) { /* We need erase_buf but WSOLA was created with * PJMEDIA_WSOLA_NO_DISCARD flag. */ pj_assert(!"WSOLA need erase buffer!"); return PJ_EINVALIDOP; } if (buf2_cnt >= ERASE_CNT * wsola->samples_per_frame) { /* Enough space to perform compress in the second buffer. */ *del_cnt = compress(wsola, buf2, buf2_cnt, *del_cnt); } else if (buf1_cnt >= ERASE_CNT * wsola->samples_per_frame) { /* Enough space to perform compress in the first buffer, but then * we need to re-arrange the buffers so there is no gap between * buffers. */ unsigned max; *del_cnt = compress(wsola, buf1, buf1_cnt, *del_cnt); max = *del_cnt; if (max > buf2_cnt) max = buf2_cnt; pjmedia_move_samples(buf1 + buf1_cnt - (*del_cnt), buf2, max); if (max < buf2_cnt) { pjmedia_move_samples(buf2, buf2+(*del_cnt), buf2_cnt-max); } } else { /* Not enough samples in either buffers to perform compress. * Need to combine the buffers in a contiguous space, the erase_buf. */ unsigned buf_size = buf1_cnt + buf2_cnt; pj_int16_t *rem; /* remainder */ unsigned rem_cnt; if (buf_size > ERASE_CNT * wsola->samples_per_frame) { buf_size = ERASE_CNT * wsola->samples_per_frame; rem_cnt = buf1_cnt + buf2_cnt - buf_size; rem = buf2 + buf2_cnt - rem_cnt; } else { rem = NULL; rem_cnt = 0; } pjmedia_copy_samples(wsola->erase_buf, buf1, buf1_cnt); pjmedia_copy_samples(wsola->erase_buf+buf1_cnt, buf2, buf_size-buf1_cnt); *del_cnt = compress(wsola, wsola->erase_buf, buf_size, *del_cnt); buf_size -= (*del_cnt); /* Copy back to buffers */ if (buf_size == buf1_cnt) { pjmedia_copy_samples(buf1, wsola->erase_buf, buf_size); if (rem_cnt) { pjmedia_move_samples(buf2, rem, rem_cnt); } } else if (buf_size < buf1_cnt) { pjmedia_copy_samples(buf1, wsola->erase_buf, buf_size); if (rem_cnt) { unsigned c = rem_cnt; if (c > buf1_cnt-buf_size) { c = buf1_cnt-buf_size; } pjmedia_copy_samples(buf1+buf_size, rem, c); rem += c; rem_cnt -= c; if (rem_cnt) pjmedia_move_samples(buf2, rem, rem_cnt); } } else { pjmedia_copy_samples(buf1, wsola->erase_buf, buf1_cnt); pjmedia_copy_samples(buf2, wsola->erase_buf+buf1_cnt, buf_size-buf1_cnt); if (rem_cnt) { pjmedia_move_samples(buf2+buf_size-buf1_cnt, rem, rem_cnt); } } } } return (*del_cnt) > 0 ? PJ_SUCCESS : PJ_ETOOSMALL; } #elif PJMEDIA_WSOLA_IMP==PJMEDIA_WSOLA_IMP_NULL /* * WSOLA implementation using NULL */ struct pjmedia_wsola { unsigned samples_per_frame; }; 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) { pjmedia_wsola *wsola; wsola = PJ_POOL_ZALLOC_T(pool, struct pjmedia_wsola); wsola->samples_per_frame = samples_per_frame; PJ_UNUSED_ARG(clock_rate); PJ_UNUSED_ARG(channel_count); PJ_UNUSED_ARG(options); *p_wsola = wsola; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_wsola_destroy(pjmedia_wsola *wsola) { PJ_UNUSED_ARG(wsola); return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_wsola_reset( pjmedia_wsola *wsola, unsigned options) { PJ_UNUSED_ARG(wsola); PJ_UNUSED_ARG(options); return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_wsola_save( pjmedia_wsola *wsola, pj_int16_t frm[], pj_bool_t prev_lost) { PJ_UNUSED_ARG(wsola); PJ_UNUSED_ARG(frm); PJ_UNUSED_ARG(prev_lost); return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_wsola_generate( pjmedia_wsola *wsola, pj_int16_t frm[]) { pjmedia_zero_samples(frm, wsola->samples_per_frame); return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_wsola_discard( pjmedia_wsola *wsola, pj_int16_t buf1[], unsigned buf1_cnt, pj_int16_t buf2[], unsigned buf2_cnt, unsigned *del_cnt) { CHECK_(buf1_cnt + buf2_cnt >= wsola->samples_per_frame); PJ_UNUSED_ARG(buf1); PJ_UNUSED_ARG(buf1_cnt); PJ_UNUSED_ARG(buf2); PJ_UNUSED_ARG(buf2_cnt); *del_cnt = wsola->samples_per_frame; return PJ_SUCCESS; } #endif /* #if PJMEDIA_WSOLA_IMP.. */