From d7707d2d2b1d91cf713354ddc02cf284a9bda6ff Mon Sep 17 00:00:00 2001 From: johzzy Date: Wed, 15 Dec 2021 23:18:01 +0800 Subject: [PATCH] Support Simulcast Stream (RID Based Simulcast) (#2659) * fix typo. * refactor: update rtc conn. * feat(simulcast): add rtc_publisher_simulcast page. * feat(kSimulcastApiVersionSpecCompliant/js): update txt url in simulcast page. * feat(kSimulcastApiVersionSpecCompliant): support simulcast, use layer=N * Refine(rtc/negotiate_publish_capability): add set_audio_track_desc/set_video_track_descs. * fix: add msid and msid tracker. * feat(kSimulcastApiVersionSpecCompliant): support layer=high,mid,low * feat(simulcast): add x-google-min-bitrate=5000;x-google-max-bitrate=8000;x-google-start-bitrate=6000 * add note about janus_rtp_header/janus_rtp_header_extension and helper functions. * fix build in c++03. ./configure --cxx11=off --cxx14=off --ffmpeg-fit=off * Refactor rid base simulcast code * Mofidy x-google* from cpp code to html Co-authored-by: hondaxiao --- trunk/research/players/js/srs.page.js | 1 + trunk/research/players/js/srs.sdk.js | 50 +++- trunk/research/players/rtc_player.html | 1 + trunk/research/players/rtc_publisher.html | 1 + .../players/rtc_publisher_simulcast.html | 143 +++++++++++ trunk/research/players/srs_bwt.html | 1 + trunk/research/players/srs_chat.html | 1 + trunk/research/players/srs_gb28181.html | 1 + trunk/research/players/srs_player.html | 1 + .../players/srs_player_deprecated.html | 1 + trunk/research/players/srs_publisher.html | 1 + trunk/research/players/vlc.html | 1 + trunk/src/app/srs_app_rtc_conn.cpp | 229 +++++++++++++----- trunk/src/app/srs_app_rtc_conn.hpp | 8 +- trunk/src/app/srs_app_rtc_sdp.cpp | 57 ++++- trunk/src/app/srs_app_rtc_sdp.hpp | 27 ++- trunk/src/app/srs_app_rtc_source.cpp | 86 +++++++ trunk/src/app/srs_app_rtc_source.hpp | 6 + trunk/src/kernel/srs_kernel_rtc_rtp.cpp | 66 ++++- trunk/src/kernel/srs_kernel_rtc_rtp.hpp | 40 ++- 20 files changed, 631 insertions(+), 91 deletions(-) create mode 100644 trunk/research/players/rtc_publisher_simulcast.html diff --git a/trunk/research/players/js/srs.page.js b/trunk/research/players/js/srs.page.js index cf7a823d6dd..e69de4352e4 100755 --- a/trunk/research/players/js/srs.page.js +++ b/trunk/research/players/js/srs.page.js @@ -18,6 +18,7 @@ function update_nav() { $("#nav_rtc_player").attr("href", "rtc_player.html" + window.location.search); $("#nav_rtc_publisher").attr("href", "rtc_publisher.html" + window.location.search); $("#nav_srs_publisher").attr("href", "srs_publisher.html" + window.location.search); + $("#nav_srs_publisher_simulcast").attr("href", "srs_publisher_simulcast.html" + window.location.search); $("#nav_srs_chat").attr("href", "srs_chat.html" + window.location.search); $("#nav_srs_bwt").attr("href", "srs_bwt.html" + window.location.search); $("#nav_vlc").attr("href", "vlc.html" + window.location.search); diff --git a/trunk/research/players/js/srs.sdk.js b/trunk/research/players/js/srs.sdk.js index e16f970d220..5c46e0f21e7 100644 --- a/trunk/research/players/js/srs.sdk.js +++ b/trunk/research/players/js/srs.sdk.js @@ -54,14 +54,20 @@ function SrsRtcPublisherAsync() { var conf = self.__internal.prepareUrl(url); const parameter = QueryParameterByName('numberOfSimulcastLayers', conf.streamUrl); const numberOfSimulcastLayers = parseInt(parameter) + var forceSetBitrate = false if (numberOfSimulcastLayers > 1) { self.constraints.video = { wіdth: 1280, height: 720 } + UpdateNativeCreateOffer(numberOfSimulcastLayers); + console.log('kSimulcastApiVersionLegacy') + } else if (parameter === 'spec3') { + self.constraints.video = { + wіdth: 1280, height: 720 + } + forceSetBitrate = true; + console.log('kSimulcastApiVersionSpecCompliant') } - UpdateNativeCreateOffer(numberOfSimulcastLayers); - self.pc.addTransceiver("audio", {direction: "sendonly"}); - self.pc.addTransceiver("video", {direction: "sendonly"}); if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') { throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`); @@ -70,7 +76,20 @@ function SrsRtcPublisherAsync() { // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack stream.getTracks().forEach(function (track) { - self.pc.addTrack(track); + if (track.kind === 'video' && parameter === 'spec3') { + self.pc.addTransceiver(track, { + active: true, + direction: "sendonly", + sendEncodings: [ + // NOTE: modify from media/engine/simulcast.cc + {rid: "high", active: true, maxBitrate: 5000 * 1024}, + {rid: "mid", active: true, maxBitrate: 1500 * 1024, scaleResolutionDownBy: 2}, + {rid: "low", active: true, maxBitrate: 400 * 1024, scaleResolutionDownBy: 4}, + ] + }) + } else { + self.pc.addTrack(track, stream); + } // Notify about local track when stream is ok. self.ontrack && self.ontrack({track: track}); @@ -98,9 +117,24 @@ function SrsRtcPublisherAsync() { xhr.setRequestHeader('Content-type', 'application/json'); xhr.send(JSON.stringify(data)); }); - await self.pc.setRemoteDescription( - new RTCSessionDescription({type: 'answer', sdp: session.sdp}) - ); + + + if (forceSetBitrate) { + var arr = session.sdp.split('\r\n'); + arr.forEach((str, i) => { + // TODO: FIXME: adapter to different browers. + if (/^a=fmtp:\d*/.test(str)) { + arr[i] = str + ';x-google-min-bitrate=5000;x-google-max-bitrate=8000;x-google-start-bitrate=6000'; + } + }); + await self.pc.setRemoteDescription( + new RTCSessionDescription({type: 'answer', sdp: arr.join('\r\n')}) + ); + } else { + await self.pc.setRemoteDescription( + new RTCSessionDescription({type: 'answer', sdp: session.sdp}) + ); + } session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/'; return session; @@ -636,4 +670,4 @@ function QueryParameterByName(name, url) { if (!results) return null; if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, ' ')); -} \ No newline at end of file +} diff --git a/trunk/research/players/rtc_player.html b/trunk/research/players/rtc_player.html index ab0ad3ea4bc..ea4c624ca8b 100644 --- a/trunk/research/players/rtc_player.html +++ b/trunk/research/players/rtc_player.html @@ -26,6 +26,7 @@
  • SRS播放器
  • RTC播放器
  • RTC推流
  • +
  • Simulcast推流
  • iOS/Andriod
  • diff --git a/trunk/research/players/rtc_publisher.html b/trunk/research/players/rtc_publisher.html index 44e73758d47..1e51787255d 100644 --- a/trunk/research/players/rtc_publisher.html +++ b/trunk/research/players/rtc_publisher.html @@ -27,6 +27,7 @@
  • SRS播放器
  • RTC播放器
  • RTC推流
  • +
  • Simulcast推流
  • iOS/Andriod
  • diff --git a/trunk/research/players/rtc_publisher_simulcast.html b/trunk/research/players/rtc_publisher_simulcast.html new file mode 100644 index 00000000000..c41da0adcba --- /dev/null +++ b/trunk/research/players/rtc_publisher_simulcast.html @@ -0,0 +1,143 @@ + + + + SRS + + + + + + + + + + + + + +
    +
    + URL: + + +
    + + + + + + SessionID: + + + Audio: +
    + Video: + + + + Simulator: Drop + + +
    + + + + + diff --git a/trunk/research/players/srs_bwt.html b/trunk/research/players/srs_bwt.html index a2858241b88..af1e8c2da00 100644 --- a/trunk/research/players/srs_bwt.html +++ b/trunk/research/players/srs_bwt.html @@ -25,6 +25,7 @@
  • SRS播放器
  • RTC播放器
  • RTC推流
  • +
  • Simulcast推流
  • SRS编码器
  • SRS会议
  • SRS测网速
  • diff --git a/trunk/research/players/srs_chat.html b/trunk/research/players/srs_chat.html index abed015fc2b..577dec10393 100644 --- a/trunk/research/players/srs_chat.html +++ b/trunk/research/players/srs_chat.html @@ -24,6 +24,7 @@
  • SRS播放器
  • RTC播放器
  • RTC推流
  • +
  • Simulcast推流
  • SRS编码器
  • SRS会议
  • SRS测网速
  • diff --git a/trunk/research/players/srs_gb28181.html b/trunk/research/players/srs_gb28181.html index e9d5a7ec04a..0d72abaf1b5 100644 --- a/trunk/research/players/srs_gb28181.html +++ b/trunk/research/players/srs_gb28181.html @@ -38,6 +38,7 @@
  • SRS播放器
  • RTC播放器
  • RTC推流
  • +
  • Simulcast推流
  • iOS/Andriod
  • diff --git a/trunk/research/players/srs_player.html b/trunk/research/players/srs_player.html index 62b89489c5e..40093345174 100755 --- a/trunk/research/players/srs_player.html +++ b/trunk/research/players/srs_player.html @@ -21,6 +21,7 @@
  • SRS播放器
  • RTC播放器
  • RTC推流
  • +
  • Simulcast推流
  • iOS/Andriod
  • diff --git a/trunk/research/players/srs_player_deprecated.html b/trunk/research/players/srs_player_deprecated.html index 26f21fcfc30..563a30b14a2 100755 --- a/trunk/research/players/srs_player_deprecated.html +++ b/trunk/research/players/srs_player_deprecated.html @@ -35,6 +35,7 @@
  • SRS播放器
  • RTC播放器
  • RTC推流
  • +
  • Simulcast推流
  • iOS/Andriod
  • diff --git a/trunk/research/players/srs_publisher.html b/trunk/research/players/srs_publisher.html index b76f41f041a..8fe4dc4fb6a 100644 --- a/trunk/research/players/srs_publisher.html +++ b/trunk/research/players/srs_publisher.html @@ -24,6 +24,7 @@
  • SRS播放器
  • RTC播放器
  • RTC推流
  • +
  • Simulcast推流
  • diff --git a/trunk/research/players/vlc.html b/trunk/research/players/vlc.html index 4422301ef39..3f677bb8e72 100644 --- a/trunk/research/players/vlc.html +++ b/trunk/research/players/vlc.html @@ -21,6 +21,7 @@
  • SRS播放器
  • RTC播放器
  • RTC推流
  • +
  • Simulcast推流
  • SRS编码器
  • SRS会议
  • SRS测网速
  • diff --git a/trunk/src/app/srs_app_rtc_conn.cpp b/trunk/src/app/srs_app_rtc_conn.cpp index cd3c52453f6..48005215342 100644 --- a/trunk/src/app/srs_app_rtc_conn.cpp +++ b/trunk/src/app/srs_app_rtc_conn.cpp @@ -1139,21 +1139,28 @@ srs_error_t SrsRtcPublishStream::initialize(SrsRequest* r, SrsRtcSourceDescripti } int twcc_id = -1; + int rid = -1; uint32_t media_ssrc = 0; // because audio_track_desc have not twcc id, for example, h5demo // fetch twcc_id from video track description, for (int i = 0; i < (int)stream_desc->video_track_descs_.size(); ++i) { SrsRtcTrackDescription* desc = stream_desc->video_track_descs_.at(i); - twcc_id = desc->get_rtp_extension_id(kTWCCExt); + twcc_id = desc->get_rtp_extension_id(kExtensions[kRtpExtTwcc].uri); + rid = desc->get_rtp_extension_id(kExtensions[kRtpExtSdesRtpStreamId].uri); media_ssrc = desc->ssrc_; break; } + if (twcc_id > 0) { twcc_id_ = twcc_id; - extension_types_.register_by_uri(twcc_id_, kTWCCExt); + extension_types_.register_by_uri(twcc_id_, kExtensions[kRtpExtTwcc].uri); rtcp_twcc_.set_media_ssrc(media_ssrc); } + if (rid > 0) { + extension_types_.register_by_uri(rid, kExtensions[kRtpExtSdesRtpStreamId].uri); + } + nack_enabled_ = _srs_config->get_rtc_nack_enabled(req_->vhost); nack_no_copy_ = _srs_config->get_rtc_nack_no_copy(req_->vhost); pt_to_drop_ = (uint16_t)_srs_config->get_rtc_drop_for_pt(req_->vhost); @@ -1748,6 +1755,14 @@ void SrsRtcPublishStream::update_send_report_time(uint32_t ssrc, const SrsNtp& n } } +void SrsRtcPublishStream::bind_rid(const SrsRidInfo &rid_info) { + // NOTE: get unused video_track, and active as rid info. + SrsRtcVideoRecvTrack* video_track = get_video_track(0); + if (video_track) { + video_track->active_as(rid_info); + } +} + ISrsRtcConnectionHijacker::ISrsRtcConnectionHijacker() { } @@ -1815,7 +1830,6 @@ SrsRtcConnection::SrsRtcConnection(SrsRtcServer* s, const SrsContextId& cid) session_timeout = 0; disposing_ = false; - twcc_id_ = 0; nn_simulate_player_nack_drop = 0; pp_address_change = new SrsErrorPithyPrint(); pli_epp = new SrsErrorPithyPrint(); @@ -1985,6 +1999,16 @@ srs_error_t SrsRtcConnection::add_publisher(SrsRtcUserConfig* ruc, SrsSdp& local return srs_error_wrap(err, "generate local sdp"); } + // NOTE: Supplement Simulcast information in publish local sdp. + size_t size = local_sdp.media_descs_.size(); + assert(size == ruc->remote_sdp_.media_descs_.size()); + for (size_t i = 0; i < size; ++i) { + SrsMediaDesc& media_desc = ruc->remote_sdp_.media_descs_.at(i); + if (media_desc.is_video() && media_desc.simulcast_spec_version()) { + local_sdp.media_descs_.at(i).session_info_.simulcast_ = media_desc.session_info_.simulcast_; + } + } + SrsRtcSource* source = NULL; if ((err = _srs_rtc_sources->fetch_or_create(req, &source)) != srs_success) { return srs_error_wrap(err, "create source"); @@ -2026,7 +2050,7 @@ srs_error_t SrsRtcConnection::add_player(SrsRtcUserConfig* ruc, SrsSdp& local_sd return srs_error_wrap(err, "play negotiate"); } - if (!play_sub_relations.size()) { + if (play_sub_relations.empty()) { return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no play relations"); } @@ -2286,13 +2310,92 @@ srs_error_t SrsRtcConnection::find_publisher(char* buf, int size, SrsRtcPublishS } map::iterator it = publishers_ssrc_map_.find(ssrc); - if(it == publishers_ssrc_map_.end()) { - return srs_error_new(ERROR_RTC_NO_PUBLISHER, "no publisher for ssrc:%u", ssrc); + // Found publisher, return directly. + if (it != publishers_ssrc_map_.end()) { + *ppublisher = it->second; + return err; } - *ppublisher = it->second; + // No found publisher, check if simulcast enabled. + if ((err = parse_rid(buf, size, ssrc, ppublisher)) != srs_success) { + return srs_error_wrap(err, "no simulcast publisher for ssrc:%u; parse_rid", ssrc); + } else { + return err; + } - return err; + return srs_error_new(ERROR_RTC_NO_PUBLISHER, "no publisher for ssrc:%u", ssrc); +} + +srs_error_t SrsRtcConnection::parse_rid(char *buf, int size, uint32_t ssrc, SrsRtcPublishStream** ppublisher) +{ + srs_error_t err = srs_success; + + for (size_t i = 0; i < remote_sdp.media_descs_.size(); ++i) { + SrsMediaDesc& media_desc = remote_sdp.media_descs_.at(i); + if (!media_desc.simulcast_spec_version()) { + continue; + } + + int rid = 0; + for(map::iterator it = media_desc.extmaps_.begin(); it != media_desc.extmaps_.end(); ++it) { + if (it->second == kExtensions[kRtpExtSdesRtpStreamId].uri) { + rid = it->first; + break; + } + } + + if (rid <= 0) { + continue; + } + + SrsSimulcastInfo &simulcast = media_desc.session_info_.simulcast_; + + std::string rtp_stream_id; + if ((err = srs_rtp_fast_parse_rid(buf, size, rid, rtp_stream_id)) != srs_success) { + return srs_error_wrap(err, "parse rid"); + } + + for (std::vector::iterator iter = simulcast.rids.begin(); iter != simulcast.rids.end(); ++iter) { + if (rtp_stream_id == iter->rid) { + srs_assert(iter->ssrc == 0); + iter->ssrc = ssrc; + if ((err = bind_rid(*iter, ppublisher)) != srs_success) { + return srs_error_wrap(err, "bind rid"); + } + + // Found publisher and bind success, return. + return err; + } + } + } + + return srs_error_new(ERROR_RTC_NO_PUBLISHER, "no rid found in extmap", ssrc); +} + +srs_error_t SrsRtcConnection::bind_rid(const SrsRidInfo &rid_info, SrsRtcPublishStream** ppublisher) { + srs_error_t err = srs_success; + SrsRtcSource *source = NULL; + if ((err = _srs_rtc_sources->fetch_or_create(req_, &source)) != srs_success) { + return srs_error_wrap(err, "fetch_or_create source"); + } + + std::vector track_descs = source->get_track_desc("video", "H264"); + for (size_t i = 0; i < track_descs.size(); ++i) { + SrsRtcTrackDescription* track_desc = track_descs.at(i); + if (track_desc->ssrc_ == 0) { + track_desc->ssrc_ = rid_info.ssrc; + track_desc->rid_ = rid_info; + // TODO: maybe select wrong publisher? + SrsRtcPublishStream* publisher = publishers_ssrc_map_.begin()->second; + publisher->bind_rid(rid_info); + publishers_ssrc_map_[track_desc->ssrc_] = publisher; + *ppublisher = publisher; + srs_warn("find track_desc to (rid='%s', ssrc=%u)", rid_info.rid.c_str(), rid_info.ssrc); + return err; + } + } + + return srs_error_new(ERROR_RTC_NO_TRACK, "no idle track_desc to (rid='%s', ssrc=%u)", rid_info.rid.c_str(), rid_info.ssrc); } srs_error_t SrsRtcConnection::on_connection_established() @@ -2828,17 +2931,22 @@ srs_error_t SrsRtcConnection::negotiate_publish_capability(SrsRtcUserConfig* ruc // Whether feature enabled in remote extmap. int remote_twcc_id = 0; if (true) { - map extmaps = remote_media_desc.get_extmaps(); + map extmaps = remote_media_desc.extmaps_; for(map::iterator it = extmaps.begin(); it != extmaps.end(); ++it) { - if (it->second == kTWCCExt) { + if (it->second == kExtensions[kRtpExtTwcc].uri) { remote_twcc_id = it->first; - break; + // break; + } else if (remote_media_desc.simulcast_spec_version()) { + // note: add all ext map in simulcast_spec_version + if (it->second == kExtensions[kRtpExtSdesRtpStreamId].uri || it->second == kExtensions[kRtpExtSdesRepairedRtpStreamId].uri) { + track_desc->add_rtp_extension_desc(it->first, it->second); + } } } } if (twcc_enabled && remote_twcc_id) { - track_desc->add_rtp_extension_desc(remote_twcc_id, kTWCCExt); + track_desc->add_rtp_extension_desc(remote_twcc_id, kExtensions[kRtpExtTwcc].uri); } if (remote_media_desc.is_audio()) { @@ -3005,49 +3113,9 @@ srs_error_t SrsRtcConnection::negotiate_publish_capability(SrsRtcUserConfig* ruc track_desc->create_auxiliary_payload(remote_media_desc.find_media_with_encoding_name("ulpfec")); if (remote_media_desc.is_audio()) { - for (int j = 0; j < (int)remote_media_desc.ssrc_infos_.size(); ++j) { - const SrsSSRCInfo& ssrc_info = remote_media_desc.ssrc_infos_.at(j); - - // ssrc have same track id, will be description in the same track description. - if (!stream_desc->audio_track_desc_) { - SrsRtcTrackDescription* track_desc_copy = track_desc->copy(); - track_desc_copy->ssrc_ = ssrc_info.ssrc_; - track_desc_copy->id_ = ssrc_info.msid_tracker_; - track_desc_copy->msid_ = ssrc_info.msid_; - stream_desc->audio_track_desc_ = track_desc_copy; - } - srs_info("%d/%d#publish audio ssrc_=%u, msid_=%s, msid_tracker_='%s'", j, - (int) remote_media_desc.ssrc_infos_.size(), - ssrc_info.ssrc_, ssrc_info.msid_.c_str(), ssrc_info.msid_tracker_.c_str()); - } + stream_desc->set_audio_track_desc(remote_media_desc, track_desc); } else if (remote_media_desc.is_video()) { - std::string track_id; - for (int j = 0; j < (int)remote_media_desc.ssrc_infos_.size(); ++j) { - const SrsSSRCInfo& ssrc_info = remote_media_desc.ssrc_infos_.at(j); - - // ssrc have same track id, will be description in the same track description. - if(track_id != ssrc_info.msid_tracker_) { - SrsRtcTrackDescription* track_desc_copy = track_desc->copy(); - track_desc_copy->ssrc_ = ssrc_info.ssrc_; - track_desc_copy->id_ = ssrc_info.msid_tracker_; - track_desc_copy->msid_ = ssrc_info.msid_; - if (remote_media_desc.is_original_ssrc(&ssrc_info)) { - // note: set ssrc related to single or simulcast(chrome munging style simulcast sdp) - // todo: support standard simulcast, like "a=simulcast:send a;b;c" - stream_desc->video_track_descs_.push_back(track_desc_copy); - srs_info("%d/%d#publish video track_id_=%s, ssrc_=%u, msid_=%s, msid_tracker_='%s'", j,(int)remote_media_desc.ssrc_infos_.size(), - track_id.c_str(), ssrc_info.ssrc_, ssrc_info.msid_.c_str(), ssrc_info.msid_tracker_.c_str()); - } else { - srs_info("%d/%d#ignore track_id_=%s, ssrc_=%u, msid_=%s, msid_tracker_='%s'", j, (int)remote_media_desc.ssrc_infos_.size(), - track_id.c_str(), ssrc_info.ssrc_, ssrc_info.msid_.c_str(), ssrc_info.msid_tracker_.c_str()); - } - track_id = ssrc_info.msid_tracker_; - } else { - srs_info("%d/%d#ignore track_id_/msid_tracker_=%s, ssrc_=%u, msid_=%s", j, (int)remote_media_desc.ssrc_infos_.size(), - track_id.c_str(), ssrc_info.ssrc_, ssrc_info.msid_.c_str()); - track_id = ""; - } - } + stream_desc->set_video_track_descs(remote_media_desc, track_desc); } else { // TODO: FIXME: Handle for others. } @@ -3112,7 +3180,7 @@ srs_error_t SrsRtcConnection::generate_publish_local_sdp(SrsRequest* req, SrsSdp local_media_desc.mid_ = audio_track->mid_; local_sdp.groups_.push_back(local_media_desc.mid_); - // anwer not need set stream_id and track_id; + // answer not need set stream_id and track_id; // local_media_desc.msid_ = stream_id; // local_media_desc.msid_tracker_ = audio_track->id_; local_media_desc.extmaps_ = audio_track->extmaps_; @@ -3145,7 +3213,7 @@ srs_error_t SrsRtcConnection::generate_publish_local_sdp(SrsRequest* req, SrsSdp local_media_desc.mid_ = video_track->mid_; local_sdp.groups_.push_back(local_media_desc.mid_); - // anwer not need set stream_id and track_id; + // answer not need set stream_id and track_id; //local_media_desc.msid_ = stream_id; //local_media_desc.msid_tracker_ = video_track->id_; local_media_desc.extmaps_ = video_track->extmaps_; @@ -3200,9 +3268,9 @@ srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRtcUserConfig* ruc, s // Whether feature enabled in remote extmap. int remote_twcc_id = 0; if (true) { - map extmaps = remote_media_desc.get_extmaps(); + map extmaps = remote_media_desc.extmaps_; for(map::iterator it = extmaps.begin(); it != extmaps.end(); ++it) { - if (it->second == kTWCCExt) { + if (it->second == kExtensions[kRtpExtTwcc].uri) { remote_twcc_id = it->first; break; } @@ -3285,6 +3353,9 @@ srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRtcUserConfig* ruc, s track->mid_ = remote_media_desc.mid_; uint32_t publish_ssrc = track->ssrc_; + if (publish_ssrc == 0) { + continue; + } vector rtcp_fb; remote_payload.rtcp_fb_.swap(rtcp_fb); @@ -3298,7 +3369,7 @@ srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRtcUserConfig* ruc, s if (rtcp_fb.at(j) == "transport-cc") { track->media_->rtcp_fbs_.push_back(rtcp_fb.at(j)); } - track->add_rtp_extension_desc(remote_twcc_id, kTWCCExt); + track->add_rtp_extension_desc(remote_twcc_id, kExtensions[kRtpExtTwcc].uri); } } @@ -3314,7 +3385,11 @@ srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRtcUserConfig* ruc, s } track->set_direction("sendonly"); + // fixme: order of track sub_relations.insert(make_pair(publish_ssrc, track)); + if (track->rid_.ssrc > 0) { + srs_info("publish_ssrc=%u, play ssrc=%u, rid ssrc=%u, rid=%s", publish_ssrc, track->ssrc_, track->rid_.ssrc, track->rid_.rid.c_str()); + } } } @@ -3372,7 +3447,7 @@ void video_track_generate_play_offer(SrsRtcTrackDescription* track, string mid, } // note: get [0,1,2] from "webrtc://127.0.0.1/live/livestream?layer=0,1,2&foo=bar" -bool parse_layers_in_url(std::vector& layers, std::string name) { +bool parse_layers_in_url(std::vector& layers, std::string name) { // livestream?layer=1 // livestream?layer=1,2,3 // livestream?layer=1,2,3&foo=bar @@ -3399,23 +3474,42 @@ bool parse_layers_in_url(std::vector& layers, std::string name) { istringstream f(layers_); string s; while (getline(f, s, ';')) { - layers.push_back(::atoi(s.c_str())); + layers.push_back(s); } return !layers.empty(); } +void add_desc_to_vector(std::vector& result, SrsRtcTrackDescription *desc) { + if (result.end() == std::find(result.begin(), result.end(), desc)) { + result.push_back(desc); + } +} + std::vector select_video_track_descs( std::vector video_track_descs, std::string url) { srs_trace("stream url: %s", url.c_str()); - std::vector layers; - if (!parse_layers_in_url(layers, url)) { + std::vector layers; + if (video_track_descs.empty() || !parse_layers_in_url(layers, url)) { return video_track_descs; } std::vector result; for (size_t i=0;i= 0 && layer < (int)video_track_descs.size(); + if (is_index) { + add_desc_to_vector(result, video_track_descs[layer]); + } else { + for (size_t ii = 0; ii < video_track_descs.size(); ++ii) { + SrsRtcTrackDescription* desc = video_track_descs.at(ii); + assert(desc); + if (desc->rid_.rid == layers[i]) { + result.push_back(desc); + add_desc_to_vector(result, desc); + } + } } } return result.empty() ? video_track_descs : result; @@ -3598,7 +3692,7 @@ srs_error_t SrsRtcConnection::create_player(SrsRequest* req, std::mapsecond->type_ == "video") { SrsRtcTrackDescription* track = it->second; - twcc_id = track->get_rtp_extension_id(kTWCCExt); + twcc_id = track->get_rtp_extension_id(kExtensions[kRtpExtTwcc].uri); } ++it; } @@ -3663,6 +3757,9 @@ srs_error_t SrsRtcConnection::create_publisher(SrsRequest* req, SrsRtcSourceDesc for(int i = 0; i < (int)stream_desc->video_track_descs_.size(); ++i) { SrsRtcTrackDescription* track_desc = stream_desc->video_track_descs_.at(i); if(publishers_ssrc_map_.end() != publishers_ssrc_map_.find(track_desc->ssrc_)) { + if (track_desc->ssrc_ == 0) { + continue; + } return srs_error_new(ERROR_RTC_DUPLICATED_SSRC, " duplicate ssrc %d, track id: %s", track_desc->ssrc_, track_desc->id_.c_str()); } diff --git a/trunk/src/app/srs_app_rtc_conn.hpp b/trunk/src/app/srs_app_rtc_conn.hpp index 34a9a98aed7..034ebe27445 100644 --- a/trunk/src/app/srs_app_rtc_conn.hpp +++ b/trunk/src/app/srs_app_rtc_conn.hpp @@ -404,6 +404,9 @@ class SrsRtcPublishStream : public ISrsRtspPacketDecodeHandler SrsRtcVideoRecvTrack* get_video_track(uint32_t ssrc); void update_rtt(uint32_t ssrc, int rtt); void update_send_report_time(uint32_t ssrc, const SrsNtp& ntp, uint32_t rtp_time); + +public: + void bind_rid(const SrsRidInfo& rid_info); }; // Callback for RTC connection. @@ -480,8 +483,6 @@ class SrsRtcConnection : public ISrsResource, public ISrsDisposingHandler, publi SrsSdp remote_sdp; SrsSdp local_sdp; private: - // twcc handler - int twcc_id_; // Simulators. int nn_simulate_player_nack_drop; // Pithy print for address change, use port as error code. @@ -575,6 +576,9 @@ class SrsRtcConnection : public ISrsResource, public ISrsDisposingHandler, publi srs_error_t generate_play_local_sdp(SrsRequest* req, SrsSdp& local_sdp, SrsRtcSourceDescription* stream_desc, bool unified_plan); srs_error_t create_player(SrsRequest* request, std::map sub_relations); srs_error_t create_publisher(SrsRequest* request, SrsRtcSourceDescription* stream_desc); + + srs_error_t parse_rid(char *buf, int size, uint32_t ssrc, SrsRtcPublishStream** ppublisher); + srs_error_t bind_rid(const SrsRidInfo &rid_info, SrsRtcPublishStream** ppublisher); }; class ISrsRtcHijacker diff --git a/trunk/src/app/srs_app_rtc_sdp.cpp b/trunk/src/app/srs_app_rtc_sdp.cpp index 2a659cf37a7..0e25ebf0333 100644 --- a/trunk/src/app/srs_app_rtc_sdp.cpp +++ b/trunk/src/app/srs_app_rtc_sdp.cpp @@ -7,6 +7,8 @@ #include #include +#include // for memset +#include // for ntohs in linux #include #include @@ -110,8 +112,29 @@ srs_error_t SrsSessionInfo::parse_attribute(const std::string& attribute, const } else if (attribute == "setup") { // @see: https://tools.ietf.org/html/rfc4145#section-4 setup_ = value; + } else if (attribute == "simulcast") { + // kSimulcastApiVersionSpecCompliant + // todo + // a=simulcast:send 0;1;2 + // ignore attribute=simulcast, value=send 0;1;2 + // ignore attribute=rid, value=0 send + // ignore attribute=rid, value=1 send + // ignore attribute=rid, value=2 send + std::istringstream is(value); + FETCH(is, simulcast_.direction); + FETCH(is, simulcast_.line); + srs_warn("kSimulcastApiVersionSpecCompliant parse: %s %s", simulcast_.direction.c_str(), simulcast_.line.c_str()); + } else if (attribute == "rid") { + // a=rid:0 send + // a=rid:1 send + // a=rid:2 send + SrsRidInfo rid; + std::istringstream is(value); + FETCH(is, rid.rid); + FETCH(is, rid.direction); + simulcast_.rids.push_back(rid); } else { - srs_trace("ignore attribute=%s, value=%s", attribute.c_str(), value.c_str()); + srs_trace("ignore attribute='%s', value='%s'", attribute.c_str(), value.c_str()); } return err; @@ -420,11 +443,19 @@ srs_error_t SrsMediaDesc::encode(std::ostringstream& os) } } - for (std::vector::iterator iter = ssrc_infos_.begin(); iter != ssrc_infos_.end(); ++iter) { - SrsSSRCInfo& ssrc_info = *iter; + if (simulcast_spec_version()) { + srs_warn("kSimulcastApiVersionSpecCompliant %s %s", session_info_.simulcast_.direction.c_str(), session_info_.simulcast_.line.c_str()); + // todo check simulcast kSimulcastApiVersionSpecCompliant + if ((err = session_info_.simulcast_.encode(os)) != srs_success) { + return srs_error_wrap(err, "encode simulcast failed"); + } + } else { + for (std::vector::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"); + if ((err = ssrc_info.encode(os)) != srs_success) { + return srs_error_wrap(err, "encode ssrc failed"); + } } } @@ -1177,3 +1208,19 @@ srs_error_t SrsSdp::update_msid(string id) return err; } +srs_error_t SrsSimulcastInfo::encode(ostringstream &os) { + srs_error_t err = srs_success; + + // a=simulcast:send 0;1;2 + // a=rid:0 send + // a=rid:1 send + // a=rid:2 send + // check direction he rids rid.direction is send + for (size_t i=0; i #include +#include #include #include #include #include -const std::string kTWCCExt = "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"; // TDOO: FIXME: Rename it, and add utest. extern std::vector split_str(const std::string& str, const std::string& delim); @@ -27,6 +27,24 @@ class SrsSessionConfig std::string dtls_version; }; +struct SrsRidInfo { + SrsRidInfo(): ssrc(0) {} + std::string rid; + std::string direction; + + uint32_t ssrc; + std::string mid; + std::string rtx; +}; + +struct SrsSimulcastInfo { + std::string direction; + std::string line; + std::vector rids; + + srs_error_t encode(std::ostringstream& os); +}; + class SrsSessionInfo { public: @@ -46,6 +64,8 @@ class SrsSessionInfo std::string fingerprint_algo_; std::string fingerprint_; std::string setup_; + + SrsSimulcastInfo simulcast_; }; class SrsSSRCInfo @@ -131,7 +151,6 @@ class SrsMediaDesc srs_error_t encode(std::ostringstream& os); SrsMediaPayloadType* find_media_with_payload_type(int payload_type); std::vector find_media_with_encoding_name(const std::string& encoding_name) const; - const std::map& get_extmaps() const { return extmaps_; } srs_error_t update_msid(std::string id); bool is_audio() const { return type_ == "audio"; } @@ -174,6 +193,10 @@ class SrsMediaDesc std::vector ssrc_infos_; std::map extmaps_; + bool simulcast_spec_version() const { + return ssrc_infos_.empty() && !session_info_.simulcast_.line.empty(); + } + public: // Whether SSRS is original stream. // @see https://github.com/ossrs/srs/pull/2420#discussion_r655792920 diff --git a/trunk/src/app/srs_app_rtc_source.cpp b/trunk/src/app/srs_app_rtc_source.cpp index d4145eb3d3e..e68178cfa83 100644 --- a/trunk/src/app/srs_app_rtc_source.cpp +++ b/trunk/src/app/srs_app_rtc_source.cpp @@ -2203,6 +2203,7 @@ SrsRtcTrackDescription* SrsRtcTrackDescription::copy() cp->red_ = red_ ? red_->copy():NULL; cp->rtx_ = rtx_ ? rtx_->copy():NULL; cp->ulpfec_ = ulpfec_ ? ulpfec_->copy():NULL; + cp->rid_ = rid_; return cp; } @@ -2252,6 +2253,73 @@ SrsRtcTrackDescription* SrsRtcSourceDescription::find_track_description_by_ssrc( return NULL; } +// NOTE: helper of SrsRtcConnection::negotiate_publish_capability +void SrsRtcSourceDescription::set_audio_track_desc(const SrsMediaDesc &remote_media_desc, + SrsRtcTrackDescription *track_desc) { + for (int j = 0; j < (int)remote_media_desc.ssrc_infos_.size(); ++j) { + const SrsSSRCInfo& ssrc_info = remote_media_desc.ssrc_infos_.at(j); + + // ssrc have same track id, will be description in the same track description. + if (!audio_track_desc_) { + SrsRtcTrackDescription* track_desc_copy = track_desc->copy(); + track_desc_copy->ssrc_ = ssrc_info.ssrc_; + track_desc_copy->id_ = ssrc_info.msid_tracker_; + track_desc_copy->msid_ = ssrc_info.msid_; + audio_track_desc_ = track_desc_copy; + } + srs_info("%d/%d#publish audio ssrc_=%u, msid_=%s, msid_tracker_='%s'", j, + (int) remote_media_desc.ssrc_infos_.size(), + ssrc_info.ssrc_, ssrc_info.msid_.c_str(), ssrc_info.msid_tracker_.c_str()); + } +} + +// NOTE: helper of SrsRtcConnection::negotiate_publish_capability +void SrsRtcSourceDescription::set_video_track_descs(const SrsMediaDesc &remote_media_desc, + SrsRtcTrackDescription *track_desc) { + if (remote_media_desc.simulcast_spec_version()) { + // todo check simulcast kSimulcastApiVersionSpecCompliant + for (size_t i=0; icopy(); + // NOTE: here ssrc is 0 + // track_desc_copy->ssrc_ = 0; + track_desc_copy->id_ = remote_media_desc.msid_tracker_; + track_desc_copy->msid_ = remote_media_desc.msid_; + video_track_descs_.push_back(track_desc_copy); + } + } else { + string track_id; + for (int j = 0; j < (int)remote_media_desc.ssrc_infos_.size(); ++j) { + const SrsSSRCInfo& ssrc_info = remote_media_desc.ssrc_infos_.at(j); + + // ssrc have same track id, will be description in the same track description. + if(track_id != ssrc_info.msid_tracker_) { + SrsRtcTrackDescription* track_desc_copy = track_desc->copy(); + track_desc_copy->ssrc_ = ssrc_info.ssrc_; + track_desc_copy->id_ = ssrc_info.msid_tracker_; + track_desc_copy->msid_ = ssrc_info.msid_; + if (remote_media_desc.is_original_ssrc(&ssrc_info)) { + // note: set ssrc related to single or simulcast(chrome munging style simulcast sdp) + // todo: support standard simulcast, like "a=simulcast:send a;b;c" + video_track_descs_.push_back(track_desc_copy); + srs_info("%d/%d#publish video track_id_=%s, ssrc_=%u, msid_=%s, msid_tracker_='%s'", j,(int)remote_media_desc.ssrc_infos_.size(), + track_id.c_str(), ssrc_info.ssrc_, ssrc_info.msid_.c_str(), ssrc_info.msid_tracker_.c_str()); + } else { + srs_info("%d/%d#ignore track_id_=%s, ssrc_=%u, msid_=%s, msid_tracker_='%s'", j, + (int) remote_media_desc.ssrc_infos_.size(), + track_id.c_str(), ssrc_info.ssrc_, ssrc_info.msid_.c_str(), + ssrc_info.msid_tracker_.c_str()); + } + track_id = ssrc_info.msid_tracker_; + } else { + srs_info("%d/%d#ignore track_id_/msid_tracker_=%s, ssrc_=%u, msid_=%s", j, + (int) remote_media_desc.ssrc_infos_.size(), + track_id.c_str(), ssrc_info.ssrc_, ssrc_info.msid_.c_str()); + track_id = ""; + } + } + } +} + SrsRtcRecvTrack::SrsRtcRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc, bool is_audio) { session_ = session; @@ -2433,6 +2501,24 @@ srs_error_t SrsRtcRecvTrack::do_check_send_nacks(uint32_t& timeout_nacks) return err; } +bool SrsRtcRecvTrack::active_as(const SrsRidInfo &rid_info) { + if (track_desc_->ssrc_ > 0) { + return false; + } + track_desc_->ssrc_ = rid_info.ssrc; + track_desc_->rid_ = rid_info; + + // TODO: Improve the generation of FEC SSRC and RTX SSRC + track_desc_->set_fec_ssrc(rid_info.ssrc + 1); + track_desc_->set_rtx_ssrc(rid_info.ssrc + 2); + + // if (track_desc_->is_active_) { + // return false; + // } + // track_desc_->is_active_ = true; + return true; +} + SrsRtcAudioRecvTrack::SrsRtcAudioRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc) : SrsRtcRecvTrack(session, track_desc, true) { diff --git a/trunk/src/app/srs_app_rtc_source.hpp b/trunk/src/app/srs_app_rtc_source.hpp index 8b5a9d78659..c65411ac3f3 100644 --- a/trunk/src/app/srs_app_rtc_source.hpp +++ b/trunk/src/app/srs_app_rtc_source.hpp @@ -362,6 +362,7 @@ class SrsVideoPayload : public SrsCodecPayload std::string profile_level_id; std::string packetization_mode; std::string level_asymmerty_allow; + std::string x_google_bitrate; }; H264SpecificParameter h264_param_; @@ -444,6 +445,7 @@ class SrsRtcTrackDescription // ssrc is the primary ssrc for this track, // if sdp has ssrc-group, it is the first ssrc of the ssrc-group uint32_t ssrc_; + SrsRidInfo rid_; // rtx ssrc is the second ssrc of "FEC" src-group, // if no rtx ssrc, rtx_ssrc_ = 0. uint32_t fec_ssrc_; @@ -501,6 +503,8 @@ class SrsRtcSourceDescription SrsRtcSourceDescription(); virtual ~SrsRtcSourceDescription(); + void set_audio_track_desc(const SrsMediaDesc &remote_media_desc, SrsRtcTrackDescription *track_desc); + void set_video_track_descs(const SrsMediaDesc &remote_media_desc, SrsRtcTrackDescription *track_desc); public: SrsRtcSourceDescription* copy(); SrsRtcTrackDescription* find_track_description_by_ssrc(uint32_t ssrc); @@ -532,6 +536,8 @@ class SrsRtcRecvTrack SrsRtcRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* stream_descs, bool is_audio); virtual ~SrsRtcRecvTrack(); public: + bool active_as(const SrsRidInfo &rid_info); + // SrsRtcSendTrack::set_nack_no_copy void set_nack_no_copy(bool v) { nack_no_copy_ = v; } bool has_ssrc(uint32_t ssrc); diff --git a/trunk/src/kernel/srs_kernel_rtc_rtp.cpp b/trunk/src/kernel/srs_kernel_rtc_rtp.cpp index 32a6d2c8e07..0635987fc09 100644 --- a/trunk/src/kernel/srs_kernel_rtc_rtp.cpp +++ b/trunk/src/kernel/srs_kernel_rtc_rtp.cpp @@ -124,6 +124,68 @@ srs_error_t srs_rtp_fast_parse_twcc(char* buf, int size, uint8_t twcc_id, uint16 return err; } +srs_error_t srs_rtp_fast_parse_rid(char* buf, int size, uint8_t rid_id, std::string& rid) +{ + srs_error_t err = srs_success; + + int need_size = 12 /*rtp head fix len*/ + 4 /* extension header len*/ + 3 /* twcc extension len*/; + if(size < (need_size)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "required %d bytes, actual %d", need_size, size); + } + + uint8_t first = buf[0]; + bool extension = (first & 0x10); + uint8_t cc = (first & 0x0F); + + if(!extension) { + return srs_error_new(ERROR_RTC_RTP, "no extension in rtp"); + } + + need_size += cc * 4; // csrc size + if(size < (need_size)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "required %d bytes, actual %d", need_size, size); + } + buf += 12 + 4*cc; + + uint16_t value = *((uint16_t*)buf); + value = ntohs(value); + if(0xBEDE != value) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "no support this type(0x%02x) extension", value); + } + buf += 2; + + uint16_t extension_length = ntohs(*((uint16_t*)buf)); + buf += 2; + extension_length *= 4; + need_size += extension_length; // entension size + if(size < (need_size)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "required %d bytes, actual %d", need_size, size); + } + + while(extension_length > 0) { + uint8_t v = buf[0]; + buf++; + extension_length--; + if(0 == v) { + continue; + } + + uint8_t id = (v & 0xF0) >>4; + uint8_t len = (v & 0x0F) + 1; + + if(id == rid_id) { + rid.append(buf, len); + return err; + } else { + buf += len; + extension_length -= len; + } + } + + + return err; +} + // If value is newer than pre_value,return true; otherwise false bool srs_seq_is_newer(uint16_t value, uint16_t pre_value) { @@ -396,7 +458,7 @@ srs_error_t SrsRtpExtensions::decode_0xbede(SrsBuffer* buf) uint8_t len = (v & 0x0F) + 1; SrsRtpExtensionType xtype = types_? types_->get_type(id) : kRtpExtensionNone; - if (xtype == kRtpExtensionTransportSequenceNumber) { + if (xtype == kRtpExtTwcc) { if (decode_twcc_extension_) { if ((err = twcc_.decode(buf)) != srs_success) { return srs_error_wrap(err, "decode twcc extension"); @@ -408,7 +470,7 @@ srs_error_t SrsRtpExtensions::decode_0xbede(SrsBuffer* buf) } buf->skip(len + 1); } - } else if (xtype == kRtpExtensionAudioLevel) { + } else if (xtype == kRtpExtSsrcAudioLevel) { if((err = audio_level_.decode(buf)) != srs_success) { return srs_error_wrap(err, "decode audio level extension"); } diff --git a/trunk/src/kernel/srs_kernel_rtc_rtp.hpp b/trunk/src/kernel/srs_kernel_rtc_rtp.hpp index 00f8c7d3fe3..38bc23fbe4d 100644 --- a/trunk/src/kernel/srs_kernel_rtc_rtp.hpp +++ b/trunk/src/kernel/srs_kernel_rtc_rtp.hpp @@ -48,6 +48,7 @@ class SrsRtpExtensionTypes; uint32_t srs_rtp_fast_parse_ssrc(char* buf, int size); uint8_t srs_rtp_fast_parse_pt(char* buf, int size); srs_error_t srs_rtp_fast_parse_twcc(char* buf, int size, uint8_t twcc_id, uint16_t& twcc_sn); +srs_error_t srs_rtp_fast_parse_rid(char* buf, int size, uint8_t rid_id, std::string& rid); // The "distance" between two uint16 number, for example: // distance(prev_value=3, value=5) === (int16_t)(uint16_t)((uint16_t)3-(uint16_t)5) === -2 @@ -76,13 +77,26 @@ int32_t srs_seq_distance(uint16_t value, uint16_t pre_value); enum SrsRtpExtensionType { kRtpExtensionNone, - kRtpExtensionTransportSequenceNumber, - kRtpExtensionAudioLevel, + + kRtpExtTwcc, + kRtpExtSsrcAudioLevel, + kRtpExtSdesMid, + kRtpExtSdesRtpStreamId, + kRtpExtSdesRepairedRtpStreamId, + kRtpExtAbsSendTime, + kRtpExtVideoTiming, + kRtpExtColorSpace, + kRtpExtCsrcAudioLevel, + kRtpExtFramemarking, + kRtpExtVideoContentType, + kRtpExtPlayoutDelay, + kRtpExtVideoOrientation, + kRtpExtToffset, + kRtpExtEncrypt, + kRtpExtensionNumberOfExtensions // Must be the last entity in the enum. }; -const std::string kAudioLevelUri = "urn:ietf:params:rtp-hdrext:ssrc-audio-level"; - struct SrsExtensionInfo { SrsRtpExtensionType type; @@ -90,8 +104,22 @@ struct SrsExtensionInfo }; const SrsExtensionInfo kExtensions[] = { - {kRtpExtensionTransportSequenceNumber, std::string("http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01")}, - {kRtpExtensionAudioLevel, kAudioLevelUri}, + {kRtpExtensionNone, std::string("")}, + {kRtpExtTwcc, std::string("http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01")}, + {kRtpExtSsrcAudioLevel, std::string("urn:ietf:params:rtp-hdrext:ssrc-audio-level")}, + {kRtpExtSdesMid, std::string("urn:ietf:params:rtp-hdrext:sdes:mid")}, + {kRtpExtSdesRtpStreamId, std::string("urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id")}, + {kRtpExtSdesRepairedRtpStreamId, std::string("urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id")}, + {kRtpExtAbsSendTime, std::string("http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time")}, + {kRtpExtVideoTiming, std::string("http://www.webrtc.org/experiments/rtp-hdrext/video-timing")}, + {kRtpExtColorSpace, std::string("http://www.webrtc.org/experiments/rtp-hdrext/color-space")}, + {kRtpExtCsrcAudioLevel, std::string("urn:ietf:params:rtp-hdrext:csrc-audio-level")}, + {kRtpExtFramemarking, std::string("http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07")}, + {kRtpExtVideoContentType, std::string("http://www.webrtc.org/experiments/rtp-hdrext/video-content-type")}, + {kRtpExtPlayoutDelay, std::string("http://www.webrtc.org/experiments/rtp-hdrext/playout-delay")}, + {kRtpExtVideoOrientation, std::string("urn:3gpp:video-orientation")}, + {kRtpExtToffset, std::string("urn:ietf:params:rtp-hdrext:toffset")}, + {kRtpExtEncrypt, std::string("urn:ietf:params:rtp-hdrext:encrypt")} }; class SrsRtpExtensionTypes