Generalize stream parameter primary/secondary ssrc checks
to ensure consistency for both FID and FEC-FR ssrc-groups.
BUG=chromium:1454860
Change-Id: I61277e73e0a28f5773260ec62c268bdc8c2cd738
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/309760
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Philipp Hancke <phancke@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#40347}
diff --git a/media/base/stream_params.cc b/media/base/stream_params.cc
index 0fe1be6..ac9daee 100644
--- a/media/base/stream_params.cc
+++ b/media/base/stream_params.cc
@@ -183,16 +183,23 @@
}
}
-void StreamParams::GetFidSsrcs(const std::vector<uint32_t>& primary_ssrcs,
- std::vector<uint32_t>* fid_ssrcs) const {
+void StreamParams::GetSecondarySsrcs(
+ const std::string& semantics,
+ const std::vector<uint32_t>& primary_ssrcs,
+ std::vector<uint32_t>* secondary_ssrcs) const {
for (uint32_t primary_ssrc : primary_ssrcs) {
- uint32_t fid_ssrc;
- if (GetFidSsrc(primary_ssrc, &fid_ssrc)) {
- fid_ssrcs->push_back(fid_ssrc);
+ uint32_t secondary_ssrc;
+ if (GetSecondarySsrc(semantics, primary_ssrc, &secondary_ssrc)) {
+ secondary_ssrcs->push_back(secondary_ssrc);
}
}
}
+void StreamParams::GetFidSsrcs(const std::vector<uint32_t>& primary_ssrcs,
+ std::vector<uint32_t>* fid_ssrcs) const {
+ return GetSecondarySsrcs(kFidSsrcGroupSemantics, primary_ssrcs, fid_ssrcs);
+}
+
bool StreamParams::AddSecondarySsrc(const std::string& semantics,
uint32_t primary_ssrc,
uint32_t secondary_ssrc) {
diff --git a/media/base/stream_params.h b/media/base/stream_params.h
index 60c67a1..89fc155 100644
--- a/media/base/stream_params.h
+++ b/media/base/stream_params.h
@@ -166,6 +166,14 @@
// the first SSRC otherwise.
void GetPrimarySsrcs(std::vector<uint32_t>* ssrcs) const;
+ // Convenience to get all the secondary SSRCs for the given primary ssrcs
+ // of a particular semantic.
+ // If a given primary SSRC does not have a secondary SSRC, the list of
+ // secondary SSRCS will be smaller than the list of primary SSRCs.
+ void GetSecondarySsrcs(const std::string& semantic,
+ const std::vector<uint32_t>& primary_ssrcs,
+ std::vector<uint32_t>* fid_ssrcs) const;
+
// Convenience to get all the FID SSRCs for the given primary ssrcs.
// If a given primary SSRC does not have a FID SSRC, the list of FID
// SSRCS will be smaller than the list of primary SSRCs.
diff --git a/media/base/test_utils.cc b/media/base/test_utils.cc
index a6d5f61..1b28873 100644
--- a/media/base/test_utils.cc
+++ b/media/base/test_utils.cc
@@ -35,26 +35,20 @@
const std::vector<uint32_t>& rtx_ssrcs) {
cricket::StreamParams sp = CreateSimStreamParams(cname, ssrcs);
for (size_t i = 0; i < ssrcs.size(); ++i) {
- sp.ssrcs.push_back(rtx_ssrcs[i]);
- std::vector<uint32_t> fid_ssrcs;
- fid_ssrcs.push_back(ssrcs[i]);
- fid_ssrcs.push_back(rtx_ssrcs[i]);
- cricket::SsrcGroup fid_group(cricket::kFidSsrcGroupSemantics, fid_ssrcs);
- sp.ssrc_groups.push_back(fid_group);
+ sp.AddFidSsrc(ssrcs[i], rtx_ssrcs[i]);
}
return sp;
}
+// There should be one fec ssrc per ssrc.
cricket::StreamParams CreatePrimaryWithFecFrStreamParams(
const std::string& cname,
uint32_t primary_ssrc,
uint32_t flexfec_ssrc) {
cricket::StreamParams sp;
- cricket::SsrcGroup sg(cricket::kFecFrSsrcGroupSemantics,
- {primary_ssrc, flexfec_ssrc});
sp.ssrcs = {primary_ssrc};
- sp.ssrc_groups.push_back(sg);
sp.cname = cname;
+ sp.AddFecFrSsrc(primary_ssrc, flexfec_ssrc);
return sp;
}
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index a8309f5..83bb39d 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -311,30 +311,37 @@
std::vector<uint32_t> primary_ssrcs;
sp.GetPrimarySsrcs(&primary_ssrcs);
- std::vector<uint32_t> rtx_ssrcs;
- sp.GetFidSsrcs(primary_ssrcs, &rtx_ssrcs);
- for (uint32_t rtx_ssrc : rtx_ssrcs) {
- bool rtx_ssrc_present = false;
- for (uint32_t sp_ssrc : sp.ssrcs) {
- if (sp_ssrc == rtx_ssrc) {
- rtx_ssrc_present = true;
- break;
+ for (const auto& semantic :
+ {kFidSsrcGroupSemantics, kFecFrSsrcGroupSemantics}) {
+ if (!sp.has_ssrc_group(semantic)) {
+ continue;
+ }
+ std::vector<uint32_t> secondary_ssrcs;
+ sp.GetSecondarySsrcs(semantic, primary_ssrcs, &secondary_ssrcs);
+ for (uint32_t secondary_ssrc : secondary_ssrcs) {
+ bool secondary_ssrc_present = false;
+ for (uint32_t sp_ssrc : sp.ssrcs) {
+ if (sp_ssrc == secondary_ssrc) {
+ secondary_ssrc_present = true;
+ break;
+ }
+ }
+ if (!secondary_ssrc_present) {
+ RTC_LOG(LS_ERROR) << "SSRC '" << secondary_ssrc
+ << "' missing from StreamParams ssrcs with semantics "
+ << semantic << ": " << sp.ToString();
+ return false;
}
}
- if (!rtx_ssrc_present) {
- RTC_LOG(LS_ERROR) << "RTX SSRC '" << rtx_ssrc
- << "' missing from StreamParams ssrcs: "
- << sp.ToString();
+ if (!secondary_ssrcs.empty() &&
+ primary_ssrcs.size() != secondary_ssrcs.size()) {
+ RTC_LOG(LS_ERROR)
+ << semantic
+ << " secondary SSRCs exist, but don't cover all SSRCs (unsupported): "
+ << sp.ToString();
return false;
}
}
- if (!rtx_ssrcs.empty() && primary_ssrcs.size() != rtx_ssrcs.size()) {
- RTC_LOG(LS_ERROR)
- << "RTX SSRCs exist, but don't cover all SSRCs (unsupported): "
- << sp.ToString();
- return false;
- }
-
return true;
}
diff --git a/pc/sdp_offer_answer.cc b/pc/sdp_offer_answer.cc
index 6596d82..fa16ec6 100644
--- a/pc/sdp_offer_answer.cc
+++ b/pc/sdp_offer_answer.cc
@@ -491,6 +491,25 @@
return RTCError::OK();
}
+RTCError ValidateSsrcGroups(const cricket::SessionDescription& description) {
+ // https://www.rfc-editor.org/rfc/rfc5576#section-4.2
+ // Every <ssrc-id> listed in an "ssrc-group" attribute MUST be
+ // defined by a corresponding "ssrc:" line in the same media
+ // description.
+ for (const ContentInfo& content : description.contents()) {
+ if (!content.media_description()) {
+ continue;
+ }
+ for (const StreamParams& sp : content.media_description()->streams()) {
+ for (const cricket::SsrcGroup& group : sp.ssrc_groups) {
+ RTC_LOG(LS_ERROR) << "YO " << group.semantics << " #"
+ << group.ssrcs.size();
+ }
+ }
+ }
+ return RTCError::OK();
+}
+
RTCError FindDuplicateHeaderExtensionIds(
const RtpExtension extension,
std::map<int, RtpExtension>& id_to_extension) {
@@ -3567,6 +3586,11 @@
return RTCError(RTCErrorType::INVALID_PARAMETER, kBundleWithoutRtcpMux);
}
+ error = ValidateSsrcGroups(*sdesc->description());
+ if (!error.ok()) {
+ return error;
+ }
+
// TODO(skvlad): When the local rtcp-mux policy is Require, reject any
// m-lines that do not rtcp-mux enabled.
diff --git a/pc/sdp_offer_answer_unittest.cc b/pc/sdp_offer_answer_unittest.cc
index e9ec17b..604ad27 100644
--- a/pc/sdp_offer_answer_unittest.cc
+++ b/pc/sdp_offer_answer_unittest.cc
@@ -599,4 +599,64 @@
EXPECT_TRUE(pc->SetRemoteDescription(std::move(answer_with_extensions)));
}
+TEST_F(SdpOfferAnswerTest, ExpectAllSsrcsSpecifiedInSsrcGroupFid) {
+ auto pc = CreatePeerConnection();
+ std::string sdp =
+ "v=0\r\n"
+ "o=- 0 3 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "t=0 0\r\n"
+ "a=group:BUNDLE 0\r\n"
+ "a=fingerprint:sha-1 "
+ "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n"
+ "a=setup:actpass\r\n"
+ "a=ice-ufrag:ETEn\r\n"
+ "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n"
+ "m=video 9 UDP/TLS/RTP/SAVPF 96 97\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "a=rtcp-mux\r\n"
+ "a=sendonly\r\n"
+ "a=mid:0\r\n"
+ "a=rtpmap:96 H264/90000\r\n"
+ "a=fmtp:96 "
+ "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id="
+ "42e01f\r\n"
+ "a=rtpmap:97 rtx/90000\r\n"
+ "a=fmtp:97 apt=96\r\n"
+ "a=ssrc-group:FID 1 2\r\n"
+ "a=ssrc:1 cname:test\r\n";
+ auto offer = CreateSessionDescription(SdpType::kOffer, sdp);
+ EXPECT_FALSE(pc->SetRemoteDescription(std::move(offer)));
+}
+
+TEST_F(SdpOfferAnswerTest, ExpectAllSsrcsSpecifiedInSsrcGroupFecFr) {
+ auto pc = CreatePeerConnection();
+ std::string sdp =
+ "v=0\r\n"
+ "o=- 0 3 IN IP4 127.0.0.1\r\n"
+ "s=-\r\n"
+ "t=0 0\r\n"
+ "a=group:BUNDLE 0\r\n"
+ "a=fingerprint:sha-1 "
+ "4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB\r\n"
+ "a=setup:actpass\r\n"
+ "a=ice-ufrag:ETEn\r\n"
+ "a=ice-pwd:OtSK0WpNtpUjkY4+86js7Z/l\r\n"
+ "m=video 9 UDP/TLS/RTP/SAVPF 96 98\r\n"
+ "c=IN IP4 0.0.0.0\r\n"
+ "a=rtcp-mux\r\n"
+ "a=sendonly\r\n"
+ "a=mid:0\r\n"
+ "a=rtpmap:96 H264/90000\r\n"
+ "a=fmtp:96 "
+ "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id="
+ "42e01f\r\n"
+ "a=rtpmap:98 flexfec-03/90000\r\n"
+ "a=fmtp:98 repair-window=10000000\r\n"
+ "a=ssrc-group:FEC-FR 1 2\r\n"
+ "a=ssrc:1 cname:test\r\n";
+ auto offer = CreateSessionDescription(SdpType::kOffer, sdp);
+ EXPECT_FALSE(pc->SetRemoteDescription(std::move(offer)));
+}
+
} // namespace webrtc