Added GSM-EFR codec support and transcoder.
git-svn-id: http://yate.null.ro/svn/yate/trunk@5945 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
parent
b02afc13c0
commit
3bba322664
|
@ -51,6 +51,7 @@ const TokenDict SDPParser::s_payloads[] = {
|
|||
{ "speex/32000", 104 },
|
||||
{ "isac/16000", 105 },
|
||||
{ "isac/32000", 106 },
|
||||
{ "gsm-efr", 107 },
|
||||
{ "mjpeg", 26 },
|
||||
{ "h261", 31 },
|
||||
{ "h263", 34 },
|
||||
|
@ -82,6 +83,7 @@ const TokenDict SDPParser::s_rtpmap[] = {
|
|||
{ "SPEEX/32000", 104 },
|
||||
{ "iSAC/16000", 105 },
|
||||
{ "iSAC/32000", 106 },
|
||||
{ "GSM-EFR/8000",107 },
|
||||
{ "JPEG/90000", 26 },
|
||||
{ "H261/90000", 31 },
|
||||
{ "H263/90000", 34 },
|
||||
|
|
|
@ -176,6 +176,7 @@ endif
|
|||
|
||||
ifneq (@HAVE_AMRNB@,no)
|
||||
PROGS := $(PROGS) amrnbcodec.yate
|
||||
PROGS := $(PROGS) efrcodec.yate
|
||||
endif
|
||||
|
||||
ifneq ($(HAVE_OPENSSL),no)
|
||||
|
@ -380,8 +381,8 @@ gsmcodec.yate: EXTERNLIBS = @GSM_LIB@
|
|||
speexcodec.yate: EXTERNFLAGS = @SPEEX_INC@
|
||||
speexcodec.yate: EXTERNLIBS = @SPEEX_LIB@
|
||||
|
||||
amrnbcodec.yate: EXTERNFLAGS = @AMRNB_INC@
|
||||
amrnbcodec.yate: EXTERNLIBS = @AMRNB_LIB@
|
||||
amrnbcodec.yate efrcodec.yate: EXTERNFLAGS = @AMRNB_INC@
|
||||
amrnbcodec.yate efrcodec.yate: EXTERNLIBS = @AMRNB_LIB@
|
||||
|
||||
faxchan.yate: EXTERNFLAGS = $(SPANDSP_INC)
|
||||
faxchan.yate: EXTERNLIBS = $(SPANDSP_LIB)
|
||||
|
|
|
@ -0,0 +1,327 @@
|
|||
/**
|
||||
* efrcodec.cpp
|
||||
* This file is part of the YATE Project http://YATE.null.ro
|
||||
*
|
||||
* GSM-EFR transcoder implemented using 3GPP AMR codec
|
||||
*
|
||||
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
||||
* Copyright (C) 2015 Null Team
|
||||
* Author: Paul Chitescu
|
||||
*
|
||||
* AMR codec library by Stanislav Brabec at http://www.penguin.cz/~utx/amr
|
||||
*
|
||||
* This software is distributed under multiple licenses;
|
||||
* see the COPYING file in the main directory for licensing
|
||||
* information for this specific distribution.
|
||||
*
|
||||
* This use of this software may be subject to additional restrictions.
|
||||
* See the LEGAL file in the main directory for details.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
#include <yatephone.h>
|
||||
|
||||
extern "C" {
|
||||
#include <interf_enc.h>
|
||||
#include <interf_dec.h>
|
||||
}
|
||||
namespace RxTypes {
|
||||
// There is a conflict between encoder and decoder so insulate in a namespace
|
||||
#include <sp_dec.h>
|
||||
};
|
||||
|
||||
using namespace TelEngine;
|
||||
namespace { // anonymous
|
||||
|
||||
#define MODNAME "efrcodec"
|
||||
|
||||
// Transcoding voice size, 20ms of 8kHz slin data
|
||||
#define SAMPLES_FRAME 160
|
||||
|
||||
// Transcoding buffer size, 2 bytes per sample
|
||||
#define BUFFER_SIZE (2*SAMPLES_FRAME)
|
||||
|
||||
// AMR Mode 7 (12.2) encoder frame size including mode
|
||||
#define AMR_MR122_SIZE 32
|
||||
|
||||
// GSM-EFR frame size
|
||||
#define EFR_FRAME_SIZE 31
|
||||
|
||||
class EfrPlugin : public Plugin, public TranslatorFactory
|
||||
{
|
||||
public:
|
||||
EfrPlugin();
|
||||
~EfrPlugin();
|
||||
virtual void initialize();
|
||||
virtual bool isBusy() const;
|
||||
virtual DataTranslator* create(const DataFormat& sFormat, const DataFormat& dFormat);
|
||||
virtual const TranslatorCaps* getCapabilities() const;
|
||||
};
|
||||
|
||||
class EfrTrans : public DataTranslator
|
||||
{
|
||||
public:
|
||||
EfrTrans(const char* sFormat, const char* dFormat, void* amrState, bool encoding);
|
||||
virtual ~EfrTrans();
|
||||
virtual unsigned long Consume(const DataBlock& data, unsigned long tStamp, unsigned long flags);
|
||||
inline bool valid() const
|
||||
{ return 0 != m_amrState; }
|
||||
protected:
|
||||
void filterBias(short* buf, unsigned int len);
|
||||
bool dataError(const char* text = 0);
|
||||
virtual bool pushData(unsigned long& tStamp, unsigned long& flags) = 0;
|
||||
void* m_amrState;
|
||||
DataBlock m_data;
|
||||
int m_bias;
|
||||
bool m_encoding;
|
||||
bool m_showError;
|
||||
};
|
||||
|
||||
// Encoding specific class
|
||||
class EfrEncoder : public EfrTrans
|
||||
{
|
||||
public:
|
||||
inline EfrEncoder(const char* sFormat, const char* dFormat)
|
||||
: EfrTrans(sFormat,dFormat,::Encoder_Interface_init(0),true)
|
||||
{ }
|
||||
virtual ~EfrEncoder();
|
||||
protected:
|
||||
virtual bool pushData(unsigned long& tStamp, unsigned long& flags);
|
||||
};
|
||||
|
||||
// Decoding specific class
|
||||
class EfrDecoder : public EfrTrans
|
||||
{
|
||||
public:
|
||||
inline EfrDecoder(const char* sFormat, const char* dFormat)
|
||||
: EfrTrans(sFormat,dFormat,::Decoder_Interface_init(),false)
|
||||
{ }
|
||||
virtual ~EfrDecoder();
|
||||
protected:
|
||||
virtual bool pushData(unsigned long& tStamp, unsigned long& flags);
|
||||
};
|
||||
|
||||
// Module data
|
||||
static int count = 0; // Created objects
|
||||
|
||||
static TranslatorCaps caps[] = {
|
||||
{ 0, 0 },
|
||||
{ 0, 0 },
|
||||
{ 0, 0 },
|
||||
};
|
||||
|
||||
|
||||
// Arbitrary type transcoder constructor
|
||||
EfrTrans::EfrTrans(const char* sFormat, const char* dFormat, void* amrState, bool encoding)
|
||||
: DataTranslator(sFormat,dFormat),
|
||||
m_amrState(amrState), m_bias(0), m_encoding(encoding), m_showError(true)
|
||||
{
|
||||
Debug(MODNAME,DebugAll,"EfrTrans::EfrTrans('%s','%s',%p,%s) [%p]",
|
||||
sFormat,dFormat,amrState,String::boolText(encoding),this);
|
||||
count++;
|
||||
}
|
||||
|
||||
// Destructor, closes the channel
|
||||
EfrTrans::~EfrTrans()
|
||||
{
|
||||
Debug(MODNAME,DebugAll,"EfrTrans::~EfrTrans() [%p]",this);
|
||||
m_amrState = 0;
|
||||
count--;
|
||||
}
|
||||
|
||||
// Actual transcoding of data
|
||||
unsigned long EfrTrans::Consume(const DataBlock& data, unsigned long tStamp, unsigned long flags)
|
||||
{
|
||||
if (!(m_amrState && getTransSource()))
|
||||
return 0;
|
||||
if (data.null() && (flags & DataSilent))
|
||||
return getTransSource()->Forward(data,tStamp,flags);
|
||||
ref();
|
||||
if (m_encoding && (tStamp != invalidStamp()) && !m_data.null())
|
||||
tStamp -= (m_data.length() / 2);
|
||||
m_data += data;
|
||||
if (m_encoding) {
|
||||
// the AMR encoder errors on biased silence so suppress it
|
||||
unsigned int len = data.length();
|
||||
if (len)
|
||||
filterBias((short*)m_data.data(m_data.length() - len),len / 2);
|
||||
}
|
||||
while (pushData(tStamp,flags))
|
||||
;
|
||||
deref();
|
||||
return invalidStamp();
|
||||
}
|
||||
|
||||
// High pass filter to get rid of the bias - for example when transcoding through A-Law
|
||||
void EfrTrans::filterBias(short* buf, unsigned int len)
|
||||
{
|
||||
if (!buf)
|
||||
return;
|
||||
while (len--) {
|
||||
int val = *buf;
|
||||
// work on integers using sample * 16
|
||||
m_bias = (m_bias * 63 + val * 16) / 64;
|
||||
// substract the averaged bias and saturate
|
||||
val -= m_bias / 16;
|
||||
if (val > 32767)
|
||||
val = 32767;
|
||||
else if (val < -32767)
|
||||
val = -32767;
|
||||
*buf++ = val;
|
||||
}
|
||||
}
|
||||
|
||||
// Data error, report error 1st time and clear buffer
|
||||
bool EfrTrans::dataError(const char* text)
|
||||
{
|
||||
if (m_showError) {
|
||||
m_showError = false;
|
||||
const char* prefix = ": ";
|
||||
if (!text)
|
||||
prefix = text = "";
|
||||
Debug(MODNAME,DebugWarn,"Error transcoding data%s%s [%p]",prefix,text,this);
|
||||
}
|
||||
m_data.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Encoder cleanup
|
||||
EfrEncoder::~EfrEncoder()
|
||||
{
|
||||
Debug(MODNAME,DebugAll,"EfrEncoder::~EfrEncoder() %p [%p]",m_amrState,this);
|
||||
if (m_amrState)
|
||||
::Encoder_Interface_exit(m_amrState);
|
||||
}
|
||||
|
||||
// Encode accumulated slin data and push it to the consumer
|
||||
bool EfrEncoder::pushData(unsigned long& tStamp, unsigned long& flags)
|
||||
{
|
||||
if (m_data.length() < BUFFER_SIZE)
|
||||
return false;
|
||||
|
||||
unsigned char unpacked[AMR_MR122_SIZE];
|
||||
if (::Encoder_Interface_Encode(m_amrState,MR122,(short*)m_data.data(),unpacked,0) != AMR_MR122_SIZE)
|
||||
return dataError("encoder");
|
||||
if (((unpacked[0] >> 3) & 0x0f) != MR122) {
|
||||
// invalid mode returned in frame - don't send the the data at all
|
||||
m_data.cut(-BUFFER_SIZE);
|
||||
tStamp += SAMPLES_FRAME;
|
||||
return (0 != m_data.length());
|
||||
}
|
||||
|
||||
unsigned char buffer[EFR_FRAME_SIZE];
|
||||
unsigned char leftover = 0xc0;
|
||||
for (int i = 1; i <= EFR_FRAME_SIZE; i++) {
|
||||
buffer[i-1] = leftover | (unpacked[i] >> 4);
|
||||
leftover = (unpacked[i] << 4) & 0xf0;
|
||||
}
|
||||
m_data.cut(-BUFFER_SIZE);
|
||||
DataBlock outData(buffer,EFR_FRAME_SIZE,false);
|
||||
getTransSource()->Forward(outData,tStamp,flags);
|
||||
outData.clear(false);
|
||||
tStamp += SAMPLES_FRAME;
|
||||
flags &= ~DataMark;
|
||||
m_showError = true;
|
||||
return (0 != m_data.length());
|
||||
}
|
||||
|
||||
|
||||
// Decoder cleanup
|
||||
EfrDecoder::~EfrDecoder()
|
||||
{
|
||||
Debug(MODNAME,DebugAll,"EfrDecoder::~EfrDecoder() %p [%p]",m_amrState,this);
|
||||
if (m_amrState)
|
||||
::Decoder_Interface_exit(m_amrState);
|
||||
}
|
||||
|
||||
// Decode AMR data and push it to the consumer
|
||||
bool EfrDecoder::pushData(unsigned long& tStamp, unsigned long& flags)
|
||||
{
|
||||
if (m_data.length() < EFR_FRAME_SIZE)
|
||||
return false;
|
||||
|
||||
unsigned const char* ptr = (unsigned const char*)m_data.data();
|
||||
if ((ptr[0] & 0xf0) != 0xc0)
|
||||
return dataError("invalid signature");
|
||||
|
||||
unsigned char unpacked[AMR_MR122_SIZE];
|
||||
unpacked[0] = (MR122 << 3) | 0x04; // Quality bit set
|
||||
unsigned char leftover = (ptr[0] << 4) & 0xf0;
|
||||
for (int i = 1; i < EFR_FRAME_SIZE; i++) {
|
||||
unpacked[i] = leftover | (ptr[i] >> 4);
|
||||
leftover = (ptr[i] << 4) & 0xf0;
|
||||
}
|
||||
unpacked[AMR_MR122_SIZE-1] = leftover;
|
||||
short buffer[SAMPLES_FRAME];
|
||||
::Decoder_Interface_Decode(m_amrState,unpacked,buffer,RxTypes::RX_SPEECH_GOOD);
|
||||
DataBlock outData(buffer,BUFFER_SIZE,false);
|
||||
getTransSource()->Forward(outData,tStamp,flags);
|
||||
outData.clear(false);
|
||||
tStamp += SAMPLES_FRAME;
|
||||
flags &= ~DataMark;
|
||||
m_data.cut(-EFR_FRAME_SIZE);
|
||||
m_showError = true;
|
||||
return (0 != m_data.length());
|
||||
}
|
||||
|
||||
|
||||
// Plugin and translator factory
|
||||
EfrPlugin::EfrPlugin()
|
||||
: Plugin("efrcodec"), TranslatorFactory("gsm-efr")
|
||||
{
|
||||
Output("Loaded module GSM-EFR codec - based on 3GPP AMR code");
|
||||
const FormatInfo* f = FormatRepository::addFormat("gsm-efr",EFR_FRAME_SIZE,20000);
|
||||
caps[0].src = caps[1].dest = f;
|
||||
caps[0].dest = caps[1].src = FormatRepository::getFormat("slin");
|
||||
// FIXME: put proper conversion costs
|
||||
caps[0].cost = caps[1].cost = 5;
|
||||
}
|
||||
|
||||
EfrPlugin::~EfrPlugin()
|
||||
{
|
||||
Output("Unloading module GSM-EFR with %d codecs still in use",count);
|
||||
}
|
||||
|
||||
bool EfrPlugin::isBusy() const
|
||||
{
|
||||
return (count != 0);
|
||||
}
|
||||
|
||||
// Create transcoder instance for requested formats
|
||||
DataTranslator* EfrPlugin::create(const DataFormat& sFormat, const DataFormat& dFormat)
|
||||
{
|
||||
if ((sFormat == "slin") && (dFormat == "gsm-efr"))
|
||||
return new EfrEncoder(sFormat,dFormat);
|
||||
else if ((sFormat == "gsm-efr") && (dFormat == "slin"))
|
||||
return new EfrDecoder(sFormat,dFormat);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const TranslatorCaps* EfrPlugin::getCapabilities() const
|
||||
{
|
||||
return caps;
|
||||
}
|
||||
|
||||
void EfrPlugin::initialize()
|
||||
{
|
||||
Output("Initializing module GSM-EFR");
|
||||
}
|
||||
|
||||
|
||||
INIT_PLUGIN(EfrPlugin);
|
||||
|
||||
UNLOAD_PLUGIN(unloadNow)
|
||||
{
|
||||
if (unloadNow)
|
||||
return !__plugin.isBusy();
|
||||
return true;
|
||||
}
|
||||
|
||||
}; // anonymous namespace
|
||||
|
||||
/* vi: set ts=8 sw=4 sts=4 noet: */
|
|
@ -59,6 +59,7 @@ static TokenDict dict_payloads[] = {
|
|||
{ "speex/32000", 104 },
|
||||
{ "isac/16000", 105 },
|
||||
{ "isac/32000", 106 },
|
||||
{ "gsm-efr", 107 },
|
||||
{ "mjpeg", 26 },
|
||||
{ "h261", 31 },
|
||||
{ "h263", 34 },
|
||||
|
|
Loading…
Reference in New Issue