diff --git a/audio/BUILD.gn b/audio/BUILD.gn index 460f2ceeff..7df741e9a7 100644 --- a/audio/BUILD.gn +++ b/audio/BUILD.gn @@ -71,6 +71,7 @@ rtc_library("audio") { "../modules/audio_coding:audio_coding_module_typedefs", "../modules/audio_coding:audio_encoder_cng", "../modules/audio_coding:audio_network_adaptor_config", + "../modules/audio_coding:red", "../modules/audio_device", "../modules/audio_processing", "../modules/audio_processing:api", diff --git a/audio/audio_send_stream.cc b/audio/audio_send_stream.cc index 62ca51403e..42705aa99a 100644 --- a/audio/audio_send_stream.cc +++ b/audio/audio_send_stream.cc @@ -31,6 +31,7 @@ #include "logging/rtc_event_log/events/rtc_event_audio_send_stream_config.h" #include "logging/rtc_event_log/rtc_stream_config.h" #include "modules/audio_coding/codecs/cng/audio_encoder_cng.h" +#include "modules/audio_coding/codecs/red/audio_encoder_copy_red.h" #include "modules/audio_processing/include/audio_processing.h" #include "modules/rtp_rtcp/source/rtp_header_extensions.h" #include "rtc_base/checks.h" @@ -659,6 +660,14 @@ bool AudioSendStream::SetupSendCodec(const Config& new_config) { new_config.send_codec_spec->format.clockrate_hz); } + // Wrap the encoder in a RED encoder, if RED is enabled. + if (spec.red_payload_type) { + AudioEncoderCopyRed::Config red_config; + red_config.payload_type = *spec.red_payload_type; + red_config.speech_encoder = std::move(encoder); + encoder = std::make_unique(std::move(red_config)); + } + // Set currently known overhead (used in ANA, opus only). // If overhead changes later, it will be updated in UpdateOverheadForEncoder. { diff --git a/audio/audio_send_stream_unittest.cc b/audio/audio_send_stream_unittest.cc index a7087b0d16..d094198721 100644 --- a/audio/audio_send_stream_unittest.cc +++ b/audio/audio_send_stream_unittest.cc @@ -371,6 +371,7 @@ TEST(AudioSendStreamTest, ConfigToString) { config.send_codec_spec->nack_enabled = true; config.send_codec_spec->transport_cc_enabled = false; config.send_codec_spec->cng_payload_type = 42; + config.send_codec_spec->red_payload_type = 43; config.encoder_factory = MockAudioEncoderFactory::CreateUnusedFactory(); config.rtp.extmap_allow_mixed = true; config.rtp.extensions.push_back( @@ -383,7 +384,7 @@ TEST(AudioSendStreamTest, ConfigToString) { "send_transport: null, " "min_bitrate_bps: 12000, max_bitrate_bps: 34000, " "send_codec_spec: {nack_enabled: true, transport_cc_enabled: false, " - "cng_payload_type: 42, payload_type: 103, " + "cng_payload_type: 42, red_payload_type: 43, payload_type: 103, " "format: {name: isac, clockrate_hz: 16000, num_channels: 1, " "parameters: {}}}}", config.ToString()); diff --git a/call/audio_send_stream.cc b/call/audio_send_stream.cc index ddcba031a7..765ece7eb9 100644 --- a/call/audio_send_stream.cc +++ b/call/audio_send_stream.cc @@ -75,6 +75,8 @@ std::string AudioSendStream::Config::SendCodecSpec::ToString() const { ss << ", transport_cc_enabled: " << (transport_cc_enabled ? "true" : "false"); ss << ", cng_payload_type: " << (cng_payload_type ? rtc::ToString(*cng_payload_type) : ""); + ss << ", red_payload_type: " + << (red_payload_type ? rtc::ToString(*red_payload_type) : ""); ss << ", payload_type: " << payload_type; ss << ", format: " << rtc::ToString(format); ss << '}'; diff --git a/call/audio_send_stream.h b/call/audio_send_stream.h index 86cea38938..d21dff4889 100644 --- a/call/audio_send_stream.h +++ b/call/audio_send_stream.h @@ -140,6 +140,7 @@ class AudioSendStream : public AudioSender { bool nack_enabled = false; bool transport_cc_enabled = false; absl::optional cng_payload_type; + absl::optional red_payload_type; // If unset, use the encoder's default target bitrate. absl::optional target_bitrate_bps; }; diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc index 4bb0e424c3..661bfd64dc 100644 --- a/media/engine/webrtc_voice_engine.cc +++ b/media/engine/webrtc_voice_engine.cc @@ -99,6 +99,12 @@ std::string ToString(const AudioCodec& codec) { return ss.Release(); } +// If this field trial is enabled, we will negotiate and use RFC 2198 +// redundancy for opus audio. +bool IsAudioRedForOpusFieldTrialEnabled() { + return webrtc::field_trial::IsEnabled("WebRTC-Audio-Red-For-Opus"); +} + bool IsCodec(const AudioCodec& codec, const char* ref_name) { return absl::EqualsIgnoreCase(codec.name, ref_name); } @@ -682,6 +688,11 @@ std::vector WebRtcVoiceEngine::CollectCodecs( } } + // Add red codec. + if (IsAudioRedForOpusFieldTrialEnabled()) { + map_format({kRedCodecName, 48000, 2}, &out); + } + // Add telephone-event codecs last. for (const auto& dtmf : generate_dtmf) { if (dtmf.second) { @@ -1542,6 +1553,8 @@ bool WebRtcVoiceMediaChannel::SetRecvCodecs( } auto format = AudioCodecToSdpAudioFormat(codec); if (!IsCodec(codec, kCnCodecName) && !IsCodec(codec, kDtmfCodecName) && + (!IsAudioRedForOpusFieldTrialEnabled() || + !IsCodec(codec, kRedCodecName)) && !engine()->decoder_factory_->IsSupportedDecoder(format)) { RTC_LOG(LS_ERROR) << "Unsupported codec: " << rtc::ToString(format); return false; @@ -1692,6 +1705,19 @@ bool WebRtcVoiceMediaChannel::SetSendCodecs( } } + if (IsAudioRedForOpusFieldTrialEnabled()) { + // Loop through the codecs to find the RED codec that matches opus + // with respect to clockrate and number of channels. + for (const AudioCodec& red_codec : codecs) { + if (IsCodec(red_codec, kRedCodecName) && + red_codec.clockrate == send_codec_spec->format.clockrate_hz && + red_codec.channels == send_codec_spec->format.num_channels) { + send_codec_spec->red_payload_type = red_codec.id; + break; + } + } + } + if (send_codec_spec_ != send_codec_spec) { send_codec_spec_ = std::move(send_codec_spec); // Apply new settings to all streams. diff --git a/media/engine/webrtc_voice_engine_unittest.cc b/media/engine/webrtc_voice_engine_unittest.cc index 9a01bd5eeb..d1556014ab 100644 --- a/media/engine/webrtc_voice_engine_unittest.cc +++ b/media/engine/webrtc_voice_engine_unittest.cc @@ -59,6 +59,7 @@ const cricket::AudioCodec kG722CodecVoE(9, "G722", 16000, 64000, 1); const cricket::AudioCodec kG722CodecSdp(9, "G722", 8000, 64000, 1); const cricket::AudioCodec kCn8000Codec(13, "CN", 8000, 0, 1); const cricket::AudioCodec kCn16000Codec(105, "CN", 16000, 0, 1); +const cricket::AudioCodec kRed48000Codec(112, "RED", 48000, 32000, 2); const cricket::AudioCodec kTelephoneEventCodec1(106, "telephone-event", 8000, @@ -1031,6 +1032,30 @@ TEST_P(WebRtcVoiceEngineTestFake, ChangeRecvCodecPayloadType) { EXPECT_TRUE(channel_->SetRecvParameters(parameters)); } +// Test that we set Opus/Red under the field trial. +TEST_P(WebRtcVoiceEngineTestFake, RecvRed) { + webrtc::test::ScopedFieldTrials override_field_trials( + "WebRTC-Audio-Red-For-Opus/Enabled/"); + + EXPECT_TRUE(SetupRecvStream()); + cricket::AudioRecvParameters parameters; + parameters.codecs.push_back(kOpusCodec); + parameters.codecs.push_back(kRed48000Codec); + EXPECT_TRUE(channel_->SetRecvParameters(parameters)); + EXPECT_THAT(GetRecvStreamConfig(kSsrcX).decoder_map, + (ContainerEq>( + {{111, {"opus", 48000, 2}}, {112, {"red", 48000, 2}}}))); +} + +// Test that we do not allow setting Opus/Red by default. +TEST_P(WebRtcVoiceEngineTestFake, RecvRedDefault) { + EXPECT_TRUE(SetupRecvStream()); + cricket::AudioRecvParameters parameters; + parameters.codecs.push_back(kOpusCodec); + parameters.codecs.push_back(kRed48000Codec); + EXPECT_FALSE(channel_->SetRecvParameters(parameters)); +} + TEST_P(WebRtcVoiceEngineTestFake, SetSendBandwidthAuto) { EXPECT_TRUE(SetupSendStream()); @@ -1442,6 +1467,37 @@ TEST_P(WebRtcVoiceEngineTestFake, SetSendCodecs) { EXPECT_FALSE(channel_->CanInsertDtmf()); } +// Test that we set Opus/Red under the field trial. +TEST_P(WebRtcVoiceEngineTestFake, SetSendCodecsRed) { + webrtc::test::ScopedFieldTrials override_field_trials( + "WebRTC-Audio-Red-For-Opus/Enabled/"); + + EXPECT_TRUE(SetupSendStream()); + cricket::AudioSendParameters parameters; + parameters.codecs.push_back(kOpusCodec); + parameters.codecs.push_back(kRed48000Codec); + parameters.codecs[0].id = 96; + SetSendParameters(parameters); + const auto& send_codec_spec = *GetSendStreamConfig(kSsrcX).send_codec_spec; + EXPECT_EQ(96, send_codec_spec.payload_type); + EXPECT_STRCASEEQ("opus", send_codec_spec.format.name.c_str()); + EXPECT_EQ(112, send_codec_spec.red_payload_type); +} + +// Test that we set do not interpret Opus/Red by default. +TEST_P(WebRtcVoiceEngineTestFake, SetSendCodecsRedDefault) { + EXPECT_TRUE(SetupSendStream()); + cricket::AudioSendParameters parameters; + parameters.codecs.push_back(kOpusCodec); + parameters.codecs.push_back(kRed48000Codec); + parameters.codecs[0].id = 96; + SetSendParameters(parameters); + const auto& send_codec_spec = *GetSendStreamConfig(kSsrcX).send_codec_spec; + EXPECT_EQ(96, send_codec_spec.payload_type); + EXPECT_STRCASEEQ("opus", send_codec_spec.format.name.c_str()); + EXPECT_EQ(absl::nullopt, send_codec_spec.red_payload_type); +} + // Test that WebRtcVoiceEngine reconfigures, rather than recreates its // AudioSendStream. TEST_P(WebRtcVoiceEngineTestFake, DontRecreateSendStream) {