/** * 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-2014 Null Team * * 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 namespace TelEngine { /* * SDPParser */ // Yate Payloads for the AV profile const TokenDict SDPParser::s_payloads[] = { { "mulaw", 0 }, { "alaw", 8 }, { "gsm", 3 }, { "lpc10", 7 }, { "2*slin", 10 }, { "slin", 11 }, { "g726", 2 }, { "g722/16000", 9 }, { "g722", 9 }, { "g723", 4 }, { "g728", 15 }, { "g729", 18 }, { "mpa", 14 }, { "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 }, { "isac/16000", 105 }, { "isac/32000", 106 }, { "gsm-efr", 107 }, { "mjpeg", 26 }, { "h261", 31 }, { "h263", 34 }, { "h263-1998", 111 }, { "h263-2000", 112 }, { "h264", 114 }, { "vp8", 113 }, { "vp9", 115 }, { "mpv", 32 }, { "mp2t", 33 }, { "mp4v", 110 }, // Stereo { "2*mulaw", 117 }, { "2*alaw", 118 }, { 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/2", 10 }, { "L16/8000", 11 }, { "G726-32/8000", 2 }, { "G722/8000", 9 }, { "G723/8000", 4 }, { "G728/8000", 15 }, { "G729/8000", 18 }, { "G729A/8000", 18 }, { "MPA/90000", 14 }, { "iLBC/8000", 98 }, { "AMR/8000", 96 }, { "AMR-WB/16000", 99 }, { "SPEEX/8000", 102 }, { "SPEEX/16000", 103 }, { "SPEEX/32000", 104 }, { "iSAC/16000", 105 }, { "iSAC/32000", 106 }, { "GSM-EFR/8000", 107 }, { "JPEG/90000", 26 }, { "H261/90000", 31 }, { "H263/90000", 34 }, { "H263-1998/90000", 111 }, { "H263-2000/90000", 112 }, { "H264/90000", 114 }, { "VP8/90000", 113 }, { "VP9/90000", 115 }, { "MPV/90000", 32 }, { "MP2T/90000", 33 }, { "MP4V-ES/90000", 110 }, // Stereo, dynamic payloads { "PCMU/8000/2", 117 }, { "PCMA/8000/2", 118 }, { 0, 0 }, }; enum SdpFormat { SdpFmtUnknown = 0, SdpIlbc, SdpG729, SdpAmr }; // We need to check parameters (fmtp line) for these formats to detect variants static const TokenDict s_sdpFmtParamsCheck[] = { { "g729", SdpG729 }, { "ilbc", SdpIlbc }, { "ilbc20", SdpIlbc }, { "ilbc30", SdpIlbc }, { "amr", SdpAmr }, { "amr-o", SdpAmr }, { "amr/16000", SdpAmr }, { "amr-o/16000", SdpAmr }, { 0, 0 }, }; // Utility used in SDPParser::parse // Retrieve a SDP line 'param' contents // Trim contents spaces static inline String& getPayloadLine(String& buf, ObjList& list, int payload, const char* param) { ObjList* o = list.skipNull(); if (!o) return buf; String match; match << param << payload; for (; o; o = o->skipNext()) { const NamedString* ns = static_cast(o->get()); if (!ns->startsWith(match,true)) continue; buf = ns->substr(match.length() + 1); buf.trimSpaces(); return buf; } return buf; } // Parse a received SDP body ObjList* SDPParser::parse(const MimeSdpBody& sdp, String& addr, ObjList* oldMedia, const String& media, bool force) { DDebug(DebugAll,"SDPParser::parse(%p,%s,%p,'%s',%s)", &sdp,addr.c_str(),oldMedia,media.safe(),String::boolText(force)); 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 == SocketAddr::ipv4NullAddr()) tmp.clear(); addr = tmp; } else if (tmp.startSkip("IN IP6")) { tmp.trimBlanks(); // Handle the case media is muted if (tmp == SocketAddr::ipv6NullAddr()) 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 if (!force) { 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 dups; ObjList params; ObjList* dest = ¶ms; bool first = true; int ptime = 0; int rfc2833 = -1; // Remember format related lines ObjList fmtLines; ObjList* fmtA = &fmtLines; ObjList* o = sdp.lines().find(c); for (o ? o = o->skipNext() : 0; o; o = o->skipNext()) { const NamedString* ns = static_cast(o->get()); if (ns->name() == YSTRING("a")) { if (ns->startsWith("fmtp:") || ns->startsWith("gpmd:")) { fmtA = fmtA->append(ns); fmtA->setDelete(false); } } else if (ns->name() == YSTRING("m")) break; } 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(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(l->get()); if (s->name() == "m") break; if (s->name() == "b") { if (first) { int pos = s->find(':'); if (pos >= 0) dest = dest->append(new NamedString("BW-" + s->substr(0,pos),s->substr(pos+1))); else dest = dest->append(new NamedString("BW-" + *s)); } continue; } if (s->name() != "a") continue; if (s->startsWith("fmtp:") || s->startsWith("gpmd:")) 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; payload.clear(); 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; if (amrOctet) { if (payload == YSTRING("amr")) payload = "amr-o"; else if (payload == YSTRING("amr/16000")) payload = "amr-o/16000"; } } } 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) dest = dest->append(new NamedString(line.substr(0,pos),line.substr(pos+1))); else dest = dest->append(new NamedString(line)); } } } if (var < 0) break; first = false; String fmtp; if (payload) { // Process 'fmtp' lines: they may change payload name if (getPayloadLine(fmtp,fmtLines,var,"fmtp:")) { bool found = false; ObjList* fmtps = 0; int checkParams = lookup(payload,s_sdpFmtParamsCheck); if (checkParams) { fmtps = fmtp.split(';',false); for (ObjList* o = fmtps->skipNull(); o; o = o->skipNext()) { String& s = *static_cast(o->get()); switch (checkParams) { case SdpIlbc: if (!s.startSkip("mode=",false)) continue; s >> mode; break; case SdpG729: if (!s.startSkip("annexb=",false)) continue; s >> annexB; break; case SdpAmr: if (s.startSkip("octet-align=",false)) { int val = 0; s >> val; amrOctet = (0 != val); if (amrOctet) { if (payload == YSTRING("amr")) payload = "amr-o"; else if (payload == YSTRING("amr/16000")) payload = "amr-o/16000"; } else if (payload == YSTRING("amr-o")) payload = "amr"; else if (payload == YSTRING("amr-o/16000")) payload = "amr/16000"; break; } continue; default: continue; } found = true; o->set(0); // Done: we are searching for a single parameter break; } } XDebug(this,DebugAll,"%s fmtp '%s' (%d) '%s'", (checkParams ? (found ? "Found" : "Checked") : "Parsed"), payload.c_str(),var,fmtp.c_str()); if (found) { fmtp.clear(); if (fmtps && fmtps->skipNull()) fmtp.append(fmtps,";"); } TelEngine::destruct(fmtps); } if (payload == YSTRING("ilbc")) { const char* forced = m_hacks.getValue(YSTRING("ilbc_forced")); if (forced) payload = forced; else if (mode == 20) payload = "ilbc20"; else if (mode == 30) payload = "ilbc30"; else if ((ptime % 30) && !(ptime % 20)) payload = "ilbc20"; else if ((ptime % 20) && !(ptime % 30)) payload = "ilbc30"; else payload = m_hacks.getValue(YSTRING("ilbc_default"),"ilbc30"); } } XDebug(this,DebugAll,"Payload %d format '%s'%s",var,payload.c_str(), (dups.find(payload) ? " (duplicated)" : "")); if (payload.null() || dups.find(payload)) continue; dups.append(new String(payload)); if (m_codecs.getBoolValue(payload,defcodecs && DataTranslator::canConvert(payload))) { if (fmt) fmt << ","; fmt << payload; if (var != defmap) { if (mappings) mappings << ","; mappings << payload << "=" << var; } String gpmd; if (getPayloadLine(gpmd,fmtLines,var,"gpmd:")) { XDebug(this,DebugAll,"Found 'gpmd:%d' format='%s' value='%s'", var,payload.c_str(),gpmd.c_str()); dest = dest->append(new NamedString("gpmd:" + payload,gpmd)); } if (fmtp) dest = dest->append(new NamedString("fmtp:" + payload,fmtp)); if ((payload == "g729") && m_hacks.getBoolValue(YSTRING("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(om->remove(false)); } bool append = false; if (net) net->update(fmt,port,-1,force); else { net = new SDPMedia(type,trans,fmt,port); append = true; } while (NamedString* par = static_cast(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.clearParams(); m_hacks.clearParams(); if (codecs) m_codecs.copyParams(*codecs); if (hacks) m_hacks.copyParams(*hacks); bool defcodecs = m_codecs.getBoolValue("default",true); bool stereo = m_codecs.getBoolValue("default_stereo"); 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)) { bool defVal = (2 != info->numChannels) ? defcodecs : stereo; if (m_codecs.getBoolValue(fmt,defVal && DataTranslator::canConvert(fmt))) m_audioFormats.append(fmt,","); } } if (m_audioFormats) Debug(this,DebugAll,"Initialized audio codecs: %s",m_audioFormats.c_str()); else { m_audioFormats = "alaw,mulaw"; Debug(this,DebugWarn,"No default audio codecs, using defaults: %s", m_audioFormats.c_str()); } m_ignorePort = m_hacks.getBoolValue("ignore_sdp_port",false); m_rfc2833 = 101; m_secure = false; m_sdpForward = false; if (general) { if (general->getBoolValue("rfc2833",true)) { m_rfc2833 = general->getIntValue("rfc2833",m_rfc2833); if (m_rfc2833 < 96 || m_rfc2833 > 127) m_rfc2833 = 101; } else m_rfc2833 = -1; 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: */