Bug 1952339 - Vendor libwebrtc from a416f1c7a9
Upstream commit: https://webrtc.googlesource.com/src/+/a416f1c7a93ea45c98bdcdca187aed182e428761 Fix rid 'send pt=X' bug and re-enable test. When creating an offer, the send PT is the payload type of the codec that we're advertising that we would like to send. It comes from the RidDescription's list of payload types (= integers prior to this CL) which is controlled from the API with `RTCRtpEncodingParameters.codec`. The problem was that the encodings[i].codec -> PT conversion step in sdp_offer_answer.cc's GetMediaDescriptionOptionsForTransceiver() was based on `transceiver->sender_internal()->GetSendCodecs()` whose PTs are inconsistent with the payload types that gets assigned to the media section in a later step using PayloadTypeSuggester. To fix this, the RidDescription is updated to have a list of `codecs` instead of a list of `payload_types` and the "codec -> PT" conversation step is... 1. Moved to SimulcastSdpSerializer and using lookup from MediaContentDescription as opposed to blindly trusting `codec.id` making PT inconsistencies impossible. 2. Uses IsSameRtpCodecIgnoringLevel() instead of MatchesRtpCodec(), ensuring codec match does not fail if the MediaContentDescription's list of codecs is not specifying default parameters (e.g. see SdpOfferAnswerTest.SimulcastAnswerWithPayloadType not specifying profile-id fails if MatchesRtpCodec is used). Another drive-by bugfix is we no longer remove entire RIDs if all PTs in the RID are invalid, see TestDeserializeRemovesRidsWithInvalidCodec. Bug: webrtc:362277533 Change-Id: Ic11ecf2a85e6d8db7fc269bc53c3ac11d5d8b127 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/378284 Commit-Queue: Henrik Boström <hbos@webrtc.org> Reviewed-by: Harald Alvestrand <hta@webrtc.org> Cr-Commit-Position: refs/heads/main@{#43982} Differential Revision: https://phabricator.services.mozilla.com/D243987
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /home/mfroman/mozilla/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
|
||||
libwebrtc updated from /home/mfroman/mozilla/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-03-07T22:29:27.219556+00:00.
|
||||
libwebrtc updated from /home/mfroman/mozilla/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-03-07T22:30:17.188893+00:00.
|
||||
# base of lastest vendoring
|
||||
b9e8c0e062
|
||||
a416f1c7a9
|
||||
|
||||
2
third_party/libwebrtc/media/BUILD.gn
vendored
2
third_party/libwebrtc/media/BUILD.gn
vendored
@@ -470,7 +470,7 @@ rtc_library("rid_description") {
|
||||
"base/rid_description.cc",
|
||||
"base/rid_description.h",
|
||||
]
|
||||
deps = []
|
||||
deps = [ ":codec" ]
|
||||
}
|
||||
|
||||
rtc_library("rtc_simulcast_encoder_adapter") {
|
||||
|
||||
@@ -21,8 +21,7 @@ RidDescription& RidDescription::operator=(const RidDescription& other) =
|
||||
default;
|
||||
bool RidDescription::operator==(const RidDescription& other) const {
|
||||
return rid == other.rid && direction == other.direction &&
|
||||
payload_types == other.payload_types &&
|
||||
restrictions == other.restrictions;
|
||||
codecs == other.codecs && restrictions == other.restrictions;
|
||||
}
|
||||
|
||||
} // namespace cricket
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "media/base/codec.h"
|
||||
|
||||
namespace cricket {
|
||||
|
||||
enum class RidDirection { kSend, kReceive };
|
||||
@@ -73,9 +75,11 @@ struct RidDescription final {
|
||||
// the stream were changed to "sendrecv" or "recvonly".
|
||||
RidDirection direction;
|
||||
|
||||
// The list of codec payload types for this stream.
|
||||
// It should be a subset of the payloads supported for the media section.
|
||||
std::vector<int> payload_types;
|
||||
// The list of codecs for this stream.
|
||||
// When the RID is serialized/deserialized, these codecs are mapped to/from
|
||||
// the payload types listed in the media section, ensuring PT consistency in
|
||||
// the SDP even when `codecs[i].id` cannot be trusted.
|
||||
std::vector<Codec> codecs;
|
||||
|
||||
// Contains key-value pairs for restrictions.
|
||||
// The keys are not validated against a known set.
|
||||
|
||||
@@ -574,7 +574,7 @@ index 0000000000..f6ff7f218f
|
||||
+ #endif
|
||||
+#endif
|
||||
diff --git a/media/BUILD.gn b/media/BUILD.gn
|
||||
index 3c42089afc..0d69ef4858 100644
|
||||
index 0195607ddb..7533eb233e 100644
|
||||
--- a/media/BUILD.gn
|
||||
+++ b/media/BUILD.gn
|
||||
@@ -76,7 +76,7 @@ rtc_library("rtc_media_base") {
|
||||
@@ -681,7 +681,7 @@ index 3c42089afc..0d69ef4858 100644
|
||||
"base/rid_description.cc",
|
||||
"base/rid_description.h",
|
||||
]
|
||||
deps = []
|
||||
deps = [ ":codec" ]
|
||||
}
|
||||
+}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/60304c5d8a86fdecf
|
||||
1 file changed, 11 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/media/BUILD.gn b/media/BUILD.gn
|
||||
index 0d69ef4858..5127f57c12 100644
|
||||
index 7533eb233e..bc8e9810ca 100644
|
||||
--- a/media/BUILD.gn
|
||||
+++ b/media/BUILD.gn
|
||||
@@ -55,6 +55,11 @@ rtc_library("rtc_media_base") {
|
||||
|
||||
@@ -35,7 +35,7 @@ index e379d18527..c810498584 100644
|
||||
# Normally, we'd use 'if (!build_with_mozilla)', but that flag isn't
|
||||
# available yet.
|
||||
diff --git a/media/BUILD.gn b/media/BUILD.gn
|
||||
index 5127f57c12..f6c7bf41b8 100644
|
||||
index bc8e9810ca..6910e0a198 100644
|
||||
--- a/media/BUILD.gn
|
||||
+++ b/media/BUILD.gn
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
@@ -470,7 +470,7 @@ index d91a446629..3c18ef1841 100644
|
||||
|
||||
group("logging") {
|
||||
diff --git a/media/BUILD.gn b/media/BUILD.gn
|
||||
index f6c7bf41b8..807993a5e0 100644
|
||||
index 6910e0a198..e993807abb 100644
|
||||
--- a/media/BUILD.gn
|
||||
+++ b/media/BUILD.gn
|
||||
@@ -6,7 +6,7 @@
|
||||
@@ -644,7 +644,7 @@ index 3053b829f2..703e561c85 100644
|
||||
import("../../webrtc.gni")
|
||||
|
||||
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
|
||||
index e6510d8b96..2169b292bf 100644
|
||||
index 8c515bc4d8..b80d1483f6 100644
|
||||
--- a/pc/BUILD.gn
|
||||
+++ b/pc/BUILD.gn
|
||||
@@ -30,8 +30,8 @@
|
||||
|
||||
@@ -38,7 +38,7 @@ index cb91c54edc..b33b252f06 100644
|
||||
]
|
||||
# Added when we removed deps in other places to avoid building
|
||||
diff --git a/media/BUILD.gn b/media/BUILD.gn
|
||||
index 807993a5e0..605000f883 100644
|
||||
index e993807abb..6ef84a1195 100644
|
||||
--- a/media/BUILD.gn
|
||||
+++ b/media/BUILD.gn
|
||||
@@ -12,12 +12,10 @@ import("../webrtc.gni")
|
||||
@@ -96,7 +96,7 @@ index 807993a5e0..605000f883 100644
|
||||
"base/rid_description.cc",
|
||||
"base/rid_description.h",
|
||||
]
|
||||
deps = []
|
||||
deps = [ ":codec" ]
|
||||
}
|
||||
-}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/b6dd815fc9d2df718
|
||||
1 file changed, 11 deletions(-)
|
||||
|
||||
diff --git a/media/BUILD.gn b/media/BUILD.gn
|
||||
index 605000f883..eaf1565b45 100644
|
||||
index 6ef84a1195..3aff3bb21c 100644
|
||||
--- a/media/BUILD.gn
|
||||
+++ b/media/BUILD.gn
|
||||
@@ -53,11 +53,6 @@ rtc_library("rtc_media_base") {
|
||||
|
||||
1
third_party/libwebrtc/pc/BUILD.gn
vendored
1
third_party/libwebrtc/pc/BUILD.gn
vendored
@@ -1250,6 +1250,7 @@ rtc_source_set("simulcast_sdp_serializer") {
|
||||
":session_description",
|
||||
":simulcast_description",
|
||||
"../api:rtc_error",
|
||||
"../media:codec",
|
||||
"../media:rid_description",
|
||||
"../modules/rtp_rtcp:rtp_rtcp_format",
|
||||
"../rtc_base:checks",
|
||||
|
||||
17
third_party/libwebrtc/pc/sdp_offer_answer.cc
vendored
17
third_party/libwebrtc/pc/sdp_offer_answer.cc
vendored
@@ -52,6 +52,7 @@
|
||||
#include "api/video/video_codec_constants.h"
|
||||
#include "call/payload_type.h"
|
||||
#include "media/base/codec.h"
|
||||
#include "media/base/codec_comparators.h"
|
||||
#include "media/base/media_engine.h"
|
||||
#include "media/base/rid_description.h"
|
||||
#include "media/base/stream_params.h"
|
||||
@@ -644,16 +645,8 @@ std::vector<RtpEncodingParameters> GetSendEncodingsFromRemoteDescription(
|
||||
auto rid_desc = std::find_if(
|
||||
desc.receive_rids().begin(), desc.receive_rids().end(),
|
||||
[&layer](const RidDescription& rid) { return rid.rid == layer.rid; });
|
||||
if (rid_desc != desc.receive_rids().end() &&
|
||||
!rid_desc->payload_types.empty()) {
|
||||
int payload_type = rid_desc->payload_types[0];
|
||||
auto codec = std::find_if(desc.codecs().begin(), desc.codecs().end(),
|
||||
[payload_type](const cricket::Codec& codec) {
|
||||
return codec.id == payload_type;
|
||||
});
|
||||
if (codec != desc.codecs().end()) {
|
||||
parameters.codec = codec->ToCodecParameters();
|
||||
}
|
||||
if (rid_desc != desc.receive_rids().end() && !rid_desc->codecs.empty()) {
|
||||
parameters.codec = rid_desc->codecs[0].ToCodecParameters();
|
||||
}
|
||||
result.push_back(parameters);
|
||||
}
|
||||
@@ -813,8 +806,8 @@ cricket::MediaDescriptionOptions GetMediaDescriptionOptionsForTransceiver(
|
||||
if (encoding.codec) {
|
||||
auto send_codecs = transceiver->sender_internal()->GetSendCodecs();
|
||||
for (const cricket::Codec& codec : send_codecs) {
|
||||
if (codec.MatchesRtpCodec(*encoding.codec)) {
|
||||
send_rid.payload_types.push_back(codec.id);
|
||||
if (IsSameRtpCodecIgnoringLevel(codec, *encoding.codec)) {
|
||||
send_rid.codecs.push_back(codec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -654,26 +654,27 @@ TEST_F(SdpOfferAnswerTest, SimulcastAnswerWithNoRidsIsRejected) {
|
||||
EXPECT_TRUE(pc->SetRemoteDescription(std::move(rejected_answer)));
|
||||
}
|
||||
|
||||
// TODO: bugs.webrtc.org/362277533 - reenable before launch.
|
||||
TEST_F(SdpOfferAnswerTest, DISABLED_SimulcastOfferWithMixedCodec) {
|
||||
TEST_F(SdpOfferAnswerTest, SimulcastOfferWithMixedCodec) {
|
||||
auto pc = CreatePeerConnection(
|
||||
FieldTrials::CreateNoGlobal("WebRTC-MixedCodecSimulcast/Enabled/"));
|
||||
|
||||
std::optional<RtpCodecCapability> vp8_codec = FindFirstSendCodecWithName(
|
||||
cricket::MEDIA_TYPE_VIDEO, cricket::kVp8CodecName);
|
||||
ASSERT_TRUE(vp8_codec);
|
||||
std::optional<RtpCodecCapability> vp9_codec = FindFirstSendCodecWithName(
|
||||
cricket::MEDIA_TYPE_VIDEO, cricket::kVp9CodecName);
|
||||
ASSERT_TRUE(vp9_codec);
|
||||
std::optional<RtpCodecCapability> vp8_codec_capability =
|
||||
FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
|
||||
cricket::kVp8CodecName);
|
||||
ASSERT_TRUE(vp8_codec_capability);
|
||||
std::optional<RtpCodecCapability> vp9_codec_capability =
|
||||
FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
|
||||
cricket::kVp9CodecName);
|
||||
ASSERT_TRUE(vp9_codec_capability);
|
||||
|
||||
RtpTransceiverInit init;
|
||||
RtpEncodingParameters rid1;
|
||||
rid1.rid = "1";
|
||||
rid1.codec = *vp8_codec;
|
||||
rid1.codec = *vp8_codec_capability;
|
||||
init.send_encodings.push_back(rid1);
|
||||
RtpEncodingParameters rid2;
|
||||
rid2.rid = "2";
|
||||
rid2.codec = *vp9_codec;
|
||||
rid2.codec = *vp9_codec_capability;
|
||||
init.send_encodings.push_back(rid2);
|
||||
|
||||
auto transceiver = pc->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
|
||||
@@ -683,28 +684,30 @@ TEST_F(SdpOfferAnswerTest, DISABLED_SimulcastOfferWithMixedCodec) {
|
||||
// Verify that the serialized SDP includes pt=.
|
||||
std::string sdp;
|
||||
offer->ToString(&sdp);
|
||||
int vp8_pt = cricket::Codec::kIdNotSet;
|
||||
int vp9_pt = cricket::Codec::kIdNotSet;
|
||||
const cricket::Codec* vp8_send_codec = nullptr;
|
||||
const cricket::Codec* vp9_send_codec = nullptr;
|
||||
for (auto& codec : send_codecs) {
|
||||
if (codec.name == vp8_codec->name && vp8_pt == cricket::Codec::kIdNotSet) {
|
||||
vp8_pt = codec.id;
|
||||
if (codec.name == vp8_codec_capability->name && !vp8_send_codec) {
|
||||
vp8_send_codec = &codec;
|
||||
}
|
||||
if (codec.name == vp9_codec->name && vp9_pt == cricket::Codec::kIdNotSet) {
|
||||
vp9_pt = codec.id;
|
||||
if (codec.name == vp9_codec_capability->name && !vp9_send_codec) {
|
||||
vp9_send_codec = &codec;
|
||||
}
|
||||
}
|
||||
EXPECT_THAT(sdp,
|
||||
testing::HasSubstr("a=rid:1 send pt=" + std::to_string(vp8_pt)));
|
||||
EXPECT_THAT(sdp,
|
||||
testing::HasSubstr("a=rid:2 send pt=" + std::to_string(vp9_pt)));
|
||||
ASSERT_TRUE(vp8_send_codec);
|
||||
ASSERT_TRUE(vp9_send_codec);
|
||||
EXPECT_THAT(sdp, testing::HasSubstr("a=rid:1 send pt=" +
|
||||
std::to_string(vp8_send_codec->id)));
|
||||
EXPECT_THAT(sdp, testing::HasSubstr("a=rid:2 send pt=" +
|
||||
std::to_string(vp9_send_codec->id)));
|
||||
// Verify that SDP containing pt= can be parsed correctly.
|
||||
auto offer2 = CreateSessionDescription(SdpType::kOffer, sdp);
|
||||
auto& offer_contents2 = offer2->description()->contents();
|
||||
auto send_rids2 = offer_contents2[0].media_description()->streams()[0].rids();
|
||||
EXPECT_EQ(send_rids2[0].payload_types.size(), 1u);
|
||||
EXPECT_EQ(send_rids2[0].payload_types[0], vp8_pt);
|
||||
EXPECT_EQ(send_rids2[1].payload_types.size(), 1u);
|
||||
EXPECT_EQ(send_rids2[1].payload_types[0], vp9_pt);
|
||||
EXPECT_EQ(send_rids2[0].codecs.size(), 1u);
|
||||
EXPECT_EQ(send_rids2[0].codecs[0], *vp8_send_codec);
|
||||
EXPECT_EQ(send_rids2[1].codecs.size(), 1u);
|
||||
EXPECT_EQ(send_rids2[1].codecs[0], *vp9_send_codec);
|
||||
}
|
||||
|
||||
TEST_F(SdpOfferAnswerTest, SimulcastAnswerWithPayloadType) {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "media/base/codec_comparators.h"
|
||||
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/string_encode.h"
|
||||
@@ -139,12 +140,13 @@ RTCErrorOr<SimulcastLayerList> ParseSimulcastLayerList(const std::string& str) {
|
||||
}
|
||||
|
||||
webrtc::RTCError ParseRidPayloadList(const std::string& payload_list,
|
||||
RidDescription* rid_description) {
|
||||
RidDescription* rid_description,
|
||||
std::vector<int>* rid_payload_types) {
|
||||
RTC_DCHECK(rid_description);
|
||||
std::vector<int>& payload_types = rid_description->payload_types;
|
||||
RTC_DCHECK(rid_payload_types);
|
||||
// Check that the description doesn't have any payload types or restrictions.
|
||||
// If the pt= field is specified, it must be first and must not repeat.
|
||||
if (!payload_types.empty()) {
|
||||
if (!rid_payload_types->empty()) {
|
||||
return ParseError("Multiple pt= found in RID Description.");
|
||||
}
|
||||
if (!rid_description->restrictions.empty()) {
|
||||
@@ -170,10 +172,10 @@ webrtc::RTCError ParseRidPayloadList(const std::string& payload_list,
|
||||
}
|
||||
|
||||
// Check if the value already appears in the payload list.
|
||||
if (absl::c_linear_search(payload_types, value.value())) {
|
||||
if (absl::c_linear_search(*rid_payload_types, value.value())) {
|
||||
return ParseError("Duplicate payload type in list: " + payload_type);
|
||||
}
|
||||
payload_types.push_back(value.value());
|
||||
rid_payload_types->push_back(value.value());
|
||||
}
|
||||
|
||||
return RTCError::OK();
|
||||
@@ -266,6 +268,7 @@ SimulcastSdpSerializer::DeserializeSimulcastDescription(
|
||||
}
|
||||
|
||||
std::string SimulcastSdpSerializer::SerializeRidDescription(
|
||||
const cricket::MediaContentDescription& media_desc,
|
||||
const RidDescription& rid_description) const {
|
||||
RTC_DCHECK(!rid_description.rid.empty());
|
||||
RTC_DCHECK(rid_description.direction == RidDirection::kSend ||
|
||||
@@ -277,7 +280,27 @@ std::string SimulcastSdpSerializer::SerializeRidDescription(
|
||||
? kSendDirection
|
||||
: kReceiveDirection);
|
||||
|
||||
const auto& payload_types = rid_description.payload_types;
|
||||
// Convert `rid_descriptions.codecs` into a list of payload types based on
|
||||
// looking up codecs from the media description, as opposed to trusting the
|
||||
// `rid_descriptions.codecs[i].id` directly as these are typically wrong.
|
||||
std::vector<int> payload_types;
|
||||
for (const cricket::Codec& codec : rid_description.codecs) {
|
||||
RtpCodec rtp_codec = codec.ToCodecParameters();
|
||||
const auto it = std::find_if(
|
||||
media_desc.codecs().begin(), media_desc.codecs().end(),
|
||||
[&rtp_codec](const cricket::Codec& m_section_codec) {
|
||||
return IsSameRtpCodecIgnoringLevel(m_section_codec, rtp_codec);
|
||||
});
|
||||
// The desired codec from setParameters() may not have been negotiated, e.g.
|
||||
// if excluded with setCodecPreferences().
|
||||
if (it == media_desc.codecs().end() ||
|
||||
it->id == cricket::Codec::kIdNotSet) {
|
||||
RTC_DCHECK_NE(it->id, cricket::Codec::kIdNotSet);
|
||||
break;
|
||||
}
|
||||
payload_types.push_back(it->id);
|
||||
}
|
||||
|
||||
const auto& restrictions = rid_description.restrictions;
|
||||
|
||||
// First property is separated by ' ', the next ones by ';'.
|
||||
@@ -321,6 +344,7 @@ std::string SimulcastSdpSerializer::SerializeRidDescription(
|
||||
// param-val = *( %x20-58 / %x60-7E )
|
||||
// ; Any printable character except semicolon
|
||||
RTCErrorOr<RidDescription> SimulcastSdpSerializer::DeserializeRidDescription(
|
||||
const cricket::MediaContentDescription& media_desc,
|
||||
absl::string_view string) const {
|
||||
std::vector<std::string> tokens;
|
||||
rtc::tokenize(std::string(string), kDelimiterSpaceChar, &tokens);
|
||||
@@ -345,6 +369,7 @@ RTCErrorOr<RidDescription> SimulcastSdpSerializer::DeserializeRidDescription(
|
||||
: RidDirection::kReceive;
|
||||
|
||||
RidDescription rid_description(tokens[0], direction);
|
||||
std::vector<int> rid_payload_types;
|
||||
|
||||
// If there is a third argument it is a payload list and/or restriction list.
|
||||
if (tokens.size() == 3) {
|
||||
@@ -369,8 +394,9 @@ RTCErrorOr<RidDescription> SimulcastSdpSerializer::DeserializeRidDescription(
|
||||
// unprintable characters, etc. which will not generate errors here but
|
||||
// will (most-likely) be ignored by components down stream.
|
||||
if (parts[0] == kPayloadType) {
|
||||
RTCError error = ParseRidPayloadList(
|
||||
parts.size() > 1 ? parts[1] : std::string(), &rid_description);
|
||||
RTCError error =
|
||||
ParseRidPayloadList(parts.size() > 1 ? parts[1] : std::string(),
|
||||
&rid_description, &rid_payload_types);
|
||||
if (!error.ok()) {
|
||||
return std::move(error);
|
||||
}
|
||||
@@ -389,6 +415,25 @@ RTCErrorOr<RidDescription> SimulcastSdpSerializer::DeserializeRidDescription(
|
||||
}
|
||||
}
|
||||
|
||||
// Look up any referenced codecs from the media section and add them to
|
||||
// `rid_description.codecs`.
|
||||
for (const int& payload_type : rid_payload_types) {
|
||||
const auto it =
|
||||
std::find_if(media_desc.codecs().begin(), media_desc.codecs().end(),
|
||||
[&payload_type](const cricket::Codec& m_section_codec) {
|
||||
return m_section_codec.id == payload_type;
|
||||
});
|
||||
if (it == media_desc.codecs().end()) {
|
||||
// This RID has a payload type that doesn't map to any known codec. While
|
||||
// this is an error on the part of the entity that generated the SDP, this
|
||||
// information falls into the "FYI" category and does not really change
|
||||
// anything, so it's safe to ignore it.
|
||||
RTC_LOG(LS_WARNING) << "A RID contains an unknown payload type.";
|
||||
continue;
|
||||
}
|
||||
rid_description.codecs.push_back(*it);
|
||||
}
|
||||
|
||||
return std::move(rid_description);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,11 +48,13 @@ class SimulcastSdpSerializer {
|
||||
// Serialization for the RID description according to
|
||||
// https://tools.ietf.org/html/draft-ietf-mmusic-rid-15#section-10
|
||||
std::string SerializeRidDescription(
|
||||
const cricket::MediaContentDescription& media_desc,
|
||||
const cricket::RidDescription& rid_description) const;
|
||||
|
||||
// Deserialization for the RidDescription according to
|
||||
// https://tools.ietf.org/html/draft-ietf-mmusic-rid-15#section-10
|
||||
RTCErrorOr<cricket::RidDescription> DeserializeRidDescription(
|
||||
const cricket::MediaContentDescription& media_desc,
|
||||
absl::string_view string) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ void ExpectEqual(const SimulcastDescription& expected,
|
||||
void ExpectEqual(const RidDescription& expected, const RidDescription& actual) {
|
||||
EXPECT_EQ(expected.rid, actual.rid);
|
||||
EXPECT_EQ(expected.direction, actual.direction);
|
||||
ExpectEqual(expected.payload_types, actual.payload_types);
|
||||
ExpectEqual(expected.codecs, actual.codecs);
|
||||
ExpectEqual(expected.restrictions, actual.restrictions);
|
||||
}
|
||||
} // namespace
|
||||
@@ -284,10 +284,11 @@ class RidDescriptionSdpSerializerTest : public TestWithParam<const char*> {
|
||||
// Runs a test for deserializing Rid Descriptions.
|
||||
// `str` - The serialized Rid Description to parse.
|
||||
// `expected` - The expected output RidDescription to compare to.
|
||||
void TestDeserialization(const std::string& str,
|
||||
void TestDeserialization(const cricket::MediaContentDescription& media_desc,
|
||||
const std::string& str,
|
||||
const RidDescription& expected) const {
|
||||
SimulcastSdpSerializer deserializer;
|
||||
auto result = deserializer.DeserializeRidDescription(str);
|
||||
auto result = deserializer.DeserializeRidDescription(media_desc, str);
|
||||
EXPECT_TRUE(result.ok());
|
||||
ExpectEqual(expected, result.value());
|
||||
}
|
||||
@@ -295,10 +296,12 @@ class RidDescriptionSdpSerializerTest : public TestWithParam<const char*> {
|
||||
// Runs a test for serializing RidDescriptions.
|
||||
// `rid_description` - The RidDescription to serialize.
|
||||
// `expected` - The expected output string to compare to.
|
||||
void TestSerialization(const RidDescription& rid_description,
|
||||
void TestSerialization(const cricket::MediaContentDescription& media_desc,
|
||||
const RidDescription& rid_description,
|
||||
const std::string& expected) const {
|
||||
SimulcastSdpSerializer serializer;
|
||||
auto result = serializer.SerializeRidDescription(rid_description);
|
||||
auto result =
|
||||
serializer.SerializeRidDescription(media_desc, rid_description);
|
||||
EXPECT_EQ(expected, result);
|
||||
}
|
||||
};
|
||||
@@ -306,27 +309,38 @@ class RidDescriptionSdpSerializerTest : public TestWithParam<const char*> {
|
||||
// Test serialization for RidDescription that only specifies send.
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Serialize_OnlyDirectionSend) {
|
||||
RidDescription rid_description("1", RidDirection::kSend);
|
||||
TestSerialization(rid_description, "1 send");
|
||||
TestSerialization(cricket::VideoContentDescription(), rid_description,
|
||||
"1 send");
|
||||
}
|
||||
|
||||
// Test serialization for RidDescription that only specifies receive.
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Serialize_OnlyDirectionReceive) {
|
||||
RidDescription rid_description("2", RidDirection::kReceive);
|
||||
TestSerialization(rid_description, "2 recv");
|
||||
TestSerialization(cricket::VideoContentDescription(), rid_description,
|
||||
"2 recv");
|
||||
}
|
||||
|
||||
// Test serialization for RidDescription with format list.
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Serialize_FormatList) {
|
||||
cricket::Codec vp8 = cricket::CreateVideoCodec(101, "VP8");
|
||||
cricket::Codec vp9 = cricket::CreateVideoCodec(102, "VP9");
|
||||
cricket::VideoContentDescription video_desc;
|
||||
video_desc.set_codecs({vp8, vp9});
|
||||
|
||||
RidDescription rid_description("3", RidDirection::kSend);
|
||||
rid_description.payload_types = {102, 101};
|
||||
TestSerialization(rid_description, "3 send pt=102,101");
|
||||
rid_description.codecs = {vp9, vp8};
|
||||
TestSerialization(video_desc, rid_description, "3 send pt=102,101");
|
||||
}
|
||||
|
||||
// Test serialization for RidDescription with format list.
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Serialize_FormatListSingleFormat) {
|
||||
cricket::Codec vp8 = cricket::CreateVideoCodec(100, "VP8");
|
||||
cricket::VideoContentDescription video_desc;
|
||||
video_desc.set_codecs({vp8});
|
||||
|
||||
RidDescription rid_description("4", RidDirection::kReceive);
|
||||
rid_description.payload_types = {100};
|
||||
TestSerialization(rid_description, "4 recv pt=100");
|
||||
rid_description.codecs = {vp8};
|
||||
TestSerialization(video_desc, rid_description, "4 recv pt=100");
|
||||
}
|
||||
|
||||
// Test serialization for RidDescription with restriction list.
|
||||
@@ -335,63 +349,99 @@ TEST_F(RidDescriptionSdpSerializerTest, Serialize_AttributeList) {
|
||||
RidDescription rid_description("5", RidDirection::kSend);
|
||||
rid_description.restrictions["max-width"] = "1280";
|
||||
rid_description.restrictions["max-height"] = "720";
|
||||
TestSerialization(rid_description, "5 send max-height=720;max-width=1280");
|
||||
TestSerialization(cricket::VideoContentDescription(), rid_description,
|
||||
"5 send max-height=720;max-width=1280");
|
||||
}
|
||||
|
||||
// Test serialization for RidDescription with format list and attribute list.
|
||||
// Note: restriction list will be sorted because it is stored in a map.
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Serialize_FormatAndAttributeList) {
|
||||
cricket::Codec vp8 = cricket::CreateVideoCodec(103, "VP8");
|
||||
cricket::Codec vp9 = cricket::CreateVideoCodec(104, "VP9");
|
||||
cricket::VideoContentDescription video_desc;
|
||||
video_desc.set_codecs({vp8, vp9});
|
||||
|
||||
RidDescription rid_description("6", RidDirection::kSend);
|
||||
rid_description.payload_types = {103, 104};
|
||||
rid_description.codecs = {vp8, vp9};
|
||||
rid_description.restrictions["max-mbps"] = "108000";
|
||||
rid_description.restrictions["max-br"] = "64000";
|
||||
TestSerialization(rid_description,
|
||||
TestSerialization(video_desc, rid_description,
|
||||
"6 send pt=103,104;max-br=64000;max-mbps=108000");
|
||||
}
|
||||
|
||||
// Test serialization for attribute list that has key with no value.
|
||||
// Note: restriction list will be sorted because it is stored in a map.
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Serialize_RestrictionWithoutValue) {
|
||||
cricket::Codec vp8 = cricket::CreateVideoCodec(103, "VP8");
|
||||
cricket::VideoContentDescription video_desc;
|
||||
video_desc.set_codecs({vp8});
|
||||
|
||||
RidDescription rid_description("7", RidDirection::kReceive);
|
||||
rid_description.payload_types = {103};
|
||||
rid_description.codecs = {vp8};
|
||||
rid_description.restrictions["max-width"] = "1280";
|
||||
rid_description.restrictions["max-height"] = "720";
|
||||
rid_description.restrictions["max-myval"] = "";
|
||||
TestSerialization(rid_description,
|
||||
TestSerialization(video_desc, rid_description,
|
||||
"7 recv pt=103;max-height=720;max-myval;max-width=1280");
|
||||
}
|
||||
|
||||
// Test simulcast deserialization with simple send stream.
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Deserialize_SimpleSendCase) {
|
||||
RidDescription rid_description("1", RidDirection::kSend);
|
||||
TestDeserialization("1 send", rid_description);
|
||||
TestDeserialization(cricket::VideoContentDescription(), "1 send",
|
||||
rid_description);
|
||||
}
|
||||
|
||||
// Test simulcast deserialization with simple receive stream.
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Deserialize_SimpleReceiveCase) {
|
||||
RidDescription rid_description("2", RidDirection::kReceive);
|
||||
TestDeserialization("2 recv", rid_description);
|
||||
TestDeserialization(cricket::VideoContentDescription(), "2 recv",
|
||||
rid_description);
|
||||
}
|
||||
|
||||
// Test simulcast deserialization with single format.
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Deserialize_WithFormat) {
|
||||
cricket::Codec vp8 = cricket::CreateVideoCodec(101, "VP8");
|
||||
cricket::VideoContentDescription video_desc;
|
||||
video_desc.set_codecs({vp8});
|
||||
|
||||
RidDescription rid_description("3", RidDirection::kSend);
|
||||
rid_description.payload_types = {101};
|
||||
TestDeserialization("3 send pt=101", rid_description);
|
||||
rid_description.codecs = {vp8};
|
||||
TestDeserialization(video_desc, "3 send pt=101", rid_description);
|
||||
}
|
||||
|
||||
// Test simulcast deserialization with valid format syntax (`pt=97`) but using
|
||||
// a payload type that does not refer to any codec in the media description.
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Deserialize_ReferencingUnknownCodec) {
|
||||
cricket::Codec vp8 = cricket::CreateVideoCodec(101, "VP8");
|
||||
cricket::VideoContentDescription video_desc;
|
||||
video_desc.set_codecs({vp8});
|
||||
|
||||
RidDescription rid_description("3", RidDirection::kSend);
|
||||
rid_description.codecs = {}; // pt=97 is ignored resulting in an empty list.
|
||||
TestDeserialization(video_desc, "3 send pt=97", rid_description);
|
||||
}
|
||||
|
||||
// Test simulcast deserialization with multiple formats.
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Deserialize_WithMultipleFormats) {
|
||||
cricket::Codec vp8 = cricket::CreateVideoCodec(101, "VP8");
|
||||
cricket::Codec vp9 = cricket::CreateVideoCodec(102, "VP9");
|
||||
cricket::Codec av1 = cricket::CreateVideoCodec(103, "AV1");
|
||||
cricket::Codec h264 = cricket::CreateVideoCodec(104, "H264");
|
||||
cricket::VideoContentDescription video_desc;
|
||||
video_desc.set_codecs({vp8, vp9, av1, h264});
|
||||
|
||||
RidDescription rid_description("4", RidDirection::kSend);
|
||||
rid_description.payload_types = {103, 104, 101, 102};
|
||||
TestDeserialization("4 send pt=103,104,101,102", rid_description);
|
||||
rid_description.codecs = {av1, h264, vp8, vp9};
|
||||
TestDeserialization(video_desc, "4 send pt=103,104,101,102", rid_description);
|
||||
}
|
||||
|
||||
// Test simulcast deserialization with restriction.
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Deserialize_WithRestriction) {
|
||||
RidDescription rid_description("5", RidDirection::kReceive);
|
||||
rid_description.restrictions["max-height"] = "720";
|
||||
TestDeserialization("5 recv max-height=720", rid_description);
|
||||
TestDeserialization(cricket::VideoContentDescription(),
|
||||
"5 recv max-height=720", rid_description);
|
||||
}
|
||||
|
||||
// Test simulcast deserialization with multiple restrictions.
|
||||
@@ -402,6 +452,7 @@ TEST_F(RidDescriptionSdpSerializerTest, Deserialize_WithMultipleRestrictions) {
|
||||
rid_description.restrictions["max-fr"] = "60";
|
||||
rid_description.restrictions["max-bps"] = "14000";
|
||||
TestDeserialization(
|
||||
cricket::VideoContentDescription(),
|
||||
"6 recv max-height=720;max-width=1920;max-bps=14000;max-fr=60",
|
||||
rid_description);
|
||||
}
|
||||
@@ -411,26 +462,37 @@ TEST_F(RidDescriptionSdpSerializerTest, Deserialize_WithCustomRestrictions) {
|
||||
RidDescription rid_description("7", RidDirection::kSend);
|
||||
rid_description.restrictions["foo"] = "bar";
|
||||
rid_description.restrictions["max-height"] = "720";
|
||||
TestDeserialization("7 send max-height=720;foo=bar", rid_description);
|
||||
TestDeserialization(cricket::VideoContentDescription(),
|
||||
"7 send max-height=720;foo=bar", rid_description);
|
||||
}
|
||||
|
||||
// Test simulcast deserialization with multiple formats and restrictions.
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Deserialize_WithFormatAndRestrictions) {
|
||||
cricket::Codec av1 = cricket::CreateVideoCodec(103, "AV1");
|
||||
cricket::Codec h264 = cricket::CreateVideoCodec(104, "H264");
|
||||
cricket::VideoContentDescription video_desc;
|
||||
video_desc.set_codecs({av1, h264});
|
||||
|
||||
RidDescription rid_description("8", RidDirection::kSend);
|
||||
rid_description.payload_types = {104, 103};
|
||||
rid_description.codecs = {h264, av1};
|
||||
rid_description.restrictions["max-height"] = "720";
|
||||
rid_description.restrictions["max-width"] = "1920";
|
||||
TestDeserialization("8 send pt=104,103;max-height=720;max-width=1920",
|
||||
TestDeserialization(video_desc,
|
||||
"8 send pt=104,103;max-height=720;max-width=1920",
|
||||
rid_description);
|
||||
}
|
||||
|
||||
// Test simulcast deserialization with restriction that has no value.
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Deserialize_RestrictionHasNoValue) {
|
||||
cricket::Codec h264 = cricket::CreateVideoCodec(104, "H264");
|
||||
cricket::VideoContentDescription video_desc;
|
||||
video_desc.set_codecs({h264});
|
||||
|
||||
RidDescription rid_description("9", RidDirection::kReceive);
|
||||
rid_description.payload_types = {104};
|
||||
rid_description.codecs = {h264};
|
||||
rid_description.restrictions["max-height"];
|
||||
rid_description.restrictions["max-width"] = "1920";
|
||||
TestDeserialization("9 recv pt=104;max-height;max-width=1920",
|
||||
TestDeserialization(video_desc, "9 recv pt=104;max-height;max-width=1920",
|
||||
rid_description);
|
||||
}
|
||||
|
||||
@@ -442,13 +504,18 @@ TEST_F(RidDescriptionSdpSerializerTest, Deserialize_RestrictionHasNoValue) {
|
||||
TEST_F(RidDescriptionSdpSerializerTest, Deserialize_AmbiguousCase) {
|
||||
RidDescription rid_description("1", RidDirection::kSend);
|
||||
rid_description.restrictions["recv"]; // No value.
|
||||
TestDeserialization("1 send recv", rid_description);
|
||||
TestDeserialization(cricket::VideoContentDescription(), "1 send recv",
|
||||
rid_description);
|
||||
}
|
||||
|
||||
// Parameterized negative test case for deserialization with invalid inputs.
|
||||
TEST_P(RidDescriptionSdpSerializerTest, RidDescriptionDeserializationFailed) {
|
||||
cricket::VideoContentDescription video_desc;
|
||||
video_desc.set_codecs({cricket::CreateVideoCodec(101, "VP8"),
|
||||
cricket::CreateVideoCodec(102, "VP9")});
|
||||
|
||||
SimulcastSdpSerializer deserializer;
|
||||
auto result = deserializer.DeserializeRidDescription(GetParam());
|
||||
auto result = deserializer.DeserializeRidDescription(video_desc, GetParam());
|
||||
EXPECT_FALSE(result.ok());
|
||||
}
|
||||
|
||||
|
||||
63
third_party/libwebrtc/pc/webrtc_sdp.cc
vendored
63
third_party/libwebrtc/pc/webrtc_sdp.cc
vendored
@@ -375,8 +375,9 @@ static bool ParseMsidAttribute(absl::string_view line,
|
||||
std::string* track_id,
|
||||
SdpParseError* error);
|
||||
|
||||
static void RemoveInvalidRidDescriptions(const std::vector<int>& payload_types,
|
||||
std::vector<RidDescription>* rids);
|
||||
static void RemoveDuplicateRidDescriptions(
|
||||
const std::vector<int>& payload_types,
|
||||
std::vector<RidDescription>* rids);
|
||||
|
||||
static SimulcastLayerList RemoveRidsFromSimulcastLayerList(
|
||||
const std::set<std::string>& to_remove,
|
||||
@@ -1712,7 +1713,7 @@ void BuildRtpContentAttributes(const MediaContentDescription* media_desc,
|
||||
for (const RidDescription& rid_description : track.rids()) {
|
||||
InitAttrLine(kAttributeRid, &os);
|
||||
os << kSdpDelimiterColon
|
||||
<< serializer.SerializeRidDescription(rid_description);
|
||||
<< serializer.SerializeRidDescription(*media_desc, rid_description);
|
||||
AddLine(os.str(), message);
|
||||
}
|
||||
}
|
||||
@@ -1720,7 +1721,7 @@ void BuildRtpContentAttributes(const MediaContentDescription* media_desc,
|
||||
for (const RidDescription& rid_description : media_desc->receive_rids()) {
|
||||
InitAttrLine(kAttributeRid, &os);
|
||||
os << kSdpDelimiterColon
|
||||
<< serializer.SerializeRidDescription(rid_description);
|
||||
<< serializer.SerializeRidDescription(*media_desc, rid_description);
|
||||
AddLine(os.str(), message);
|
||||
}
|
||||
|
||||
@@ -2396,57 +2397,19 @@ static bool ParseMsidAttribute(absl::string_view line,
|
||||
return true;
|
||||
}
|
||||
|
||||
static void RemoveInvalidRidDescriptions(const std::vector<int>& payload_types,
|
||||
std::vector<RidDescription>* rids) {
|
||||
static void RemoveDuplicateRidDescriptions(
|
||||
const std::vector<int>& payload_types,
|
||||
std::vector<RidDescription>* rids) {
|
||||
RTC_DCHECK(rids);
|
||||
std::set<std::string> to_remove;
|
||||
std::set<std::string> unique_rids;
|
||||
|
||||
// Check the rids to see which ones should be removed.
|
||||
// Find duplicate RIDs to remove.
|
||||
for (RidDescription& rid : *rids) {
|
||||
// In the case of a duplicate, the entire "a=rid" line, and all "a=rid"
|
||||
// lines with rid-ids that duplicate this line, are discarded and MUST NOT
|
||||
// be included in the SDP Answer.
|
||||
auto pair = unique_rids.insert(rid.rid);
|
||||
// Insert will "fail" if element already exists.
|
||||
if (!pair.second) {
|
||||
if (!unique_rids.insert(rid.rid).second) {
|
||||
to_remove.insert(rid.rid);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the "a=rid" line contains a "pt=", the list of payload types
|
||||
// is verified against the list of valid payload types for the media
|
||||
// section (that is, those listed on the "m=" line). Any PT missing
|
||||
// from the "m=" line is discarded from the set of values in the
|
||||
// "pt=". If no values are left in the "pt=" parameter after this
|
||||
// processing, then the "a=rid" line is discarded.
|
||||
if (rid.payload_types.empty()) {
|
||||
// If formats were not specified, rid should not be removed.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Note: Spec does not mention how to handle duplicate formats.
|
||||
// Media section does not handle duplicates either.
|
||||
std::set<int> removed_formats;
|
||||
for (int payload_type : rid.payload_types) {
|
||||
if (!absl::c_linear_search(payload_types, payload_type)) {
|
||||
removed_formats.insert(payload_type);
|
||||
}
|
||||
}
|
||||
|
||||
rid.payload_types.erase(
|
||||
std::remove_if(rid.payload_types.begin(), rid.payload_types.end(),
|
||||
[&removed_formats](int format) {
|
||||
return removed_formats.count(format) > 0;
|
||||
}),
|
||||
rid.payload_types.end());
|
||||
|
||||
// If all formats were removed then remove the rid alogether.
|
||||
if (rid.payload_types.empty()) {
|
||||
to_remove.insert(rid.rid);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove every rid description that appears in the to_remove list.
|
||||
if (!to_remove.empty()) {
|
||||
rids->erase(std::remove_if(rids->begin(), rids->end(),
|
||||
@@ -3301,7 +3264,7 @@ bool ParseContent(absl::string_view message,
|
||||
}
|
||||
RTCErrorOr<RidDescription> error_or_rid_description =
|
||||
deserializer.DeserializeRidDescription(
|
||||
line->substr(kRidPrefixLength));
|
||||
*media_desc, line->substr(kRidPrefixLength));
|
||||
|
||||
// Malformed a=rid lines are discarded.
|
||||
if (!error_or_rid_description.ok()) {
|
||||
@@ -3351,8 +3314,8 @@ bool ParseContent(absl::string_view message,
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicate or inconsistent rids.
|
||||
RemoveInvalidRidDescriptions(payload_types, &rids);
|
||||
// Remove duplicate rids.
|
||||
RemoveDuplicateRidDescriptions(payload_types, &rids);
|
||||
|
||||
// If simulcast is specifed, split the rids into send and receive.
|
||||
// Rids that do not appear in simulcast attribute will be removed.
|
||||
|
||||
14
third_party/libwebrtc/pc/webrtc_sdp_unittest.cc
vendored
14
third_party/libwebrtc/pc/webrtc_sdp_unittest.cc
vendored
@@ -4589,7 +4589,7 @@ TEST_F(WebRtcSdpTest, TestDeserializeIgnoresMalformedRidLines) {
|
||||
TEST_F(WebRtcSdpTest, TestDeserializeRemovesRidsWithInvalidCodec) {
|
||||
std::string sdp = kUnifiedPlanSdpFullStringNoSsrc;
|
||||
sdp += "a=rid:1 send pt=121,120\r\n"; // Should remove 121 and keep RID.
|
||||
sdp += "a=rid:2 send pt=121\r\n"; // Should remove RID altogether.
|
||||
sdp += "a=rid:2 send pt=121\r\n"; // Should remove 121 and keep RID.
|
||||
sdp += "a=simulcast:send 1;2\r\n";
|
||||
JsepSessionDescription output(kDummyType);
|
||||
SdpParseError error;
|
||||
@@ -4600,15 +4600,17 @@ TEST_F(WebRtcSdpTest, TestDeserializeRemovesRidsWithInvalidCodec) {
|
||||
EXPECT_TRUE(media->HasSimulcast());
|
||||
const SimulcastDescription& simulcast = media->simulcast_description();
|
||||
EXPECT_TRUE(simulcast.receive_layers().empty());
|
||||
EXPECT_EQ(1ul, simulcast.send_layers().size());
|
||||
EXPECT_EQ(1ul, simulcast.send_layers().GetAllLayers().size());
|
||||
EXPECT_EQ(2ul, simulcast.send_layers().size());
|
||||
EXPECT_EQ(2ul, simulcast.send_layers().GetAllLayers().size());
|
||||
EXPECT_EQ("1", simulcast.send_layers()[0][0].rid);
|
||||
EXPECT_EQ(1ul, media->streams().size());
|
||||
const std::vector<RidDescription>& rids = media->streams()[0].rids();
|
||||
EXPECT_EQ(1ul, rids.size());
|
||||
EXPECT_EQ(2ul, rids.size());
|
||||
EXPECT_EQ("1", rids[0].rid);
|
||||
EXPECT_EQ(1ul, rids[0].payload_types.size());
|
||||
EXPECT_EQ(120, rids[0].payload_types[0]);
|
||||
EXPECT_EQ(1ul, rids[0].codecs.size());
|
||||
EXPECT_EQ(120, rids[0].codecs[0].id);
|
||||
EXPECT_EQ("2", rids[1].rid);
|
||||
EXPECT_EQ(0ul, rids[1].codecs.size());
|
||||
}
|
||||
|
||||
// Ignores duplicate rid lines
|
||||
|
||||
Reference in New Issue
Block a user