Implement codec selection api

The implementation covers the latest specification, but does not
support mixed-codec simulcast at the moment.
Changing codec for audio and video is supported.

Bug: webrtc:15064
Change-Id: I09082f39e2a7d54dd4a663a8a57bf9df5a851690
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/311663
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Florent Castelli <orphis@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40616}
diff --git a/api/rtp_parameters.h b/api/rtp_parameters.h
index e277e7d..91455e2 100644
--- a/api/rtp_parameters.h
+++ b/api/rtp_parameters.h
@@ -520,6 +520,9 @@
   // https://w3c.github.io/webrtc-extensions/#dom-rtcrtpencodingparameters-adaptiveptime
   bool adaptive_ptime = false;
 
+  // Allow changing the used codec for this encoding.
+  absl::optional<RtpCodec> codec;
+
   bool operator==(const RtpEncodingParameters& o) const {
     return ssrc == o.ssrc && bitrate_priority == o.bitrate_priority &&
            network_priority == o.network_priority &&
@@ -530,7 +533,7 @@
            scale_resolution_down_by == o.scale_resolution_down_by &&
            active == o.active && rid == o.rid &&
            adaptive_ptime == o.adaptive_ptime &&
-           requested_resolution == o.requested_resolution;
+           requested_resolution == o.requested_resolution && codec == o.codec;
   }
   bool operator!=(const RtpEncodingParameters& o) const {
     return !(*this == o);
diff --git a/media/base/codec.cc b/media/base/codec.cc
index 70a8d90..7ecf383 100644
--- a/media/base/codec.cc
+++ b/media/base/codec.cc
@@ -211,8 +211,7 @@
   return matches_id && matches_type_specific();
 }
 
-bool Codec::MatchesCapability(
-    const webrtc::RtpCodecCapability& codec_capability) const {
+bool Codec::MatchesRtpCodec(const webrtc::RtpCodec& codec_capability) const {
   webrtc::RtpCodecParameters codec_parameters = ToCodecParameters();
 
   return codec_parameters.name == codec_capability.name &&
diff --git a/media/base/codec.h b/media/base/codec.h
index 74c5cc2..5595708 100644
--- a/media/base/codec.h
+++ b/media/base/codec.h
@@ -114,7 +114,7 @@
   // H264 levels are not compared.
   bool Matches(const Codec& codec,
                const webrtc::FieldTrialsView* field_trials = nullptr) const;
-  bool MatchesCapability(const webrtc::RtpCodecCapability& capability) const;
+  bool MatchesRtpCodec(const webrtc::RtpCodec& capability) const;
 
   // Find the parameter for `name` and write the value to `out`.
   bool GetParam(const std::string& name, std::string* out) const;
diff --git a/media/base/media_engine.cc b/media/base/media_engine.cc
index 0efbd71..7304ab0 100644
--- a/media/base/media_engine.cc
+++ b/media/base/media_engine.cc
@@ -67,34 +67,69 @@
 
 webrtc::RTCError CheckScalabilityModeValues(
     const webrtc::RtpParameters& rtp_parameters,
-    rtc::ArrayView<cricket::VideoCodec> codecs) {
+    rtc::ArrayView<cricket::Codec> codec_preferences,
+    absl::optional<cricket::Codec> send_codec) {
   using webrtc::RTCErrorType;
 
-  if (codecs.empty()) {
+  if (codec_preferences.empty()) {
     // This is an audio sender or an extra check in the stack where the codec
     // list is not available and we can't check the scalability_mode values.
     return webrtc::RTCError::OK();
   }
 
   for (size_t i = 0; i < rtp_parameters.encodings.size(); ++i) {
+    if (rtp_parameters.encodings[i].codec) {
+      bool codecFound = false;
+      for (const cricket::VideoCodec& codec : codec_preferences) {
+        if (codec.MatchesRtpCodec(*rtp_parameters.encodings[i].codec)) {
+          codecFound = true;
+          send_codec = codec;
+          break;
+        }
+      }
+      if (!codecFound) {
+        LOG_AND_RETURN_ERROR(
+            RTCErrorType::INVALID_MODIFICATION,
+            "Attempted to use an unsupported codec for layer " +
+                std::to_string(i));
+      }
+    }
     if (rtp_parameters.encodings[i].scalability_mode) {
-      bool scalabilityModeFound = false;
-      for (const cricket::VideoCodec& codec : codecs) {
-        for (const auto& scalability_mode : codec.scalability_modes) {
+      if (!send_codec) {
+        bool scalabilityModeFound = false;
+        for (const cricket::VideoCodec& codec : codec_preferences) {
+          for (const auto& scalability_mode : codec.scalability_modes) {
+            if (ScalabilityModeToString(scalability_mode) ==
+                *rtp_parameters.encodings[i].scalability_mode) {
+              scalabilityModeFound = true;
+              break;
+            }
+          }
+          if (scalabilityModeFound)
+            break;
+        }
+
+        if (!scalabilityModeFound) {
+          LOG_AND_RETURN_ERROR(
+              RTCErrorType::INVALID_MODIFICATION,
+              "Attempted to set RtpParameters scalabilityMode "
+              "to an unsupported value for the current codecs.");
+        }
+      } else {
+        bool scalabilityModeFound = false;
+        for (const auto& scalability_mode : send_codec->scalability_modes) {
           if (ScalabilityModeToString(scalability_mode) ==
               *rtp_parameters.encodings[i].scalability_mode) {
             scalabilityModeFound = true;
             break;
           }
         }
-        if (scalabilityModeFound)
-          break;
-      }
-
-      if (!scalabilityModeFound) {
-        LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION,
-                             "Attempted to set RtpParameters scalabilityMode "
-                             "to an unsupported value for the current codecs.");
+        if (!scalabilityModeFound) {
+          LOG_AND_RETURN_ERROR(
+              RTCErrorType::INVALID_MODIFICATION,
+              "Attempted to set RtpParameters scalabilityMode "
+              "to an unsupported value for the current codecs.");
+        }
       }
     }
   }
@@ -104,7 +139,8 @@
 
 webrtc::RTCError CheckRtpParametersValues(
     const webrtc::RtpParameters& rtp_parameters,
-    rtc::ArrayView<cricket::VideoCodec> codecs) {
+    rtc::ArrayView<cricket::Codec> codec_preferences,
+    absl::optional<cricket::Codec> send_codec) {
   using webrtc::RTCErrorType;
 
   for (size_t i = 0; i < rtp_parameters.encodings.size(); ++i) {
@@ -151,22 +187,31 @@
                            "Attempted to set scale_resolution_down_by and "
                            "requested_resolution simultaniously.");
     }
+
+    if (i > 0 && rtp_parameters.encodings[i - 1].codec !=
+                     rtp_parameters.encodings[i].codec) {
+      LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION,
+                           "Attempted to use different codec values for "
+                           "different encodings.");
+    }
   }
 
-  return CheckScalabilityModeValues(rtp_parameters, codecs);
+  return CheckScalabilityModeValues(rtp_parameters, codec_preferences,
+                                    send_codec);
 }
 
 webrtc::RTCError CheckRtpParametersInvalidModificationAndValues(
     const webrtc::RtpParameters& old_rtp_parameters,
     const webrtc::RtpParameters& rtp_parameters) {
-  return CheckRtpParametersInvalidModificationAndValues(old_rtp_parameters,
-                                                        rtp_parameters, {});
+  return CheckRtpParametersInvalidModificationAndValues(
+      old_rtp_parameters, rtp_parameters, {}, absl::nullopt);
 }
 
 webrtc::RTCError CheckRtpParametersInvalidModificationAndValues(
     const webrtc::RtpParameters& old_rtp_parameters,
     const webrtc::RtpParameters& rtp_parameters,
-    rtc::ArrayView<cricket::VideoCodec> codecs) {
+    rtc::ArrayView<cricket::Codec> codec_preferences,
+    absl::optional<cricket::Codec> send_codec) {
   using webrtc::RTCErrorType;
   if (rtp_parameters.encodings.size() != old_rtp_parameters.encodings.size()) {
     LOG_AND_RETURN_ERROR(
@@ -201,7 +246,8 @@
                          "Attempted to set RtpParameters with modified SSRC");
   }
 
-  return CheckRtpParametersValues(rtp_parameters, codecs);
+  return CheckRtpParametersValues(rtp_parameters, codec_preferences,
+                                  send_codec);
 }
 
 CompositeMediaEngine::CompositeMediaEngine(
diff --git a/media/base/media_engine.h b/media/base/media_engine.h
index b3d7491..4281235 100644
--- a/media/base/media_engine.h
+++ b/media/base/media_engine.h
@@ -42,20 +42,23 @@
 // least one video codec of the list. If the list is empty, no check is done.
 webrtc::RTCError CheckScalabilityModeValues(
     const webrtc::RtpParameters& new_parameters,
-    rtc::ArrayView<cricket::VideoCodec> codecs);
+    rtc::ArrayView<cricket::Codec> codec_preferences,
+    absl::optional<cricket::Codec> send_codec);
 
 // Checks the parameters have valid and supported values, and checks parameters
 // with CheckScalabilityModeValues().
 webrtc::RTCError CheckRtpParametersValues(
     const webrtc::RtpParameters& new_parameters,
-    rtc::ArrayView<cricket::VideoCodec> codecs);
+    rtc::ArrayView<cricket::Codec> codec_preferences,
+    absl::optional<cricket::Codec> send_codec);
 
 // Checks that the immutable values have not changed in new_parameters and
 // checks all parameters with CheckRtpParametersValues().
 webrtc::RTCError CheckRtpParametersInvalidModificationAndValues(
     const webrtc::RtpParameters& old_parameters,
     const webrtc::RtpParameters& new_parameters,
-    rtc::ArrayView<cricket::VideoCodec> codecs);
+    rtc::ArrayView<cricket::Codec> codec_preferences,
+    absl::optional<cricket::Codec> send_codec);
 
 // Checks that the immutable values have not changed in new_parameters and
 // checks parameters (except SVC) with CheckRtpParametersValues(). It should
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index f041dce..504aad9 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -994,9 +994,36 @@
       codec.flexfec_payload_type = -1;
   }
 
+  absl::optional<VideoCodecSettings> force_codec;
+  if (!send_streams_.empty()) {
+    // Since we do not support mixed-codec simulcast yet,
+    // all send streams must have the same codec value.
+    auto rtp_parameters = send_streams_.begin()->second->GetRtpParameters();
+    if (rtp_parameters.encodings[0].codec) {
+      auto matched_codec =
+          absl::c_find_if(negotiated_codecs, [&](auto negotiated_codec) {
+            return negotiated_codec.codec.MatchesRtpCodec(
+                *rtp_parameters.encodings[0].codec);
+          });
+      if (matched_codec != negotiated_codecs.end()) {
+        force_codec = *matched_codec;
+      } else {
+        // The requested codec has been negotiated away, we clear it from the
+        // parameters.
+        for (auto& encoding : rtp_parameters.encodings) {
+          encoding.codec.reset();
+        }
+        send_streams_.begin()->second->SetRtpParameters(rtp_parameters,
+                                                        nullptr);
+      }
+    }
+  }
+
   if (negotiated_codecs_ != negotiated_codecs) {
     if (negotiated_codecs.empty()) {
       changed_params->send_codec = absl::nullopt;
+    } else if (force_codec) {
+      changed_params->send_codec = force_codec;
     } else if (send_codec() != negotiated_codecs.front()) {
       changed_params->send_codec = negotiated_codecs.front();
     }
@@ -1264,6 +1291,24 @@
         new_dscp = rtc::DSCP_AF41;
         break;
     }
+
+    // TODO(orphis): Support mixed-codec simulcast
+    if (parameters.encodings[0].codec && send_codec_ &&
+        !send_codec_->codec.MatchesRtpCodec(*parameters.encodings[0].codec)) {
+      RTC_LOG(LS_ERROR) << "Trying to change codec to "
+                        << parameters.encodings[0].codec->name;
+      auto matched_codec =
+          absl::c_find_if(negotiated_codecs_, [&](auto negotiated_codec) {
+            return negotiated_codec.codec.MatchesRtpCodec(
+                *parameters.encodings[0].codec);
+          });
+      RTC_CHECK(matched_codec != negotiated_codecs_.end());
+
+      ChangedSendParameters params;
+      params.send_codec = *matched_codec;
+      ApplyChangedParams(params);
+    }
+
     SetPreferredDscp(new_dscp);
   }
 
diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc
index bf1e396..7719dc2 100644
--- a/media/engine/webrtc_video_engine_unittest.cc
+++ b/media/engine/webrtc_video_engine_unittest.cc
@@ -4836,6 +4836,39 @@
   EXPECT_FALSE(send_channel_->SetSendParameters(send_parameters_));
 }
 
+TEST_F(WebRtcVideoChannelTest,
+       SetSendParametersRemovesSelectedCodecFromRtpParameters) {
+  EXPECT_TRUE(AddSendStream());
+  cricket::VideoSenderParameters parameters;
+  parameters.codecs.push_back(cricket::CreateVideoCodec(100, "VP8"));
+  parameters.codecs.push_back(cricket::CreateVideoCodec(100, "VP9"));
+  send_channel_->SetSendParameters(parameters);
+
+  webrtc::RtpParameters initial_params =
+      send_channel_->GetRtpSendParameters(last_ssrc_);
+
+  webrtc::RtpCodec vp9_rtp_codec;
+  vp9_rtp_codec.name = "VP9";
+  vp9_rtp_codec.kind = cricket::MEDIA_TYPE_VIDEO;
+  vp9_rtp_codec.clock_rate = 90000;
+  initial_params.encodings[0].codec = vp9_rtp_codec;
+
+  // We should be able to set the params with the VP9 codec that has been
+  // negotiated.
+  EXPECT_TRUE(
+      send_channel_->SetRtpSendParameters(last_ssrc_, initial_params).ok());
+
+  parameters.codecs.clear();
+  parameters.codecs.push_back(cricket::CreateVideoCodec(100, "VP8"));
+  send_channel_->SetSendParameters(parameters);
+
+  // Since VP9 is no longer negotiated, the RTP parameters should not have a
+  // forced codec anymore.
+  webrtc::RtpParameters new_params =
+      send_channel_->GetRtpSendParameters(last_ssrc_);
+  EXPECT_EQ(new_params.encodings[0].codec, absl::nullopt);
+}
+
 // Test that when both the codec-specific bitrate params and max_bandwidth_bps
 // are present in the same send parameters, the settings are combined correctly.
 TEST_F(WebRtcVideoChannelTest, SetSendCodecsWithBitratesAndMaxSendBandwidth) {
diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc
index 25fdbdc..65363c2 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -1271,7 +1271,34 @@
   // TODO(pthatcher): Refactor this to be more clean now that we have
   // all the information at once.
 
-  if (!SetSendCodecs(params.codecs)) {
+  // Finding if the RtpParameters force a specific codec
+  absl::optional<Codec> force_codec;
+  if (send_streams_.size() == 1) {
+    // Since audio simulcast is not supported, currently, only PlanB
+    // has multiple tracks and we don't care about getting the
+    // functionality working there properly.
+    auto rtp_parameters = send_streams_.begin()->second->rtp_parameters();
+    if (rtp_parameters.encodings[0].codec) {
+      auto matched_codec =
+          absl::c_find_if(params.codecs, [&](auto negotiated_codec) {
+            return negotiated_codec.MatchesRtpCodec(
+                *rtp_parameters.encodings[0].codec);
+          });
+      if (matched_codec != params.codecs.end()) {
+        force_codec = *matched_codec;
+      } else {
+        // The requested codec has been negotiated away, we clear it from the
+        // parameters.
+        for (auto& encoding : rtp_parameters.encodings) {
+          encoding.codec.reset();
+        }
+        send_streams_.begin()->second->SetRtpParameters(rtp_parameters,
+                                                        nullptr);
+      }
+    }
+  }
+
+  if (!SetSendCodecs(params.codecs, force_codec)) {
     return false;
   }
 
@@ -1319,13 +1346,14 @@
 // codec settings from the given list of codecs (originally from SDP). Both send
 // and receive streams may be reconfigured based on the new settings.
 bool WebRtcVoiceSendChannel::SetSendCodecs(
-    const std::vector<AudioCodec>& codecs) {
+    const std::vector<Codec>& codecs,
+    absl::optional<Codec> preferred_codec) {
   RTC_DCHECK_RUN_ON(worker_thread_);
   dtmf_payload_type_ = absl::nullopt;
   dtmf_payload_freq_ = -1;
 
   // Validate supplied codecs list.
-  for (const AudioCodec& codec : codecs) {
+  for (const Codec& codec : codecs) {
     // TODO(solenberg): Validate more aspects of input - that payload types
     //                  don't overlap, remove redundant/unsupported codecs etc -
     //                  the same way it is done for RtpHeaderExtensions.
@@ -1339,8 +1367,8 @@
   // Find PT of telephone-event codec with lowest clockrate, as a fallback, in
   // case we don't have a DTMF codec with a rate matching the send codec's, or
   // if this function returns early.
-  std::vector<AudioCodec> dtmf_codecs;
-  for (const AudioCodec& codec : codecs) {
+  std::vector<Codec> dtmf_codecs;
+  for (const Codec& codec : codecs) {
     if (IsCodec(codec, kDtmfCodecName)) {
       dtmf_codecs.push_back(codec);
       if (!dtmf_payload_type_ || codec.clockrate < dtmf_payload_freq_) {
@@ -1356,10 +1384,11 @@
   webrtc::BitrateConstraints bitrate_config;
   absl::optional<webrtc::AudioCodecInfo> voice_codec_info;
   size_t send_codec_position = 0;
-  for (const AudioCodec& voice_codec : codecs) {
+  for (const Codec& voice_codec : codecs) {
     if (!(IsCodec(voice_codec, kCnCodecName) ||
           IsCodec(voice_codec, kDtmfCodecName) ||
-          IsCodec(voice_codec, kRedCodecName))) {
+          IsCodec(voice_codec, kRedCodecName)) &&
+        (!preferred_codec || preferred_codec->Matches(voice_codec))) {
       webrtc::SdpAudioFormat format(voice_codec.name, voice_codec.clockrate,
                                     voice_codec.channels, voice_codec.params);
 
@@ -1391,7 +1420,7 @@
   if (voice_codec_info->allow_comfort_noise) {
     // Loop through the codecs list again to find the CN codec.
     // TODO(solenberg): Break out into a separate function?
-    for (const AudioCodec& cn_codec : codecs) {
+    for (const Codec& cn_codec : codecs) {
       if (IsCodec(cn_codec, kCnCodecName) &&
           cn_codec.clockrate == send_codec_spec->format.clockrate_hz &&
           cn_codec.channels == voice_codec_info->num_channels) {
@@ -1410,7 +1439,7 @@
     }
 
     // Find the telephone-event PT exactly matching the preferred send codec.
-    for (const AudioCodec& dtmf_codec : dtmf_codecs) {
+    for (const Codec& dtmf_codec : dtmf_codecs) {
       if (dtmf_codec.clockrate == send_codec_spec->format.clockrate_hz) {
         dtmf_payload_type_ = dtmf_codec.id;
         dtmf_payload_freq_ = dtmf_codec.clockrate;
@@ -1421,15 +1450,15 @@
 
   // Loop through the codecs to find the RED codec that matches opus
   // with respect to clockrate and number of channels.
-  size_t red_codec_position = 0;
-  for (const AudioCodec& red_codec : codecs) {
-    if (red_codec_position < send_codec_position &&
-        IsCodec(red_codec, kRedCodecName) &&
+  // RED codec needs to be negotiated before the actual codec they
+  // reference.
+  for (size_t i = 0; i < send_codec_position; ++i) {
+    const Codec& red_codec = codecs[i];
+    if (IsCodec(red_codec, kRedCodecName) &&
         CheckRedParameters(red_codec, *send_codec_spec)) {
       send_codec_spec->red_payload_type = red_codec.id;
       break;
     }
-    red_codec_position++;
   }
 
   if (send_codec_spec_ != send_codec_spec) {
@@ -1839,6 +1868,22 @@
         break;
     }
     SetPreferredDscp(new_dscp);
+
+    absl::optional<cricket::Codec> send_codec = GetSendCodec();
+    // TODO(orphis): Support mixed-codec simulcast
+    if (parameters.encodings[0].codec && send_codec &&
+        !send_codec->MatchesRtpCodec(*parameters.encodings[0].codec)) {
+      RTC_LOG(LS_ERROR) << "Trying to change codec to "
+                        << parameters.encodings[0].codec->name;
+      auto matched_codec =
+          absl::c_find_if(send_codecs_, [&](auto negotiated_codec) {
+            return negotiated_codec.MatchesRtpCodec(
+                *parameters.encodings[0].codec);
+          });
+      RTC_DCHECK(matched_codec != send_codecs_.end());
+
+      SetSendCodecs(send_codecs_, *matched_codec);
+    }
   }
 
   // TODO(minyue): The following legacy actions go into
diff --git a/media/engine/webrtc_voice_engine.h b/media/engine/webrtc_voice_engine.h
index 925d086..868c78b 100644
--- a/media/engine/webrtc_voice_engine.h
+++ b/media/engine/webrtc_voice_engine.h
@@ -290,7 +290,8 @@
 
  private:
   bool SetOptions(const AudioOptions& options);
-  bool SetSendCodecs(const std::vector<AudioCodec>& codecs);
+  bool SetSendCodecs(const std::vector<Codec>& codecs,
+                     absl::optional<Codec> preferred_codec);
   bool SetLocalSource(uint32_t ssrc, AudioSource* source);
   bool MuteStream(uint32_t ssrc, bool mute);
 
diff --git a/media/engine/webrtc_voice_engine_unittest.cc b/media/engine/webrtc_voice_engine_unittest.cc
index aa3d426..d3902e4 100644
--- a/media/engine/webrtc_voice_engine_unittest.cc
+++ b/media/engine/webrtc_voice_engine_unittest.cc
@@ -15,8 +15,10 @@
 
 #include "absl/memory/memory.h"
 #include "absl/strings/match.h"
+#include "absl/types/optional.h"
 #include "api/audio_codecs/builtin_audio_decoder_factory.h"
 #include "api/audio_codecs/builtin_audio_encoder_factory.h"
+#include "api/media_types.h"
 #include "api/rtc_event_log/rtc_event_log.h"
 #include "api/rtp_parameters.h"
 #include "api/scoped_refptr.h"
@@ -1370,6 +1372,41 @@
   EXPECT_EQ(initial_params, send_channel_->GetRtpSendParameters(kSsrcX));
 }
 
+// Test that we remove the codec from RTP parameters if it's not negotiated
+// anymore.
+TEST_P(WebRtcVoiceEngineTestFake,
+       SetSendParametersRemovesSelectedCodecFromRtpParameters) {
+  EXPECT_TRUE(SetupSendStream());
+  cricket::AudioSenderParameter parameters;
+  parameters.codecs.push_back(kOpusCodec);
+  parameters.codecs.push_back(kPcmuCodec);
+  SetSendParameters(parameters);
+
+  webrtc::RtpParameters initial_params =
+      send_channel_->GetRtpSendParameters(kSsrcX);
+
+  webrtc::RtpCodec opus_rtp_codec;
+  opus_rtp_codec.name = "opus";
+  opus_rtp_codec.kind = cricket::MEDIA_TYPE_AUDIO;
+  opus_rtp_codec.num_channels = 2;
+  opus_rtp_codec.clock_rate = 48000;
+  initial_params.encodings[0].codec = opus_rtp_codec;
+
+  // We should be able to set the params with the opus codec that has been
+  // negotiated.
+  EXPECT_TRUE(send_channel_->SetRtpSendParameters(kSsrcX, initial_params).ok());
+
+  parameters.codecs.clear();
+  parameters.codecs.push_back(kPcmuCodec);
+  SetSendParameters(parameters);
+
+  // Since Opus is no longer negotiated, the RTP parameters should not have a
+  // forced codec anymore.
+  webrtc::RtpParameters new_params =
+      send_channel_->GetRtpSendParameters(kSsrcX);
+  EXPECT_EQ(new_params.encodings[0].codec, absl::nullopt);
+}
+
 // Test that max_bitrate_bps in send stream config gets updated correctly when
 // SetRtpSendParameters is called.
 TEST_P(WebRtcVoiceEngineTestFake, SetRtpSendParameterUpdatesMaxBitrate) {
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 8b2e128..60006c4 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -2770,6 +2770,7 @@
       "../api:media_stream_interface",
       "../api:rtc_error",
       "../api:rtc_stats_api",
+      "../api:rtp_parameters",
       "../api:scoped_refptr",
       "../api:sequence_checker",
       "../api/audio:audio_mixer_api",
@@ -2827,6 +2828,7 @@
     ]
     absl_deps = [
       "//third_party/abseil-cpp/absl/algorithm:container",
+      "//third_party/abseil-cpp/absl/strings",
       "//third_party/abseil-cpp/absl/types:optional",
     ]
   }
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index b20e536..767e5a4 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -1106,14 +1106,20 @@
   }
 
   std::vector<cricket::VideoCodec> codecs;
+  // Gather the current codec capabilities to allow checking scalabilityMode and
+  // codec selection against supported values.
   if (media_type == cricket::MEDIA_TYPE_VIDEO) {
-    // Gather the current codec capabilities to allow checking scalabilityMode
-    // against supported values.
     codecs = context_->media_engine()->video().send_codecs(false);
+  } else {
+    codecs = context_->media_engine()->voice().send_codecs();
   }
 
-  auto result = cricket::CheckRtpParametersValues(parameters, codecs);
+  auto result =
+      cricket::CheckRtpParametersValues(parameters, codecs, absl::nullopt);
   if (!result.ok()) {
+    if (result.type() == RTCErrorType::INVALID_MODIFICATION) {
+      result.set_type(RTCErrorType::UNSUPPORTED_OPERATION);
+    }
     LOG_AND_RETURN_ERROR(result.type(), result.message());
   }
 
diff --git a/pc/peer_connection_encodings_integrationtest.cc b/pc/peer_connection_encodings_integrationtest.cc
index 4ebcc0a..5b25e29 100644
--- a/pc/peer_connection_encodings_integrationtest.cc
+++ b/pc/peer_connection_encodings_integrationtest.cc
@@ -11,11 +11,17 @@
 #include <string>
 #include <vector>
 
+#include "absl/strings/match.h"
+#include "absl/types/optional.h"
 #include "api/audio_codecs/builtin_audio_decoder_factory.h"
 #include "api/audio_codecs/builtin_audio_encoder_factory.h"
 #include "api/audio_codecs/opus_audio_decoder_factory.h"
 #include "api/audio_codecs/opus_audio_encoder_factory.h"
+#include "api/media_types.h"
+#include "api/rtc_error.h"
 #include "api/rtp_parameters.h"
+#include "api/rtp_transceiver_direction.h"
+#include "api/rtp_transceiver_interface.h"
 #include "api/stats/rtcstats_objects.h"
 #include "api/units/data_rate.h"
 #include "api/video_codecs/video_decoder_factory_template.h"
@@ -115,8 +121,8 @@
   rtc::scoped_refptr<PeerConnectionTestWrapper> CreatePc() {
     auto pc_wrapper = rtc::make_ref_counted<PeerConnectionTestWrapper>(
         "pc", &pss_, background_thread_.get(), background_thread_.get());
-    pc_wrapper->CreatePc({}, webrtc::CreateOpusAudioEncoderFactory(),
-                         webrtc::CreateOpusAudioDecoderFactory());
+    pc_wrapper->CreatePc({}, webrtc::CreateBuiltinAudioEncoderFactory(),
+                         webrtc::CreateBuiltinAudioDecoderFactory());
     return pc_wrapper;
   }
 
@@ -183,8 +189,7 @@
 
   void NegotiateWithSimulcastTweaks(
       rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper,
-      rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper,
-      std::vector<cricket::SimulcastLayer> init_layers) {
+      rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper) {
     // Create and set offer for `local_pc_wrapper`.
     std::unique_ptr<SessionDescriptionInterface> offer =
         CreateOffer(local_pc_wrapper);
@@ -226,6 +231,29 @@
     return callback->report();
   }
 
+  bool IsCodecIdDifferent(
+      rtc::scoped_refptr<PeerConnectionTestWrapper> pc_wrapper,
+      size_t index,
+      const std::string& codec_id) {
+    return IsCodecIdDifferentWithScalabilityMode(pc_wrapper, index, codec_id,
+                                                 absl::nullopt);
+  }
+
+  bool IsCodecIdDifferentWithScalabilityMode(
+      rtc::scoped_refptr<PeerConnectionTestWrapper> pc_wrapper,
+      size_t index,
+      const std::string& codec_id,
+      absl::optional<std::string> wanted_scalability_mode) {
+    rtc::scoped_refptr<const RTCStatsReport> report = GetStats(pc_wrapper);
+    std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
+        report->GetStatsOfType<RTCOutboundRtpStreamStats>();
+    return outbound_rtps[index]->codec_id.value() != codec_id &&
+           (!wanted_scalability_mode ||
+            (outbound_rtps[index]->scalability_mode.has_value() &&
+             outbound_rtps[index]->scalability_mode.value() ==
+                 wanted_scalability_mode));
+  }
+
   bool HasOutboundRtpBytesSent(
       rtc::scoped_refptr<PeerConnectionTestWrapper> pc_wrapper,
       size_t num_layers) {
@@ -387,7 +415,7 @@
       GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP8");
   transceiver->SetCodecPreferences(codecs);
 
-  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers);
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
   local_pc_wrapper->WaitForConnection();
   remote_pc_wrapper->WaitForConnection();
 
@@ -434,7 +462,7 @@
   ASSERT_EQ(parameters.encodings.size(), 1u);
   EXPECT_THAT(parameters.encodings[0].scalability_mode, Eq(absl::nullopt));
 
-  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers);
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
   local_pc_wrapper->WaitForConnection();
   remote_pc_wrapper->WaitForConnection();
 
@@ -488,7 +516,7 @@
               Optional(std::string("L3T3_KEY")));
 
   // Negotiate, this results in VP8 being picked and fallback happening.
-  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers);
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
   local_pc_wrapper->WaitForConnection();
   remote_pc_wrapper->WaitForConnection();
   // `scalaiblity_mode` is assigned the fallback value "L1T2" which is different
@@ -533,7 +561,7 @@
       GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP9");
   transceiver->SetCodecPreferences(codecs);
 
-  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers);
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
   local_pc_wrapper->WaitForConnection();
   remote_pc_wrapper->WaitForConnection();
 
@@ -585,7 +613,7 @@
   parameters.encodings[0].scale_resolution_down_by = 1;
   EXPECT_TRUE(sender->SetParameters(parameters).ok());
 
-  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers);
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
   local_pc_wrapper->WaitForConnection();
   remote_pc_wrapper->WaitForConnection();
 
@@ -640,7 +668,7 @@
   parameters.encodings[2].active = false;
   EXPECT_TRUE(sender->SetParameters(parameters).ok());
 
-  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers);
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
   local_pc_wrapper->WaitForConnection();
   remote_pc_wrapper->WaitForConnection();
 
@@ -683,7 +711,7 @@
 
   // The original negotiation triggers legacy SVC because we didn't specify
   // any scalability mode.
-  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers);
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
   local_pc_wrapper->WaitForConnection();
   remote_pc_wrapper->WaitForConnection();
 
@@ -745,7 +773,7 @@
   parameters.encodings[2].active = false;
   sender->SetParameters(parameters);
 
-  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers);
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
   local_pc_wrapper->WaitForConnection();
   remote_pc_wrapper->WaitForConnection();
 
@@ -784,7 +812,7 @@
   parameters.encodings[2].active = false;
   sender->SetParameters(parameters);
 
-  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers);
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
   local_pc_wrapper->WaitForConnection();
   remote_pc_wrapper->WaitForConnection();
 
@@ -822,7 +850,7 @@
   parameters.encodings[2].active = true;
   sender->SetParameters(parameters);
 
-  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers);
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
   local_pc_wrapper->WaitForConnection();
   remote_pc_wrapper->WaitForConnection();
 
@@ -872,7 +900,7 @@
   parameters.encodings[2].active = false;
   sender->SetParameters(parameters);
 
-  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers);
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
   local_pc_wrapper->WaitForConnection();
   remote_pc_wrapper->WaitForConnection();
 
@@ -896,6 +924,714 @@
   EXPECT_LE(target_bitrate.kbps(), kVp9ExpectedMaxBitrateForL1T3.kbps());
 }
 
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       EncodingParameterCodecIsEmptyWhenCreatedAudio) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
+  rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
+      transceiver_or_error.MoveValue();
+  webrtc::RtpParameters parameters =
+      audio_transceiver->sender()->GetParameters();
+  EXPECT_FALSE(parameters.encodings[0].codec.has_value());
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       EncodingParameterCodecIsEmptyWhenCreatedVideo) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
+  rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
+      transceiver_or_error.MoveValue();
+  webrtc::RtpParameters parameters =
+      video_transceiver->sender()->GetParameters();
+  EXPECT_FALSE(parameters.encodings[0].codec.has_value());
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       EncodingParameterCodecIsSetByAddTransceiverAudio) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+  rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
+  ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+  rtc::scoped_refptr<webrtc::MediaStreamInterface> stream =
+      local_pc_wrapper->GetUserMedia(
+          /*audio=*/true, {}, /*video=*/false, {});
+  rtc::scoped_refptr<AudioTrackInterface> track = stream->GetAudioTracks()[0];
+
+  absl::optional<webrtc::RtpCodecCapability> pcmu =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
+                                                   "pcmu");
+  ASSERT_TRUE(pcmu);
+
+  webrtc::RtpTransceiverInit init;
+  init.direction = webrtc::RtpTransceiverDirection::kSendOnly;
+  webrtc::RtpEncodingParameters encoding_parameters;
+  encoding_parameters.codec = pcmu;
+  init.send_encodings.push_back(encoding_parameters);
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(track, init);
+  rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
+      transceiver_or_error.MoveValue();
+  webrtc::RtpParameters parameters =
+      audio_transceiver->sender()->GetParameters();
+  EXPECT_EQ(*parameters.encodings[0].codec, *pcmu);
+
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+  local_pc_wrapper->WaitForConnection();
+  remote_pc_wrapper->WaitForConnection();
+
+  rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
+  std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
+      report->GetStatsOfType<RTCOutboundRtpStreamStats>();
+  ASSERT_EQ(outbound_rtps.size(), 1u);
+  std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
+  EXPECT_STRCASEEQ(("audio/" + pcmu->name).c_str(), codec_name.c_str());
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       EncodingParameterCodecIsSetByAddTransceiverVideo) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+  rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
+  ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+  rtc::scoped_refptr<webrtc::MediaStreamInterface> stream =
+      local_pc_wrapper->GetUserMedia(
+          /*audio=*/false, {}, /*video=*/true, {.width = 1280, .height = 720});
+  rtc::scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0];
+
+  absl::optional<webrtc::RtpCodecCapability> vp9 =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
+                                                   "vp9");
+  ASSERT_TRUE(vp9);
+
+  webrtc::RtpTransceiverInit init;
+  init.direction = webrtc::RtpTransceiverDirection::kSendOnly;
+  webrtc::RtpEncodingParameters encoding_parameters;
+  encoding_parameters.codec = vp9;
+  encoding_parameters.scalability_mode = "L3T3";
+  init.send_encodings.push_back(encoding_parameters);
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(track, init);
+  rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
+      transceiver_or_error.MoveValue();
+  webrtc::RtpParameters parameters =
+      audio_transceiver->sender()->GetParameters();
+  EXPECT_EQ(*parameters.encodings[0].codec, *vp9);
+
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+  local_pc_wrapper->WaitForConnection();
+  remote_pc_wrapper->WaitForConnection();
+
+  EXPECT_TRUE_WAIT(
+      IsCodecIdDifferentWithScalabilityMode(local_pc_wrapper, 0, "", "L3T3"),
+      kDefaultTimeout.ms());
+
+  rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
+  std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
+      report->GetStatsOfType<RTCOutboundRtpStreamStats>();
+  ASSERT_EQ(outbound_rtps.size(), 1u);
+  std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
+  EXPECT_STRCASEEQ(("video/" + vp9->name).c_str(), codec_name.c_str());
+  EXPECT_EQ(outbound_rtps[0]->scalability_mode.value(), "L3T3");
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       EncodingParameterCodecIsSetBySetParametersBeforeNegotiationAudio) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+  rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
+  ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+  rtc::scoped_refptr<webrtc::MediaStreamInterface> stream =
+      local_pc_wrapper->GetUserMedia(
+          /*audio=*/true, {}, /*video=*/false, {});
+  rtc::scoped_refptr<AudioTrackInterface> track = stream->GetAudioTracks()[0];
+
+  absl::optional<webrtc::RtpCodecCapability> pcmu =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
+                                                   "pcmu");
+
+  auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track);
+  rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
+      transceiver_or_error.MoveValue();
+  webrtc::RtpParameters parameters =
+      audio_transceiver->sender()->GetParameters();
+  parameters.encodings[0].codec = pcmu;
+  EXPECT_TRUE(audio_transceiver->sender()->SetParameters(parameters).ok());
+
+  parameters = audio_transceiver->sender()->GetParameters();
+  EXPECT_EQ(parameters.encodings[0].codec, pcmu);
+
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+  local_pc_wrapper->WaitForConnection();
+  remote_pc_wrapper->WaitForConnection();
+
+  rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
+  std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
+      report->GetStatsOfType<RTCOutboundRtpStreamStats>();
+  ASSERT_EQ(outbound_rtps.size(), 1u);
+  std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
+  EXPECT_STRCASEEQ(("audio/" + pcmu->name).c_str(), codec_name.c_str());
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       EncodingParameterCodecIsSetBySetParametersAfterNegotiationAudio) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+  rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
+  ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+  rtc::scoped_refptr<webrtc::MediaStreamInterface> stream =
+      local_pc_wrapper->GetUserMedia(
+          /*audio=*/true, {}, /*video=*/false, {});
+  rtc::scoped_refptr<AudioTrackInterface> track = stream->GetAudioTracks()[0];
+
+  absl::optional<webrtc::RtpCodecCapability> pcmu =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
+                                                   "pcmu");
+
+  auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track);
+  rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
+      transceiver_or_error.MoveValue();
+
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+  local_pc_wrapper->WaitForConnection();
+  remote_pc_wrapper->WaitForConnection();
+
+  rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
+  std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
+      report->GetStatsOfType<RTCOutboundRtpStreamStats>();
+  ASSERT_EQ(outbound_rtps.size(), 1u);
+  std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
+  EXPECT_STRCASENE(("audio/" + pcmu->name).c_str(), codec_name.c_str());
+  std::string last_codec_id = outbound_rtps[0]->codec_id.value();
+
+  webrtc::RtpParameters parameters =
+      audio_transceiver->sender()->GetParameters();
+  parameters.encodings[0].codec = pcmu;
+  EXPECT_TRUE(audio_transceiver->sender()->SetParameters(parameters).ok());
+
+  parameters = audio_transceiver->sender()->GetParameters();
+  EXPECT_EQ(parameters.encodings[0].codec, pcmu);
+
+  EXPECT_TRUE_WAIT(IsCodecIdDifferent(local_pc_wrapper, 0, last_codec_id),
+                   kDefaultTimeout.ms());
+
+  report = GetStats(local_pc_wrapper);
+  outbound_rtps = report->GetStatsOfType<RTCOutboundRtpStreamStats>();
+  ASSERT_EQ(outbound_rtps.size(), 1u);
+  codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
+  EXPECT_STRCASEEQ(("audio/" + pcmu->name).c_str(), codec_name.c_str());
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       EncodingParameterCodecIsSetBySetParametersBeforeNegotiationVideo) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+  rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
+  ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+  rtc::scoped_refptr<webrtc::MediaStreamInterface> stream =
+      local_pc_wrapper->GetUserMedia(
+          /*audio=*/false, {}, /*video=*/true, {.width = 1280, .height = 720});
+  rtc::scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0];
+
+  absl::optional<webrtc::RtpCodecCapability> vp9 =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
+                                                   "vp9");
+
+  auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track);
+  rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
+      transceiver_or_error.MoveValue();
+  webrtc::RtpParameters parameters =
+      video_transceiver->sender()->GetParameters();
+  parameters.encodings[0].codec = vp9;
+  parameters.encodings[0].scalability_mode = "L3T3";
+  EXPECT_TRUE(video_transceiver->sender()->SetParameters(parameters).ok());
+
+  parameters = video_transceiver->sender()->GetParameters();
+  EXPECT_EQ(parameters.encodings[0].codec, vp9);
+  EXPECT_EQ(parameters.encodings[0].scalability_mode, "L3T3");
+
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+  local_pc_wrapper->WaitForConnection();
+  remote_pc_wrapper->WaitForConnection();
+
+  EXPECT_TRUE_WAIT(
+      IsCodecIdDifferentWithScalabilityMode(local_pc_wrapper, 0, "", "L3T3"),
+      kDefaultTimeout.ms());
+  rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
+  std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
+      report->GetStatsOfType<RTCOutboundRtpStreamStats>();
+  ASSERT_EQ(outbound_rtps.size(), 1u);
+  std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
+  EXPECT_STRCASEEQ(("video/" + vp9->name).c_str(), codec_name.c_str());
+  EXPECT_EQ(outbound_rtps[0]->scalability_mode.ValueOrDefault(""), "L3T3");
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       EncodingParameterCodecIsSetBySetParametersAfterNegotiationVideo) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+  rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
+  ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+  rtc::scoped_refptr<webrtc::MediaStreamInterface> stream =
+      local_pc_wrapper->GetUserMedia(
+          /*audio=*/false, {}, /*video=*/true, {.width = 1280, .height = 720});
+  rtc::scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0];
+
+  absl::optional<webrtc::RtpCodecCapability> vp9 =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
+                                                   "vp9");
+
+  auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track);
+  rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
+      transceiver_or_error.MoveValue();
+
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+  local_pc_wrapper->WaitForConnection();
+  remote_pc_wrapper->WaitForConnection();
+
+  rtc::scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
+  std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
+      report->GetStatsOfType<RTCOutboundRtpStreamStats>();
+  ASSERT_EQ(outbound_rtps.size(), 1u);
+  std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
+  EXPECT_STRCASENE(("audio/" + vp9->name).c_str(), codec_name.c_str());
+  std::string last_codec_id = outbound_rtps[0]->codec_id.value();
+
+  webrtc::RtpParameters parameters =
+      video_transceiver->sender()->GetParameters();
+  parameters.encodings[0].codec = vp9;
+  parameters.encodings[0].scalability_mode = "L3T3";
+  EXPECT_TRUE(video_transceiver->sender()->SetParameters(parameters).ok());
+
+  parameters = video_transceiver->sender()->GetParameters();
+  EXPECT_EQ(parameters.encodings[0].codec, vp9);
+  EXPECT_EQ(parameters.encodings[0].scalability_mode, "L3T3");
+
+  EXPECT_TRUE_WAIT(IsCodecIdDifferentWithScalabilityMode(local_pc_wrapper, 0,
+                                                         last_codec_id, "L3T3"),
+                   kDefaultTimeout.ms());
+
+  report = GetStats(local_pc_wrapper);
+  outbound_rtps = report->GetStatsOfType<RTCOutboundRtpStreamStats>();
+  ASSERT_EQ(outbound_rtps.size(), 1u);
+  codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]);
+  EXPECT_STRCASEEQ(("video/" + vp9->name).c_str(), codec_name.c_str());
+  EXPECT_EQ(outbound_rtps[0]->scalability_mode.value(), "L3T3");
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       AddTransceiverRejectsUnknownCodecParameterAudio) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+
+  webrtc::RtpCodec dummy_codec;
+  dummy_codec.kind = cricket::MEDIA_TYPE_AUDIO;
+  dummy_codec.name = "FOOBAR";
+  dummy_codec.clock_rate = 90000;
+  dummy_codec.num_channels = 2;
+
+  webrtc::RtpTransceiverInit init;
+  init.direction = webrtc::RtpTransceiverDirection::kSendOnly;
+  webrtc::RtpEncodingParameters encoding_parameters;
+  encoding_parameters.codec = dummy_codec;
+  init.send_encodings.push_back(encoding_parameters);
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init);
+  EXPECT_FALSE(transceiver_or_error.ok());
+  EXPECT_EQ(transceiver_or_error.error().type(),
+            RTCErrorType::UNSUPPORTED_OPERATION);
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       AddTransceiverRejectsUnknownCodecParameterVideo) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+
+  webrtc::RtpCodec dummy_codec;
+  dummy_codec.kind = cricket::MEDIA_TYPE_VIDEO;
+  dummy_codec.name = "FOOBAR";
+  dummy_codec.clock_rate = 90000;
+
+  webrtc::RtpTransceiverInit init;
+  init.direction = webrtc::RtpTransceiverDirection::kSendOnly;
+  webrtc::RtpEncodingParameters encoding_parameters;
+  encoding_parameters.codec = dummy_codec;
+  init.send_encodings.push_back(encoding_parameters);
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
+  EXPECT_FALSE(transceiver_or_error.ok());
+  EXPECT_EQ(transceiver_or_error.error().type(),
+            RTCErrorType::UNSUPPORTED_OPERATION);
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       SetParametersRejectsUnknownCodecParameterAudio) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+
+  webrtc::RtpCodec dummy_codec;
+  dummy_codec.kind = cricket::MEDIA_TYPE_AUDIO;
+  dummy_codec.name = "FOOBAR";
+  dummy_codec.clock_rate = 90000;
+  dummy_codec.num_channels = 2;
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
+  ASSERT_TRUE(transceiver_or_error.ok());
+  rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
+      transceiver_or_error.MoveValue();
+
+  webrtc::RtpParameters parameters =
+      audio_transceiver->sender()->GetParameters();
+  parameters.encodings[0].codec = dummy_codec;
+  RTCError error = audio_transceiver->sender()->SetParameters(parameters);
+  EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       SetParametersRejectsUnknownCodecParameterVideo) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+
+  webrtc::RtpCodec dummy_codec;
+  dummy_codec.kind = cricket::MEDIA_TYPE_VIDEO;
+  dummy_codec.name = "FOOBAR";
+  dummy_codec.clock_rate = 90000;
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
+  ASSERT_TRUE(transceiver_or_error.ok());
+  rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
+      transceiver_or_error.MoveValue();
+
+  webrtc::RtpParameters parameters =
+      video_transceiver->sender()->GetParameters();
+  parameters.encodings[0].codec = dummy_codec;
+  RTCError error = video_transceiver->sender()->SetParameters(parameters);
+  EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       SetParametersRejectsNonPreferredCodecParameterAudio) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+
+  absl::optional<webrtc::RtpCodecCapability> opus =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
+                                                   "opus");
+  ASSERT_TRUE(opus);
+
+  std::vector<webrtc::RtpCodecCapability> not_opus_codecs =
+      local_pc_wrapper->pc_factory()
+          ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_AUDIO)
+          .codecs;
+  not_opus_codecs.erase(
+      std::remove_if(not_opus_codecs.begin(), not_opus_codecs.end(),
+                     [&](const auto& codec) {
+                       return absl::EqualsIgnoreCase(codec.name, opus->name);
+                     }),
+      not_opus_codecs.end());
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
+  ASSERT_TRUE(transceiver_or_error.ok());
+  rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
+      transceiver_or_error.MoveValue();
+  ASSERT_TRUE(audio_transceiver->SetCodecPreferences(not_opus_codecs).ok());
+
+  webrtc::RtpParameters parameters =
+      audio_transceiver->sender()->GetParameters();
+  parameters.encodings[0].codec = opus;
+  RTCError error = audio_transceiver->sender()->SetParameters(parameters);
+  EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       SetParametersRejectsNonPreferredCodecParameterVideo) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+
+  absl::optional<webrtc::RtpCodecCapability> vp8 =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
+                                                   "vp8");
+  ASSERT_TRUE(vp8);
+
+  std::vector<webrtc::RtpCodecCapability> not_vp8_codecs =
+      local_pc_wrapper->pc_factory()
+          ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
+          .codecs;
+  not_vp8_codecs.erase(
+      std::remove_if(not_vp8_codecs.begin(), not_vp8_codecs.end(),
+                     [&](const auto& codec) {
+                       return absl::EqualsIgnoreCase(codec.name, vp8->name);
+                     }),
+      not_vp8_codecs.end());
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
+  ASSERT_TRUE(transceiver_or_error.ok());
+  rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
+      transceiver_or_error.MoveValue();
+  ASSERT_TRUE(video_transceiver->SetCodecPreferences(not_vp8_codecs).ok());
+
+  webrtc::RtpParameters parameters =
+      video_transceiver->sender()->GetParameters();
+  parameters.encodings[0].codec = vp8;
+  RTCError error = video_transceiver->sender()->SetParameters(parameters);
+  EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       SetParametersRejectsNonNegotiatedCodecParameterAudio) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+  rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
+  ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+  absl::optional<webrtc::RtpCodecCapability> opus =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
+                                                   "opus");
+  ASSERT_TRUE(opus);
+
+  std::vector<webrtc::RtpCodecCapability> not_opus_codecs =
+      local_pc_wrapper->pc_factory()
+          ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_AUDIO)
+          .codecs;
+  not_opus_codecs.erase(
+      std::remove_if(not_opus_codecs.begin(), not_opus_codecs.end(),
+                     [&](const auto& codec) {
+                       return absl::EqualsIgnoreCase(codec.name, opus->name);
+                     }),
+      not_opus_codecs.end());
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
+  ASSERT_TRUE(transceiver_or_error.ok());
+  rtc::scoped_refptr<RtpTransceiverInterface> audio_transceiver =
+      transceiver_or_error.MoveValue();
+  ASSERT_TRUE(audio_transceiver->SetCodecPreferences(not_opus_codecs).ok());
+
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+  local_pc_wrapper->WaitForConnection();
+  remote_pc_wrapper->WaitForConnection();
+
+  webrtc::RtpParameters parameters =
+      audio_transceiver->sender()->GetParameters();
+  parameters.encodings[0].codec = opus;
+  RTCError error = audio_transceiver->sender()->SetParameters(parameters);
+  EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       SetParametersRejectsNonNegotiatedCodecParameterVideo) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+  rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
+  ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+  absl::optional<webrtc::RtpCodecCapability> vp8 =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
+                                                   "vp8");
+  ASSERT_TRUE(vp8);
+
+  std::vector<webrtc::RtpCodecCapability> not_vp8_codecs =
+      local_pc_wrapper->pc_factory()
+          ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
+          .codecs;
+  not_vp8_codecs.erase(
+      std::remove_if(not_vp8_codecs.begin(), not_vp8_codecs.end(),
+                     [&](const auto& codec) {
+                       return absl::EqualsIgnoreCase(codec.name, vp8->name);
+                     }),
+      not_vp8_codecs.end());
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
+  ASSERT_TRUE(transceiver_or_error.ok());
+  rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
+      transceiver_or_error.MoveValue();
+  ASSERT_TRUE(video_transceiver->SetCodecPreferences(not_vp8_codecs).ok());
+
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+  local_pc_wrapper->WaitForConnection();
+  remote_pc_wrapper->WaitForConnection();
+
+  webrtc::RtpParameters parameters =
+      video_transceiver->sender()->GetParameters();
+  parameters.encodings[0].codec = vp8;
+  RTCError error = video_transceiver->sender()->SetParameters(parameters);
+  EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       EncodingParametersCodecRemovedAfterNegotiationAudio) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+  rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
+  ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+  absl::optional<webrtc::RtpCodecCapability> opus =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_AUDIO,
+                                                   "opus");
+  ASSERT_TRUE(opus);
+
+  std::vector<webrtc::RtpCodecCapability> not_opus_codecs =
+      local_pc_wrapper->pc_factory()
+          ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_AUDIO)
+          .codecs;
+  not_opus_codecs.erase(
+      std::remove_if(not_opus_codecs.begin(), not_opus_codecs.end(),
+                     [&](const auto& codec) {
+                       return absl::EqualsIgnoreCase(codec.name, opus->name);
+                     }),
+      not_opus_codecs.end());
+
+  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();
+
+  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);
+
+  ASSERT_TRUE(audio_transceiver->SetCodecPreferences(not_opus_codecs).ok());
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+
+  parameters = audio_transceiver->sender()->GetParameters();
+  EXPECT_FALSE(parameters.encodings[0].codec);
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       SetParametersRejectsScalabilityModeForSelectedCodec) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+
+  absl::optional<webrtc::RtpCodecCapability> vp8 =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
+                                                   "vp8");
+  ASSERT_TRUE(vp8);
+
+  webrtc::RtpTransceiverInit init;
+  init.direction = webrtc::RtpTransceiverDirection::kSendOnly;
+  webrtc::RtpEncodingParameters encoding_parameters;
+  encoding_parameters.codec = vp8;
+  encoding_parameters.scalability_mode = "L1T3";
+  init.send_encodings.push_back(encoding_parameters);
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
+  ASSERT_TRUE(transceiver_or_error.ok());
+  rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
+      transceiver_or_error.MoveValue();
+
+  webrtc::RtpParameters parameters =
+      video_transceiver->sender()->GetParameters();
+  parameters.encodings[0].scalability_mode = "L3T3";
+  RTCError error = video_transceiver->sender()->SetParameters(parameters);
+  EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION);
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       EncodingParametersCodecRemovedByNegotiationVideo) {
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+  rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
+  ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+  absl::optional<webrtc::RtpCodecCapability> vp8 =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
+                                                   "vp8");
+  ASSERT_TRUE(vp8);
+
+  std::vector<webrtc::RtpCodecCapability> not_vp8_codecs =
+      local_pc_wrapper->pc_factory()
+          ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
+          .codecs;
+  not_vp8_codecs.erase(
+      std::remove_if(not_vp8_codecs.begin(), not_vp8_codecs.end(),
+                     [&](const auto& codec) {
+                       return absl::EqualsIgnoreCase(codec.name, vp8->name);
+                     }),
+      not_vp8_codecs.end());
+
+  webrtc::RtpTransceiverInit init;
+  init.direction = webrtc::RtpTransceiverDirection::kSendOnly;
+  webrtc::RtpEncodingParameters encoding_parameters;
+  encoding_parameters.rid = "h";
+  encoding_parameters.codec = vp8;
+  encoding_parameters.scale_resolution_down_by = 2;
+  init.send_encodings.push_back(encoding_parameters);
+  encoding_parameters.rid = "f";
+  encoding_parameters.scale_resolution_down_by = 1;
+  init.send_encodings.push_back(encoding_parameters);
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
+  ASSERT_TRUE(transceiver_or_error.ok());
+  rtc::scoped_refptr<RtpTransceiverInterface> video_transceiver =
+      transceiver_or_error.MoveValue();
+
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+  local_pc_wrapper->WaitForConnection();
+  remote_pc_wrapper->WaitForConnection();
+
+  webrtc::RtpParameters parameters =
+      video_transceiver->sender()->GetParameters();
+  ASSERT_EQ(parameters.encodings.size(), 2u);
+  EXPECT_EQ(parameters.encodings[0].codec, vp8);
+  EXPECT_EQ(parameters.encodings[1].codec, vp8);
+
+  ASSERT_TRUE(video_transceiver->SetCodecPreferences(not_vp8_codecs).ok());
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+
+  parameters = video_transceiver->sender()->GetParameters();
+  EXPECT_FALSE(parameters.encodings[0].codec);
+  EXPECT_FALSE(parameters.encodings[1].codec);
+}
+
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+       AddTransceiverRejectsMixedCodecSimulcast) {
+  // Mixed Codec Simulcast is not yet supported, so we ensure that we reject
+  // such parameters.
+  rtc::scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+  rtc::scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
+  ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
+
+  absl::optional<webrtc::RtpCodecCapability> vp8 =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
+                                                   "vp8");
+  ASSERT_TRUE(vp8);
+  absl::optional<webrtc::RtpCodecCapability> vp9 =
+      local_pc_wrapper->FindFirstSendCodecWithName(cricket::MEDIA_TYPE_VIDEO,
+                                                   "vp9");
+
+  webrtc::RtpTransceiverInit init;
+  init.direction = webrtc::RtpTransceiverDirection::kSendOnly;
+  webrtc::RtpEncodingParameters encoding_parameters;
+  encoding_parameters.rid = "h";
+  encoding_parameters.codec = vp8;
+  encoding_parameters.scale_resolution_down_by = 2;
+  init.send_encodings.push_back(encoding_parameters);
+  encoding_parameters.rid = "f";
+  encoding_parameters.codec = vp9;
+  encoding_parameters.scale_resolution_down_by = 1;
+  init.send_encodings.push_back(encoding_parameters);
+
+  auto transceiver_or_error =
+      local_pc_wrapper->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
+  ASSERT_FALSE(transceiver_or_error.ok());
+  EXPECT_EQ(transceiver_or_error.error().type(),
+            RTCErrorType::UNSUPPORTED_OPERATION);
+}
+
 // Tests that use the standard path (specifying both `scalability_mode` and
 // `scale_resolution_down_by`) should pass for all codecs.
 class PeerConnectionEncodingsIntegrationParameterizedTest
@@ -952,7 +1688,7 @@
   parameters.encodings[2].active = false;
   sender->SetParameters(parameters);
 
-  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers);
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
   local_pc_wrapper->WaitForConnection();
   remote_pc_wrapper->WaitForConnection();
 
@@ -995,7 +1731,7 @@
   parameters.encodings[2].scale_resolution_down_by = 1;
   sender->SetParameters(parameters);
 
-  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers);
+  NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
   local_pc_wrapper->WaitForConnection();
   remote_pc_wrapper->WaitForConnection();
 
diff --git a/pc/peer_connection_media_unittest.cc b/pc/peer_connection_media_unittest.cc
index 87e018b..4855419 100644
--- a/pc/peer_connection_media_unittest.cc
+++ b/pc/peer_connection_media_unittest.cc
@@ -1521,7 +1521,7 @@
          absl::c_equal(
              capabilities_no_rtx, codecs_no_rtx,
              [](const webrtc::RtpCodecCapability& capability, const C& codec) {
-               return codec.MatchesCapability(capability);
+               return codec.MatchesRtpCodec(capability);
              });
 }
 
diff --git a/pc/peer_connection_svc_integrationtest.cc b/pc/peer_connection_svc_integrationtest.cc
index 6b579b1..672f3ee 100644
--- a/pc/peer_connection_svc_integrationtest.cc
+++ b/pc/peer_connection_svc_integrationtest.cc
@@ -139,7 +139,7 @@
   parameters.encodings[0].scalability_mode = "L3T3";
   auto result = transceiver->sender()->SetParameters(parameters);
   EXPECT_FALSE(result.ok());
-  EXPECT_EQ(result.type(), webrtc::RTCErrorType::UNSUPPORTED_OPERATION);
+  EXPECT_EQ(result.type(), webrtc::RTCErrorType::INVALID_MODIFICATION);
 }
 
 TEST_F(PeerConnectionSVCIntegrationTest,
@@ -212,7 +212,7 @@
   parameters.encodings[0].scalability_mode = "L3T3";
   auto result = transceiver->sender()->SetParameters(parameters);
   EXPECT_FALSE(result.ok());
-  EXPECT_EQ(result.type(), webrtc::RTCErrorType::UNSUPPORTED_OPERATION);
+  EXPECT_EQ(result.type(), webrtc::RTCErrorType::INVALID_MODIFICATION);
 }
 
 TEST_F(PeerConnectionSVCIntegrationTest,
@@ -237,7 +237,7 @@
   parameters.encodings[0].scalability_mode = "FOOBAR";
   auto result = transceiver->sender()->SetParameters(parameters);
   EXPECT_FALSE(result.ok());
-  EXPECT_EQ(result.type(), webrtc::RTCErrorType::UNSUPPORTED_OPERATION);
+  EXPECT_EQ(result.type(), webrtc::RTCErrorType::INVALID_MODIFICATION);
 }
 
 TEST_F(PeerConnectionSVCIntegrationTest, FallbackToL1Tx) {
diff --git a/pc/rtp_sender.cc b/pc/rtp_sender.cc
index 6c5acac..cdae159 100644
--- a/pc/rtp_sender.cc
+++ b/pc/rtp_sender.cc
@@ -248,7 +248,7 @@
   }
   if (!media_channel_ || !ssrc_) {
     auto result = cricket::CheckRtpParametersInvalidModificationAndValues(
-        init_parameters_, parameters, video_codec_preferences_);
+        init_parameters_, parameters, codec_preferences_, absl::nullopt);
     if (result.ok()) {
       init_parameters_ = parameters;
     }
@@ -272,7 +272,7 @@
       return;
     }
 
-    result = CheckSVCParameters(rtp_parameters);
+    result = CheckCodecParameters(rtp_parameters);
     if (!result.ok()) {
       webrtc::InvokeSetParametersCallback(callback, result);
       return;
@@ -299,7 +299,7 @@
   }
   if (!media_channel_ || !ssrc_) {
     auto result = cricket::CheckRtpParametersInvalidModificationAndValues(
-        init_parameters_, parameters, video_codec_preferences_);
+        init_parameters_, parameters, codec_preferences_, absl::nullopt);
     if (result.ok()) {
       init_parameters_ = parameters;
     }
@@ -338,6 +338,26 @@
   return RTCError::OK();
 }
 
+RTCError RtpSenderBase::CheckCodecParameters(const RtpParameters& parameters) {
+  absl::optional<cricket::Codec> send_codec = media_channel_->GetSendCodec();
+
+  // Match the currently used codec against the codec preferences to gather
+  // the SVC capabilities.
+  absl::optional<cricket::Codec> send_codec_with_svc_info;
+  if (send_codec && send_codec->type == cricket::Codec::Type::kVideo) {
+    auto codec_match =
+        absl::c_find_if(codec_preferences_, [&](auto& codec_preference) {
+          return send_codec->Matches(codec_preference);
+        });
+    if (codec_match != codec_preferences_.end()) {
+      send_codec_with_svc_info = *codec_match;
+    }
+  }
+
+  return cricket::CheckScalabilityModeValues(parameters, codec_preferences_,
+                                             send_codec_with_svc_info);
+}
+
 RTCError RtpSenderBase::SetParameters(const RtpParameters& parameters) {
   RTC_DCHECK_RUN_ON(signaling_thread_);
   TRACE_EVENT0("webrtc", "RtpSenderBase::SetParameters");
@@ -876,23 +896,4 @@
       [&] { video_media_channel()->SetVideoSend(ssrc_, nullptr, nullptr); });
 }
 
-RTCError VideoRtpSender::CheckSVCParameters(const RtpParameters& parameters) {
-  absl::optional<cricket::VideoCodec> send_codec =
-      video_media_channel()->GetSendCodec();
-
-  // Match the currently used codec against the codec preferences to gather
-  // the SVC capabilities.
-  std::vector<cricket::VideoCodec> codecs;
-  if (send_codec) {
-    for (const auto& codec_preference : video_codec_preferences_) {
-      if (send_codec->Matches(codec_preference)) {
-        codecs.push_back(codec_preference);
-        break;
-      }
-    }
-  }
-
-  return cricket::CheckScalabilityModeValues(parameters, codecs);
-}
-
 }  // namespace webrtc
diff --git a/pc/rtp_sender.h b/pc/rtp_sender.h
index fdeedd5..232f747 100644
--- a/pc/rtp_sender.h
+++ b/pc/rtp_sender.h
@@ -85,8 +85,8 @@
   virtual RTCError SetParametersInternalWithAllLayers(
       const RtpParameters& parameters) = 0;
 
-  // Additional checks that are specific to the Sender type
-  virtual RTCError CheckSVCParameters(const RtpParameters& parameters) {
+  // Additional checks that are specific to the current codec settings
+  virtual RTCError CheckCodecParameters(const RtpParameters& parameters) {
     return webrtc::RTCError::OK();
   }
 
@@ -104,8 +104,8 @@
 
   // Used by the owning transceiver to inform the sender on the currently
   // selected codecs.
-  virtual void SetVideoCodecPreferences(
-      std::vector<cricket::VideoCodec> codec_preferences) = 0;
+  virtual void SetCodecPreferences(
+      std::vector<cricket::Codec> codec_preferences) = 0;
 };
 
 // Shared implementation for RtpSenderInternal interface.
@@ -144,6 +144,7 @@
                              SetParametersCallback callback = nullptr,
                              bool blocking = true) override;
   RTCError CheckSetParameters(const RtpParameters& parameters);
+  RTCError CheckCodecParameters(const RtpParameters& parameters) override;
   RtpParameters GetParametersInternalWithAllLayers() const override;
   RTCError SetParametersInternalWithAllLayers(
       const RtpParameters& parameters) override;
@@ -222,9 +223,9 @@
     is_transceiver_stopped_ = true;
   }
 
-  void SetVideoCodecPreferences(
-      std::vector<cricket::VideoCodec> codec_preferences) override {
-    video_codec_preferences_ = codec_preferences;
+  void SetCodecPreferences(
+      std::vector<cricket::Codec> codec_preferences) override {
+    codec_preferences_ = codec_preferences;
   }
 
  protected:
@@ -262,7 +263,7 @@
 
   std::vector<std::string> stream_ids_;
   RtpParameters init_parameters_;
-  std::vector<cricket::VideoCodec> video_codec_preferences_;
+  std::vector<cricket::Codec> codec_preferences_;
 
   // TODO(tommi): `media_channel_` and several other member variables in this
   // class (ssrc_, stopped_, etc) are accessed from more than one thread without
@@ -422,8 +423,6 @@
   rtc::scoped_refptr<DtmfSenderInterface> GetDtmfSender() const override;
   RTCError GenerateKeyFrame(const std::vector<std::string>& rids) override;
 
-  RTCError CheckSVCParameters(const RtpParameters& parameters) override;
-
  protected:
   VideoRtpSender(rtc::Thread* worker_thread,
                  const std::string& id,
diff --git a/pc/rtp_transceiver.cc b/pc/rtp_transceiver.cc
index e705b0f..815ec9d 100644
--- a/pc/rtp_transceiver.cc
+++ b/pc/rtp_transceiver.cc
@@ -56,7 +56,7 @@
                codec.name != cricket::kFlexfecCodecName &&
                absl::c_any_of(recv_codecs,
                               [&codec](const cricket::Codec& recv_codec) {
-                                return recv_codec.MatchesCapability(codec);
+                                return recv_codec.MatchesRtpCodec(codec);
                               });
       })) {
     LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_MODIFICATION,
@@ -70,7 +70,7 @@
                codec.name != cricket::kFlexfecCodecName &&
                absl::c_any_of(send_codecs,
                               [&codec](const cricket::Codec& send_codec) {
-                                return send_codec.MatchesCapability(codec);
+                                return send_codec.MatchesRtpCodec(codec);
                               });
       })) {
     LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_MODIFICATION,
@@ -85,12 +85,12 @@
   for (const auto& codec_preference : codecs) {
     bool is_recv_codec = absl::c_any_of(
         recv_codecs, [&codec_preference](const cricket::Codec& codec) {
-          return codec.MatchesCapability(codec_preference);
+          return codec.MatchesRtpCodec(codec_preference);
         });
 
     bool is_send_codec = absl::c_any_of(
         send_codecs, [&codec_preference](const cricket::Codec& codec) {
-          return codec.MatchesCapability(codec_preference);
+          return codec.MatchesRtpCodec(codec_preference);
         });
 
     if (!is_recv_codec && !is_send_codec) {
@@ -126,7 +126,7 @@
 
   for (const auto& codec_preference : codecs) {
     for (const cricket::VideoCodec& send_codec : send_codecs) {
-      if (send_codec.MatchesCapability(codec_preference)) {
+      if (send_codec.MatchesRtpCodec(codec_preference)) {
         result.push_back(send_codec);
       }
     }
@@ -171,9 +171,10 @@
   RTC_DCHECK(media_type_ == cricket::MEDIA_TYPE_AUDIO ||
              media_type_ == cricket::MEDIA_TYPE_VIDEO);
   RTC_DCHECK_EQ(sender->media_type(), receiver->media_type());
-  if (sender->media_type() == cricket::MEDIA_TYPE_VIDEO)
-    sender->internal()->SetVideoCodecPreferences(
-        media_engine()->video().send_codecs(false));
+  sender->internal()->SetCodecPreferences(
+      sender->media_type() == cricket::MEDIA_TYPE_VIDEO
+          ? media_engine()->video().send_codecs(false)
+          : media_engine()->voice().send_codecs());
   senders_.push_back(sender);
   receivers_.push_back(receiver);
 }
@@ -412,14 +413,15 @@
   RTC_DCHECK(sender);
   RTC_DCHECK_EQ(media_type(), sender->media_type());
   RTC_DCHECK(!absl::c_linear_search(senders_, sender));
-  if (media_type() == cricket::MEDIA_TYPE_VIDEO) {
-    std::vector<cricket::VideoCodec> send_codecs =
-        media_engine()->video().send_codecs(false);
-    sender->internal()->SetVideoCodecPreferences(
-        codec_preferences_.empty()
-            ? send_codecs
-            : MatchCodecPreferences(codec_preferences_, send_codecs));
-  }
+
+  std::vector<cricket::Codec> send_codecs =
+      media_type() == cricket::MEDIA_TYPE_VIDEO
+          ? media_engine()->video().send_codecs(false)
+          : media_engine()->voice().send_codecs();
+  sender->internal()->SetCodecPreferences(
+      codec_preferences_.empty()
+          ? send_codecs
+          : MatchCodecPreferences(codec_preferences_, send_codecs));
   senders_.push_back(sender);
 }
 
@@ -668,9 +670,10 @@
   // to codecs and abort these steps.
   if (codec_capabilities.empty()) {
     codec_preferences_.clear();
-    if (media_type() == cricket::MEDIA_TYPE_VIDEO)
-      senders_.front()->internal()->SetVideoCodecPreferences(
-          media_engine()->video().send_codecs(false));
+    senders_.front()->internal()->SetCodecPreferences(
+        media_type() == cricket::MEDIA_TYPE_VIDEO
+            ? media_engine()->video().send_codecs(false)
+            : media_engine()->voice().send_codecs());
     return RTCError::OK();
   }
 
@@ -683,24 +686,19 @@
 
   // 6. to 8.
   RTCError result;
+  std::vector<cricket::Codec> recv_codecs, send_codecs;
   if (media_type_ == cricket::MEDIA_TYPE_AUDIO) {
-    result =
-        VerifyCodecPreferences(codecs, media_engine()->voice().send_codecs(),
-                               media_engine()->voice().recv_codecs());
+    send_codecs = media_engine()->voice().send_codecs();
+    recv_codecs = media_engine()->voice().recv_codecs();
   } else if (media_type_ == cricket::MEDIA_TYPE_VIDEO) {
-    std::vector<cricket::Codec> send_codecs =
-        media_engine()->video().send_codecs(context()->use_rtx());
-    result = VerifyCodecPreferences(
-        codecs, send_codecs,
-        media_engine()->video().recv_codecs(context()->use_rtx()));
-
-    if (result.ok()) {
-      senders_.front()->internal()->SetVideoCodecPreferences(
-          MatchCodecPreferences(codecs, send_codecs));
-    }
+    send_codecs = media_engine()->video().send_codecs(context()->use_rtx());
+    recv_codecs = media_engine()->video().recv_codecs(context()->use_rtx());
   }
+  result = VerifyCodecPreferences(codecs, send_codecs, recv_codecs);
 
   if (result.ok()) {
+    senders_.front()->internal()->SetCodecPreferences(
+        MatchCodecPreferences(codecs, send_codecs));
     codec_preferences_ = codecs;
   }
 
diff --git a/pc/test/mock_rtp_sender_internal.h b/pc/test/mock_rtp_sender_internal.h
index 8ed0ede..4cfb2cf 100644
--- a/pc/test/mock_rtp_sender_internal.h
+++ b/pc/test/mock_rtp_sender_internal.h
@@ -64,9 +64,12 @@
               SetParametersInternalWithAllLayers,
               (const RtpParameters&),
               (override));
-  MOCK_METHOD(RTCError, CheckSVCParameters, (const RtpParameters&), (override));
+  MOCK_METHOD(RTCError,
+              CheckCodecParameters,
+              (const RtpParameters&),
+              (override));
   MOCK_METHOD(void,
-              SetVideoCodecPreferences,
+              SetCodecPreferences,
               (std::vector<cricket::VideoCodec>),
               (override));
   MOCK_METHOD(rtc::scoped_refptr<DtmfSenderInterface>,
diff --git a/pc/test/peer_connection_test_wrapper.cc b/pc/test/peer_connection_test_wrapper.cc
index 112f04d..1a3dd31 100644
--- a/pc/test/peer_connection_test_wrapper.cc
+++ b/pc/test/peer_connection_test_wrapper.cc
@@ -17,9 +17,11 @@
 #include <utility>
 #include <vector>
 
+#include "absl/strings/match.h"
 #include "absl/types/optional.h"
 #include "api/audio/audio_mixer.h"
 #include "api/create_peerconnection_factory.h"
+#include "api/media_types.h"
 #include "api/sequence_checker.h"
 #include "api/video_codecs/video_decoder_factory.h"
 #include "api/video_codecs/video_decoder_factory_template.h"
@@ -201,6 +203,20 @@
   return result.MoveValue();
 }
 
+absl::optional<webrtc::RtpCodecCapability>
+PeerConnectionTestWrapper::FindFirstSendCodecWithName(
+    cricket::MediaType media_type,
+    const std::string& name) const {
+  std::vector<webrtc::RtpCodecCapability> codecs =
+      peer_connection_factory_->GetRtpSenderCapabilities(media_type).codecs;
+  for (const auto& codec : codecs) {
+    if (absl::EqualsIgnoreCase(codec.name, name)) {
+      return codec;
+    }
+  }
+  return absl::nullopt;
+}
+
 void PeerConnectionTestWrapper::WaitForNegotiation() {
   EXPECT_TRUE_WAIT(!pending_negotiation_, kMaxWait);
 }
diff --git a/pc/test/peer_connection_test_wrapper.h b/pc/test/peer_connection_test_wrapper.h
index cfc423f..751c946 100644
--- a/pc/test/peer_connection_test_wrapper.h
+++ b/pc/test/peer_connection_test_wrapper.h
@@ -23,6 +23,7 @@
 #include "api/media_stream_interface.h"
 #include "api/peer_connection_interface.h"
 #include "api/rtc_error.h"
+#include "api/rtp_parameters.h"
 #include "api/rtp_receiver_interface.h"
 #include "api/scoped_refptr.h"
 #include "api/sequence_checker.h"
@@ -64,6 +65,10 @@
       const std::string& label,
       const webrtc::DataChannelInit& init);
 
+  absl::optional<webrtc::RtpCodecCapability> FindFirstSendCodecWithName(
+      cricket::MediaType media_type,
+      const std::string& name) const;
+
   void WaitForNegotiation();
 
   // Implements PeerConnectionObserver.