mirror of
https://github.com/ossrs/srs.git
synced 2025-02-15 04:42:04 +00:00
1074 lines
29 KiB
C++
1074 lines
29 KiB
C++
/**
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2013-2020 John
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
* this software and associated documentation files (the "Software"), to deal in
|
|
* the Software without restriction, including without limitation the rights to
|
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
|
* subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <srs_app_rtc_sdp.hpp>
|
|
using namespace std;
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
#include <srs_kernel_error.hpp>
|
|
#include <srs_kernel_log.hpp>
|
|
|
|
// TODO: FIXME: Maybe we should use json.encode to escape it?
|
|
const std::string kCRLF = "\r\n";
|
|
|
|
#define FETCH(is,word) \
|
|
if (!(is >> word)) {\
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "fetch failed");\
|
|
}\
|
|
|
|
#define FETCH_WITH_DELIM(is,word,delim) \
|
|
if (!getline(is,word,delim)) {\
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "fetch with delim failed");\
|
|
}\
|
|
|
|
std::vector<std::string> split_str(const std::string& str, const std::string& delim)
|
|
{
|
|
std::vector<std::string> ret;
|
|
size_t pre_pos = 0;
|
|
std::string tmp;
|
|
size_t pos = 0;
|
|
do {
|
|
pos = str.find(delim, pre_pos);
|
|
tmp = str.substr(pre_pos, pos - pre_pos);
|
|
ret.push_back(tmp);
|
|
pre_pos = pos + delim.size();
|
|
} while (pos != std::string::npos);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void skip_first_spaces(std::string& str)
|
|
{
|
|
while (! str.empty() && str[0] == ' ') {
|
|
str.erase(0, 1);
|
|
}
|
|
}
|
|
|
|
srs_error_t srs_parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264_param)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
std::vector<std::string> vec = split_str(fmtp, ";");
|
|
for (size_t i = 0; i < vec.size(); ++i) {
|
|
std::vector<std::string> kv = split_str(vec[i], "=");
|
|
if (kv.size() == 2) {
|
|
if (kv[0] == "profile-level-id") {
|
|
h264_param.profile_level_id = kv[1];
|
|
} else if (kv[0] == "packetization-mode") {
|
|
// 6.3. Non-Interleaved Mode
|
|
// This mode is in use when the value of the OPTIONAL packetization-mode
|
|
// media type parameter is equal to 1. This mode SHOULD be supported.
|
|
// It is primarily intended for low-delay applications. Only single NAL
|
|
// unit packets, STAP-As, and FU-As MAY be used in this mode. STAP-Bs,
|
|
// MTAPs, and FU-Bs MUST NOT be used. The transmission order of NAL
|
|
// units MUST comply with the NAL unit decoding order.
|
|
// @see https://tools.ietf.org/html/rfc6184#section-6.3
|
|
h264_param.packetization_mode = kv[1];
|
|
} else if (kv[0] == "level-asymmetry-allowed") {
|
|
h264_param.level_asymmerty_allow = kv[1];
|
|
} else {
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid h264 param=%s", kv[0].c_str());
|
|
}
|
|
} else {
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid h264 param=%s", vec[i].c_str());
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
SrsSessionInfo::SrsSessionInfo()
|
|
{
|
|
}
|
|
|
|
SrsSessionInfo::~SrsSessionInfo()
|
|
{
|
|
}
|
|
|
|
srs_error_t SrsSessionInfo::parse_attribute(const std::string& attribute, const std::string& value)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
if (attribute == "ice-ufrag") {
|
|
ice_ufrag_ = value;
|
|
} else if (attribute == "ice-pwd") {
|
|
ice_pwd_ = value;
|
|
} else if (attribute == "ice-options") {
|
|
ice_options_ = value;
|
|
} else if (attribute == "fingerprint") {
|
|
std::istringstream is(value);
|
|
FETCH(is, fingerprint_algo_);
|
|
FETCH(is, fingerprint_);
|
|
} else if (attribute == "setup") {
|
|
// @see: https://tools.ietf.org/html/rfc4145#section-4
|
|
setup_ = value;
|
|
} else {
|
|
srs_trace("ignore attribute=%s, value=%s", attribute.c_str(), value.c_str());
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsSessionInfo::encode(std::ostringstream& os)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
if (!ice_ufrag_.empty()) {
|
|
os << "a=ice-ufrag:" << ice_ufrag_ << kCRLF;
|
|
}
|
|
|
|
if (!ice_pwd_.empty()) {
|
|
os << "a=ice-pwd:" << ice_pwd_ << kCRLF;
|
|
}
|
|
|
|
// For ICE-lite, we never set the trickle.
|
|
if (!ice_options_.empty()) {
|
|
os << "a=ice-options:" << ice_options_ << kCRLF;
|
|
}
|
|
|
|
if (!fingerprint_algo_.empty() && ! fingerprint_.empty()) {
|
|
os << "a=fingerprint:" << fingerprint_algo_ << " " << fingerprint_ << kCRLF;
|
|
}
|
|
|
|
if (!setup_.empty()) {
|
|
os << "a=setup:" << setup_ << kCRLF;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
bool SrsSessionInfo::operator=(const SrsSessionInfo& rhs)
|
|
{
|
|
return ice_ufrag_ == rhs.ice_ufrag_ &&
|
|
ice_pwd_ == rhs.ice_pwd_ &&
|
|
ice_options_ == rhs.ice_options_ &&
|
|
fingerprint_algo_ == rhs.fingerprint_algo_ &&
|
|
fingerprint_ == rhs.fingerprint_ &&
|
|
setup_ == rhs.setup_;
|
|
}
|
|
|
|
SrsSSRCInfo::SrsSSRCInfo()
|
|
{
|
|
ssrc_ = 0;
|
|
}
|
|
|
|
SrsSSRCInfo::SrsSSRCInfo(uint32_t ssrc, std::string cname, std::string stream_id, std::string track_id)
|
|
{
|
|
ssrc_ = ssrc;
|
|
cname_ = cname;
|
|
msid_ = stream_id;
|
|
msid_tracker_ = track_id;
|
|
mslabel_ = msid_;
|
|
label_ = msid_tracker_;
|
|
}
|
|
|
|
SrsSSRCInfo::~SrsSSRCInfo()
|
|
{
|
|
}
|
|
|
|
srs_error_t SrsSSRCInfo::encode(std::ostringstream& os)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
if (ssrc_ == 0) {
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid ssrc");
|
|
}
|
|
|
|
os << "a=ssrc:" << ssrc_ << " cname:" << cname_ << kCRLF;
|
|
if (!msid_.empty()) {
|
|
os << "a=ssrc:" << ssrc_ << " msid:" << msid_;
|
|
if (!msid_tracker_.empty()) {
|
|
os << " " << msid_tracker_;
|
|
}
|
|
os << kCRLF;
|
|
}
|
|
if (!mslabel_.empty()) {
|
|
os << "a=ssrc:" << ssrc_ << " mslabel:" << mslabel_ << kCRLF;
|
|
}
|
|
if (!label_.empty()) {
|
|
os << "a=ssrc:" << ssrc_ << " label:" << label_ << kCRLF;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
SrsSSRCGroup::SrsSSRCGroup()
|
|
{
|
|
}
|
|
|
|
SrsSSRCGroup::~SrsSSRCGroup()
|
|
{
|
|
}
|
|
|
|
SrsSSRCGroup::SrsSSRCGroup(const std::string& semantic, const std::vector<uint32_t>& ssrcs)
|
|
{
|
|
semantic_ = semantic;
|
|
ssrcs_ = ssrcs;
|
|
}
|
|
|
|
srs_error_t SrsSSRCGroup::encode(std::ostringstream& os)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
if (semantic_.empty()) {
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid semantics");
|
|
}
|
|
|
|
if (ssrcs_.size() == 0) {
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid ssrcs");
|
|
}
|
|
|
|
os << "a=ssrc-group:" << semantic_;
|
|
for (int i = 0; i < (int)ssrcs_.size(); i++) {
|
|
os << " " << ssrcs_[i];
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
SrsMediaPayloadType::SrsMediaPayloadType(int payload_type)
|
|
{
|
|
payload_type_ = payload_type;
|
|
clock_rate_ = 0;
|
|
}
|
|
|
|
SrsMediaPayloadType::~SrsMediaPayloadType()
|
|
{
|
|
}
|
|
|
|
srs_error_t SrsMediaPayloadType::encode(std::ostringstream& os)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
os << "a=rtpmap:" << payload_type_ << " " << encoding_name_ << "/" << clock_rate_;
|
|
if (!encoding_param_.empty()) {
|
|
os << "/" << encoding_param_;
|
|
}
|
|
os << kCRLF;
|
|
|
|
for (std::vector<std::string>::iterator iter = rtcp_fb_.begin(); iter != rtcp_fb_.end(); ++iter) {
|
|
os << "a=rtcp-fb:" << payload_type_ << " " << *iter << kCRLF;
|
|
}
|
|
|
|
if (!format_specific_param_.empty()) {
|
|
os << "a=fmtp:" << payload_type_ << " " << format_specific_param_
|
|
// TODO: FIXME: Remove the test code bellow.
|
|
// << ";x-google-max-bitrate=6000;x-google-min-bitrate=5100;x-google-start-bitrate=5000"
|
|
<< kCRLF;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
SrsMediaDesc::SrsMediaDesc(const std::string& type)
|
|
{
|
|
type_ = type;
|
|
|
|
port_ = 0;
|
|
rtcp_mux_ = false;
|
|
rtcp_rsize_ = false;
|
|
|
|
sendrecv_ = false;
|
|
recvonly_ = false;
|
|
sendonly_ = false;
|
|
inactive_ = false;
|
|
}
|
|
|
|
SrsMediaDesc::~SrsMediaDesc()
|
|
{
|
|
}
|
|
|
|
SrsMediaPayloadType* SrsMediaDesc::find_media_with_payload_type(int payload_type)
|
|
{
|
|
for (size_t i = 0; i < payload_types_.size(); ++i) {
|
|
if (payload_types_[i].payload_type_ == payload_type) {
|
|
return &payload_types_[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
vector<SrsMediaPayloadType> SrsMediaDesc::find_media_with_encoding_name(const std::string& encoding_name) const
|
|
{
|
|
std::vector<SrsMediaPayloadType> payloads;
|
|
|
|
std::string lower_name(encoding_name), upper_name(encoding_name);
|
|
transform(encoding_name.begin(), encoding_name.end(), lower_name.begin(), ::tolower);
|
|
transform(encoding_name.begin(), encoding_name.end(), upper_name.begin(), ::toupper);
|
|
|
|
for (size_t i = 0; i < payload_types_.size(); ++i) {
|
|
if (payload_types_[i].encoding_name_ == std::string(lower_name.c_str()) ||
|
|
payload_types_[i].encoding_name_ == std::string(upper_name.c_str())) {
|
|
payloads.push_back(payload_types_[i]);
|
|
}
|
|
}
|
|
|
|
return payloads;
|
|
}
|
|
|
|
srs_error_t SrsMediaDesc::parse_line(const std::string& line)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
std::string content = line.substr(2);
|
|
|
|
switch (line[0]) {
|
|
case 'a': {
|
|
return parse_attribute(content);
|
|
}
|
|
case 'c': {
|
|
// TODO: process c-line
|
|
break;
|
|
}
|
|
default: {
|
|
srs_trace("ignore media line=%s", line.c_str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsMediaDesc::encode(std::ostringstream& os)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
os << "m=" << type_ << " " << port_ << " " << protos_;
|
|
for (std::vector<SrsMediaPayloadType>::iterator iter = payload_types_.begin(); iter != payload_types_.end(); ++iter) {
|
|
os << " " << iter->payload_type_;
|
|
}
|
|
|
|
os << kCRLF;
|
|
|
|
// TODO:nettype and address type
|
|
os << "c=IN IP4 0.0.0.0" << kCRLF;
|
|
|
|
if ((err = session_info_.encode(os)) != srs_success) {
|
|
return srs_error_wrap(err, "encode session info failed");
|
|
}
|
|
|
|
os << "a=mid:" << mid_ << kCRLF;
|
|
if (!msid_.empty()) {
|
|
os << "a=msid:" << msid_;
|
|
|
|
if (!msid_tracker_.empty()) {
|
|
os << " " << msid_tracker_;
|
|
}
|
|
|
|
os << kCRLF;
|
|
}
|
|
|
|
for(map<int, string>::iterator it = extmaps_.begin(); it != extmaps_.end(); ++it) {
|
|
os << "a=extmap:"<< it->first<< " "<< it->second<< kCRLF;
|
|
}
|
|
if (sendonly_) {
|
|
os << "a=sendonly" << kCRLF;
|
|
}
|
|
if (recvonly_) {
|
|
os << "a=recvonly" << kCRLF;
|
|
}
|
|
if (sendrecv_) {
|
|
os << "a=sendrecv" << kCRLF;
|
|
}
|
|
if (inactive_) {
|
|
os << "a=inactive" << kCRLF;
|
|
}
|
|
|
|
if (rtcp_mux_) {
|
|
os << "a=rtcp-mux" << kCRLF;
|
|
}
|
|
|
|
if (rtcp_rsize_) {
|
|
os << "a=rtcp-rsize" << kCRLF;
|
|
}
|
|
|
|
for (std::vector<SrsMediaPayloadType>::iterator iter = payload_types_.begin(); iter != payload_types_.end(); ++iter) {
|
|
if ((err = iter->encode(os)) != srs_success) {
|
|
return srs_error_wrap(err, "encode media payload failed");
|
|
}
|
|
}
|
|
|
|
for (std::vector<SrsSSRCInfo>::iterator iter = ssrc_infos_.begin(); iter != ssrc_infos_.end(); ++iter) {
|
|
SrsSSRCInfo& ssrc_info = *iter;
|
|
|
|
if ((err = ssrc_info.encode(os)) != srs_success) {
|
|
return srs_error_wrap(err, "encode ssrc failed");
|
|
}
|
|
}
|
|
|
|
int foundation = 0;
|
|
int component_id = 1; /* RTP */
|
|
for (std::vector<SrsCandidate>::iterator iter = candidates_.begin(); iter != candidates_.end(); ++iter) {
|
|
// @see: https://tools.ietf.org/html/draft-ietf-ice-rfc5245bis-00#section-4.2
|
|
uint32_t priority = (1<<24)*(126) + (1<<8)*(65535) + (1)*(256 - component_id);
|
|
|
|
// @see: https://tools.ietf.org/id/draft-ietf-mmusic-ice-sip-sdp-14.html#rfc.section.5.1
|
|
os << "a=candidate:" << foundation++ << " "
|
|
<< component_id << " udp " << priority << " "
|
|
<< iter->ip_ << " " << iter->port_
|
|
<< " typ " << iter->type_
|
|
<< " generation 0" << kCRLF;
|
|
|
|
srs_verbose("local SDP candidate line=%s", os.str().c_str());
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsMediaDesc::parse_attribute(const std::string& content)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
std::string attribute = "";
|
|
std::string value = "";
|
|
size_t pos = content.find_first_of(":");
|
|
|
|
if (pos != std::string::npos) {
|
|
attribute = content.substr(0, pos);
|
|
value = content.substr(pos + 1);
|
|
} else {
|
|
attribute = content;
|
|
}
|
|
|
|
if (attribute == "extmap") {
|
|
return parse_attr_extmap(value);
|
|
} else if (attribute == "rtpmap") {
|
|
return parse_attr_rtpmap(value);
|
|
} else if (attribute == "rtcp") {
|
|
return parse_attr_rtcp(value);
|
|
} else if (attribute == "rtcp-fb") {
|
|
return parse_attr_rtcp_fb(value);
|
|
} else if (attribute == "fmtp") {
|
|
return parse_attr_fmtp(value);
|
|
} else if (attribute == "mid") {
|
|
return parse_attr_mid(value);
|
|
} else if (attribute == "msid") {
|
|
return parse_attr_msid(value);
|
|
} else if (attribute == "ssrc") {
|
|
return parse_attr_ssrc(value);
|
|
} else if (attribute == "ssrc-group") {
|
|
return parse_attr_ssrc_group(value);
|
|
} else if (attribute == "rtcp-mux") {
|
|
rtcp_mux_ = true;
|
|
} else if (attribute == "rtcp-rsize") {
|
|
rtcp_rsize_ = true;
|
|
} else if (attribute == "recvonly") {
|
|
recvonly_ = true;
|
|
} else if (attribute == "sendonly") {
|
|
sendonly_ = true;
|
|
} else if (attribute == "sendrecv") {
|
|
sendrecv_ = true;
|
|
} else if (attribute == "inactive") {
|
|
inactive_ = true;
|
|
} else {
|
|
return session_info_.parse_attribute(attribute, value);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
srs_error_t SrsMediaDesc::parse_attr_extmap(const std::string& value)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
std::istringstream is(value);
|
|
int id = 0;
|
|
FETCH(is, id);
|
|
if(extmaps_.end() != extmaps_.find(id)) {
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "duplicate ext id: %d", id);
|
|
}
|
|
string ext;
|
|
FETCH(is, ext);
|
|
extmaps_[id] = ext;
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsMediaDesc::parse_attr_rtpmap(const std::string& value)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
// @see: https://tools.ietf.org/html/rfc4566#page-25
|
|
// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
|
|
|
|
std::istringstream is(value);
|
|
|
|
int payload_type = 0;
|
|
FETCH(is, payload_type);
|
|
|
|
SrsMediaPayloadType* payload = find_media_with_payload_type(payload_type);
|
|
if (payload == NULL) {
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "can not find payload %d when pase rtpmap", payload_type);
|
|
}
|
|
|
|
std::string word;
|
|
FETCH(is, word);
|
|
|
|
std::vector<std::string> vec = split_str(word, "/");
|
|
if (vec.size() < 2) {
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid rtpmap line=%s", value.c_str());
|
|
}
|
|
|
|
payload->encoding_name_ = vec[0];
|
|
payload->clock_rate_ = atoi(vec[1].c_str());
|
|
|
|
if (vec.size() == 3) {
|
|
payload->encoding_param_ = vec[2];
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsMediaDesc::parse_attr_rtcp(const std::string& value)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
// TODO:parse rtcp attribute
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsMediaDesc::parse_attr_rtcp_fb(const std::string& value)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
// @see: https://tools.ietf.org/html/rfc5104#section-7.1
|
|
|
|
std::istringstream is(value);
|
|
|
|
int payload_type = 0;
|
|
FETCH(is, payload_type);
|
|
|
|
SrsMediaPayloadType* payload = find_media_with_payload_type(payload_type);
|
|
if (payload == NULL) {
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "can not find payload %d when pase rtcp-fb", payload_type);
|
|
}
|
|
|
|
std::string rtcp_fb = is.str().substr(is.tellg());
|
|
skip_first_spaces(rtcp_fb);
|
|
|
|
payload->rtcp_fb_.push_back(rtcp_fb);
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsMediaDesc::parse_attr_fmtp(const std::string& value)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
// @see: https://tools.ietf.org/html/rfc4566#page-30
|
|
// a=fmtp:<format> <format specific parameters>
|
|
|
|
std::istringstream is(value);
|
|
|
|
int payload_type = 0;
|
|
FETCH(is, payload_type);
|
|
|
|
SrsMediaPayloadType* payload = find_media_with_payload_type(payload_type);
|
|
if (payload == NULL) {
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "can not find payload %d when pase fmtp", payload_type);
|
|
}
|
|
|
|
std::string word;
|
|
FETCH(is, word);
|
|
|
|
payload->format_specific_param_ = word;
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsMediaDesc::parse_attr_mid(const std::string& value)
|
|
{
|
|
// @see: https://tools.ietf.org/html/rfc3388#section-3
|
|
srs_error_t err = srs_success;
|
|
std::istringstream is(value);
|
|
// mid_ means m-line id
|
|
FETCH(is, mid_);
|
|
srs_verbose("mid=%s", mid_.c_str());
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsMediaDesc::parse_attr_msid(const std::string& value)
|
|
{
|
|
// @see: https://tools.ietf.org/id/draft-ietf-mmusic-msid-08.html#rfc.section.2
|
|
// TODO: msid and msid_tracker
|
|
srs_error_t err = srs_success;
|
|
std::istringstream is(value);
|
|
// msid_ means media stream id
|
|
FETCH(is, msid_);
|
|
is >> msid_tracker_;
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsMediaDesc::parse_attr_ssrc(const std::string& value)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
// @see: https://tools.ietf.org/html/rfc5576#section-4.1
|
|
|
|
std::istringstream is(value);
|
|
|
|
uint32_t ssrc = 0;
|
|
FETCH(is, ssrc);
|
|
|
|
std::string ssrc_attr = "";
|
|
FETCH_WITH_DELIM(is, ssrc_attr, ':');
|
|
skip_first_spaces(ssrc_attr);
|
|
|
|
std::string ssrc_value = is.str().substr(is.tellg());
|
|
skip_first_spaces(ssrc_value);
|
|
|
|
SrsSSRCInfo& ssrc_info = fetch_or_create_ssrc_info(ssrc);
|
|
|
|
if (ssrc_attr == "cname") {
|
|
// @see: https://tools.ietf.org/html/rfc5576#section-6.1
|
|
ssrc_info.cname_ = ssrc_value;
|
|
ssrc_info.ssrc_ = ssrc;
|
|
} else if (ssrc_attr == "msid") {
|
|
// @see: https://tools.ietf.org/html/draft-alvestrand-mmusic-msid-00#section-2
|
|
std::vector<std::string> vec = split_str(ssrc_value, " ");
|
|
if (vec.empty()) {
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid ssrc line=%s", value.c_str());
|
|
}
|
|
|
|
ssrc_info.msid_ = vec[0];
|
|
if (vec.size() > 1) {
|
|
ssrc_info.msid_tracker_ = vec[1];
|
|
}
|
|
} else if (ssrc_attr == "mslabel") {
|
|
ssrc_info.mslabel_ = ssrc_value;
|
|
} else if (ssrc_attr == "label") {
|
|
ssrc_info.label_ = ssrc_value;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsMediaDesc::parse_attr_ssrc_group(const std::string& value)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
// @see: https://tools.ietf.org/html/rfc5576#section-4.2
|
|
// a=ssrc-group:<semantics> <ssrc-id> ...
|
|
|
|
std::istringstream is(value);
|
|
|
|
std::string semantics;
|
|
FETCH(is, semantics);
|
|
|
|
std::string ssrc_ids = is.str().substr(is.tellg());
|
|
skip_first_spaces(ssrc_ids);
|
|
|
|
std::vector<std::string> vec = split_str(ssrc_ids, " ");
|
|
if (vec.size() == 0) {
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid ssrc-group line=%s", value.c_str());
|
|
}
|
|
|
|
std::vector<uint32_t> ssrcs;
|
|
for (size_t i = 0; i < vec.size(); ++i) {
|
|
std::istringstream in_stream(vec[i]);
|
|
uint32_t ssrc = 0;
|
|
in_stream >> ssrc;
|
|
ssrcs.push_back(ssrc);
|
|
}
|
|
ssrc_groups_.push_back(SrsSSRCGroup(semantics, ssrcs));
|
|
|
|
return err;
|
|
}
|
|
|
|
SrsSSRCInfo& SrsMediaDesc::fetch_or_create_ssrc_info(uint32_t ssrc)
|
|
{
|
|
for (size_t i = 0; i < ssrc_infos_.size(); ++i) {
|
|
if (ssrc_infos_[i].ssrc_ == ssrc) {
|
|
return ssrc_infos_[i];
|
|
}
|
|
}
|
|
|
|
SrsSSRCInfo ssrc_info;
|
|
ssrc_info.ssrc_ = ssrc;
|
|
ssrc_infos_.push_back(ssrc_info);
|
|
|
|
return ssrc_infos_.back();
|
|
}
|
|
|
|
SrsSdp::SrsSdp()
|
|
{
|
|
in_media_session_ = false;
|
|
|
|
start_time_ = 0;
|
|
end_time_ = 0;
|
|
}
|
|
|
|
SrsSdp::~SrsSdp()
|
|
{
|
|
}
|
|
|
|
srs_error_t SrsSdp::parse(const std::string& sdp_str)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
// All webrtc SrsSdp annotated example
|
|
// @see: https://tools.ietf.org/html/draft-ietf-rtcweb-SrsSdp-11
|
|
// Sdp example
|
|
// session info
|
|
// v=
|
|
// o=
|
|
// s=
|
|
// t=
|
|
// media description
|
|
// m=
|
|
// a=
|
|
// ...
|
|
// media description
|
|
// m=
|
|
// a=
|
|
// ...
|
|
std::istringstream is(sdp_str);
|
|
std::string line;
|
|
while (getline(is, line)) {
|
|
srs_verbose("%s", line.c_str());
|
|
if (line.size() < 2 || line[1] != '=') {
|
|
return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid sdp line=%s", line.c_str());
|
|
}
|
|
if (!line.empty() && line[line.size()-1] == '\r') {
|
|
line.erase(line.size()-1, 1);
|
|
}
|
|
|
|
if ((err = parse_line(line)) != srs_success) {
|
|
return srs_error_wrap(err, "parse sdp line failed");
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsSdp::encode(std::ostringstream& os)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
os << "v=" << version_ << kCRLF;
|
|
os << "o=" << username_ << " " << session_id_ << " " << session_version_ << " " << nettype_ << " " << addrtype_ << " " << unicast_address_ << kCRLF;
|
|
os << "s=" << session_name_ << kCRLF;
|
|
os << "t=" << start_time_ << " " << end_time_ << kCRLF;
|
|
// ice-lite is a minimal version of the ICE specification, intended for servers running on a public IP address.
|
|
os << "a=ice-lite" << kCRLF;
|
|
|
|
if (!groups_.empty()) {
|
|
os << "a=group:" << group_policy_;
|
|
for (std::vector<std::string>::iterator iter = groups_.begin(); iter != groups_.end(); ++iter) {
|
|
os << " " << *iter;
|
|
}
|
|
os << kCRLF;
|
|
}
|
|
|
|
os << "a=msid-semantic: " << msid_semantic_;
|
|
for (std::vector<std::string>::iterator iter = msids_.begin(); iter != msids_.end(); ++iter) {
|
|
os << " " << *iter;
|
|
}
|
|
os << kCRLF;
|
|
|
|
if ((err = session_info_.encode(os)) != srs_success) {
|
|
return srs_error_wrap(err, "encode session info failed");
|
|
}
|
|
|
|
for (std::vector<SrsMediaDesc>::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) {
|
|
if ((err = (*iter).encode(os)) != srs_success) {
|
|
return srs_error_wrap(err, "encode media description failed");
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
const SrsMediaDesc* SrsSdp::find_media_desc(const std::string& type) const
|
|
{
|
|
for (std::vector<SrsMediaDesc>::const_iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) {
|
|
if (iter->type_ == type) {
|
|
return &(*iter);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void SrsSdp::set_ice_ufrag(const std::string& ufrag)
|
|
{
|
|
for (std::vector<SrsMediaDesc>::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) {
|
|
iter->session_info_.ice_ufrag_ = ufrag;
|
|
}
|
|
}
|
|
|
|
void SrsSdp::set_ice_pwd(const std::string& pwd)
|
|
{
|
|
for (std::vector<SrsMediaDesc>::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) {
|
|
iter->session_info_.ice_pwd_ = pwd;
|
|
}
|
|
}
|
|
|
|
void SrsSdp::set_dtls_role(const std::string& dtls_role)
|
|
{
|
|
for (std::vector<SrsMediaDesc>::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) {
|
|
iter->session_info_.setup_ = dtls_role;
|
|
}
|
|
}
|
|
|
|
void SrsSdp::set_fingerprint_algo(const std::string& algo)
|
|
{
|
|
for (std::vector<SrsMediaDesc>::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) {
|
|
iter->session_info_.fingerprint_algo_ = algo;
|
|
}
|
|
}
|
|
|
|
void SrsSdp::set_fingerprint(const std::string& fingerprint)
|
|
{
|
|
for (std::vector<SrsMediaDesc>::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) {
|
|
iter->session_info_.fingerprint_ = fingerprint;
|
|
}
|
|
}
|
|
|
|
void SrsSdp::add_candidate(const std::string& ip, const int& port, const std::string& type)
|
|
{
|
|
// @see: https://tools.ietf.org/id/draft-ietf-mmusic-ice-sip-sdp-14.html#rfc.section.5.1
|
|
SrsCandidate candidate;
|
|
candidate.ip_ = ip;
|
|
candidate.port_ = port;
|
|
candidate.type_ = type;
|
|
|
|
for (std::vector<SrsMediaDesc>::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) {
|
|
iter->candidates_.push_back(candidate);
|
|
}
|
|
}
|
|
|
|
std::string SrsSdp::get_ice_ufrag() const
|
|
{
|
|
// Becaues we use BUNDLE, so we can choose the first element.
|
|
for (std::vector<SrsMediaDesc>::const_iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) {
|
|
return iter->session_info_.ice_ufrag_;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
std::string SrsSdp::get_ice_pwd() const
|
|
{
|
|
// Becaues we use BUNDLE, so we can choose the first element.
|
|
for (std::vector<SrsMediaDesc>::const_iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) {
|
|
return iter->session_info_.ice_pwd_;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
std::string SrsSdp::get_dtls_role() const
|
|
{
|
|
// Becaues we use BUNDLE, so we can choose the first element.
|
|
for (std::vector<SrsMediaDesc>::const_iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) {
|
|
return iter->session_info_.setup_;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
srs_error_t SrsSdp::parse_line(const std::string& line)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
std::string content = line.substr(2);
|
|
|
|
switch (line[0]) {
|
|
case 'o': {
|
|
return parse_origin(content);
|
|
}
|
|
case 'v': {
|
|
return parse_version(content);
|
|
}
|
|
case 's': {
|
|
return parse_session_name(content);
|
|
}
|
|
case 't': {
|
|
return parse_timing(content);
|
|
}
|
|
case 'a': {
|
|
if (in_media_session_) {
|
|
return media_descs_.back().parse_line(line);
|
|
}
|
|
return parse_attribute(content);
|
|
}
|
|
case 'm': {
|
|
return parse_media_description(content);
|
|
}
|
|
case 'c': {
|
|
// TODO: process c-line
|
|
break;
|
|
}
|
|
default: {
|
|
srs_trace("ignore sdp line=%s", line.c_str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsSdp::parse_origin(const std::string& content)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
// @see: https://tools.ietf.org/html/rfc4566#section-5.2
|
|
// o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
|
|
// eg. o=- 9164462281920464688 2 IN IP4 127.0.0.1
|
|
std::istringstream is(content);
|
|
|
|
FETCH(is, username_);
|
|
FETCH(is, session_id_);
|
|
FETCH(is, session_version_);
|
|
FETCH(is, nettype_);
|
|
FETCH(is, addrtype_);
|
|
FETCH(is, unicast_address_);
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsSdp::parse_version(const std::string& content)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
// @see: https://tools.ietf.org/html/rfc4566#section-5.1
|
|
|
|
std::istringstream is(content);
|
|
|
|
FETCH(is, version_);
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsSdp::parse_session_name(const std::string& content)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
// @see: https://tools.ietf.org/html/rfc4566#section-5.3
|
|
// s=<session name>
|
|
|
|
session_name_ = content;
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsSdp::parse_timing(const std::string& content)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
// @see: https://tools.ietf.org/html/rfc4566#section-5.9
|
|
// t=<start-time> <stop-time>
|
|
|
|
std::istringstream is(content);
|
|
|
|
FETCH(is, start_time_);
|
|
FETCH(is, end_time_);
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsSdp::parse_attribute(const std::string& content)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
// @see: https://tools.ietf.org/html/rfc4566#section-5.13
|
|
// a=<attribute>
|
|
// a=<attribute>:<value>
|
|
|
|
std::string attribute = "";
|
|
std::string value = "";
|
|
size_t pos = content.find_first_of(":");
|
|
|
|
if (pos != std::string::npos) {
|
|
attribute = content.substr(0, pos);
|
|
value = content.substr(pos + 1);
|
|
}
|
|
|
|
if (attribute == "group") {
|
|
return parse_attr_group(value);
|
|
} else if (attribute == "msid-semantic") {
|
|
std::istringstream is(value);
|
|
FETCH(is, msid_semantic_);
|
|
|
|
std::string msid;
|
|
while (is >> msid) {
|
|
msids_.push_back(msid);
|
|
}
|
|
} else {
|
|
return session_info_.parse_attribute(attribute, value);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsSdp::parse_attr_group(const std::string& value)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
// @see: https://tools.ietf.org/html/rfc5888#section-5
|
|
|
|
std::istringstream is(value);
|
|
|
|
FETCH(is, group_policy_);
|
|
|
|
std::string word;
|
|
while (is >> word) {
|
|
groups_.push_back(word);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
srs_error_t SrsSdp::parse_media_description(const std::string& content)
|
|
{
|
|
srs_error_t err = srs_success;
|
|
|
|
// @see: https://tools.ietf.org/html/rfc4566#section-5.14
|
|
// m=<media> <port> <proto> <fmt> ...
|
|
// m=<media> <port>/<number of ports> <proto> <fmt> ...
|
|
std::istringstream is(content);
|
|
|
|
std::string media;
|
|
FETCH(is, media);
|
|
|
|
int port;
|
|
FETCH(is, port);
|
|
|
|
std::string proto;
|
|
FETCH(is, proto);
|
|
|
|
media_descs_.push_back(SrsMediaDesc(media));
|
|
media_descs_.back().protos_ = proto;
|
|
media_descs_.back().port_ = port;
|
|
|
|
int fmt;
|
|
while (is >> fmt) {
|
|
media_descs_.back().payload_types_.push_back(SrsMediaPayloadType(fmt));
|
|
}
|
|
|
|
if (!in_media_session_) {
|
|
in_media_session_ = true;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
bool SrsSdp::is_unified() const
|
|
{
|
|
// TODO: FIXME: Maybe we should consider other situations.
|
|
return media_descs_.size() > 2;
|
|
}
|
|
|