Added support for setting AMR and other codecs' parameters in SDP.

Build and package separately the AMR-NB and GSM-EFR codecs.


git-svn-id: http://yate.null.ro/svn/yate/trunk@6151 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
paulc 2016-11-25 15:45:34 +00:00
parent 0b184401a3
commit 09ff4188b9
10 changed files with 292 additions and 29 deletions

View File

@ -6,5 +6,14 @@
; 4.75, 5.15, 5.90, 6.70, 7.40, 7.95, 10.2, 12.2
;mode=12.2
; mode-set: list: Allowed encoder set of AMR modes
;mode-set=0,1,2,3,4,5,6,7
; mode-change-neighbor: bool: Only perform change to the neighbor mode in the set
;mode-change-neighbor=false
; mode-change-period: int: Frame block period at which mode changes are allowed
;mode-change-period=1
; discontinuous: bool: Use DTX (Discontinuous Transmission) frames
;discontinuous=no

View File

@ -416,6 +416,9 @@
; gsm: bool: European GSM 06.10 (GSM/8000)
;gsm=default
; gsm-efr: bool: European GSM 06.60 (GSM-EFR/8000)
;gsm-efr=default
; lpc10: bool: Linear Prediction Codec (LPC/8000)
;lpc10=default

View File

@ -535,6 +535,11 @@ bool DataSource::valid() const
return false;
}
bool DataSource::control(NamedList& params)
{
return m_translator && m_translator->control(params);
}
unsigned long DataSource::Forward(const DataBlock& data, unsigned long tStamp, unsigned long flags)
{
Lock mylock(this,100000);
@ -1053,10 +1058,12 @@ bool DataEndpoint::clearData(DataNode* node)
bool DataEndpoint::control(NamedList& params)
{
// TODO how do we handle this case????? operation-status
DataSource* peerSrc = m_consumer ? m_consumer->getConnSource() : 0;
return (m_source && m_source->control(params)) ||
(m_consumer && m_consumer->control(params)) ||
(m_peerRecord && m_peerRecord->control(params)) ||
(m_callRecord && m_callRecord->control(params));
(m_callRecord && m_callRecord->control(params)) ||
(peerSrc && peerSrc->control(params));
}
@ -1578,6 +1585,8 @@ bool DataTranslator::attachChain(DataSource* source, DataConsumer* consumer, boo
DataTranslator* trans = trans2->getFirstTranslator();
trans2->getTransSource()->attach(consumer,override);
source->attach(trans);
trans->attached(true);
trans2->attached(true);
trans->deref();
retv = true;
}
@ -1773,6 +1782,7 @@ DataTranslator* ChainedFactory::create(const DataFormat& sFormat, const DataForm
// trans2 may be a chain itself so find the first translator
DataTranslator* trans1 = trans2->getFirstTranslator();
trans->getTransSource()->attach(trans1);
trans1->attached(true);
trans1->deref();
}
else

View File

@ -229,8 +229,8 @@ ObjList* SDPParser::parse(const MimeSdpBody& sdp, String& addr, ObjList* oldMedi
}
else if (line.startSkip("fmtp:",false)) {
int num = var - 1;
line >> num >> " ";
if (num == var) {
line >> num;
if ((num == var) && line.trimSpaces()) {
if (line.startSkip("mode=",false))
line >> mode;
else if (line.startSkip("annexb=",false))
@ -240,6 +240,8 @@ ObjList* SDPParser::parse(const MimeSdpBody& sdp, String& addr, ObjList* oldMedi
line >> val;
amrOctet = (0 != val);
}
else
dest = dest->append(new NamedString("fmtp:" + payload,line));
}
}
else if (first) {

View File

@ -202,10 +202,10 @@ bool SDPSession::startRtp(RefObject* context)
}
// Update from parameters. Build a default SDP if no media is found in params
bool SDPSession::updateSDP(const NamedList& params)
bool SDPSession::updateSDP(const NamedList& params, bool defaults)
{
DDebug(m_enabler,DebugAll,"SDPSession::updateSdp('%s') [%p]",params.c_str(),m_ptr);
bool defaults = true;
DDebug(m_enabler,DebugAll,"SDPSession::updateSdp('%s',%s) [%p]",
params.c_str(),String::boolText(defaults),m_ptr);
const char* sdpPrefix = params.getValue("osdp-prefix","osdp");
ObjList* lst = 0;
unsigned int n = params.length();
@ -215,7 +215,7 @@ bool SDPSession::updateSDP(const NamedList& params)
const NamedString* p = params.getParam(i);
if (!p)
continue;
// search for rtp_port or rtp_port_MEDIANAME parameters
// search for media or media_MEDIANAME parameters
String tmp(p->name());
if (!tmp.startSkip("media",false))
continue;
@ -257,12 +257,14 @@ bool SDPSession::updateSDP(const NamedList& params)
}
rtp->crypto(crypto,false);
if (sdpPrefix) {
String prefix;
prefix << sdpPrefix << rtp->suffix() << "_";
for (unsigned int j = 0; j < n; j++) {
const NamedString* param = params.getParam(j);
if (!param)
continue;
tmp = param->name();
if (tmp.startSkip(sdpPrefix + rtp->suffix() + "_",false) && (tmp.find('_') < 0))
if (tmp.startSkip(prefix,false) && (tmp.find('_') < 0))
rtp->parameter(tmp,*param,append);
}
}
@ -473,16 +475,22 @@ MimeSdpBody* SDPSession::createSDP(const char* addr, ObjList* mediaList)
((0 != l->find("g729b")) ? "yes" : "no");
dest = dest->append(temp);
}
else if (*s == "amr") {
else if (*s == "amr" || s->startsWith("amr/")) {
temp = new String("fmtp:");
*temp << payload << " octet-align=0";
dest = dest->append(temp);
}
else if (*s == "amr-o") {
else if (*s == "amr-o" || s->startsWith("amr-o/")) {
temp = new String("fmtp:");
*temp << payload << " octet-align=1";
dest = dest->append(temp);
}
const String* fmtp = m->getParam("fmtp:" + *s);
if (fmtp) {
temp = new String("fmtp:");
*temp << payload << " " << *fmtp;
dest = dest->append(temp);
}
}
}
}
@ -526,7 +534,7 @@ MimeSdpBody* SDPSession::createSDP(const char* addr, ObjList* mediaList)
unsigned int n = m->length();
for (unsigned int i = 0; i < n; i++) {
const NamedString* param = m->getParam(i);
if (param) {
if (param && (param->name().find(':') < 0)) {
const char* type = "a";
String tmp = param->name();
if (tmp.startSkip("BW-",false)) {
@ -625,7 +633,7 @@ void SDPSession::updateFormats(const NamedList& msg, bool changeMedia)
const NamedString* p = msg.getParam(i);
if (!p)
continue;
// search for media_MEDIANAME parameters
// search for media or media_MEDIANAME parameters
String tmp = p->name();
if (!tmp.startSkip("media",false))
continue;

View File

@ -443,11 +443,12 @@ public:
bool startRtp(RefObject* context = 0);
/**
* Update from parameters. Build a default SDP from parser formats if no media is found in params
* Update from parameters and optionally build a default SDP.
* @param params List of parameters to update from
* @param defaults Build a default SDP from parser formats if no media is found in params
* @return True if media changed
*/
bool updateSDP(const NamedList& params);
bool updateSDP(const NamedList& params, bool defaults = true);
/**
* Update RTP/SDP data from parameters

View File

@ -56,6 +56,15 @@ namespace { // anonymous
// Discontinuous Transmission (DTX)
static bool s_discontinuous = false;
// Change mode only to neighbor
static bool s_neighbor = false;
// Mode change period in frames
static uint8_t s_period = 1;
// Supported modes mask
static uint8_t s_mask = 0xff;
// Default mode
static Mode s_mode = MR122;
@ -84,6 +93,7 @@ public:
protected:
void filterBias(short* buf, unsigned int len);
bool dataError(const char* text = 0);
void setMode(Mode mode);
virtual bool pushData(unsigned long& tStamp, unsigned long& flags) = 0;
void* m_amrState;
DataBlock m_data;
@ -100,13 +110,21 @@ class AmrEncoder : public AmrTrans
public:
inline AmrEncoder(const char* sFormat, const char* dFormat, bool octetAlign, bool discont = false)
: AmrTrans(sFormat,dFormat,::Encoder_Interface_init(discont ? 1 : 0),octetAlign,true),
m_mode(s_mode), m_silent(false)
m_mode(s_mode), m_desired(s_mode), m_change(0), m_mask(s_mask),
m_period(s_period), m_neighbor(false), m_silent(false)
{ }
virtual ~AmrEncoder();
virtual bool control(NamedList& params);
protected:
void setMode(Mode mode);
virtual void attached(bool added);
virtual bool pushData(unsigned long& tStamp, unsigned long& flags);
Mode m_mode;
Mode m_desired;
int m_change;
uint8_t m_mask;
uint8_t m_period;
bool m_neighbor;
bool m_silent;
};
@ -152,6 +170,77 @@ static const TokenDict s_modes[] = {
{ 0, 0 }
};
// Helper function, parses a mode-set string to a bit mask
static uint8_t parseMask(const String& modes, uint8_t defMask = 0xff)
{
if (modes.null())
return defMask;
uint8_t mask = 0;
ObjList* lst = modes.split(',',false);
for (ObjList* l = lst->skipNull(); l; l = l->skipNext()) {
int mode = static_cast<const String*>(l->get())->toInteger(s_modes,-1);
if (mode >= MR475 && mode <= MR122)
mask |= (1 << mode);
}
TelEngine::destruct(lst);
return mask ? mask : defMask;
}
// Helper function, parses a possibly NULL mode-set string pointer
inline static uint8_t parseMask(const String* modes, uint8_t defMask = 0xff)
{
return modes ? parseMask(*modes) : defMask;
}
// Helper function returning nearest allowed mode
static Mode getMode(int mode, uint8_t mask, int oldMode)
{
if (mode < MR475)
mode = MR475;
else if (mode > MR122)
mode = MR122;
if (mask & (1 << mode))
return (Mode)mode;
if (oldMode > mode) {
for (int m = mode + 1; m <= MR122; m++)
if (mask & (1 << m))
return (Mode)m;
}
while (--mode >= MR475)
if (mask & (1 << mode))
return (Mode)mode;
return (Mode)oldMode;
}
// Helper function returning nearest neighbor allowed mode
static Mode getNeighbor(int mode, uint8_t mask, int oldMode)
{
if (mode < MR475)
mode = MR475;
else if (mode > MR122)
mode = MR122;
if (mode == oldMode)
return (Mode)mode;
int m;
if (oldMode < mode) {
for (m = oldMode + 1; m <= mode; m++) {
if (mask & (1 << m))
break;
}
}
else {
for (m = oldMode - 1; m >= mode; m--) {
if (mask & (1 << m))
break;
}
}
if (m < MR475)
m = MR475;
else if (m > MR122)
m = MR122;
return (Mode)m;
}
// Helper function, gets a number of bits and advances pointer, return -1 for error
static int getBits(unsigned const char*& ptr, int& len, int& bpos, unsigned char bits)
{
@ -267,6 +356,19 @@ bool AmrEncoder::pushData(unsigned long& tStamp, unsigned long& flags)
if (m_data.length() < BUFFER_SIZE)
return false;
if ((m_mode != m_desired) && (--m_change <= 0)) {
Mode mode = m_neighbor
? getNeighbor(m_desired,m_mask,m_mode)
: getMode(m_desired,m_mask,m_mode);
if (mode == m_mode)
m_desired = mode;
else {
DDebug(MODNAME,DebugAll,"Encode mode changing %d -> %d, desired %d",m_mode,mode,m_desired);
m_mode = mode;
m_change = (mode == m_desired) ? 0 : m_period;
}
}
unsigned char unpacked[MAX_AMRNB_SIZE+1];
int len = ::Encoder_Interface_Encode(m_amrState,m_mode,(short*)m_data.data(),unpacked,0);
if ((len <= 0) || (len >= MAX_AMRNB_SIZE))
@ -284,7 +386,7 @@ bool AmrEncoder::pushData(unsigned long& tStamp, unsigned long& flags)
m_silent = silent;
unpacked[len] = 0;
XDebug(MODNAME,DebugAll,"Encoded mode %d frame to %d bytes first %02x [%p]",
m_mode,len,unpacked[0],this);
mode,len,unpacked[0],this);
unsigned char buffer[MAX_AMRNB_SIZE];
// build a TOC with just one entry
if (m_octetAlign) {
@ -307,6 +409,11 @@ bool AmrEncoder::pushData(unsigned long& tStamp, unsigned long& flags)
buffer[i] = leftover | (unpacked[i] >> 2);
leftover = (unpacked[i] << 6) & 0xc0;
}
switch (modeBits[mode] & 7) {
case 0:
case 7:
buffer[len++] = leftover;
}
}
m_data.cut(-BUFFER_SIZE);
DataBlock outData(buffer,len,false);
@ -314,19 +421,35 @@ bool AmrEncoder::pushData(unsigned long& tStamp, unsigned long& flags)
outData.clear(false);
tStamp += SAMPLES_FRAME;
flags &= ~DataMark;
m_showError = true;
return (0 != m_data.length());
}
// Change the desired mode
void AmrEncoder::setMode(Mode mode)
{
m_desired = mode;
if (m_period)
m_change = m_period;
else
m_mode = mode;
}
// Execute control operations
bool AmrEncoder::control(NamedList& params)
{
bool ok = false;
int mode = params[YSTRING("mode")].toInteger(s_modes,-1);
int mode = params.getIntValue(YSTRING("mode"),s_modes,-1);
if (mode >= MR475 && mode <= MR122) {
m_mode = (Mode)mode;
if (params.getBoolValue(YSTRING("force"))) {
m_mode = m_desired = (Mode)mode;
m_change = 0;
}
else
setMode(getMode(mode,m_mask,m_desired));
ok = true;
}
mode = params[YSTRING("cmr")].toInteger(s_modes,-1);
mode = params.getIntValue(YSTRING("cmr"),s_modes,-1);
if (mode >= MR475 && mode <= MR122) {
m_cmr = (Mode)mode;
ok = true;
@ -334,6 +457,32 @@ bool AmrEncoder::control(NamedList& params)
return TelEngine::controlReturn(&params,AmrTrans::control(params) || ok);
}
// Callback to pick AMR parameters from consumer (typically RTP)
void AmrEncoder::attached(bool added)
{
AmrTrans::attached(added);
if (!added)
return;
ObjList* lst = getConsumers();
if (!lst)
return;
RefPointer<DataConsumer> cons = static_cast<DataConsumer*>(lst->get());
if (!cons)
return;
const DataFormat& fmt = cons->getFormat();
m_mask = parseMask(fmt[YSTRING("mode-set")],s_mask);
m_neighbor = fmt.getIntValue(YSTRING("mode-change-neighbor"),0) != 0;
int tmp = fmt.getIntValue(YSTRING("mode-change-period"),s_period);
if (tmp < 0)
tmp = 0;
else if (tmp > 4)
tmp = 4;
m_period = (uint8_t)tmp;
Debug(MODNAME,DebugAll,"AmrEncoder picked mask=0x%02X neigh=%s period=%d [%p]",
m_mask,String::boolText(m_neighbor),tmp,this);
setMode(getMode(m_mode,m_mask,m_desired));
}
// Decoder cleanup
AmrDecoder::~AmrDecoder()
@ -350,9 +499,13 @@ bool AmrDecoder::pushData(unsigned long& tStamp, unsigned long& flags)
return false;
unsigned const char* ptr = (unsigned const char*)m_data.data();
int len = m_data.length();
bool octetHint = m_octetAlign;
// an octet aligned packet should have 0 in the 4 reserved bits of CMR
// and in the lower 2 bits of first TOC entry octet
bool octetHint = ((ptr[0] & 0x0f) | (ptr[1] & 0x03)) == 0;
if ((ptr[0] & 0x0f) | (ptr[1] & 0x03))
octetHint = false;
else if (((ptr[1] & 0xc0) == 0) && (len > 14))
octetHint = true;
if (octetHint != m_octetAlign) {
Debug(MODNAME,DebugNote,"Decoder switching from %s to %s mode [%p]",
alignName(m_octetAlign),alignName(octetHint),this);
@ -428,6 +581,7 @@ bool AmrDecoder::pushData(unsigned long& tStamp, unsigned long& flags)
m_cmr = (Mode)cmr;
// TODO: find and notify paired encoder about the mode change request
}
m_showError = true;
return (0 != m_data.length());
}
@ -483,10 +637,16 @@ void AmrPlugin::initialize()
{
Output("Initializing module AMR-NB");
Configuration cfg(Engine::configFile("amrnbcodec"));
int mode = cfg.getIntValue("general","mode",s_modes,MR122);
if (mode >= MR475 && mode <= MR122)
s_mode = (Mode)mode;
s_mask = parseMask(cfg.getKey("general","mode-set"));
s_mode = getMode(cfg.getIntValue("general","mode",s_modes,MR122),s_mask,MR122);
s_discontinuous = cfg.getBoolValue("general","discontinuous",false);
s_neighbor = cfg.getBoolValue("general","mode-change-neighbor",false);
int tmp = cfg.getIntValue("general","mode-change-period",1);
if (tmp < 0)
tmp = 0;
else if (tmp > 4)
tmp = 4;
s_period = tmp;
}

View File

@ -171,6 +171,7 @@ private:
bool startSRTP(const String& suite, const String& keyParams, const ObjList* paramList);
bool setupUDPTL(Message& msg);
void setTimeout(const Message& msg, int timeOut);
static void setFormat(DataFormat& dest, const char* format, const NamedList& params);
YRTPSession* m_rtp;
YUDPTLSession* m_udptl;
RTPSession::Direction m_dir;
@ -620,6 +621,31 @@ void YRTPWrapper::setTimeout(const Message& msg, int timeOut)
session()->setTimeout(timeOut);
}
void YRTPWrapper::setFormat(DataFormat& dest, const char* format, const NamedList& params)
{
dest.clearParams();
dest = format;
const String& fmtp = params["sdp_fmtp:" + dest];
if (fmtp.null())
return;
ObjList* lst = fmtp.split(';');
for (ObjList* l = lst->skipNull(); l; l = l->skipNext()) {
const String* s = static_cast<const String*>(l->get());
int pos = s->find('=');
String name;
String value;
if (pos > 0) {
name = s->substr(0,pos);
value = s->substr(pos + 1);
}
else if (pos)
name = *s;
if (name.trimSpaces())
dest.addParam(name,value.trimSpaces());
}
TelEngine::destruct(lst);
}
bool YRTPWrapper::startRTP(const char* raddr, unsigned int rport, Message& msg)
{
Debug(&splugin,DebugAll,"YRTPWrapper::startRTP(\"%s\",%u) [%p]",raddr,rport,this);
@ -681,7 +707,7 @@ bool YRTPWrapper::startRTP(const char* raddr, unsigned int rport, Message& msg)
m_source->ref();
m_conn->setSource(0,m_media);
}
m_source->m_format = format;
setFormat(m_source->m_format,format,msg);
if (m_conn) {
m_conn->setSource(m_source,m_media);
m_source->deref();
@ -692,7 +718,7 @@ bool YRTPWrapper::startRTP(const char* raddr, unsigned int rport, Message& msg)
m_consumer->ref();
m_conn->setConsumer(0,m_media);
}
m_consumer->m_format = format;
setFormat(m_consumer->m_format,format,msg);
m_consumer->setSplitable();
if (m_conn) {
m_conn->setConsumer(m_consumer,m_media);

View File

@ -8,6 +8,7 @@
# to disable USB support run rpmbuild --define 'nousb 1'
# to disable SCTP support run rpmbuild --define 'nosctp 1'
# to disable H.323 support run rpmbuild --define 'noh323 1'
# to disable AMR codecs run rpmbuild --define 'noamr 1'
# to disable the GUI clients run rpmbuild --define 'nogui 1'
# to disable only Zaptel support run rpmbuild --define 'nozap 1'
# to disable only TDMV support run rpmbuild --define 'notdm 1'
@ -22,6 +23,7 @@
%{?nousb:%define no_usb 1}
%{?nosctp:%define no_sctp 1}
%{?noh323:%define no_h323 1}
%{?noamr:%define no_amr 1}
%{?nogui:%define no_gui 1}
%{?nozap:%define no_zaptel 1}
%{?notdm:%define no_tdmapi 1}
@ -283,6 +285,29 @@ provides moderate compression and good voice quality.
%{_libdir}/yate/gsmcodec.yate
%if "%{no_amr}" != "1"
%package amr
Summary: GSM-AMR and GSM-EFR audio codecs for Yate
Group: Applications/Communication
Requires: %{name} = %{version}-%{release}
%description amr
GSM-AMR and GSM-EFR audio codecs for Yate. AMR is a multi-rate codec that provides moderate
to high compression rate and good voice quality. EFR is just a different payload stucture
for 12.2 kbit/s AMR-NB.
%files amr
%{_libdir}/yate/amrnbcodec.yate
%{_libdir}/yate/efrcodec.yate
%config(noreplace) %{_sysconfdir}/yate/amrnbcodec.conf
%define conf_amr %{nil}
%else
%define conf_amr --without-amrnb
%endif
%package speex
Summary: Speex audio codec for Yate
Group: Applications/Communication
@ -610,6 +635,9 @@ The devel package must still be installed separately.
%if "%{no_h323}" == "1"
%define _unpackaged_files_terminate_build 0
%endif
%if "%{no_amr}" == "1"
%define _unpackaged_files_terminate_build 0
%endif
%if "%{no_gui}" == "1"
%define _unpackaged_files_terminate_build 0
%endif
@ -654,15 +682,14 @@ chmod +x %{local_find_requires} %{local_find_provides}
./configure --prefix=%{prefix} --sysconfdir=%{_sysconfdir} \
--datadir=%{_datadir} --includedir=%{_includedir} \
--libdir=%{_libdir} --mandir=%{_mandir} --with-archlib=%{_lib} \
--without-amrnb \
--without-spandsp --without-coredumper \
%{conf_sctp} %{conf_h323} %{conf_pstn} %{conf_usb} %{conf_gui} %{?extra_conf}
%{conf_sctp} %{conf_h323} %{conf_amr} %{conf_pstn} %{conf_usb} %{conf_gui} \
%{?extra_conf}
make %{stripped} %{?extra_make}
%{?extra_step}
%install
make install DESTDIR=%{buildroot} %{?extra_make}
rm %{buildroot}%{_sysconfdir}/yate/amrnbcodec.conf
rm %{buildroot}%{_sysconfdir}/yate/radiotest.conf
%if "%{systemd}" != "0"
mkdir -p %{buildroot}%{_unitdir}
@ -684,6 +711,9 @@ rm -rf %{buildroot}
%changelog
* Fri Nov 25 2016 Paul Chitescu <paulc@voip.null.ro>
- Added package for the AMR codec
* Thu Nov 26 2015 Paul Chitescu <paulc@voip.null.ro>
- More tweaking to suppress unwanted dependencies in newer RPM

View File

@ -373,7 +373,6 @@ public:
inline static unsigned long invalidStamp()
{ return (unsigned long)-1; }
protected:
/**
* Owner attach and detach notification.
* This method is called with @ref DataEndpoint::commonMutex() held
@ -382,6 +381,7 @@ protected:
virtual void attached(bool added)
{ }
protected:
DataFormat m_format;
unsigned long m_timestamp;
};
@ -505,6 +505,13 @@ public:
*/
virtual bool valid() const;
/**
* Modify source parameters, calls translator if one is set
* @param params The list of parameters to change
* @return True if processed
*/
virtual bool control(NamedList& params);
/**
* Forwards the data to its consumers
* @param data The raw data block to forward
@ -786,6 +793,13 @@ public:
static void setMaxChain(unsigned int maxChain);
protected:
/**
* Get access to the list of consumers of the data source
* @return Pointer to list entry of first consumer, NULL if none attached
*/
inline ObjList* getConsumers() const
{ return m_tsource ? m_tsource->m_consumers.skipNull() : 0; }
/**
* Synchronize the consumer with a source
* @param source Data source to copy the timestamp from