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:
parent
835ab76937
commit
5fad3e2d00
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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__ */
|
||||
|
||||
|
||||
|
|
|
@ -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__ */
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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 */
|
||||
|
|
@ -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. */
|
||||
|
|
Loading…
Reference in New Issue