Makes sure that RED is not added twice to the list of codecs when it is used with Opus.
Bug: webrtc:15606
Change-Id: I3ab3ee287f5d2e3a0a46520608e5c0931e0bff90
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/325180
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Commit-Queue: Tomas Lundqvist <tomasl@google.com>
Cr-Commit-Position: refs/heads/main@{#41028}
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index e9a49ca..7c22a26 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -2106,6 +2106,7 @@
":rtc_pc",
":rtcp_mux_filter",
":rtp_media_utils",
+ ":rtp_parameters_conversion",
":rtp_transport",
":rtp_transport_internal",
":sctp_transport",
diff --git a/pc/media_session.cc b/pc/media_session.cc
index bc5d80b..a763919 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -1045,6 +1045,7 @@
want_red = true;
}
}
+ bool red_was_added = false;
for (const auto& codec_preference : codec_preferences) {
auto found_codec = absl::c_find_if(
supported_codecs, [&codec_preference](const Codec& codec) {
@@ -1062,7 +1063,13 @@
absl::optional<Codec> found_codec_with_correct_pt = FindMatchingCodec(
supported_codecs, codecs, *found_codec, field_trials);
if (found_codec_with_correct_pt) {
- filtered_codecs.push_back(*found_codec_with_correct_pt);
+ // RED may already have been added if its primary codec is before RED
+ // in the codec list.
+ bool is_red_codec = IsRedCodec(*found_codec_with_correct_pt);
+ if (!is_red_codec || !red_was_added) {
+ filtered_codecs.push_back(*found_codec_with_correct_pt);
+ red_was_added = is_red_codec ? true : red_was_added;
+ }
std::string id = rtc::ToString(found_codec_with_correct_pt->id);
// Search for the matching rtx or red codec.
if (want_red || want_rtx) {
@@ -1083,11 +1090,11 @@
if (fmtp != codec.params.end()) {
std::vector<absl::string_view> redundant_payloads =
rtc::split(fmtp->second, '/');
- if (redundant_payloads.size() > 0 &&
+ if (!redundant_payloads.empty() &&
redundant_payloads[0] == id) {
- if (std::find(filtered_codecs.begin(), filtered_codecs.end(),
- codec) == filtered_codecs.end()) {
+ if (!red_was_added) {
filtered_codecs.push_back(codec);
+ red_was_added = true;
}
break;
}
diff --git a/pc/media_session_unittest.cc b/pc/media_session_unittest.cc
index 9351c06..a1770c1 100644
--- a/pc/media_session_unittest.cc
+++ b/pc/media_session_unittest.cc
@@ -26,6 +26,7 @@
#include "absl/types/optional.h"
#include "api/candidate.h"
#include "api/crypto_params.h"
+#include "api/rtp_parameters.h"
#include "media/base/codec.h"
#include "media/base/media_constants.h"
#include "media/base/test_utils.h"
@@ -35,6 +36,7 @@
#include "p2p/base/transport_info.h"
#include "pc/media_protocol_names.h"
#include "pc/rtp_media_utils.h"
+#include "pc/rtp_parameters_conversion.h"
#include "rtc_base/arraysize.h"
#include "rtc_base/checks.h"
#include "rtc_base/fake_ssl_identity.h"
@@ -111,8 +113,16 @@
using webrtc::RtpExtension;
using webrtc::RtpTransceiverDirection;
+static AudioCodec createRedAudioCodec(absl::string_view encoding_id) {
+ AudioCodec red = cricket::CreateAudioCodec(63, "red", 48000, 2);
+ red.SetParam(cricket::kCodecParamNotInNameValueFormat,
+ std::string(encoding_id) + '/' + std::string(encoding_id));
+ return red;
+}
+
static const AudioCodec kAudioCodecs1[] = {
- cricket::CreateAudioCodec(103, "ISAC", 16000, 1),
+ cricket::CreateAudioCodec(111, "opus", 48000, 2),
+ createRedAudioCodec("111"),
cricket::CreateAudioCodec(102, "iLBC", 8000, 1),
cricket::CreateAudioCodec(0, "PCMU", 8000, 1),
cricket::CreateAudioCodec(8, "PCMA", 8000, 1),
@@ -820,6 +830,65 @@
EXPECT_EQ(cricket::kMediaProtocolSavpf, acd->protocol());
}
+// Create an offer with just Opus and RED.
+TEST_F(MediaSessionDescriptionFactoryTest,
+ TestCreateAudioOfferWithJustOpusAndRed) {
+ f1_.set_secure(SEC_ENABLED);
+ // First, prefer to only use opus and red.
+ std::vector<webrtc::RtpCodecCapability> preferences;
+ preferences.push_back(
+ webrtc::ToRtpCodecCapability(f1_.audio_sendrecv_codecs()[0]));
+ preferences.push_back(
+ webrtc::ToRtpCodecCapability(f1_.audio_sendrecv_codecs()[1]));
+ EXPECT_EQ("opus", preferences[0].name);
+ EXPECT_EQ("red", preferences[1].name);
+
+ auto opts = CreatePlanBMediaSessionOptions();
+ opts.media_description_options.at(0).codec_preferences = preferences;
+ std::unique_ptr<SessionDescription> offer =
+ f1_.CreateOfferOrError(opts, nullptr).MoveValue();
+ ASSERT_TRUE(offer.get());
+ const ContentInfo* ac = offer->GetContentByName("audio");
+ const ContentInfo* vc = offer->GetContentByName("video");
+ ASSERT_TRUE(ac != NULL);
+ ASSERT_TRUE(vc == NULL);
+ EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
+ const AudioContentDescription* acd = ac->media_description()->as_audio();
+ EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+ EXPECT_EQ(2U, acd->codecs().size());
+ EXPECT_EQ("opus", acd->codecs()[0].name);
+ EXPECT_EQ("red", acd->codecs()[1].name);
+}
+
+// Create an offer with RED before Opus, which enables RED with Opus encoding.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioOfferWithRedForOpus) {
+ f1_.set_secure(SEC_ENABLED);
+ // First, prefer to only use opus and red.
+ std::vector<webrtc::RtpCodecCapability> preferences;
+ preferences.push_back(
+ webrtc::ToRtpCodecCapability(f1_.audio_sendrecv_codecs()[1]));
+ preferences.push_back(
+ webrtc::ToRtpCodecCapability(f1_.audio_sendrecv_codecs()[0]));
+ EXPECT_EQ("red", preferences[0].name);
+ EXPECT_EQ("opus", preferences[1].name);
+
+ auto opts = CreatePlanBMediaSessionOptions();
+ opts.media_description_options.at(0).codec_preferences = preferences;
+ std::unique_ptr<SessionDescription> offer =
+ f1_.CreateOfferOrError(opts, nullptr).MoveValue();
+ ASSERT_TRUE(offer.get());
+ const ContentInfo* ac = offer->GetContentByName("audio");
+ const ContentInfo* vc = offer->GetContentByName("video");
+ ASSERT_TRUE(ac != NULL);
+ ASSERT_TRUE(vc == NULL);
+ EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
+ const AudioContentDescription* acd = ac->media_description()->as_audio();
+ EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+ EXPECT_EQ(2U, acd->codecs().size());
+ EXPECT_EQ("red", acd->codecs()[0].name);
+ EXPECT_EQ("opus", acd->codecs()[1].name);
+}
+
// Create a typical video offer, and ensure it matches what we expect.
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoOffer) {
MediaSessionOptions opts;
@@ -4663,13 +4732,13 @@
MAKE_VECTOR(kAudioCodecsAnswer);
const std::vector<AudioCodec> no_codecs;
- RTC_CHECK_EQ(send_codecs[1].name, "iLBC")
+ RTC_CHECK_EQ(send_codecs[2].name, "iLBC")
<< "Please don't change shared test data!";
RTC_CHECK_EQ(recv_codecs[2].name, "iLBC")
<< "Please don't change shared test data!";
// Alter iLBC send codec to have zero channels, to test that that is handled
// properly.
- send_codecs[1].channels = 0;
+ send_codecs[2].channels = 0;
// Alter iLBC receive codec to be lowercase, to test that case conversions
// are handled properly.
diff --git a/pc/peer_connection_encodings_integrationtest.cc b/pc/peer_connection_encodings_integrationtest.cc
index 4a786e0..c7181c5 100644
--- a/pc/peer_connection_encodings_integrationtest.cc
+++ b/pc/peer_connection_encodings_integrationtest.cc
@@ -1679,6 +1679,67 @@
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
+ EncodingParametersRedEnabledBeforeNegotiationAudio) {
+ rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+ rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
+ ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+ std::vector<webrtc::RtpCodecCapability> send_codecs =
+ local_pc_wrapper->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_AUDIO)
+ .codecs;
+
+ absl::optional<webrtc::RtpCodecCapability> opus =
+ local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
+ "opus");
+ ASSERT_TRUE(opus);
+
+ absl::optional<webrtc::RtpCodecCapability> red =
+ local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
+ "red");
+ ASSERT_TRUE(red);
+
+ webrtc::RtpTransceiverInit init;
+ init.direction = webrtc::RtpTransceiverDirection::kSendOnly;
+ webrtc::RtpEncodingParameters encoding_parameters;
+ encoding_parameters.codec = opus;
+ init.send_encodings.push_back(encoding_parameters);
+
+ auto transceiver_or_error =
+ local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init);
+ ASSERT_TRUE(transceiver_or_error.ok());
+ rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
+ transceiver_or_error.MoveValue();
+
+ // Preferring RED over Opus should enable RED with Opus encoding.
+ send_codecs[0] = red.value();
+ send_codecs[1] = opus.value();
+
+ ASSERT_TRUE(audio_transceiver->SetCodecPreferences(send_codecs).ok());
+ NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+ local_pc_wrapper->WaitForConnection();
+ remote_pc_wrapper->WaitForConnection();
+
+ webrtc::RtpParameters parameters =
+ audio_transceiver->sender()->GetParameters();
+ EXPECT_EQ(parameters.encodings[0].codec, opus);
+ EXPECT_EQ(parameters.codecs[0].payload_type, red->preferred_payload_type);
+ EXPECT_EQ(parameters.codecs[0].name, red->name);
+
+ // Check that it's possible to switch back to Opus without RED.
+ send_codecs[0] = opus.value();
+ send_codecs[1] = red.value();
+
+ ASSERT_TRUE(audio_transceiver->SetCodecPreferences(send_codecs).ok());
+ NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+
+ parameters = audio_transceiver->sender()->GetParameters();
+ EXPECT_EQ(parameters.encodings[0].codec, opus);
+ EXPECT_EQ(parameters.codecs[0].payload_type, opus->preferred_payload_type);
+ EXPECT_EQ(parameters.codecs[0].name, opus->name);
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
SetParametersRejectsScalabilityModeForSelectedCodec) {
rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();