From 3bba32266486ff0442e2ece70c1f997f9ebb270b Mon Sep 17 00:00:00 2001 From: paulc Date: Thu, 26 Feb 2015 10:26:51 +0000 Subject: [PATCH] Added GSM-EFR codec support and transcoder. git-svn-id: http://yate.null.ro/svn/yate/trunk@5945 acf43c95-373e-0410-b603-e72c3f656dc1 --- libs/ysdp/parser.cpp | 2 + modules/Makefile.in | 5 +- modules/efrcodec.cpp | 327 +++++++++++++++++++++++++++++++++++++++++++ modules/yrtpchan.cpp | 1 + 4 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 modules/efrcodec.cpp diff --git a/libs/ysdp/parser.cpp b/libs/ysdp/parser.cpp index ab696d4f..7387e3be 100644 --- a/libs/ysdp/parser.cpp +++ b/libs/ysdp/parser.cpp @@ -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 }, diff --git a/modules/Makefile.in b/modules/Makefile.in index c408475e..b58ed65d 100644 --- a/modules/Makefile.in +++ b/modules/Makefile.in @@ -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) diff --git a/modules/efrcodec.cpp b/modules/efrcodec.cpp new file mode 100644 index 00000000..938e9b40 --- /dev/null +++ b/modules/efrcodec.cpp @@ -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 + +extern "C" { +#include +#include +} +namespace RxTypes { +// There is a conflict between encoder and decoder so insulate in a namespace +#include +}; + +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: */ diff --git a/modules/yrtpchan.cpp b/modules/yrtpchan.cpp index b5e3a78b..e0028c1b 100644 --- a/modules/yrtpchan.cpp +++ b/modules/yrtpchan.cpp @@ -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 },