Support for octet aligned AMR-NB RTP payload.
git-svn-id: http://voip.null.ro/svn/yate@2028 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
parent
97f368bc47
commit
78a636c49c
|
@ -154,6 +154,10 @@
|
|||
; NOTE: RFC 3555 specifies the default should be yes
|
||||
;g729_annexb=no
|
||||
|
||||
; amr_octet: bool: Octet aligned AMR RTP payload default (if not in SDP)
|
||||
; NOTE: RFC 4867 (and older 3267) specifies the default is bandwidth efficient
|
||||
;amr_octet=no
|
||||
|
||||
|
||||
[methods]
|
||||
; Use this section to allow server processing of various SIP methods by
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace { // anonymous
|
|||
#define BUFFER_SIZE (2*SAMPLES_FRAME)
|
||||
|
||||
// Maximum compressed frame size
|
||||
#define MAX_AMRNB_SIZE 32
|
||||
#define MAX_AMRNB_SIZE 33
|
||||
|
||||
// Maximum number of frames we are willing to decode in a packet
|
||||
#define MAX_PKT_FRAMES 4
|
||||
|
@ -70,17 +70,20 @@ public:
|
|||
class AmrTrans : public DataTranslator
|
||||
{
|
||||
public:
|
||||
AmrTrans(const char* sFormat, const char* dFormat, void* amrState);
|
||||
AmrTrans(const char* sFormat, const char* dFormat, void* amrState, bool octetAlign = false);
|
||||
virtual ~AmrTrans();
|
||||
virtual void Consume(const DataBlock& data, unsigned long tStamp);
|
||||
inline bool valid() const
|
||||
{ return 0 != m_amrState; }
|
||||
static inline const char* alignName(bool align)
|
||||
{ return align ? "octet aligned" : "bandwidth efficient"; }
|
||||
protected:
|
||||
bool dataError(const char* text = 0);
|
||||
virtual bool pushData(unsigned long& tStamp) = 0;
|
||||
void* m_amrState;
|
||||
DataBlock m_data;
|
||||
bool m_showError;
|
||||
bool m_octetAlign;
|
||||
Mode m_cmr;
|
||||
};
|
||||
|
||||
|
@ -88,8 +91,8 @@ protected:
|
|||
class AmrEncoder : public AmrTrans
|
||||
{
|
||||
public:
|
||||
inline AmrEncoder(const char* sFormat, const char* dFormat, bool discont = false)
|
||||
: AmrTrans(sFormat,dFormat,::Encoder_Interface_init(discont ? 1 : 0)),
|
||||
inline AmrEncoder(const char* sFormat, const char* dFormat, bool octetAlign, bool discont = false)
|
||||
: AmrTrans(sFormat,dFormat,::Encoder_Interface_init(discont ? 1 : 0),octetAlign),
|
||||
m_mode(MR122)
|
||||
{ }
|
||||
virtual ~AmrEncoder();
|
||||
|
@ -102,8 +105,8 @@ protected:
|
|||
class AmrDecoder : public AmrTrans
|
||||
{
|
||||
public:
|
||||
inline AmrDecoder(const char* sFormat, const char* dFormat)
|
||||
: AmrTrans(sFormat,dFormat,::Decoder_Interface_init())
|
||||
inline AmrDecoder(const char* sFormat, const char* dFormat, bool octetAlign)
|
||||
: AmrTrans(sFormat,dFormat,::Decoder_Interface_init(),octetAlign)
|
||||
{ }
|
||||
virtual ~AmrDecoder();
|
||||
protected:
|
||||
|
@ -114,6 +117,8 @@ protected:
|
|||
static int count = 0; // Created objects
|
||||
|
||||
static TranslatorCaps caps[] = {
|
||||
{ 0, 0 },
|
||||
{ 0, 0 },
|
||||
{ 0, 0 },
|
||||
{ 0, 0 },
|
||||
{ 0, 0 }
|
||||
|
@ -128,7 +133,6 @@ static int modeBits[16] = {
|
|||
// Discontinuous Transmission (DTX)
|
||||
bool s_discontinuous = false;
|
||||
|
||||
|
||||
// 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)
|
||||
{
|
||||
|
@ -155,12 +159,13 @@ static int getBits(unsigned const char*& ptr, int& len, int& bpos, unsigned char
|
|||
|
||||
|
||||
// Arbitrary type transcoder constructor
|
||||
AmrTrans::AmrTrans(const char* sFormat, const char* dFormat, void* amrState)
|
||||
AmrTrans::AmrTrans(const char* sFormat, const char* dFormat, void* amrState, bool octetAlign)
|
||||
: DataTranslator(sFormat,dFormat),
|
||||
m_amrState(amrState), m_showError(true), m_cmr(MR122)
|
||||
m_amrState(amrState), m_showError(true),
|
||||
m_octetAlign(octetAlign), m_cmr(MR122)
|
||||
{
|
||||
Debug(MODNAME,DebugAll,"AmrTrans::AmrTrans('%s','%s',%p) [%p]",
|
||||
sFormat,dFormat,amrState,this);
|
||||
Debug(MODNAME,DebugAll,"AmrTrans::AmrTrans('%s','%s',%p,%s) [%p]",
|
||||
sFormat,dFormat,amrState,String::boolText(octetAlign),this);
|
||||
count++;
|
||||
}
|
||||
|
||||
|
@ -217,20 +222,33 @@ bool AmrEncoder::pushData(unsigned long& tStamp)
|
|||
|
||||
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))
|
||||
if ((len <= 0) || (len >= MAX_AMRNB_SIZE))
|
||||
return dataError("encoder");
|
||||
unpacked[len] = 0;
|
||||
XDebug(MODNAME,DebugAll,"Encoded mode %d frame to %d bytes first %02x [%p]",
|
||||
m_mode,len,unpacked[0],this);
|
||||
unsigned char buffer[MAX_AMRNB_SIZE];
|
||||
// build a TOC with just one entry
|
||||
// 4 bit CMR, 1 bit follows (forced 0), 3 bits of mode
|
||||
buffer[0] = (m_cmr << 4) | ((unpacked[0] >> 4) & 0x07);
|
||||
// 1 bit of mode and 1 bit Q
|
||||
unsigned char leftover = (unpacked[0] << 4) & 0xc0;
|
||||
for (int i = 1; i < len; i++) {
|
||||
buffer[i] = leftover | (unpacked[i] >> 2);
|
||||
leftover = (unpacked[i] << 6) & 0xc0;
|
||||
if (m_octetAlign) {
|
||||
// 4 bit CMR, 4 bits reserved
|
||||
buffer[0] = (m_cmr << 4);
|
||||
// 1 bit follows (0), 4 bits of mode, 1 bit Q, 2 bits padding (0)
|
||||
buffer[1] = unpacked[0] & 0x7c;
|
||||
// AMR data
|
||||
for (int i = 1; i < len; i++)
|
||||
buffer[i+1] = unpacked[i];
|
||||
len++;
|
||||
}
|
||||
else {
|
||||
// 4 bit CMR, 1 bit follows (forced 0), 3 bits of mode
|
||||
buffer[0] = (m_cmr << 4) | ((unpacked[0] >> 4) & 0x07);
|
||||
// 1 bit of mode and 1 bit Q
|
||||
unsigned char leftover = (unpacked[0] << 4) & 0xc0;
|
||||
// AMR data
|
||||
for (int i = 1; i < len; i++) {
|
||||
buffer[i] = leftover | (unpacked[i] >> 2);
|
||||
leftover = (unpacked[i] << 6) & 0xc0;
|
||||
}
|
||||
}
|
||||
m_data.cut(-BUFFER_SIZE);
|
||||
DataBlock outData(buffer,len,false);
|
||||
|
@ -256,20 +274,35 @@ bool AmrDecoder::pushData(unsigned long& tStamp)
|
|||
return false;
|
||||
unsigned const char* ptr = (unsigned const char*)m_data.data();
|
||||
int len = m_data.length();
|
||||
// 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 (octetHint != m_octetAlign) {
|
||||
Debug(MODNAME,DebugNote,"Decoder switching from %s to %s mode [%p]",
|
||||
alignName(m_octetAlign),alignName(octetHint),this);
|
||||
m_octetAlign = octetHint;
|
||||
// TODO: find and notify paired encoder about the new alignment
|
||||
}
|
||||
int bpos = 0;
|
||||
unsigned char cmr = getBits(ptr,len,bpos,4) >> 4;
|
||||
if (m_octetAlign)
|
||||
getBits(ptr,len,bpos,4);
|
||||
unsigned int tocLen = 0;
|
||||
unsigned char toc[MAX_PKT_FRAMES];
|
||||
int dataBits = 0;
|
||||
// read the TOC
|
||||
for (;;) {
|
||||
int ft = getBits(ptr,len,bpos,6);
|
||||
if (m_octetAlign)
|
||||
getBits(ptr,len,bpos,2);
|
||||
if (ft < 0)
|
||||
return dataError("TOC truncated");
|
||||
int nBits = modeBits[(ft >> 3) & 0x0f];
|
||||
// discard the entire packet if an invalid frame is found
|
||||
if (nBits < 0)
|
||||
return dataError("invalid mode");
|
||||
if (m_octetAlign)
|
||||
nBits = (nBits + 7) & (~7);
|
||||
dataBits += nBits;
|
||||
toc[tocLen++] = ft & 0x7c; // keep type and quality bit
|
||||
// does another TOC follow?
|
||||
|
@ -282,11 +315,15 @@ bool AmrDecoder::pushData(unsigned long& tStamp)
|
|||
return dataError("data truncated");
|
||||
// We read the TOC, now pick the following voice frames and decode
|
||||
for (unsigned int idx = 0; idx < tocLen; idx++) {
|
||||
if (m_octetAlign && (bpos != 0))
|
||||
return dataError("internal alignment error");
|
||||
int mode = (toc[idx] >> 3) & 0x0f;
|
||||
bool good = 0 != (toc[idx] & 0x04);
|
||||
int nBits = modeBits[mode];
|
||||
XDebug(MODNAME,DebugAll,"Decoding %d bits %s mode %d frame %u [%p]",
|
||||
nBits,(good ? "good" : "bad"),mode,idx,this);
|
||||
if (m_octetAlign)
|
||||
nBits = (nBits + 7) & (~7);
|
||||
unsigned char unpacked[MAX_AMRNB_SIZE];
|
||||
unpacked[0] = toc[idx];
|
||||
for (unsigned int i = 1; i < MAX_AMRNB_SIZE; i++) {
|
||||
|
@ -324,9 +361,11 @@ AmrPlugin::AmrPlugin()
|
|||
Output("Loaded module AMR-NB codec - based on 3GPP code");
|
||||
const FormatInfo* f = FormatRepository::addFormat("amr",0,20000);
|
||||
caps[0].src = caps[1].dest = f;
|
||||
caps[0].dest = caps[1].src = FormatRepository::getFormat("slin");
|
||||
f = FormatRepository::addFormat("amr-o",0,20000);
|
||||
caps[2].src = caps[3].dest = f;
|
||||
caps[0].dest = caps[1].src = caps[2].dest = caps[3].src = FormatRepository::getFormat("slin");
|
||||
// FIXME: put proper conversion costs
|
||||
caps[0].cost = caps[1].cost = 5;
|
||||
caps[0].cost = caps[1].cost = caps[2].cost = caps[3].cost = 5;
|
||||
}
|
||||
|
||||
AmrPlugin::~AmrPlugin()
|
||||
|
@ -342,11 +381,19 @@ bool AmrPlugin::isBusy() const
|
|||
// Create transcoder instance for requested formats
|
||||
DataTranslator* AmrPlugin::create(const DataFormat& sFormat, const DataFormat& dFormat)
|
||||
{
|
||||
if (sFormat == "slin" && dFormat == "amr")
|
||||
return new AmrEncoder(sFormat,dFormat,s_discontinuous);
|
||||
else if (sFormat == "amr" && dFormat == "slin")
|
||||
return new AmrDecoder(sFormat,dFormat);
|
||||
else return 0;
|
||||
if (sFormat == "slin") {
|
||||
if (dFormat == "amr")
|
||||
return new AmrEncoder(sFormat,dFormat,false,s_discontinuous);
|
||||
if (dFormat == "amr-o")
|
||||
return new AmrEncoder(sFormat,dFormat,true,s_discontinuous);
|
||||
}
|
||||
else if (dFormat == "slin") {
|
||||
if (sFormat == "amr")
|
||||
return new AmrDecoder(sFormat,dFormat,false);
|
||||
if (sFormat == "amr-o")
|
||||
return new AmrDecoder(sFormat,dFormat,true);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const TranslatorCaps* AmrPlugin::getCapabilities() const
|
||||
|
|
|
@ -53,7 +53,9 @@ static TokenDict dict_payloads[] = {
|
|||
{ "ilbc20", 98 },
|
||||
{ "ilbc30", 98 },
|
||||
{ "amr", 96 },
|
||||
{ "amr-o", 96 },
|
||||
{ "amr/16000", 99 },
|
||||
{ "amr-o/16000", 99 },
|
||||
{ "speex", 102 },
|
||||
{ "speex/16000", 103 },
|
||||
{ "speex/32000", 104 },
|
||||
|
|
|
@ -51,7 +51,9 @@ static TokenDict dict_payloads[] = {
|
|||
{ "ilbc20", 98 },
|
||||
{ "ilbc30", 98 },
|
||||
{ "amr", 96 },
|
||||
{ "amr-o", 96 },
|
||||
{ "amr/16000", 99 },
|
||||
{ "amr-o/16000", 99 },
|
||||
{ "speex", 102 },
|
||||
{ "speex/16000", 103 },
|
||||
{ "speex/32000", 104 },
|
||||
|
@ -645,6 +647,7 @@ static ObjList* parseSDP(const MimeSdpBody* sdp, String& addr, ObjList* oldMedia
|
|||
continue;
|
||||
int mode = 0;
|
||||
bool annexB = s_cfg.getBoolValue("codecs","g729_annexb",false);
|
||||
bool amrOctet = s_cfg.getBoolValue("codecs","amr_octet",false);
|
||||
int defmap = -1;
|
||||
String payload(lookup(var,dict_payloads));
|
||||
|
||||
|
@ -691,6 +694,8 @@ static ObjList* parseSDP(const MimeSdpBody* sdp, String& addr, ObjList* oldMedia
|
|||
line >> mode;
|
||||
else if (line.startSkip("annexb=",false))
|
||||
line >> annexB;
|
||||
else if (line.startSkip("octet-align=",false))
|
||||
amrOctet = (0 != line.toInteger(0));
|
||||
}
|
||||
}
|
||||
else if (first) {
|
||||
|
@ -715,6 +720,9 @@ static ObjList* parseSDP(const MimeSdpBody* sdp, String& addr, ObjList* oldMedia
|
|||
payload = s_cfg.getValue("hacks","ilbc_default","ilbc30");
|
||||
}
|
||||
|
||||
if (amrOctet && payload == "amr")
|
||||
payload = "amr-o";
|
||||
|
||||
XDebug(&plugin,DebugAll,"Payload %d format '%s'",var,payload.c_str());
|
||||
if (payload && s_cfg.getBoolValue("codecs",payload,defcodecs && DataTranslator::canConvert(payload))) {
|
||||
if (fmt)
|
||||
|
@ -2796,6 +2804,16 @@ MimeSdpBody* YateSIPConnection::createSDP(const char* addr, ObjList* mediaList)
|
|||
((0 != l->find("g729b")) ? "yes" : "no");
|
||||
rtpmap.append(temp);
|
||||
}
|
||||
else if (*s == "amr") {
|
||||
temp = new String("fmtp:");
|
||||
*temp << payload << " octet-align=0";
|
||||
rtpmap.append(temp);
|
||||
}
|
||||
else if (*s == "amr-o") {
|
||||
temp = new String("fmtp:");
|
||||
*temp << payload << " octet-align=1";
|
||||
rtpmap.append(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue