Added Packet Lost Concealment (PLC) framework, with two backend algorithms (simple replay and G.711 Appendix I)

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@417 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
Benny Prijono 2006-04-28 14:48:02 +00:00
parent 835ab76937
commit 5fad3e2d00
8 changed files with 833 additions and 55 deletions

View File

@ -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)

View File

@ -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

View File

@ -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>

View File

@ -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__ */

View File

@ -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__ */

View File

@ -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);
}

View File

@ -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 */

View File

@ -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. */