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}
This commit is contained in:
Philipp Hancke 2023-06-26 12:27:31 +02:00 committed by WebRTC LUCI CQ
parent 48a2af35e1
commit 0776415a41
6 changed files with 133 additions and 33 deletions

View file

@ -183,16 +183,23 @@ void StreamParams::GetPrimarySsrcs(std::vector<uint32_t>* ssrcs) const {
} }
} }
void StreamParams::GetFidSsrcs(const std::vector<uint32_t>& primary_ssrcs, void StreamParams::GetSecondarySsrcs(
std::vector<uint32_t>* fid_ssrcs) const { 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) { for (uint32_t primary_ssrc : primary_ssrcs) {
uint32_t fid_ssrc; uint32_t secondary_ssrc;
if (GetFidSsrc(primary_ssrc, &fid_ssrc)) { if (GetSecondarySsrc(semantics, primary_ssrc, &secondary_ssrc)) {
fid_ssrcs->push_back(fid_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, bool StreamParams::AddSecondarySsrc(const std::string& semantics,
uint32_t primary_ssrc, uint32_t primary_ssrc,
uint32_t secondary_ssrc) { uint32_t secondary_ssrc) {

View file

@ -166,6 +166,14 @@ struct StreamParams {
// the first SSRC otherwise. // the first SSRC otherwise.
void GetPrimarySsrcs(std::vector<uint32_t>* ssrcs) const; 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. // 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 // 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. // SSRCS will be smaller than the list of primary SSRCs.

View file

@ -35,26 +35,20 @@ cricket::StreamParams CreateSimWithRtxStreamParams(
const std::vector<uint32_t>& rtx_ssrcs) { const std::vector<uint32_t>& rtx_ssrcs) {
cricket::StreamParams sp = CreateSimStreamParams(cname, ssrcs); cricket::StreamParams sp = CreateSimStreamParams(cname, ssrcs);
for (size_t i = 0; i < ssrcs.size(); ++i) { for (size_t i = 0; i < ssrcs.size(); ++i) {
sp.ssrcs.push_back(rtx_ssrcs[i]); sp.AddFidSsrc(ssrcs[i], 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);
} }
return sp; return sp;
} }
// There should be one fec ssrc per ssrc.
cricket::StreamParams CreatePrimaryWithFecFrStreamParams( cricket::StreamParams CreatePrimaryWithFecFrStreamParams(
const std::string& cname, const std::string& cname,
uint32_t primary_ssrc, uint32_t primary_ssrc,
uint32_t flexfec_ssrc) { uint32_t flexfec_ssrc) {
cricket::StreamParams sp; cricket::StreamParams sp;
cricket::SsrcGroup sg(cricket::kFecFrSsrcGroupSemantics,
{primary_ssrc, flexfec_ssrc});
sp.ssrcs = {primary_ssrc}; sp.ssrcs = {primary_ssrc};
sp.ssrc_groups.push_back(sg);
sp.cname = cname; sp.cname = cname;
sp.AddFecFrSsrc(primary_ssrc, flexfec_ssrc);
return sp; return sp;
} }

View file

@ -311,30 +311,37 @@ static bool ValidateStreamParams(const StreamParams& sp) {
std::vector<uint32_t> primary_ssrcs; std::vector<uint32_t> primary_ssrcs;
sp.GetPrimarySsrcs(&primary_ssrcs); sp.GetPrimarySsrcs(&primary_ssrcs);
std::vector<uint32_t> rtx_ssrcs; for (const auto& semantic :
sp.GetFidSsrcs(primary_ssrcs, &rtx_ssrcs); {kFidSsrcGroupSemantics, kFecFrSsrcGroupSemantics}) {
for (uint32_t rtx_ssrc : rtx_ssrcs) { if (!sp.has_ssrc_group(semantic)) {
bool rtx_ssrc_present = false; continue;
for (uint32_t sp_ssrc : sp.ssrcs) { }
if (sp_ssrc == rtx_ssrc) { std::vector<uint32_t> secondary_ssrcs;
rtx_ssrc_present = true; sp.GetSecondarySsrcs(semantic, primary_ssrcs, &secondary_ssrcs);
break; 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) { if (!secondary_ssrcs.empty() &&
RTC_LOG(LS_ERROR) << "RTX SSRC '" << rtx_ssrc primary_ssrcs.size() != secondary_ssrcs.size()) {
<< "' missing from StreamParams ssrcs: " RTC_LOG(LS_ERROR)
<< sp.ToString(); << semantic
<< " secondary SSRCs exist, but don't cover all SSRCs (unsupported): "
<< sp.ToString();
return false; 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; return true;
} }

View file

@ -491,6 +491,25 @@ RTCError ValidateBundledPayloadTypes(
return RTCError::OK(); 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( RTCError FindDuplicateHeaderExtensionIds(
const RtpExtension extension, const RtpExtension extension,
std::map<int, RtpExtension>& id_to_extension) { std::map<int, RtpExtension>& id_to_extension) {
@ -3567,6 +3586,11 @@ RTCError SdpOfferAnswerHandler::ValidateSessionDescription(
return RTCError(RTCErrorType::INVALID_PARAMETER, kBundleWithoutRtcpMux); 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 // TODO(skvlad): When the local rtcp-mux policy is Require, reject any
// m-lines that do not rtcp-mux enabled. // m-lines that do not rtcp-mux enabled.

View file

@ -599,4 +599,64 @@ TEST_F(SdpOfferAnswerTest, SimulcastAnswerWithNoRidsIsRejected) {
EXPECT_TRUE(pc->SetRemoteDescription(std::move(answer_with_extensions))); 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 } // namespace webrtc