332 lines
8.9 KiB
C++
332 lines
8.9 KiB
C++
/**
|
|
* parser.cpp
|
|
* This file is part of the YATE Project http://YATE.null.ro
|
|
*
|
|
* SDP media handling
|
|
*
|
|
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
|
* Copyright (C) 2004-2009 Null Team
|
|
*
|
|
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <yatesdp.h>
|
|
|
|
namespace TelEngine {
|
|
|
|
/*
|
|
* SDPParser
|
|
*/
|
|
// Yate Payloads for the AV profile
|
|
const TokenDict SDPParser::s_payloads[] = {
|
|
{ "mulaw", 0 },
|
|
{ "alaw", 8 },
|
|
{ "gsm", 3 },
|
|
{ "lpc10", 7 },
|
|
{ "slin", 11 },
|
|
{ "g726", 2 },
|
|
{ "g722", 9 },
|
|
{ "g723", 4 },
|
|
{ "g728", 15 },
|
|
{ "g729", 18 },
|
|
{ "ilbc", 98 },
|
|
{ "ilbc20", 98 },
|
|
{ "ilbc30", 98 },
|
|
{ "amr", 96 },
|
|
{ "amr-o", 96 },
|
|
{ "amr/16000", 99 },
|
|
{ "amr-o/16000", 99 },
|
|
{ "speex", 102 },
|
|
{ "speex/16000", 103 },
|
|
{ "speex/32000", 104 },
|
|
{ "h261", 31 },
|
|
{ "h263", 34 },
|
|
{ "mpv", 32 },
|
|
{ 0, 0 },
|
|
};
|
|
|
|
// SDP Payloads for the AV profile
|
|
const TokenDict SDPParser::s_rtpmap[] = {
|
|
{ "PCMU/8000", 0 },
|
|
{ "PCMA/8000", 8 },
|
|
{ "GSM/8000", 3 },
|
|
{ "LPC/8000", 7 },
|
|
{ "L16/8000", 11 },
|
|
{ "G726-32/8000", 2 },
|
|
{ "G722/8000", 9 },
|
|
{ "G723/8000", 4 },
|
|
{ "G728/8000", 15 },
|
|
{ "G729/8000", 18 },
|
|
{ "G729A/8000", 18 },
|
|
{ "ILBC/8000", 98 },
|
|
{ "AMR/8000", 96 },
|
|
{ "AMR-WB/16000", 99 },
|
|
{ "SPEEX/8000", 102 },
|
|
{ "SPEEX/16000", 103 },
|
|
{ "SPEEX/32000", 104 },
|
|
{ "H261/90000", 31 },
|
|
{ "H263/90000", 34 },
|
|
{ "MPV/90000", 32 },
|
|
{ 0, 0 },
|
|
};
|
|
|
|
// Parse a received SDP body
|
|
ObjList* SDPParser::parse(const MimeSdpBody& sdp, String& addr, ObjList* oldMedia,
|
|
const String& media)
|
|
{
|
|
DDebug(DebugAll,"SDPParser::parse(%p,%s,%p,'%s')",&sdp,addr.c_str(),oldMedia,media.safe());
|
|
const NamedString* c = sdp.getLine("c");
|
|
if (c) {
|
|
String tmp(*c);
|
|
if (tmp.startSkip("IN IP4")) {
|
|
tmp.trimBlanks();
|
|
// Handle the case media is muted
|
|
if (tmp == "0.0.0.0")
|
|
tmp.clear();
|
|
addr = tmp;
|
|
}
|
|
}
|
|
Lock lock(this);
|
|
ObjList* lst = 0;
|
|
bool defcodecs = m_codecs.getBoolValue("default",true);
|
|
c = sdp.getLine("m");
|
|
for (; c; c = sdp.getNextLine(c)) {
|
|
String tmp(*c);
|
|
int sep = tmp.find(' ');
|
|
if (sep < 1)
|
|
continue;
|
|
String type = tmp.substr(0,sep);
|
|
tmp >> " ";
|
|
if (media && (type != media))
|
|
continue;
|
|
int port = 0;
|
|
tmp >> port >> " ";
|
|
sep = tmp.find(' ');
|
|
if (sep < 1)
|
|
continue;
|
|
bool rtp = true;
|
|
String trans(tmp,sep);
|
|
tmp = tmp.c_str() + sep;
|
|
if ((trans &= "RTP/AVP") || (trans &= "RTP/SAVP") ||
|
|
(trans &= "RTP/AVPF") || (trans &= "RTP/SAVPF"))
|
|
trans.toUpper();
|
|
else if ((trans &= "udptl") || (trans &= "tcp")) {
|
|
trans.toLower();
|
|
rtp = false;
|
|
}
|
|
else {
|
|
Debug(this,DebugWarn,"Unknown SDP transport '%s' for media '%s'",
|
|
trans.c_str(),type.c_str());
|
|
continue;
|
|
}
|
|
String fmt;
|
|
String aux;
|
|
String mappings;
|
|
String crypto;
|
|
ObjList params;
|
|
bool first = true;
|
|
int ptime = 0;
|
|
int rfc2833 = -1;
|
|
while (tmp[0] == ' ') {
|
|
int var = -1;
|
|
tmp >> " " >> var;
|
|
if (var < 0) {
|
|
if (rtp || fmt || aux || tmp.null())
|
|
continue;
|
|
// brutal but effective
|
|
for (char* p = const_cast<char*>(tmp.c_str()); *p; p++) {
|
|
if (*p == ' ')
|
|
*p = ',';
|
|
}
|
|
Debug(this,DebugInfo,"Assuming format list '%s' for media '%s'",
|
|
tmp.c_str(),type.c_str());
|
|
fmt = tmp;
|
|
tmp.clear();
|
|
}
|
|
int mode = 0;
|
|
bool annexB = m_codecs.getBoolValue("g729_annexb",false);
|
|
bool amrOctet = m_codecs.getBoolValue("amr_octet",false);
|
|
int defmap = -1;
|
|
String payload(lookup(var,s_payloads));
|
|
|
|
const ObjList* l = sdp.lines().find(c);
|
|
while (l && (l = l->skipNext())) {
|
|
const NamedString* s = static_cast<NamedString*>(l->get());
|
|
if (s->name() == "m")
|
|
break;
|
|
if (s->name() != "a")
|
|
continue;
|
|
String line(*s);
|
|
if (line.startSkip("ptime:",false))
|
|
line >> ptime;
|
|
else if (line.startSkip("rtpmap:",false)) {
|
|
int num = var - 1;
|
|
line >> num >> " ";
|
|
if (num == var) {
|
|
line.trimBlanks().toUpper();
|
|
if (line.startsWith("G729B/")) {
|
|
// some devices add a second map for same payload
|
|
annexB = true;
|
|
continue;
|
|
}
|
|
if (line.startsWith("TELEPHONE-EVENT/")) {
|
|
rfc2833 = var;
|
|
continue;
|
|
}
|
|
const char* pload = 0;
|
|
for (const TokenDict* map = s_rtpmap; map->token; map++) {
|
|
if (line.startsWith(map->token,false,true)) {
|
|
defmap = map->value;
|
|
pload = lookup(defmap,s_payloads);
|
|
break;
|
|
}
|
|
}
|
|
payload = pload;
|
|
}
|
|
}
|
|
else if (line.startSkip("fmtp:",false)) {
|
|
int num = var - 1;
|
|
line >> num >> " ";
|
|
if (num == var) {
|
|
if (line.startSkip("mode=",false))
|
|
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) {
|
|
if (line.startSkip("crypto:",false)) {
|
|
if (crypto.null())
|
|
crypto = line;
|
|
else
|
|
Debug(this,DebugMild,"Ignoring SDES: '%s'",line.c_str());
|
|
}
|
|
else {
|
|
int pos = line.find(':');
|
|
if (pos >= 0)
|
|
params.append(new NamedString(line.substr(0,pos),line.substr(pos+1)));
|
|
else
|
|
params.append(new NamedString(line));
|
|
}
|
|
}
|
|
}
|
|
if (var < 0)
|
|
break;
|
|
first = false;
|
|
|
|
if (payload == "ilbc") {
|
|
const char* forced = m_hacks.getValue("ilbc_forced");
|
|
if (forced)
|
|
payload = forced;
|
|
else if ((mode == 20) || (ptime == 20))
|
|
payload = "ilbc20";
|
|
else if ((mode == 30) || (ptime == 30))
|
|
payload = "ilbc30";
|
|
else
|
|
payload = m_hacks.getValue("ilbc_default","ilbc30");
|
|
}
|
|
|
|
if (amrOctet && payload == "amr")
|
|
payload = "amr-o";
|
|
|
|
XDebug(this,DebugAll,"Payload %d format '%s'",var,payload.c_str());
|
|
if (payload && m_codecs.getBoolValue(payload,defcodecs && DataTranslator::canConvert(payload))) {
|
|
if (fmt)
|
|
fmt << ",";
|
|
fmt << payload;
|
|
if (var != defmap) {
|
|
if (mappings)
|
|
mappings << ",";
|
|
mappings << payload << "=" << var;
|
|
}
|
|
if ((payload == "g729") && m_hacks.getBoolValue("g729_annexb",annexB))
|
|
aux << ",g729b";
|
|
}
|
|
}
|
|
fmt += aux;
|
|
DDebug(this,DebugAll,"Formats '%s' mappings '%s'",fmt.c_str(),mappings.c_str());
|
|
SDPMedia* net = 0;
|
|
// try to take the media descriptor from the old list
|
|
if (oldMedia) {
|
|
ObjList* om = oldMedia->find(type);
|
|
if (om)
|
|
net = static_cast<SDPMedia*>(om->remove(false));
|
|
}
|
|
bool append = false;
|
|
if (net)
|
|
net->update(fmt,port);
|
|
else {
|
|
net = new SDPMedia(type,trans,fmt,port);
|
|
append = true;
|
|
}
|
|
while (NamedString* par = static_cast<NamedString*>(params.remove(false)))
|
|
net->parameter(par,append);
|
|
net->setModified(false);
|
|
net->mappings(mappings);
|
|
net->rfc2833(rfc2833);
|
|
net->crypto(crypto,true);
|
|
if (!lst)
|
|
lst = new ObjList;
|
|
lst->append(net);
|
|
// found media - get out
|
|
if (media)
|
|
break;
|
|
}
|
|
return lst;
|
|
}
|
|
|
|
// Update configuration
|
|
void SDPParser::initialize(const NamedList* codecs, const NamedList* hacks, const NamedList* general)
|
|
{
|
|
Lock lock(this);
|
|
m_codecs.clear();
|
|
m_hacks.clear();
|
|
if (codecs)
|
|
m_codecs.copyParams(*codecs);
|
|
if (hacks)
|
|
m_hacks.copyParams(*hacks);
|
|
bool defcodecs = m_codecs.getBoolValue("default",true);
|
|
m_audioFormats = "";
|
|
String audio = "audio";
|
|
for (const TokenDict* dict = s_payloads; dict->token; dict++) {
|
|
DataFormat fmt(dict->token);
|
|
const FormatInfo* info = fmt.getInfo();
|
|
if (info && (audio == info->type)) {
|
|
if (m_codecs.getBoolValue(fmt,defcodecs && DataTranslator::canConvert(fmt)))
|
|
m_audioFormats.append(fmt,",");
|
|
}
|
|
}
|
|
if (!m_audioFormats) {
|
|
Debug(this,DebugWarn,"No default audio codecs, using defaults");
|
|
m_audioFormats = "alaw,mulaw";
|
|
}
|
|
DDebug(this,DebugNote,"Default audio codecs: %s",m_audioFormats.c_str());
|
|
m_ignorePort = m_hacks.getBoolValue("ignore_sdp_port",false);
|
|
m_rfc2833 = true;
|
|
m_secure = false;
|
|
m_sdpForward = false;
|
|
if (general) {
|
|
m_rfc2833 = general->getBoolValue("rfc2833",m_rfc2833);
|
|
m_secure = general->getBoolValue("secure",m_secure);
|
|
m_sdpForward = general->getBoolValue("forward_sdp",m_sdpForward);
|
|
}
|
|
}
|
|
|
|
}; // namespace TelEngine
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|