| /* $Id$ */ |
| /* |
| * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) |
| * Copyright (C) 2003-2008 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/wsola.h> |
| #include <pjmedia/circbuf.h> |
| #include <pjmedia/errno.h> |
| #include <pj/assert.h> |
| #include <pj/log.h> |
| #include <pj/math.h> |
| #include <pj/pool.h> |
| |
| /* |
| * 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<template_cnt; ++i) |
| frm_sum += frm[i]; |
| |
| for (sr=beg; sr!=end; ++sr) { |
| int corr = frm_sum; |
| int abs_corr = 0; |
| |
| /* Do calculation on 8 samples at once */ |
| for (i = 0; i<template_cnt-8; i+=8) { |
| corr -= (int)sr[i+0] + |
| (int)sr[i+1] + |
| (int)sr[i+2] + |
| (int)sr[i+3] + |
| (int)sr[i+4] + |
| (int)sr[i+5] + |
| (int)sr[i+6] + |
| (int)sr[i+7]; |
| } |
| |
| /* Process remaining samples */ |
| for (; i<template_cnt; ++i) |
| corr -= (int)sr[i]; |
| |
| abs_corr = corr > 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<template_cnt-8; i += 8) { |
| corr += ((float)frm[i+0]) * ((float)sr[i+0]) + |
| ((float)frm[i+1]) * ((float)sr[i+1]) + |
| ((float)frm[i+2]) * ((float)sr[i+2]) + |
| ((float)frm[i+3]) * ((float)sr[i+3]) + |
| ((float)frm[i+4]) * ((float)sr[i+4]) + |
| ((float)frm[i+5]) * ((float)sr[i+5]) + |
| ((float)frm[i+6]) * ((float)sr[i+6]) + |
| ((float)frm[i+7]) * ((float)sr[i+7]); |
| } |
| |
| /* Process remaining samples. */ |
| for (; i<template_cnt; ++i) { |
| corr += ((float)frm[i]) * ((float)sr[i]); |
| } |
| |
| if (first) { |
| if (corr > 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<count; ++i) { |
| dst[i] = (pj_int16_t)(l[i] * w[count-1-i] + r[i] * w[i]); |
| } |
| } |
| |
| static void overlapp_add_simple(pj_int16_t dst[], unsigned count, |
| pj_int16_t l[], pj_int16_t r[]) |
| { |
| float step = (float)(1.0 / count), stepdown = 1.0; |
| unsigned i; |
| |
| for (i=0; i<count; ++i) { |
| dst[i] = (pj_int16_t)(l[i] * stepdown + r[i] * (1-stepdown)); |
| stepdown -= step; |
| } |
| } |
| |
| static void create_win(pj_pool_t *pool, float **pw, unsigned count) |
| { |
| unsigned i; |
| float *w = (float*)pj_pool_calloc(pool, count, sizeof(float)); |
| |
| *pw = w; |
| |
| for (i=0;i<count; i++) { |
| w[i] = (float)(0.5 - 0.5 * cos(2.0 * PJ_PI * i / (count*2-1)) ); |
| } |
| } |
| |
| #else /* PJ_HAS_FLOATING_POINT */ |
| /* |
| * Fixed point version. |
| */ |
| #define WINDOW_BITS 15 |
| enum { WINDOW_MAX_VAL = (1 << WINDOW_BITS)-1 }; |
| |
| #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; |
| pj_int64_t best_corr = 0; |
| |
| |
| for (sr=beg; sr!=end; ++sr) { |
| pj_int64_t corr = 0; |
| unsigned i; |
| |
| /* Do calculation on 8 samples at once */ |
| for (i=0; i<template_cnt-8; i+=8) { |
| corr += ((int)frm[i+0]) * ((int)sr[i+0]) + |
| ((int)frm[i+1]) * ((int)sr[i+1]) + |
| ((int)frm[i+2]) * ((int)sr[i+2]) + |
| ((int)frm[i+3]) * ((int)sr[i+3]) + |
| ((int)frm[i+4]) * ((int)sr[i+4]) + |
| ((int)frm[i+5]) * ((int)sr[i+5]) + |
| ((int)frm[i+6]) * ((int)sr[i+6]) + |
| ((int)frm[i+7]) * ((int)sr[i+7]); |
| } |
| |
| /* Process remaining samples. */ |
| for (; i<template_cnt; ++i) { |
| corr += ((int)frm[i]) * ((int)sr[i]); |
| } |
| |
| if (first) { |
| if (corr > 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<count; ++i) { |
| dst[i] = (pj_int16_t)(((int)(l[i]) * (int)(w[count-1-i]) + |
| (int)(r[i]) * (int)(w[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<count; ++i) { |
| dst[i]=(pj_int16_t)((l[i] * stepdown + r[i] * (1-stepdown)) >> 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; i<count; i++) { |
| #if PJ_HAS_INT64 && !PJMEDIA_WSOLA_LINEAR_WIN |
| pj_uint32_t phase; |
| pj_uint64_t cos_val; |
| |
| /* w[i] = (float)(0.5 - 0.5 * cos(2.0 * PJ_PI * i / (count*2-1)) ); */ |
| |
| phase = (pj_uint32_t)(PJ_INT64(0xFFFFFFFF) * i / (count*2-1)); |
| cos_val = approx_cos(phase); |
| |
| w[i] = (pj_uint16_t)(WINDOW_MAX_VAL - |
| (WINDOW_MAX_VAL * cos_val) / 0xFFFFFFFF); |
| #else |
| /* Revert to linear */ |
| w[i] = (pj_uint16_t)(i * WINDOW_MAX_VAL / count); |
| #endif |
| } |
| } |
| |
| #endif /* PJ_HAS_FLOATING_POINT */ |
| |
| /* Apply fade-in to the buffer. |
| * - fade_cnt is the number of samples on which the volume |
| * will go from zero to 100% |
| * - fade_pos is current sample position within fade_cnt range. |
| * It is zero for the first sample, so the first sample will |
| * have zero volume. This value is increasing. |
| */ |
| static void fade_in(pj_int16_t buf[], int count, |
| int fade_in_pos, int fade_cnt) |
| { |
| #if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0 |
| float fade_pos = (float)fade_in_pos; |
| #else |
| int fade_pos = fade_in_pos; |
| #endif |
| |
| if (fade_cnt - fade_pos < count) { |
| for (; fade_pos < fade_cnt; ++fade_pos, ++buf) { |
| *buf = (pj_int16_t)(*buf * fade_pos / fade_cnt); |
| } |
| /* Leave the remaining samples as is */ |
| } else { |
| pj_int16_t *end = buf + count; |
| for (; buf != end; ++fade_pos, ++buf) { |
| *buf = (pj_int16_t)(*buf * fade_pos / fade_cnt); |
| } |
| } |
| } |
| |
| /* Apply fade-out to the buffer. */ |
| static void wsola_fade_out(pjmedia_wsola *wsola, |
| pj_int16_t buf[], int count) |
| { |
| pj_int16_t *end = buf + count; |
| int fade_cnt = wsola->max_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_LOG(3, (THIS_FILE, "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.. */ |
| |
| |