Fix payload type assignment issue with SetCodecPreferences
When SetCodecPreferences was used, the media session was adding codecs
from a list that didn't have corrected payload type mappings. As a
result, it's possible to generate offers or answers that use the same
payload type for audio and video codecs, which is a clear violation.
Bug: webrtc:12169
Change-Id: Ib7be73b4b3b4c57b8d2f374dba8b039c7a3df5a8
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/231620
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Commit-Queue: Taylor Brandstetter <deadbeef@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#34961}
diff --git a/pc/media_session.cc b/pc/media_session.cc
index 4bbb877..615dbff 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -944,17 +944,23 @@
}
}
+// `codecs` is a full list of codecs with correct payload type mappings, which
+// don't conflict with mappings of the other media type; `supported_codecs` is
+// a list filtered for the media section`s direction but with default payload
+// types.
template <typename Codecs>
static Codecs MatchCodecPreference(
const std::vector<webrtc::RtpCodecCapability>& codec_preferences,
- const Codecs& codecs) {
+ const Codecs& codecs,
+ const Codecs& supported_codecs) {
Codecs filtered_codecs;
std::set<std::string> kept_codecs_ids;
bool want_rtx = false;
for (const auto& codec_preference : codec_preferences) {
auto found_codec = absl::c_find_if(
- codecs, [&codec_preference](const typename Codecs::value_type& codec) {
+ supported_codecs,
+ [&codec_preference](const typename Codecs::value_type& codec) {
webrtc::RtpCodecParameters codec_parameters =
codec.ToCodecParameters();
return codec_parameters.name == codec_preference.name &&
@@ -965,9 +971,13 @@
codec_parameters.parameters == codec_preference.parameters;
});
- if (found_codec != codecs.end()) {
- filtered_codecs.push_back(*found_codec);
- kept_codecs_ids.insert(std::to_string(found_codec->id));
+ if (found_codec != supported_codecs.end()) {
+ typename Codecs::value_type found_codec_with_correct_pt;
+ if (FindMatchingCodec(supported_codecs, codecs, *found_codec,
+ &found_codec_with_correct_pt)) {
+ filtered_codecs.push_back(found_codec_with_correct_pt);
+ kept_codecs_ids.insert(std::to_string(found_codec_with_correct_pt.id));
+ }
} else if (IsRtxCodec(codec_preference)) {
want_rtx = true;
}
@@ -2144,8 +2154,9 @@
if (!media_description_options.codec_preferences.empty()) {
// Add the codecs from the current transceiver's codec preferences.
// They override any existing codecs from previous negotiations.
- filtered_codecs = MatchCodecPreference(
- media_description_options.codec_preferences, supported_audio_codecs);
+ filtered_codecs =
+ MatchCodecPreference(media_description_options.codec_preferences,
+ audio_codecs, supported_audio_codecs);
} else {
// Add the codecs from current content if it exists and is not rejected nor
// recycled.
@@ -2233,8 +2244,9 @@
if (!media_description_options.codec_preferences.empty()) {
// Add the codecs from the current transceiver's codec preferences.
// They override any existing codecs from previous negotiations.
- filtered_codecs = MatchCodecPreference(
- media_description_options.codec_preferences, supported_video_codecs);
+ filtered_codecs =
+ MatchCodecPreference(media_description_options.codec_preferences,
+ video_codecs, supported_video_codecs);
} else {
// Add the codecs from current content if it exists and is not rejected nor
// recycled.
@@ -2424,8 +2436,9 @@
AudioCodecs filtered_codecs;
if (!media_description_options.codec_preferences.empty()) {
- filtered_codecs = MatchCodecPreference(
- media_description_options.codec_preferences, supported_audio_codecs);
+ filtered_codecs =
+ MatchCodecPreference(media_description_options.codec_preferences,
+ audio_codecs, supported_audio_codecs);
} else {
// Add the codecs from current content if it exists and is not rejected nor
// recycled.
@@ -2539,8 +2552,9 @@
VideoCodecs filtered_codecs;
if (!media_description_options.codec_preferences.empty()) {
- filtered_codecs = MatchCodecPreference(
- media_description_options.codec_preferences, supported_video_codecs);
+ filtered_codecs =
+ MatchCodecPreference(media_description_options.codec_preferences,
+ video_codecs, supported_video_codecs);
} else {
// Add the codecs from current content if it exists and is not rejected nor
// recycled.
diff --git a/pc/peer_connection_media_unittest.cc b/pc/peer_connection_media_unittest.cc
index d5d0b92..067dc14 100644
--- a/pc/peer_connection_media_unittest.cc
+++ b/pc/peer_connection_media_unittest.cc
@@ -13,6 +13,7 @@
// the media-related aspects of SDP.
#include <memory>
+#include <set>
#include <tuple>
#include "absl/algorithm/container.h"
@@ -846,6 +847,29 @@
return false;
}
+bool HasPayloadTypeConflict(const cricket::SessionDescription* desc) {
+ std::set<int> payload_types;
+ const auto* audio_desc = cricket::GetFirstAudioContentDescription(desc);
+ if (audio_desc) {
+ for (const auto& codec : audio_desc->codecs()) {
+ if (payload_types.count(codec.id) > 0) {
+ return true;
+ }
+ payload_types.insert(codec.id);
+ }
+ }
+ const auto* video_desc = cricket::GetFirstVideoContentDescription(desc);
+ if (video_desc) {
+ for (const auto& codec : video_desc->codecs()) {
+ if (payload_types.count(codec.id) > 0) {
+ return true;
+ }
+ payload_types.insert(codec.id);
+ }
+ }
+ return false;
+}
+
TEST_P(PeerConnectionMediaTest,
CreateOfferWithNoVoiceActivityDetectionIncludesNoComfortNoiseCodecs) {
auto fake_engine = std::make_unique<FakeMediaEngine>();
@@ -1793,6 +1817,147 @@
EXPECT_FALSE(HasAnyComfortNoiseCodecs(offer->description()));
}
+// If the "default" payload types of audio/video codecs are the same, and
+// audio/video are bundled (as is the default), payload types should be
+// remapped to avoid conflict, as normally happens without using
+// SetCodecPreferences.
+TEST_F(PeerConnectionMediaTestUnifiedPlan,
+ SetCodecPreferencesAvoidsPayloadTypeConflictInOffer) {
+ auto fake_engine = std::make_unique<cricket::FakeMediaEngine>();
+
+ std::vector<cricket::AudioCodec> audio_codecs;
+ audio_codecs.emplace_back(100, "foo", 0, 0, 1);
+ audio_codecs.emplace_back(101, cricket::kRtxCodecName, 0, 0, 1);
+ audio_codecs.back().params[cricket::kCodecParamAssociatedPayloadType] = "100";
+ fake_engine->SetAudioCodecs(audio_codecs);
+
+ std::vector<cricket::VideoCodec> video_codecs;
+ video_codecs.emplace_back(100, "bar");
+ video_codecs.emplace_back(101, cricket::kRtxCodecName);
+ video_codecs.back().params[cricket::kCodecParamAssociatedPayloadType] = "100";
+ fake_engine->SetVideoCodecs(video_codecs);
+
+ auto caller = CreatePeerConnectionWithAudioVideo(std::move(fake_engine));
+ auto transceivers = caller->pc()->GetTransceivers();
+ ASSERT_EQ(2u, transceivers.size());
+
+ auto audio_transceiver = caller->pc()->GetTransceivers()[0];
+ auto capabilities = caller->pc_factory()->GetRtpSenderCapabilities(
+ cricket::MediaType::MEDIA_TYPE_AUDIO);
+ EXPECT_TRUE(audio_transceiver->SetCodecPreferences(capabilities.codecs).ok());
+
+ auto video_transceiver = caller->pc()->GetTransceivers()[1];
+ capabilities = caller->pc_factory()->GetRtpSenderCapabilities(
+ cricket::MediaType::MEDIA_TYPE_VIDEO);
+ EXPECT_TRUE(video_transceiver->SetCodecPreferences(capabilities.codecs).ok());
+
+ RTCOfferAnswerOptions options;
+ auto offer = caller->CreateOffer(options);
+ EXPECT_FALSE(HasPayloadTypeConflict(offer->description()));
+ // Sanity check that we got the primary codec and RTX.
+ EXPECT_EQ(2u, cricket::GetFirstAudioContentDescription(offer->description())
+ ->codecs()
+ .size());
+ EXPECT_EQ(2u, cricket::GetFirstVideoContentDescription(offer->description())
+ ->codecs()
+ .size());
+}
+
+// Same as above, but preferences set for the answer.
+TEST_F(PeerConnectionMediaTestUnifiedPlan,
+ SetCodecPreferencesAvoidsPayloadTypeConflictInAnswer) {
+ auto fake_engine = std::make_unique<cricket::FakeMediaEngine>();
+
+ std::vector<cricket::AudioCodec> audio_codecs;
+ audio_codecs.emplace_back(100, "foo", 0, 0, 1);
+ audio_codecs.emplace_back(101, cricket::kRtxCodecName, 0, 0, 1);
+ audio_codecs.back().params[cricket::kCodecParamAssociatedPayloadType] = "100";
+ fake_engine->SetAudioCodecs(audio_codecs);
+
+ std::vector<cricket::VideoCodec> video_codecs;
+ video_codecs.emplace_back(100, "bar");
+ video_codecs.emplace_back(101, cricket::kRtxCodecName);
+ video_codecs.back().params[cricket::kCodecParamAssociatedPayloadType] = "100";
+ fake_engine->SetVideoCodecs(video_codecs);
+
+ auto caller = CreatePeerConnectionWithAudioVideo(std::move(fake_engine));
+
+ RTCOfferAnswerOptions options;
+ caller->SetRemoteDescription(caller->CreateOffer(options));
+
+ auto transceivers = caller->pc()->GetTransceivers();
+ ASSERT_EQ(2u, transceivers.size());
+
+ auto audio_transceiver = caller->pc()->GetTransceivers()[0];
+ auto capabilities = caller->pc_factory()->GetRtpSenderCapabilities(
+ cricket::MediaType::MEDIA_TYPE_AUDIO);
+ EXPECT_TRUE(audio_transceiver->SetCodecPreferences(capabilities.codecs).ok());
+
+ auto video_transceiver = caller->pc()->GetTransceivers()[1];
+ capabilities = caller->pc_factory()->GetRtpSenderCapabilities(
+ cricket::MediaType::MEDIA_TYPE_VIDEO);
+ EXPECT_TRUE(video_transceiver->SetCodecPreferences(capabilities.codecs).ok());
+
+ auto answer = caller->CreateAnswer(options);
+
+ EXPECT_FALSE(HasPayloadTypeConflict(answer->description()));
+ // Sanity check that we got the primary codec and RTX.
+ EXPECT_EQ(2u, cricket::GetFirstAudioContentDescription(answer->description())
+ ->codecs()
+ .size());
+ EXPECT_EQ(2u, cricket::GetFirstVideoContentDescription(answer->description())
+ ->codecs()
+ .size());
+}
+
+// Same as above, but preferences set for a subsequent offer.
+TEST_F(PeerConnectionMediaTestUnifiedPlan,
+ SetCodecPreferencesAvoidsPayloadTypeConflictInSubsequentOffer) {
+ auto fake_engine = std::make_unique<cricket::FakeMediaEngine>();
+
+ std::vector<cricket::AudioCodec> audio_codecs;
+ audio_codecs.emplace_back(100, "foo", 0, 0, 1);
+ audio_codecs.emplace_back(101, cricket::kRtxCodecName, 0, 0, 1);
+ audio_codecs.back().params[cricket::kCodecParamAssociatedPayloadType] = "100";
+ fake_engine->SetAudioCodecs(audio_codecs);
+
+ std::vector<cricket::VideoCodec> video_codecs;
+ video_codecs.emplace_back(100, "bar");
+ video_codecs.emplace_back(101, cricket::kRtxCodecName);
+ video_codecs.back().params[cricket::kCodecParamAssociatedPayloadType] = "100";
+ fake_engine->SetVideoCodecs(video_codecs);
+
+ auto caller = CreatePeerConnectionWithAudioVideo(std::move(fake_engine));
+
+ RTCOfferAnswerOptions options;
+ caller->SetRemoteDescription(caller->CreateOffer(options));
+ caller->SetLocalDescription(caller->CreateAnswer(options));
+
+ auto transceivers = caller->pc()->GetTransceivers();
+ ASSERT_EQ(2u, transceivers.size());
+
+ auto audio_transceiver = caller->pc()->GetTransceivers()[0];
+ auto capabilities = caller->pc_factory()->GetRtpSenderCapabilities(
+ cricket::MediaType::MEDIA_TYPE_AUDIO);
+ EXPECT_TRUE(audio_transceiver->SetCodecPreferences(capabilities.codecs).ok());
+
+ auto video_transceiver = caller->pc()->GetTransceivers()[1];
+ capabilities = caller->pc_factory()->GetRtpSenderCapabilities(
+ cricket::MediaType::MEDIA_TYPE_VIDEO);
+ EXPECT_TRUE(video_transceiver->SetCodecPreferences(capabilities.codecs).ok());
+
+ auto reoffer = caller->CreateOffer(options);
+
+ EXPECT_FALSE(HasPayloadTypeConflict(reoffer->description()));
+ // Sanity check that we got the primary codec and RTX.
+ EXPECT_EQ(2u, cricket::GetFirstAudioContentDescription(reoffer->description())
+ ->codecs()
+ .size());
+ EXPECT_EQ(2u, cricket::GetFirstVideoContentDescription(reoffer->description())
+ ->codecs()
+ .size());
+}
+
INSTANTIATE_TEST_SUITE_P(PeerConnectionMediaTest,
PeerConnectionMediaTest,
Values(SdpSemantics::kPlanB,