summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2006-04-28 14:48:02 +0000
committerBenny Prijono <bennylp@teluu.com>2006-04-28 14:48:02 +0000
commiteae40f4fafeba6da36c80dc58a1472cff4affdad (patch)
treeb2e0e376d3a43b2fa100fb4d162f5310c73acd5f
parent74e48f1aa4f7620cd85b5f6180d1a1485f092e6e (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/Makefile3
-rw-r--r--pjmedia/build/pjmedia.dsp12
-rw-r--r--pjmedia/include/pjmedia.h1
-rw-r--r--pjmedia/include/pjmedia/config.h10
-rw-r--r--pjmedia/include/pjmedia/plc.h102
-rw-r--r--pjmedia/src/pjmedia/plc_common.c227
-rw-r--r--pjmedia/src/pjmedia/plc_g711.c462
-rw-r--r--pjmedia/src/pjmedia/sound_port.c71
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. */