diff options
author | Benny Prijono <bennylp@teluu.com> | 2006-04-28 14:48:02 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2006-04-28 14:48:02 +0000 |
commit | eae40f4fafeba6da36c80dc58a1472cff4affdad (patch) | |
tree | b2e0e376d3a43b2fa100fb4d162f5310c73acd5f | |
parent | 74e48f1aa4f7620cd85b5f6180d1a1485f092e6e (diff) |
Added Packet Lost Concealment (PLC) framework, with two backend algorithms (simple replay and G.711 Appendix I)
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@417 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r-- | pjmedia/build/Makefile | 3 | ||||
-rw-r--r-- | pjmedia/build/pjmedia.dsp | 12 | ||||
-rw-r--r-- | pjmedia/include/pjmedia.h | 1 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/config.h | 10 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/plc.h | 102 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/plc_common.c | 227 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/plc_g711.c | 462 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sound_port.c | 71 |
8 files changed, 833 insertions, 55 deletions
diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile index 6fe93566..969898ca 100644 --- a/pjmedia/build/Makefile +++ b/pjmedia/build/Makefile @@ -66,7 +66,8 @@ export PJMEDIA_SRCDIR = ../src/pjmedia export PJMEDIA_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ clock_thread.o codec.o conference.o endpoint.o errno.o \ wav_player.o wav_writer.o g711.o jbuf.o \ - master_port.o null_port.o port.o resample.o \ + master_port.o null_port.o plc_common.o plc_g711.o \ + port.o resample.o \ resample_port.o rtcp.o rtp.o sdp.o sdp_cmp.o sdp_neg.o \ session.o silencedet.o sound_port.o stream.o wave.o \ $(SOUND_OBJS) $(NULLSOUND_OBJS) diff --git a/pjmedia/build/pjmedia.dsp b/pjmedia/build/pjmedia.dsp index aa29ff7e..af4d9d7d 100644 --- a/pjmedia/build/pjmedia.dsp +++ b/pjmedia/build/pjmedia.dsp @@ -135,6 +135,14 @@ SOURCE=..\src\pjmedia\pasound.c # End Source File
# Begin Source File
+SOURCE=..\src\pjmedia\plc_common.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\src\pjmedia\plc_g711.c
+# End Source File
+# Begin Source File
+
SOURCE=..\src\pjmedia\port.c
# End Source File
# Begin Source File
@@ -247,6 +255,10 @@ SOURCE=..\include\pjmedia.h # End Source File
# Begin Source File
+SOURCE=..\include\pjmedia\plc.h
+# End Source File
+# Begin Source File
+
SOURCE=..\include\pjmedia\port.h
# End Source File
# Begin Source File
diff --git a/pjmedia/include/pjmedia.h b/pjmedia/include/pjmedia.h index d0728ff5..c0869e9d 100644 --- a/pjmedia/include/pjmedia.h +++ b/pjmedia/include/pjmedia.h @@ -34,6 +34,7 @@ #include <pjmedia/jbuf.h> #include <pjmedia/master_port.h> #include <pjmedia/null_port.h> +#include <pjmedia/plc.h> #include <pjmedia/port.h> #include <pjmedia/resample.h> #include <pjmedia/rtcp.h> diff --git a/pjmedia/include/pjmedia/config.h b/pjmedia/include/pjmedia/config.h index 918ab3ea..980d377f 100644 --- a/pjmedia/include/pjmedia/config.h +++ b/pjmedia/include/pjmedia/config.h @@ -117,6 +117,16 @@ #endif +/** + * G.711 Appendix I Packet Lost Concealment (PLC). + * Enabled only when floating point is enabled. + */ +#ifndef PJMEDIA_HAS_G711_PLC +# define PJMEDIA_HAS_G711_PLC PJ_HAS_FLOATING_POINT +#endif + + + #endif /* __PJMEDIA_CONFIG_H__ */ diff --git a/pjmedia/include/pjmedia/plc.h b/pjmedia/include/pjmedia/plc.h new file mode 100644 index 00000000..e0631c73 --- /dev/null +++ b/pjmedia/include/pjmedia/plc.h @@ -0,0 +1,102 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 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 + */ +#ifndef __PJMEDIA_PLC_H__ +#define __PJMEDIA_PLC_H__ + + +/** + * @file plc.h + * @brief Packet Lost Concealment (PLC) API. + */ +#include <pjmedia/types.h> + +/** + * @defgroup PJMED_PLC Packet Lost Concealment + * @ingroup PJMEDIA + * @{ + * + */ + + +PJ_BEGIN_DECL + + +/** + * Opaque declaration for PLC. + */ +typedef struct pjmedia_plc pjmedia_plc; + + + +/** + * Create PLC session. This function will select the PLC algorithm to + * use based on the arguments. + * + * @param pool Pool to allocate memory for the PLC. + * @param clock_rate Media sampling rate. + * @param samples_per_frame Number of samples per frame. + * @param options Must be zero for now. + * @param p_plc Pointer to receive the PLC instance. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_plc_create( pj_pool_t *pool, + unsigned clock_rate, + unsigned samples_per_frame, + unsigned options, + pjmedia_plc **p_plc); + + +/** + * Save a good frame to PLC. + * + * @param plc The PLC session. + * @param frame The good frame to be stored to PLC. This frame + * must have the same length as the configured + * samples per frame. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_plc_save( pjmedia_plc *plc, + pj_int16_t *frame ); + + +/** + * Generate a replacement for lost frame. + * + * @param plc The PLC session. + * @param frame Buffer to receive the generated frame. This buffer + * must be able to store the frame. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_plc_generate( pjmedia_plc *plc, + pj_int16_t *frame ); + + + + +PJ_END_DECL + +/** + * @} + */ + +#endif /* __PJMEDIA_PLC_H__ */ + diff --git a/pjmedia/src/pjmedia/plc_common.c b/pjmedia/src/pjmedia/plc_common.c new file mode 100644 index 00000000..d5289251 --- /dev/null +++ b/pjmedia/src/pjmedia/plc_common.c @@ -0,0 +1,227 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 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/plc.h> +#include <pjmedia/errno.h> +#include <pj/assert.h> +#include <pj/pool.h> +#include <pj/string.h> + + +static void* plc_replay_create(pj_pool_t*, unsigned c, unsigned f); +static void plc_replay_save(void*, pj_int16_t*); +static void plc_replay_generate(void*, pj_int16_t*); + +static void* noplc_create(pj_pool_t*, unsigned c, unsigned f); +static void noplc_save(void*, pj_int16_t*); +static void noplc_generate(void*, pj_int16_t*); + +extern void* pjmedia_plc_g711_create(pj_pool_t*, unsigned c, unsigned f); +extern void pjmedia_plc_g711_save(void*, pj_int16_t*); +extern void pjmedia_plc_g711_generate(void*, pj_int16_t*); + + +/** + * This struct is used internally to represent a PLC backend. + */ +struct plc_alg +{ + void* (*plc_create)(pj_pool_t*, unsigned c, unsigned f); + void (*plc_save)(void*, pj_int16_t*); + void (*plc_generate)(void*, pj_int16_t*); +}; + + +#if defined(PJMEDIA_HAS_G711_PLC) && PJMEDIA_HAS_G711_PLC!=0 +static struct plc_alg plc_g711 = +{ + &pjmedia_plc_g711_create, + &pjmedia_plc_g711_save, + &pjmedia_plc_g711_generate +}; +#endif + + +static struct plc_alg plc_replay = +{ + &plc_replay_create, + &plc_replay_save, + &plc_replay_generate +}; + + +static struct plc_alg no_plc = +{ + &noplc_create, + &noplc_save, + &noplc_generate +}; + + +struct pjmedia_plc +{ + void *obj; + struct plc_alg *op; +}; + + +/* + * Create PLC session. This function will select the PLC algorithm to + * use based on the arguments. + */ +PJ_DEF(pj_status_t) pjmedia_plc_create( pj_pool_t *pool, + unsigned clock_rate, + unsigned samples_per_frame, + unsigned options, + pjmedia_plc **p_plc) +{ + pjmedia_plc *plc; + + PJ_ASSERT_RETURN(pool && clock_rate && samples_per_frame && p_plc, + PJ_EINVAL); + PJ_ASSERT_RETURN(options == 0, PJ_EINVAL); + + PJ_UNUSED_ARG(options); + + plc = pj_pool_zalloc(pool, sizeof(pjmedia_plc)); + + if (0) + ; +#if defined(PJMEDIA_HAS_G711_PLC) && PJMEDIA_HAS_G711_PLC!=0 + else if (clock_rate == 8000) + plc->op = &plc_g711; +#endif + else + plc->op = &plc_replay; + + plc->obj = plc->op->plc_create(pool, clock_rate, samples_per_frame); + + *p_plc = plc; + + return PJ_SUCCESS; +} + + +/* + * Save a good frame to PLC. + */ +PJ_DEF(pj_status_t) pjmedia_plc_save( pjmedia_plc *plc, + pj_int16_t *frame ) +{ + PJ_ASSERT_RETURN(plc && frame, PJ_EINVAL); + + plc->op->plc_save(plc->obj, frame); + return PJ_SUCCESS; +} + + +/* + * Generate a replacement for lost frame. + */ +PJ_DEF(pj_status_t) pjmedia_plc_generate( pjmedia_plc *plc, + pj_int16_t *frame ) +{ + PJ_ASSERT_RETURN(plc && frame, PJ_EINVAL); + + plc->op->plc_generate(plc->obj, frame); + return PJ_SUCCESS; +} + + +////////////////////////////////////////////////////////////////////////////// +/* + * Simple replay based plc + */ +struct replay_plc +{ + unsigned size; + unsigned replay_cnt; + pj_int16_t *frame; +}; + + +static void* plc_replay_create(pj_pool_t *pool, unsigned clock_rate, + unsigned samples_per_frame) +{ + struct replay_plc *o; + + PJ_UNUSED_ARG(clock_rate); + + o = pj_pool_alloc(pool, sizeof(struct replay_plc)); + o->size = samples_per_frame * 2; + o->replay_cnt = 0; + o->frame = pj_pool_zalloc(pool, o->size); + + return o; +} + +static void plc_replay_save(void *plc, pj_int16_t *frame) +{ + struct replay_plc *o = plc; + + pj_memcpy(o->frame, frame, o->size); + o->replay_cnt = 0; +} + +static void plc_replay_generate(void *plc, pj_int16_t *frame) +{ + struct replay_plc *o = plc; + unsigned i, count; + pj_int16_t *samp; + + ++o->replay_cnt; + + if (o->replay_cnt < 16) { + pj_memcpy(frame, o->frame, o->size); + + + count = o->size / 2; + samp = o->frame; + for (i=0; i<count; ++i) + samp[i] = (pj_int16_t)(samp[i] >> 1); + } else { + pj_memset(frame, 0, o->size); + } +} + + + +////////////////////////////////////////////////////////////////////////////// +/* + * No PLC + */ +static void* noplc_create(pj_pool_t *pool, unsigned clock_rate, + unsigned samples_per_sec) +{ + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(clock_rate); + return (void*) samples_per_sec; +} + +static void noplc_save(void *plc, pj_int16_t *frame) +{ + PJ_UNUSED_ARG(plc); + PJ_UNUSED_ARG(frame); +} + +static void noplc_generate(void *plc, pj_int16_t *frame) +{ + unsigned samples_per_sec = (unsigned)plc; + pj_memset(frame, 0, samples_per_sec); +} + diff --git a/pjmedia/src/pjmedia/plc_g711.c b/pjmedia/src/pjmedia/plc_g711.c new file mode 100644 index 00000000..08820cdb --- /dev/null +++ b/pjmedia/src/pjmedia/plc_g711.c @@ -0,0 +1,462 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 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/types.h> +#include <pj/assert.h> +#include <pj/pool.h> + + +/* + * Only build when PJMEDIA_HAS_G711_PLC is enabled. + */ +#if defined(PJMEDIA_HAS_G711_PLC) && PJMEDIA_HAS_G711_PLC!=0 + + +#include <math.h> + +typedef float Float; + +#define PITCH_MIN 40 /* minimum allowed pitch, 200 Hz */ +#define PITCH_MAX 120 /* maximum allowed pitch, 66 Hz */ +#define PITCHDIFF (PITCH_MAX - PITCH_MIN) +#define POVERLAPMAX (PITCH_MAX >> 2) /* maximum pitch OLA window */ +#define HISTORYLEN (PITCH_MAX * 3 + POVERLAPMAX) /* history buffer length*/ +#define NDEC 2 /* 2:1 decimation */ +#define CORRLEN 160 /* 20 ms correlation length */ +#define CORRBUFLEN (CORRLEN + PITCH_MAX) /* correlation buffer length */ +#define CORRMINPOWER ((Float)250.) /* minimum power */ +#define EOVERLAPINCR 32 /* end OLA increment per frame, 4 ms */ +#define FRAMESZ 80 /* 10 ms at 8 KHz */ +#define ATTENFAC ((Float).2) /* attenuation factor per 10 ms frame */ +#define ATTENINCR (ATTENFAC/FRAMESZ) /* attenuation per sample */ + + +typedef struct LowcFE LowcFE; + +static void dofe(LowcFE *sess, short *s); /* synthesize speech for erasure */ +static void addtohistory(LowcFE *sess, short *s); /* add a good frame to history buffer */ +static void scalespeech(LowcFE *sess, short *out); +static void getfespeech(LowcFE *sess, short *out, int sz); +static void savespeech(LowcFE *sess, short *s); +static int findpitch(LowcFE *sess); +static void overlapadd_r(Float *l, Float *r, Float *o, int cnt); +static void overlapadd_i(short *l, short *r, short *o, int cnt); +static void overlapaddatend(LowcFE *sess, short *s, short *f, int cnt); +static void convertsf(short *f, Float *t, int cnt); +static void convertfs(Float *f, short *t, int cnt); +static void copyf(Float *f, Float *t, int cnt); +static void copys(short *f, short *t, int cnt); +static void zeros(short *s, int cnt); + +struct LowcFE +{ + unsigned samples_per_frame; + + int erasecnt; /* consecutive erased frames */ + int poverlap; /* overlap based on pitch */ + int poffset; /* offset into pitch period */ + int pitch; /* pitch estimate */ + int pitchblen; /* current pitch buffer length */ + Float *pitchbufend; /* end of pitch buffer */ + Float *pitchbufstart; /* start of pitch buffer */ + Float pitchbuf[HISTORYLEN]; /* buffer for cycles of speech */ + Float lastq[POVERLAPMAX]; /* saved last quarter wavelength */ + short history[HISTORYLEN]; /* history buffer */ +}; + + + +static void convertsf(short *f, Float *t, int cnt) +{ + int i; + for (i = 0; i < cnt; i++) + t[i] = (Float)f[i]; +} + +static void convertfs(Float *f, short *t, int cnt) +{ + int i; + for (i = 0; i < cnt; i++) + t[i] = (short)f[i]; +} + +void copyf(Float *f, Float *t, int cnt) +{ + int i; + for (i = 0; i < cnt; i++) + t[i] = f[i]; +} + +void copys(short *f, short *t, int cnt) +{ + int i; + for (i = 0; i < cnt; i++) + t[i] = f[i]; +} + +void zeros(short *s, int cnt) +{ + int i; + for (i = 0; i < cnt; i++) + s[i] = 0; +} + + +void init_LowcFE(LowcFE *sess) +{ + sess->erasecnt = 0; + sess->pitchbufend = &sess->pitchbuf[HISTORYLEN]; + zeros(sess->history, HISTORYLEN); +} + + +/* + * Save a frames worth of new speech in the history buffer. + * Return the output speech delayed by POVERLAPMAX. + */ +static void savespeech(LowcFE *sess, short *s) +{ + /* make room for new signal */ + copys(&sess->history[FRAMESZ], sess->history, HISTORYLEN - FRAMESZ); + + /* copy in the new frame */ + copys(s, &sess->history[HISTORYLEN - FRAMESZ], FRAMESZ); + + /* copy out the delayed frame */ + copys(&sess->history[HISTORYLEN - FRAMESZ - POVERLAPMAX], s, FRAMESZ); +} + + + +/* + * A good frame was received and decoded. + * If right after an erasure, do an overlap add with the synthetic signal. + * Add the frame to history buffer. + */ +static void addtohistory(LowcFE *sess, short *s) +{ + if (sess->erasecnt) { + short overlapbuf[FRAMESZ]; + + /* + * longer erasures require longer overlaps + * to smooth the transition between the synthetic + * and real signal. + */ + int olen = sess->poverlap + (sess->erasecnt - 1) * EOVERLAPINCR; + if (olen > FRAMESZ) + olen = FRAMESZ; + + getfespeech(sess, overlapbuf, olen); + overlapaddatend(sess, s, overlapbuf, olen); + sess->erasecnt = 0; + } + + savespeech(sess, s); +} + + + +/* + * Generate the synthetic signal. + * At the beginning of an erasure determine the pitch, and extract + * one pitch period from the tail of the signal. Do an OLA for 1/4 + * of the pitch to smooth the signal. Then repeat the extracted signal + * for the length of the erasure. If the erasure continues for more than + * 10 ms, increase the number of periods in the pitchbuffer. At the end + * of an erasure, do an OLA with the start of the first good frame. + * The gain decays as the erasure gets longer. + */ +static void dofe(LowcFE *sess, short *out) +{ + if (sess->erasecnt == 0) { + convertsf(sess->history, sess->pitchbuf, HISTORYLEN); /* get history */ + sess->pitch = findpitch(sess); /* find pitch */ + sess->poverlap = sess->pitch >> 2; /* OLA 1/4 wavelength */ + /* save original last poverlap samples */ + copyf(sess->pitchbufend - sess->poverlap, sess->lastq, sess->poverlap); + sess->poffset = 0; /* create pitch buffer with 1 period */ + sess->pitchblen = sess->pitch; + sess->pitchbufstart = sess->pitchbufend - sess->pitchblen; + overlapadd_r(sess->lastq, sess->pitchbufstart - sess->poverlap, + sess->pitchbufend - sess->poverlap, sess->poverlap); + /* update last 1/4 wavelength in history buffer */ + convertfs(sess->pitchbufend - sess->poverlap, &sess->history[HISTORYLEN-sess->poverlap], + sess->poverlap); + getfespeech(sess, out, FRAMESZ); /* get synthesized speech */ + + } else if (sess->erasecnt == 1 || sess->erasecnt == 2) { + /* tail of previous pitch estimate */ + short tmp[POVERLAPMAX]; + int saveoffset = sess->poffset; /* save offset for OLA */ + getfespeech(sess, tmp, sess->poverlap); /* continue with old pitchbuf */ + /* add periods to the pitch buffer */ + sess->poffset = saveoffset; + while (sess->poffset > sess->pitch) + sess->poffset -= sess->pitch; + sess->pitchblen += sess->pitch; /* add a period */ + sess->pitchbufstart = sess->pitchbufend - sess->pitchblen; + overlapadd_r(sess->lastq, sess->pitchbufstart - sess->poverlap, + sess->pitchbufend - sess->poverlap, sess->poverlap); + /* overlap add old pitchbuffer with new */ + getfespeech(sess, out, FRAMESZ); + overlapadd_i(tmp, out, out, sess->poverlap); + scalespeech(sess, out); + } else if (sess->erasecnt > 5) { + zeros(out, FRAMESZ); + } else { + getfespeech(sess, out, FRAMESZ); + scalespeech(sess, out); + } + sess->erasecnt++; + savespeech(sess, out); +} + + +/* + * Estimate the pitch. + * l - pointer to first sample in last 20 ms of speech. + * r - points to the sample PITCH_MAX before l + */ +static int findpitch(LowcFE *sess) +{ + int i, j, k; + int bestmatch; + Float bestcorr; + Float corr; /* correlation */ + Float energy; /* running energy */ + Float scale; /* scale correlation by average power */ + Float *rp; /* segment to match */ + Float *l = sess->pitchbufend - CORRLEN; + Float *r = sess->pitchbufend - CORRBUFLEN; + + /* coarse search */ + rp = r; + energy = 0.f; + corr = 0.f; + for (i = 0; i < CORRLEN; i += NDEC) { + energy += rp[i] * rp[i]; + corr += rp[i] * l[i]; + } + scale = energy; + if (scale < CORRMINPOWER) + scale = CORRMINPOWER; + corr = corr / (Float)sqrt(scale); + bestcorr = corr; + bestmatch = 0; + for (j = NDEC; j <= PITCHDIFF; j += NDEC) { + energy -= rp[0] * rp[0]; + energy += rp[CORRLEN] * rp[CORRLEN]; + rp += NDEC; + corr = 0.f; + for (i = 0; i < CORRLEN; i += NDEC) + corr += rp[i] * l[i]; + scale = energy; + if (scale < CORRMINPOWER) + scale = CORRMINPOWER; + corr /= (Float)sqrt(scale); + if (corr >= bestcorr) { + bestcorr = corr; + bestmatch = j; + } + } + /* fine search */ + j = bestmatch - (NDEC - 1); + if (j < 0) + j = 0; + k = bestmatch + (NDEC - 1); + if (k > PITCHDIFF) + k = PITCHDIFF; + rp = &r[j]; + energy = 0.f; + corr = 0.f; + for (i = 0; i < CORRLEN; i++) { + energy += rp[i] * rp[i]; + corr += rp[i] * l[i]; + } + scale = energy; + if (scale < CORRMINPOWER) + scale = CORRMINPOWER; + corr = corr / (Float)sqrt(scale); + bestcorr = corr; + bestmatch = j; + for (j++; j <= k; j++) { + energy -= rp[0] * rp[0]; + energy += rp[CORRLEN] * rp[CORRLEN]; + rp++; + corr = 0.f; + for (i = 0; i < CORRLEN; i++) + corr += rp[i] * l[i]; + scale = energy; + if (scale < CORRMINPOWER) + scale = CORRMINPOWER; + corr = corr / (Float)sqrt(scale); + if (corr > bestcorr) { + bestcorr = corr; + bestmatch = j; + } + } + return PITCH_MAX - bestmatch; +} + + + +/* + * Get samples from the circular pitch buffer. Update poffset so + * when subsequent frames are erased the signal continues. + */ +static void getfespeech(LowcFE *sess, short *out, int sz) +{ + while (sz) { + int cnt = sess->pitchblen - sess->poffset; + if (cnt > sz) + cnt = sz; + convertfs(&sess->pitchbufstart[sess->poffset], out, cnt); + sess->poffset += cnt; + if (sess->poffset == sess->pitchblen) + sess->poffset = 0; + out += cnt; + sz -= cnt; + } +} + +static void scalespeech(LowcFE *sess, short *out) +{ + Float g = (Float)1. - (sess->erasecnt - 1) * ATTENFAC; + int i; + for (i = 0; i < FRAMESZ; i++) { + out[i] = (short)(out[i] * g); + g -= ATTENINCR; + } +} + + + +static void overlapadd_r(Float *l, Float *r, Float *o, int cnt) +{ + Float incr = (Float)1. / cnt; + Float lw = (Float)1. - incr; + Float rw = incr; + int i; + for (i = 0; i < cnt; i++) { + Float t = lw * l[i] + rw * r[i]; + if (t > 32767.) + t = 32767.; + else if (t < -32768.) + t = -32768.; + o[i] = t; + lw -= incr; + rw += incr; + } +} + +static void overlapadd_i(short *l, short *r, short *o, int cnt) +{ + Float incr = (Float)1. / cnt; + Float lw = (Float)1. - incr; + Float rw = incr; + int i; + for (i = 0; i < cnt; i++) { + Float t = lw * l[i] + rw * r[i]; + if (t > 32767.) + t = 32767.; + else if (t < -32768.) + t = -32768.; + o[i] = (short)t; + lw -= incr; + rw += incr; + } +} + +/* + * Overlap add the end of the erasure with the start of the first good frame + * Scale the synthetic speech by the gain factor before the OLA. + */ +static void overlapaddatend(LowcFE *sess, short *s, short *f, int cnt) +{ + Float incr = (Float)1. / cnt; + Float gain; + Float incrg; + Float lw; + Float rw; + int i; + + gain = (Float)1. - (sess->erasecnt - 1) * ATTENFAC; + if (gain < 0.) + gain = (Float)0.; + incrg = incr * gain; + lw = ((Float)1. - incr) * gain; + rw = incr; + + + for (i = 0; i < cnt; i++) { + Float t = lw * f[i] + rw * s[i]; + if (t > 32767.) + t = 32767.; + else if (t < -32768.) + t = -32768.; + s[i] = (short)t; + lw -= incrg; + rw += incr; + } +} + + +void* pjmedia_plc_g711_create(pj_pool_t *pool, unsigned clock_rate, + unsigned samples_per_frame) +{ + LowcFE *o; + + pj_assert(clock_rate == 8000 && (samples_per_frame % FRAMESZ) == 0); + + o = pj_pool_zalloc(pool, sizeof(LowcFE)); + o->samples_per_frame = samples_per_frame; + init_LowcFE(o); + + return o; +} + + +void pjmedia_plc_g711_save(void *plc, pj_int16_t *frame) +{ + LowcFE *o = plc; + unsigned pos; + + pos = 0; + while (pos < o->samples_per_frame) { + addtohistory(o, frame + pos); + pos += FRAMESZ; + } +} + + +void pjmedia_plc_g711_generate(void *plc, pj_int16_t *frame) +{ + LowcFE *o = plc; + unsigned pos; + + pos = 0; + while (pos < o->samples_per_frame) { + dofe(o, frame+pos); + pos += FRAMESZ; + } +} + + +#endif /* PJMEDIA_HAS_G711_PLC */ + diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c index 05bd10e1..016bdecf 100644 --- a/pjmedia/src/pjmedia/sound_port.c +++ b/pjmedia/src/pjmedia/sound_port.c @@ -18,13 +18,14 @@ */ #include <pjmedia/sound_port.h> #include <pjmedia/errno.h> +#include <pjmedia/plc.h> #include <pj/assert.h> #include <pj/log.h> #include <pj/rand.h> #include <pj/string.h> /* pj_memset() */ -//#define SIMULATE_LOST_PCT 10 +//#define SIMULATE_LOST_PCT 20 #define THIS_FILE "sound_port.c" @@ -34,8 +35,8 @@ enum PJMEDIA_PLC_ENABLED = 1, }; -#define DEFAULT_OPTIONS PJMEDIA_PLC_ENABLED - +//#define DEFAULT_OPTIONS PJMEDIA_PLC_ENABLED +#define DEFAULT_OPTIONS 0 struct pjmedia_snd_port @@ -47,9 +48,7 @@ struct pjmedia_snd_port pjmedia_port *port; unsigned options; - void *last_frame; - unsigned last_frame_size; - unsigned last_replay_count; + pjmedia_plc *plc; unsigned clock_rate; unsigned channel_count; @@ -103,56 +102,20 @@ static pj_status_t play_cb(/* in */ void *user_data, } #endif - /* Keep frame if PLC is enabled. */ - if (snd_port->options & PJMEDIA_PLC_ENABLED) { - /* Must have the same length as last_frame_size */ - pj_assert(frame.size == snd_port->last_frame_size); - - /* Copy frame to last_frame */ - pj_memcpy(snd_port->last_frame, output, snd_port->last_frame_size); - - snd_port->last_replay_count = 0; - } + if (snd_port->plc) + pjmedia_plc_save(snd_port->plc, output); return PJ_SUCCESS; no_frame: - /* Replay last frame if PLC is enabled */ - if ((snd_port->options & PJMEDIA_PLC_ENABLED) && - snd_port->last_replay_count < 8) - { - - /* Must have the same length as last_frame_size */ - pj_assert(size == snd_port->last_frame_size); - - /* Replay last frame */ - pj_memcpy(output, snd_port->last_frame, snd_port->last_frame_size); - - /* Reduce replay frame signal level to half */ - if (snd_port->bits_per_sample == 16) { - unsigned i, count; - pj_int16_t *samp; - - count = snd_port->last_frame_size / 2; - samp = (pj_int16_t *) snd_port->last_frame; - - for (i=0; i<count; ++i) - samp[i] = (pj_int16_t) (samp[i] >> 2); - - } + /* Apply PLC */ + if (snd_port->plc) { + pjmedia_plc_generate(snd_port->plc, output); #ifdef SIMULATE_LOST_PCT - PJ_LOG(4,(THIS_FILE, "Frame replayed")); + PJ_LOG(4,(THIS_FILE, "Lost frame generated")); #endif - - ++snd_port->last_replay_count; - - } else { - - /* Just zero the frame */ - pj_memset(output, 0, size); - } @@ -254,12 +217,12 @@ static pj_status_t start_sound_device( pj_pool_t *pool, if ((snd_port->dir & PJMEDIA_DIR_PLAYBACK) && (snd_port->options & PJMEDIA_PLC_ENABLED)) { - - snd_port->last_frame_size = snd_port->samples_per_frame * - snd_port->channel_count * - snd_port->bits_per_sample / 8; - snd_port->last_frame = pj_pool_zalloc(pool, - snd_port->last_frame_size); + status = pjmedia_plc_create(pool, snd_port->clock_rate, + snd_port->samples_per_frame * + snd_port->channel_count, + 0, &snd_port->plc); + if (status != PJ_SUCCESS) + snd_port->plc = NULL; } /* Start sound stream. */ |