negotiate RED codec for audio

negotiates the RED codec for opus audio behind a field trial
  WebRTC-Audio-Redundancy
This adds the following line to the SDP:
  a=rtpmap:someid RED/48000/2

To test start Chrome with
  --force-fieldtrials=WebRTC-Audio-Red-For-Opus/Enabled

BUG=webrtc:11640

Change-Id: I8fa9fb07d03db5f90cdb08765baaa03d3d0458cc
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/176372
Reviewed-by: Henrik Lundin <henrik.lundin@webrtc.org>
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#31562}
diff --git a/audio/BUILD.gn b/audio/BUILD.gn
index 460f2ce..7df741e 100644
--- a/audio/BUILD.gn
+++ b/audio/BUILD.gn
@@ -71,6 +71,7 @@
     "../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 62ca514..42705aa 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 @@
                            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<AudioEncoderCopyRed>(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 a7087b0..d094198 100644
--- a/audio/audio_send_stream_unittest.cc
+++ b/audio/audio_send_stream_unittest.cc
@@ -371,6 +371,7 @@
   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 @@
       "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 ddcba03..765ece7 100644
--- a/call/audio_send_stream.cc
+++ b/call/audio_send_stream.cc
@@ -75,6 +75,8 @@
   ss << ", transport_cc_enabled: " << (transport_cc_enabled ? "true" : "false");
   ss << ", cng_payload_type: "
      << (cng_payload_type ? rtc::ToString(*cng_payload_type) : "<unset>");
+  ss << ", red_payload_type: "
+     << (red_payload_type ? rtc::ToString(*red_payload_type) : "<unset>");
   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 86cea38..d21dff4 100644
--- a/call/audio_send_stream.h
+++ b/call/audio_send_stream.h
@@ -140,6 +140,7 @@
       bool nack_enabled = false;
       bool transport_cc_enabled = false;
       absl::optional<int> cng_payload_type;
+      absl::optional<int> red_payload_type;
       // If unset, use the encoder's default target bitrate.
       absl::optional<int> target_bitrate_bps;
     };
diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc
index 4bb0e42..661bfd6 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -99,6 +99,12 @@
   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 @@
     }
   }
 
+  // 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 @@
     }
     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 @@
     }
   }
 
+  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 9a01bd5..d155601 100644
--- a/media/engine/webrtc_voice_engine_unittest.cc
+++ b/media/engine/webrtc_voice_engine_unittest.cc
@@ -59,6 +59,7 @@
 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 @@
   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<std::map<int, webrtc::SdpAudioFormat>>(
+                  {{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 @@
   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) {