C++ version of the sdp-transform JavaScript library exposing the same API.
libsdptransform is a simple parser and writer of SDP. Defines internal grammar based on RFC4566 - SDP, RFC5245 - ICE, and many more.
Once installed (see Installation below):
#include "sdptransform/sdptransform.hpp"
The libsdptransform API is exposed in the sdptransform
C++ namespace.
libsdptransform integrates the JSON for Modern C++ library and exposes it under the json
C++ namespace.
It's important to recall that this is not JavaScript but C++. Operations that are safe on a JavaScript Object
may not be safe in a C++ JSON object.
So, before reading a JSON value, make sure that its corresponding key
does exist and also check its type (int
, std::string
, nullptr
, etc.) before assigning it to a C++ variable.
- For example, assuming that the parsed SDP
session
does NOT have as=
line (name), the following code would crash:
std::string sdpName = session.at("name");
// =>
// terminating with uncaught exception of type nlohmann::detail::out_of_range:
// [json.exception.out_of_range.403] key 'name' not found
- The safe way is:
std::string sdpName;
if (session.find("name") != session.end())
{
// NOTE: The API guarantees that the SDP name is a string (otherwise this
// would crash).
sdpName = session.at("name");
}
- And a more efficient way is:
std::string sdpName;
auto it = session.find("name");
if (it != session.end())
{
// NOTE: The API guarantees that the SDP name is a string (otherwise this
// would crash).
sdpName = it->get<std::string>();
// or just:
sdpName = *it;
}
-
Also, as in C++ maps, using the
[]
operator on a JSON object for reading the value of a givenkey
will insert such akey
in thejson
object with valuenullptr
if it did not exist before. -
So, when using
parseParams()
orparseImageAttributes()
exposed API, the application should do some checks before reading a value of a supposed type. So, for instance, let's assume that the firsta=fmtp
line in avideo
media section isa=fmtp:97 profile-level-id=4d0028;packetization-mode=1
. The safe way to read its values is:
auto h264Fmtp = sdptransform::parseParams(video.at("fmtp")[0].at("config"));
std::string profileLevelId;
int packetizationMode;
if (
h264Fmtp.find("profile-level-id") != h264Fmtp.end() &&
h264Fmtp["profile-level-id"].is_string()
)
{
profileLevelId = h264Fmtp.at("profile-level-id");
}
if (
h264Fmtp.find("packetization-mode") != h264Fmtp.end() &&
h264Fmtp["packetization-mode"].is_number_unsigned()
)
{
packetizationMode = h264Fmtp.at("packetization-mode");
}
- And much more efficient:
auto h264Fmtp = sdptransform::parseParams(video.at("fmtp")[0].at("config"));
std::string profileLevelId;
int packetizationMode;
auto profileLevelIdIterator = h264Fmtp.find("profile-level-id");
if (
profileLevelIdIterator != h264Fmtp.end() &&
profileLevelIdIterator->is_string()
)
{
profileLevelId = *profileLevelIdIterator;
}
auto packetizationModeIterator = h264Fmtp.find("packetization-mode");
if (
packetizationModeIterator != h264Fmtp.end() &&
packetizationModeIterator->is_number_unsigned()
)
{
packetizationMode = *packetizationModeIterator;
}
It's strongly recommended to read the JSON documentation and, before reading a parsed SDP, check whether the desired field exists and it has the desired type (string, integer, float, etc).
json parse(const std::string& sdp)
Parses an unprocessed SDP string and returns a JSON object. SDP lines can be terminated on \r\n
(as per specification) or just \n
.
The syntax of the generated SDP object and each SDP line is documented here.
std::string sdpStr = R"(v=0
o=- 20518 0 IN IP4 203.0.113.1
s=
t=0 0
c=IN IP4 203.0.113.1
a=ice-ufrag:F7gI
a=ice-pwd:x9cml/YzichV2+XlhiMu8g
a=fingerprint:sha-1 42:89:c5:c6:55:9d:6e:c8:e8:83:55:2a:39:f9:b6:eb:e9:a3:a9:e7
m=audio 54400 RTP/SAVPF 0 96
a=rtpmap:0 PCMU/8000
a=rtpmap:96 opus/48000
a=ptime:20
a=sendrecv
a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
a=candidate:1 2 UDP 2113667326 203.0.113.1 54401 typ host
m=video 55400 RTP/SAVPF 97 98
a=rtcp-fb:* nack
a=rtpmap:97 H264/90000
a=fmtp:97 profile-level-id=4d0028;packetization-mode=1
a=rtcp-fb:97 trr-int 100
a=rtcp-fb:97 nack rpsi
a=rtpmap:98 VP8/90000
a=rtcp-fb:98 trr-int 100
a=rtcp-fb:98 nack rpsi
a=sendrecv
a=candidate:0 1 UDP 2113667327 203.0.113.1 55400 typ host
a=candidate:1 2 UDP 2113667326 203.0.113.1 55401 typ host
a=ssrc:1399694169 foo:bar
a=ssrc:1399694169 baz
)";
json session = sdptransform::parse(sdpStr);
Resulting session
is a JSON object as follows:
{
"connection": {
"ip": "203.0.113.1",
"version": 4
},
"fingerprint": {
"hash": "42:89:c5:c6:55:9d:6e:c8:e8:83:55:2a:39:f9:b6:eb:e9:a3:a9:e7",
"type": "sha-1"
},
"icePwd": "x9cml/YzichV2+XlhiMu8g",
"iceUfrag": "F7gI",
"media": [
{
"candidates": [
{
"component": 1,
"foundation": "0",
"ip": "203.0.113.1",
"port": 54400,
"priority": 2113667327,
"transport": "UDP",
"type": "host"
},
{
"component": 2,
"foundation": "1",
"ip": "203.0.113.1",
"port": 54401,
"priority": 2113667326,
"transport": "UDP",
"type": "host"
}
],
"direction": "sendrecv",
"fmtp": [],
"payloads": "0 96",
"port": 54400,
"protocol": "RTP/SAVPF",
"ptime": 20,
"rtp": [
{
"codec": "PCMU",
"payload": 0,
"rate": 8000
},
{
"codec": "opus",
"payload": 96,
"rate": 48000
}
],
"type": "audio"
},
{
"candidates": [
{
"component": 1,
"foundation": "0",
"ip": "203.0.113.1",
"port": 55400,
"priority": 2113667327,
"transport": "UDP",
"type": "host"
},
{
"component": 2,
"foundation": "1",
"ip": "203.0.113.1",
"port": 55401,
"priority": 2113667326,
"transport": "UDP",
"type": "host"
}
],
"direction": "sendrecv",
"fmtp": [
{
"config": "profile-level-id=4d0028;packetization-mode=1",
"payload": 97
}
],
"payloads": "97 98",
"port": 55400,
"protocol": "RTP/SAVPF",
"rtcpFb": [
{
"payload": "*",
"type": "nack"
},
{
"payload": "97",
"subtype": "rpsi",
"type": "nack"
},
{
"payload": "98",
"subtype": "rpsi",
"type": "nack"
}
],
"rtcpFbTrrInt": [
{
"payload": "97",
"value": 100
},
{
"payload": "98",
"value": 100
}
],
"rtp": [
{
"codec": "H264",
"payload": 97,
"rate": 90000
},
{
"codec": "VP8",
"payload": 98,
"rate": 90000
}
],
"ssrcs": [
{
"attribute": "foo",
"id": 1399694169,
"value": "bar"
},
{
"attribute": "baz",
"id": 1399694169
}
],
"type": "video"
}
],
"name": "",
"origin": {
"address": "203.0.113.1",
"ipVer": 4,
"netType": "IN",
"sessionId": 20518,
"sessionVersion": 0,
"username": "-"
},
"timing": {
"start": 0,
"stop": 0
},
"version": 0
}
No excess parsing is done to the raw strings because the writer is built to be the inverse of the parser. That said, a few helpers have been built in:
json parseParams(const std::string& str)
Parses fmtp.at("config")
and others such as rid.at("params")
and returns an object with all the params in a key/value fashion.
NOTE: The type of each value is auto-detected, so it can be a string, integer or float. Do NOT assume the type of a value! (read the This is not JavaScript! section above).
// a=fmtp:97 profile-level-id=4d0028;packetization-mode=1
json params =
sdptransform::parseParams(session.at("media")[1].at("fmtp")[0].at("config"));
Resulting params
is a JSON object as follows:
{
"packetization-mode": 1,
"profile-level-id": "4d0028"
}
std::vector<int> parsePayloads(const std::string& str)
Returns a vector with all the payload advertised in the corresponding m-line.
// m=video 55400 RTP/SAVPF 97 98
json payloads =
sdptransform::parsePayloads(session.at("media")[1].at("payloads"));
Resulting payloads
is a C++ vector of int
elements as follows:
[ 97, 98 ]
json parseImageAttributes(const std::string& str)
Parses Generic Image Attributes. Must be provided with the attrs1
or attrs2
string of a a=imageattr
line. Returns an array of key/value objects.
NOTE: The type of each value is auto-detected, so it can be a string, integer or float. Do NOT assume the type of a value! (read the This is not JavaScript! section above).
// a=imageattr:97 send [x=1280,y=720] recv [x=1280,y=720] [x=320,y=180]
std::string imageAttributesStr = "[x=1280,y=720] [x=320,y=180]";
json imageAttributes = sdptransform::parseImageAttributes(imageAttributesStr);
Resulting imageAttributes
is a JSON array as follows:
[
{ "x": 1280, "y": 720 },
{ "x": 320, "y": 180 }
]
json parseSimulcastStreamList(const std::string& str)
Parses simulcast streams/formats. Must be provided with the attrs1
or attrs2
string of the a=simulcast
line.
Returns an array of simulcast streams. Each entry is an array of alternative simulcast formats, which are objects with two keys:
scid
: Simulcast identifier (string)paused
: Whether the simulcast format is paused (boolean)
// // a=simulcast:send 1,~4;2;3 recv c
std::string simulcastAttributesStr = "1,~4;2;3";
json simulcastAttributes =
sdptransform::parseSimulcastStreamList(simulcastAttributesStr);
Resulting simulcastAttributes
is a JSON array as follows:
[
[ { "scid": "1", "paused": false }, { "scid": "4", "paused": true } ],
[ { "scid": "2", "paused": false } ],
[ { "scid": "3", "paused": false } ]
]
std::string write(json& session)
The writer is the inverse of the parser, and will need a struct equivalent to the one returned by it.
std::string newSdpStr = sdptransform::write(session); // session parsed above
Resulting newSdpStr
is a string as follows:
v=0
o=- 20518 0 IN IP4 203.0.113.1
s=
c=IN IP4 203.0.113.1
t=0 0
a=ice-ufrag:F7gI
a=ice-pwd:x9cml/YzichV2+XlhiMu8g
a=fingerprint:sha-1 42:89:c5:c6:55:9d:6e:c8:e8:83:55:2a:39:f9:b6:eb:e9:a3:a9:e7
m=audio 54400 RTP/SAVPF 0 96
a=rtpmap:0 PCMU/8000
a=rtpmap:96 opus/48000
a=ptime:20
a=sendrecv
a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
a=candidate:1 2 UDP 2113667326 203.0.113.1 54401 typ host
m=video 55400 RTP/SAVPF 97 98
a=rtpmap:97 H264/90000
a=rtpmap:98 VP8/90000
a=fmtp:97 profile-level-id=4d0028;packetization-mode=1
a=rtcp-fb:97 trr-int 100
a=rtcp-fb:98 trr-int 100
a=rtcp-fb:* nack
a=rtcp-fb:97 nack rpsi
a=rtcp-fb:98 nack rpsi
a=sendrecv
a=candidate:0 1 UDP 2113667327 203.0.113.1 55400 typ host
a=candidate:1 2 UDP 2113667326 203.0.113.1 55401 typ host
a=ssrc:1399694169 foo:bar
a=ssrc:1399694169 baz
The only thing different from the original input is we follow the order specified by the SDP RFC, and we will always do so.
git clone https://github.com/ibc/libsdptransform.git
cd libsdptransform/
cmake . -Bbuild
make install -C build/ # or: cd build/ && make install
Depending on the host, it will generate the following static lib and header files:
-- Installing: /usr/local/lib/libsdptransform.a
-- Up-to-date: /usr/local/include/sdptransform/sdptransform.hpp
-- Up-to-date: /usr/local/include/sdptransform/json.hpp
- Build the lib:
$ cmake . -Bbuild
- Run test units:
$ ./scripts/test.sh
Iñaki Baz Castillo [website|github]
Special thanks to Eirik Albrigtsen, the author of the sdp-transform JavaScript library.