Reland "Use two MediaChannels for 2 directions."

This reverts commit 18c869bc36b342cd4a79947067e52a93a04a7808.

Reason for revert: Added a field trial that allows landing the code without affecting performance in prod.

This CL also incorporates subsequent CLs that also had to be reverted.

Original change's description:
> Revert "Use two MediaChannels for 2 directions."
>
> This reverts commit 8981a6fac3d665beac4a58b9453e6c39988a024f.
>
> Reason for revert: Quality regression detected.
>
> Original change's description:
> > Use two MediaChannels for 2 directions.
> >
> > This CL separates the two directions of MediaChannel into two separate objects that do not couple with each other.
> >
> > The notable API change is that receiver local SSRC now has to be set explicitly - before, it was done implicitly when the send-side MediaChannel had a stream added to it.
> >
> > Bug: webrtc:13931
> > Change-Id: I83c2e3c8e79f89872d5adda1bc2899f7049748b3
> > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/288400
> > Commit-Queue: Harald Alvestrand <hta@webrtc.org>
> > Reviewed-by: Henrik Boström <hbos@webrtc.org>
> > Cr-Commit-Position: refs/heads/main@{#39340}
>
> No-Try: true
> Bug: webrtc:13931
> Change-Id: I791997ad9eff75c3ac9cd2e4bbacf5bc6c3a3a79
> Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/295663
> Commit-Queue: Harald Alvestrand <hta@webrtc.org>
> Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
> Cr-Commit-Position: refs/heads/main@{#39445}

Bug: webrtc:13931
Change-Id: I1318910a685188e2b846c9040e1efc04c2c894ac
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/296080
Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org>
Reviewed-by: Per Kjellander <perkj@webrtc.org>
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#39494}
diff --git a/media/base/fake_media_engine.cc b/media/base/fake_media_engine.cc
index 7a8a409..b252883 100644
--- a/media/base/fake_media_engine.cc
+++ b/media/base/fake_media_engine.cc
@@ -463,14 +463,43 @@
 
   FakeVoiceMediaChannel* ch =
       new FakeVoiceMediaChannel(role, this, options, call->network_thread());
-  channels_.push_back(ch);
+  switch (role) {
+    case MediaChannel::Role::kSend:
+      send_channels_.push_back(ch);
+      break;
+    case MediaChannel::Role::kReceive:
+      receive_channels_.push_back(ch);
+      break;
+    case MediaChannel::Role::kBoth:
+      send_channels_.push_back(ch);
+      receive_channels_.push_back(ch);
+      break;
+    default:
+      RTC_CHECK_NOTREACHED();
+  }
   return ch;
 }
-FakeVoiceMediaChannel* FakeVoiceEngine::GetChannel(size_t index) {
-  return (channels_.size() > index) ? channels_[index] : NULL;
+FakeVoiceMediaChannel* FakeVoiceEngine::GetSendChannel(size_t index) {
+  return (send_channels_.size() > index) ? send_channels_[index] : NULL;
+}
+FakeVoiceMediaChannel* FakeVoiceEngine::GetReceiveChannel(size_t index) {
+  return (receive_channels_.size() > index) ? receive_channels_[index] : NULL;
 }
 void FakeVoiceEngine::UnregisterChannel(VoiceMediaChannel* channel) {
-  channels_.erase(absl::c_find(channels_, channel));
+  switch (channel->role()) {
+    case MediaChannel::Role::kSend:
+      send_channels_.erase(absl::c_find(send_channels_, channel));
+      break;
+    case MediaChannel::Role::kReceive:
+      receive_channels_.erase(absl::c_find(receive_channels_, channel));
+      break;
+    case MediaChannel::Role::kBoth:
+      send_channels_.erase(absl::c_find(send_channels_, channel));
+      receive_channels_.erase(absl::c_find(receive_channels_, channel));
+      break;
+    default:
+      RTC_CHECK_NOTREACHED();
+  }
 }
 const std::vector<AudioCodec>& FakeVoiceEngine::send_codecs() const {
   return send_codecs_;
@@ -535,16 +564,52 @@
 
   FakeVideoMediaChannel* ch =
       new FakeVideoMediaChannel(role, this, options, call->network_thread());
-  channels_.emplace_back(ch);
+  switch (role) {
+    case MediaChannel::Role::kSend:
+      send_channels_.emplace_back(ch);
+      break;
+    case MediaChannel::Role::kReceive:
+      receive_channels_.emplace_back(ch);
+      break;
+    case MediaChannel::Role::kBoth:
+      send_channels_.push_back(ch);
+      receive_channels_.push_back(ch);
+      break;
+    default:
+      RTC_CHECK_NOTREACHED();
+  }
   return ch;
 }
-FakeVideoMediaChannel* FakeVideoEngine::GetChannel(size_t index) {
-  return (channels_.size() > index) ? channels_[index] : nullptr;
+FakeVideoMediaChannel* FakeVideoEngine::GetSendChannel(size_t index) {
+  return (send_channels_.size() > index) ? send_channels_[index] : nullptr;
+}
+FakeVideoMediaChannel* FakeVideoEngine::GetReceiveChannel(size_t index) {
+  return (receive_channels_.size() > index) ? receive_channels_[index]
+                                            : nullptr;
 }
 void FakeVideoEngine::UnregisterChannel(VideoMediaChannel* channel) {
-  auto it = absl::c_find(channels_, channel);
-  RTC_DCHECK(it != channels_.end());
-  channels_.erase(it);
+  switch (channel->role()) {
+    case MediaChannel::Role::kSend: {
+      auto it = absl::c_find(send_channels_, channel);
+      RTC_DCHECK(it != send_channels_.end());
+      send_channels_.erase(it);
+    } break;
+    case MediaChannel::Role::kReceive: {
+      auto it = absl::c_find(receive_channels_, channel);
+      RTC_DCHECK(it != receive_channels_.end());
+      receive_channels_.erase(it);
+    } break;
+    case MediaChannel::Role::kBoth: {
+      auto it = absl::c_find(send_channels_, channel);
+      RTC_DCHECK(it != send_channels_.end());
+      send_channels_.erase(it);
+      it = absl::c_find(receive_channels_, channel);
+      RTC_DCHECK(it != receive_channels_.end());
+      receive_channels_.erase(it);
+    } break;
+    default:
+      RTC_CHECK_NOTREACHED();
+  }
 }
 std::vector<VideoCodec> FakeVideoEngine::send_codecs(bool use_rtx) const {
   return send_codecs_;
@@ -597,11 +662,17 @@
   video_->SetRecvCodecs(codecs);
 }
 
-FakeVoiceMediaChannel* FakeMediaEngine::GetVoiceChannel(size_t index) {
-  return voice_->GetChannel(index);
+FakeVoiceMediaChannel* FakeMediaEngine::GetVoiceSendChannel(size_t index) {
+  return voice_->GetSendChannel(index);
 }
-FakeVideoMediaChannel* FakeMediaEngine::GetVideoChannel(size_t index) {
-  return video_->GetChannel(index);
+FakeVideoMediaChannel* FakeMediaEngine::GetVideoSendChannel(size_t index) {
+  return video_->GetSendChannel(index);
+}
+FakeVoiceMediaChannel* FakeMediaEngine::GetVoiceReceiveChannel(size_t index) {
+  return voice_->GetReceiveChannel(index);
+}
+FakeVideoMediaChannel* FakeMediaEngine::GetVideoReceiveChannel(size_t index) {
+  return video_->GetReceiveChannel(index);
 }
 
 void FakeMediaEngine::set_fail_create_channel(bool fail) {
diff --git a/media/base/fake_media_engine.h b/media/base/fake_media_engine.h
index 7e62877..34369e7 100644
--- a/media/base/fake_media_engine.h
+++ b/media/base/fake_media_engine.h
@@ -408,6 +408,10 @@
       std::unique_ptr<webrtc::AudioSinkInterface> sink) override;
 
   std::vector<webrtc::RtpSource> GetSources(uint32_t ssrc) const override;
+  bool SenderNackEnabled() const override { return false; }
+  bool SenderNonSenderRttEnabled() const override { return false; }
+  void SetReceiveNackEnabled(bool enabled) {}
+  void SetReceiveNonSenderRttEnabled(bool enabled) {}
 
  private:
   class VoiceChannelAudioSink : public AudioSource::Sink {
@@ -509,6 +513,20 @@
   void RequestRecvKeyFrame(uint32_t ssrc) override;
   void GenerateSendKeyFrame(uint32_t ssrc,
                             const std::vector<std::string>& rids) override;
+  webrtc::RtcpMode SendCodecRtcpMode() const override {
+    return webrtc::RtcpMode::kCompound;
+  }
+  void SetSendCodecChangedCallback(
+      absl::AnyInvocable<void()> callback) override {}
+  bool SendCodecHasLntf() const override { return false; }
+  bool SendCodecHasNack() const override { return false; }
+  absl::optional<int> SendCodecRtxTime() const override {
+    return absl::nullopt;
+  }
+  void SetReceiverFeedbackParameters(bool lntf_enabled,
+                                     bool nack_enabled,
+                                     webrtc::RtcpMode rtcp_mode,
+                                     absl::optional<int> rtx_time) override {}
 
  private:
   bool SetRecvCodecs(const std::vector<VideoCodec>& codecs);
@@ -539,7 +557,8 @@
       const AudioOptions& options,
       const webrtc::CryptoOptions& crypto_options,
       webrtc::AudioCodecPairId codec_pair_id) override;
-  FakeVoiceMediaChannel* GetChannel(size_t index);
+  FakeVoiceMediaChannel* GetSendChannel(size_t index);
+  FakeVoiceMediaChannel* GetReceiveChannel(size_t index);
   void UnregisterChannel(VoiceMediaChannel* channel);
 
   // TODO(ossu): For proper testing, These should either individually settable
@@ -560,7 +579,8 @@
       std::vector<webrtc::RtpHeaderExtensionCapability> header_extensions);
 
  private:
-  std::vector<FakeVoiceMediaChannel*> channels_;
+  std::vector<FakeVoiceMediaChannel*> send_channels_;
+  std::vector<FakeVoiceMediaChannel*> receive_channels_;
   std::vector<AudioCodec> recv_codecs_;
   std::vector<AudioCodec> send_codecs_;
   bool fail_create_channel_;
@@ -581,7 +601,8 @@
       const webrtc::CryptoOptions& crypto_options,
       webrtc::VideoBitrateAllocatorFactory* video_bitrate_allocator_factory)
       override;
-  FakeVideoMediaChannel* GetChannel(size_t index);
+  FakeVideoMediaChannel* GetSendChannel(size_t index);
+  FakeVideoMediaChannel* GetReceiveChannel(size_t index);
   void UnregisterChannel(VideoMediaChannel* channel);
   std::vector<VideoCodec> send_codecs() const override {
     return send_codecs(true);
@@ -600,7 +621,8 @@
       std::vector<webrtc::RtpHeaderExtensionCapability> header_extensions);
 
  private:
-  std::vector<FakeVideoMediaChannel*> channels_;
+  std::vector<FakeVideoMediaChannel*> send_channels_;
+  std::vector<FakeVideoMediaChannel*> receive_channels_;
   std::vector<VideoCodec> send_codecs_;
   std::vector<VideoCodec> recv_codecs_;
   bool capture_;
@@ -622,8 +644,10 @@
   void SetAudioSendCodecs(const std::vector<AudioCodec>& codecs);
   void SetVideoCodecs(const std::vector<VideoCodec>& codecs);
 
-  FakeVoiceMediaChannel* GetVoiceChannel(size_t index);
-  FakeVideoMediaChannel* GetVideoChannel(size_t index);
+  FakeVoiceMediaChannel* GetVoiceSendChannel(size_t index);
+  FakeVideoMediaChannel* GetVideoSendChannel(size_t index);
+  FakeVoiceMediaChannel* GetVoiceReceiveChannel(size_t index);
+  FakeVideoMediaChannel* GetVideoReceiveChannel(size_t index);
 
   void set_fail_create_channel(bool fail);
 
diff --git a/media/base/media_channel.h b/media/base/media_channel.h
index 1191986..571d661 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -888,6 +888,8 @@
   // DTMF event 0-9, *, #, A-D.
   virtual bool InsertDtmf(uint32_t ssrc, int event, int duration) = 0;
   virtual bool GetStats(VoiceMediaSendInfo* stats) = 0;
+  virtual bool SenderNackEnabled() const = 0;
+  virtual bool SenderNonSenderRttEnabled() const = 0;
 };
 
 class VoiceMediaReceiveChannelInterface : public MediaReceiveChannelInterface {
@@ -912,6 +914,8 @@
   virtual void SetDefaultRawAudioSink(
       std::unique_ptr<webrtc::AudioSinkInterface> sink) = 0;
   virtual bool GetStats(VoiceMediaReceiveInfo* stats, bool reset_legacy) = 0;
+  virtual void SetReceiveNackEnabled(bool enabled) = 0;
+  virtual void SetReceiveNonSenderRttEnabled(bool enabled) = 0;
 };
 
 // TODO(deadbeef): Rename to VideoSenderParameters, since they're intended to
@@ -955,6 +959,11 @@
   virtual void SetVideoCodecSwitchingEnabled(bool enabled) = 0;
   virtual bool GetStats(VideoMediaSendInfo* stats) = 0;
   virtual void FillBitrateInfo(BandwidthEstimationInfo* bwe_info) = 0;
+  // Information queries to support SetReceiverFeedbackParameters
+  virtual webrtc::RtcpMode SendCodecRtcpMode() const = 0;
+  virtual bool SendCodecHasLntf() const = 0;
+  virtual bool SendCodecHasNack() const = 0;
+  virtual absl::optional<int> SendCodecRtxTime() const = 0;
 };
 
 class VideoMediaReceiveChannelInterface : public MediaReceiveChannelInterface {
@@ -984,6 +993,10 @@
   // Clear recordable encoded frame callback for `ssrc`
   virtual void ClearRecordableEncodedFrameCallback(uint32_t ssrc) = 0;
   virtual bool GetStats(VideoMediaReceiveInfo* stats) = 0;
+  virtual void SetReceiverFeedbackParameters(bool lntf_enabled,
+                                             bool nack_enabled,
+                                             webrtc::RtcpMode rtcp_mode,
+                                             absl::optional<int> rtx_time) = 0;
 };
 
 // Info about data received in DataMediaChannel.  For use in
diff --git a/media/base/media_channel_impl.h b/media/base/media_channel_impl.h
index f18670f..1887181 100644
--- a/media/base/media_channel_impl.h
+++ b/media/base/media_channel_impl.h
@@ -77,7 +77,8 @@
   enum class Role {
     kSend,
     kReceive,
-    kBoth  // Temporary value for non-converted test code
+    kBoth  // Temporary value for non-converted test and downstream code
+    // TODO(bugs.webrtc.org/13931): Remove kBoth when usage is removed.
   };
 
   explicit MediaChannel(Role role,
@@ -248,6 +249,10 @@
   virtual bool GetSendStats(VideoMediaSendInfo* info) = 0;
   virtual bool GetReceiveStats(VideoMediaReceiveInfo* info) = 0;
 
+  // TODO(bugs.webrtc.org/13931): Remove when configuration is more sensible
+  virtual void SetSendCodecChangedCallback(
+      absl::AnyInvocable<void()> callback) = 0;
+
  private:
   // Functions not implemented on this interface
   bool GetStats(VideoMediaSendInfo* info) override {
@@ -423,6 +428,10 @@
   bool GetStats(VoiceMediaSendInfo* info) override {
     return impl_->GetSendStats(info);
   }
+  bool SenderNackEnabled() const override { return impl_->SenderNackEnabled(); }
+  bool SenderNonSenderRttEnabled() const override {
+    return impl_->SenderNonSenderRttEnabled();
+  }
   MediaChannel* ImplForTesting() override { return impl_; }
 
  private:
@@ -539,6 +548,12 @@
   bool GetStats(VoiceMediaReceiveInfo* info, bool reset_legacy) override {
     return impl_->GetReceiveStats(info, reset_legacy);
   }
+  void SetReceiveNackEnabled(bool enabled) override {
+    impl_->SetReceiveNackEnabled(enabled);
+  }
+  void SetReceiveNonSenderRttEnabled(bool enabled) override {
+    impl_->SetReceiveNonSenderRttEnabled(enabled);
+  }
   MediaChannel* ImplForTesting() override { return impl_; }
 
  private:
@@ -641,6 +656,15 @@
   void FillBitrateInfo(BandwidthEstimationInfo* bwe_info) override {
     return impl_->FillBitrateInfo(bwe_info);
   }
+  // Information queries to support SetReceiverFeedbackParameters
+  webrtc::RtcpMode SendCodecRtcpMode() const override {
+    return impl()->SendCodecRtcpMode();
+  }
+  bool SendCodecHasLntf() const override { return impl()->SendCodecHasLntf(); }
+  bool SendCodecHasNack() const override { return impl()->SendCodecHasNack(); }
+  absl::optional<int> SendCodecRtxTime() const override {
+    return impl()->SendCodecRtxTime();
+  }
 
   MediaChannel* ImplForTesting() override { return impl_; }
 
@@ -764,6 +788,13 @@
   bool GetStats(VideoMediaReceiveInfo* info) override {
     return impl_->GetReceiveStats(info);
   }
+  void SetReceiverFeedbackParameters(bool lntf_enabled,
+                                     bool nack_enabled,
+                                     webrtc::RtcpMode rtcp_mode,
+                                     absl::optional<int> rtx_time) override {
+    impl()->SetReceiverFeedbackParameters(lntf_enabled, nack_enabled, rtcp_mode,
+                                          rtx_time);
+  }
   MediaChannel* ImplForTesting() override { return impl_; }
 
  private:
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index 14c4979..38e0467 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -982,23 +982,45 @@
   for (auto& kv : send_streams_) {
     kv.second->SetSendParameters(changed_params);
   }
-  if (changed_params.send_codec || changed_params.rtcp_mode) {
-    // Update receive feedback parameters from new codec or RTCP mode.
-    RTC_LOG(LS_INFO)
-        << "SetFeedbackParameters on all the receive streams because the send "
-           "codec or RTCP mode has changed.";
-    for (auto& kv : receive_streams_) {
-      RTC_DCHECK(kv.second != nullptr);
-      kv.second->SetFeedbackParameters(
-          HasLntf(send_codec_->codec), HasNack(send_codec_->codec),
-          send_params_.rtcp.reduced_size ? webrtc::RtcpMode::kReducedSize
-                                         : webrtc::RtcpMode::kCompound,
-          send_codec_->rtx_time);
+  if (role() == MediaChannel::Role::kBoth) {
+    if (changed_params.send_codec || changed_params.rtcp_mode) {
+      // Update receive feedback parameters from new codec or RTCP mode.
+      if (send_codec_) {
+        RTC_LOG(LS_INFO) << "SetFeedbackParameters on all the receive streams "
+                            "because the send "
+                            "codec or RTCP mode has changed.";
+        SetReceiverFeedbackParameters(
+            HasLntf(send_codec_->codec), HasNack(send_codec_->codec),
+            send_params_.rtcp.reduced_size ? webrtc::RtcpMode::kReducedSize
+                                           : webrtc::RtcpMode::kCompound,
+            send_codec_->rtx_time);
+      }
+    }
+  } else {
+    if (changed_params.send_codec || changed_params.rtcp_mode) {
+      send_codec_changed_callback_();
     }
   }
   return true;
 }
 
+void WebRtcVideoChannel::SetReceiverFeedbackParameters(
+    bool lntf_enabled,
+    bool nack_enabled,
+    webrtc::RtcpMode rtcp_mode,
+    absl::optional<int> rtx_time) {
+  RTC_DCHECK_RUN_ON(&thread_checker_);
+
+  RTC_DCHECK(role() == MediaChannel::Role::kReceive ||
+             role() == MediaChannel::Role::kBoth);
+  // Update receive feedback parameters from new codec or RTCP mode.
+  for (auto& kv : receive_streams_) {
+    RTC_DCHECK(kv.second != nullptr);
+    kv.second->SetFeedbackParameters(lntf_enabled, nack_enabled, rtcp_mode,
+                                     rtx_time);
+  }
+}
+
 webrtc::RtpParameters WebRtcVideoChannel::GetRtpSendParameters(
     uint32_t ssrc) const {
   RTC_DCHECK_RUN_ON(&thread_checker_);
diff --git a/media/engine/webrtc_video_engine.h b/media/engine/webrtc_video_engine.h
index 495de88..e6c9c28 100644
--- a/media/engine/webrtc_video_engine.h
+++ b/media/engine/webrtc_video_engine.h
@@ -16,6 +16,7 @@
 #include <memory>
 #include <set>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "absl/types/optional.h"
@@ -184,6 +185,11 @@
   absl::optional<int> GetBaseMinimumPlayoutDelayMs(
       uint32_t ssrc) const override;
 
+  void SetSendCodecChangedCallback(
+      absl::AnyInvocable<void()> callback) override {
+    send_codec_changed_callback_ = std::move(callback);
+  }
+
   // Implemented for VideoMediaChannelTest.
   bool sending() const {
     RTC_DCHECK_RUN_ON(&thread_checker_);
@@ -231,6 +237,39 @@
       rtc::scoped_refptr<webrtc::FrameTransformerInterface> frame_transformer)
       override;
 
+  // Information queries to support SetReceiverFeedbackParameters
+  webrtc::RtcpMode SendCodecRtcpMode() const override {
+    RTC_DCHECK_RUN_ON(&thread_checker_);
+    return send_params_.rtcp.reduced_size ? webrtc::RtcpMode::kReducedSize
+                                          : webrtc::RtcpMode::kCompound;
+  }
+
+  bool SendCodecHasLntf() const override {
+    RTC_DCHECK_RUN_ON(&thread_checker_);
+    if (!send_codec_) {
+      return false;
+    }
+    return HasLntf(send_codec_->codec);
+  }
+  bool SendCodecHasNack() const override {
+    RTC_DCHECK_RUN_ON(&thread_checker_);
+    if (!send_codec_) {
+      return false;
+    }
+    return HasNack(send_codec_->codec);
+  }
+  absl::optional<int> SendCodecRtxTime() const override {
+    RTC_DCHECK_RUN_ON(&thread_checker_);
+    if (!send_codec_) {
+      return absl::nullopt;
+    }
+    return send_codec_->rtx_time;
+  }
+  void SetReceiverFeedbackParameters(bool lntf_enabled,
+                                     bool nack_enabled,
+                                     webrtc::RtcpMode rtcp_mode,
+                                     absl::optional<int> rtx_time) override;
+
  private:
   class WebRtcVideoReceiveStream;
 
@@ -663,6 +702,10 @@
   // of multiple negotiated codecs allows generic encoder fallback on failures.
   // Presence of EncoderSelector allows switching to specific encoders.
   bool allow_codec_switching_ = false;
+
+  // Callback invoked whenever the send codec changes.
+  // TODO(bugs.webrtc.org/13931): Remove again when coupling isn't needed.
+  absl::AnyInvocable<void()> send_codec_changed_callback_;
 };
 
 }  // namespace cricket
diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc
index 7bc6a8b..ab9a3fc 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -417,7 +417,7 @@
     webrtc::AudioCodecPairId codec_pair_id) {
   RTC_DCHECK_RUN_ON(call->worker_thread());
   return new WebRtcVoiceMediaChannel(role, this, config, options,
-                                     crypto_options, call);
+                                     crypto_options, call, codec_pair_id);
 }
 
 void WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) {
@@ -1256,12 +1256,14 @@
     const MediaConfig& config,
     const AudioOptions& options,
     const webrtc::CryptoOptions& crypto_options,
-    webrtc::Call* call)
+    webrtc::Call* call,
+    webrtc::AudioCodecPairId codec_pair_id)
     : VoiceMediaChannel(role, call->network_thread(), config.enable_dscp),
       worker_thread_(call->worker_thread()),
       engine_(engine),
       call_(call),
       audio_config_(config.audio),
+      codec_pair_id_(codec_pair_id),
       crypto_options_(crypto_options) {
   network_thread_checker_.Detach();
   RTC_LOG(LS_VERBOSE) << "WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel";
@@ -1735,30 +1737,42 @@
   }
   call_->GetTransportControllerSend()->SetSdpBitrateParameters(bitrate_config);
 
-  // Check if the NACK status has changed on the
-  // preferred send codec, and in that case reconfigure all receive streams.
-  if (recv_nack_enabled_ != send_codec_spec_->nack_enabled) {
-    RTC_LOG(LS_INFO) << "Changing NACK status on receive streams.";
-    recv_nack_enabled_ = send_codec_spec_->nack_enabled;
-    for (auto& kv : recv_streams_) {
-      kv.second->SetUseNack(recv_nack_enabled_);
-    }
-  }
+  // In legacy kBoth mode, the MediaChannel sets the NACK status.
+  // In other modes, this is done externally.
 
-  // Check if the receive-side RTT status has changed on the preferred send
-  // codec, in that case reconfigure all receive streams.
-  if (enable_non_sender_rtt_ != send_codec_spec_->enable_non_sender_rtt) {
-    RTC_LOG(LS_INFO) << "Changing receive-side RTT status on receive streams.";
-    enable_non_sender_rtt_ = send_codec_spec_->enable_non_sender_rtt;
-    for (auto& kv : recv_streams_) {
-      kv.second->SetNonSenderRttMeasurement(enable_non_sender_rtt_);
-    }
+  if (role() == MediaChannel::Role::kBoth) {
+    SetReceiveNackEnabled(send_codec_spec_->nack_enabled);
+    SetReceiveNonSenderRttEnabled(send_codec_spec_->enable_non_sender_rtt);
   }
 
   send_codecs_ = codecs;
   return true;
 }
 
+void WebRtcVoiceMediaChannel::SetReceiveNackEnabled(bool enabled) {
+  // Check if the NACK status has changed on the
+  // preferred send codec, and in that case reconfigure all receive streams.
+  if (recv_nack_enabled_ != enabled) {
+    RTC_LOG(LS_INFO) << "Changing NACK status on receive streams.";
+    recv_nack_enabled_ = enabled;
+    for (auto& kv : recv_streams_) {
+      kv.second->SetUseNack(recv_nack_enabled_);
+    }
+  }
+}
+
+void WebRtcVoiceMediaChannel::SetReceiveNonSenderRttEnabled(bool enabled) {
+  // Check if the receive-side RTT status has changed on the preferred send
+  // codec, in that case reconfigure all receive streams.
+  if (enable_non_sender_rtt_ != enabled) {
+    RTC_LOG(LS_INFO) << "Changing receive-side RTT status on receive streams.";
+    enable_non_sender_rtt_ = enabled;
+    for (auto& kv : recv_streams_) {
+      kv.second->SetNonSenderRttMeasurement(enable_non_sender_rtt_);
+    }
+  }
+}
+
 void WebRtcVoiceMediaChannel::SetPlayout(bool playout) {
   TRACE_EVENT0("webrtc", "WebRtcVoiceMediaChannel::SetPlayout");
   RTC_DCHECK_RUN_ON(worker_thread_);
diff --git a/media/engine/webrtc_voice_engine.h b/media/engine/webrtc_voice_engine.h
index 5f9eb5d..6d25397 100644
--- a/media/engine/webrtc_voice_engine.h
+++ b/media/engine/webrtc_voice_engine.h
@@ -16,6 +16,7 @@
 #include <string>
 #include <vector>
 
+#include "api/audio_codecs/audio_codec_pair_id.h"
 #include "api/audio_codecs/audio_encoder_factory.h"
 #include "api/field_trials_view.h"
 #include "api/scoped_refptr.h"
@@ -74,8 +75,7 @@
       const MediaConfig& config,
       const AudioOptions& options,
       const webrtc::CryptoOptions& crypto_options,
-      webrtc::AudioCodecPairId codec_pair_id =
-          webrtc::AudioCodecPairId::Create()) override;
+      webrtc::AudioCodecPairId codec_pair_id) override;
 
   const std::vector<AudioCodec>& send_codecs() const override;
   const std::vector<AudioCodec>& recv_codecs() const override;
@@ -147,7 +147,8 @@
                           const MediaConfig& config,
                           const AudioOptions& options,
                           const webrtc::CryptoOptions& crypto_options,
-                          webrtc::Call* call);
+                          webrtc::Call* call,
+                          webrtc::AudioCodecPairId codec_pair_id);
 
   WebRtcVoiceMediaChannel() = delete;
   WebRtcVoiceMediaChannel(const WebRtcVoiceMediaChannel&) = delete;
@@ -248,6 +249,22 @@
 
   bool SendRtcp(const uint8_t* data, size_t len) override;
 
+  bool SenderNackEnabled() const override {
+    if (!send_codec_spec_) {
+      return false;
+    }
+    return send_codec_spec_->nack_enabled;
+  }
+  bool SenderNonSenderRttEnabled() const override {
+    if (!send_codec_spec_) {
+      return false;
+    }
+    return send_codec_spec_->enable_non_sender_rtt;
+  }
+
+  void SetReceiveNackEnabled(bool enabled) override;
+  void SetReceiveNonSenderRttEnabled(bool enabled) override;
+
  private:
   bool SetOptions(const AudioOptions& options);
   bool SetRecvCodecs(const std::vector<AudioCodec>& codecs);
@@ -327,8 +344,7 @@
       send_codec_spec_;
 
   // TODO(kwiberg): Per-SSRC codec pair IDs?
-  const webrtc::AudioCodecPairId codec_pair_id_ =
-      webrtc::AudioCodecPairId::Create();
+  const webrtc::AudioCodecPairId codec_pair_id_;
 
   // Per peer connection crypto options that last for the lifetime of the peer
   // connection.
diff --git a/media/engine/webrtc_voice_engine_unittest.cc b/media/engine/webrtc_voice_engine_unittest.cc
index 70cc461..1393d0b 100644
--- a/media/engine/webrtc_voice_engine_unittest.cc
+++ b/media/engine/webrtc_voice_engine_unittest.cc
@@ -232,7 +232,8 @@
   bool SetupChannel() {
     channel_ = engine_->CreateMediaChannel(
         cricket::MediaChannel::Role::kBoth, &call_, cricket::MediaConfig(),
-        cricket::AudioOptions(), webrtc::CryptoOptions());
+        cricket::AudioOptions(), webrtc::CryptoOptions(),
+        webrtc::AudioCodecPairId::Create());
     send_channel_ = std::make_unique<cricket::VoiceMediaSendChannel>(channel_);
     receive_channel_ =
         std::make_unique<cricket::VoiceMediaReceiveChannel>(channel_);
@@ -3031,7 +3032,8 @@
   std::unique_ptr<cricket::VoiceMediaChannel> channel(
       engine_->CreateMediaChannel(
           cricket::MediaChannel::Role::kBoth, &call_, cricket::MediaConfig(),
-          cricket::AudioOptions(), webrtc::CryptoOptions()));
+          cricket::AudioOptions(), webrtc::CryptoOptions(),
+          webrtc::AudioCodecPairId::Create()));
 
   channel->SetSend(true);
 }
@@ -3047,7 +3049,8 @@
   std::unique_ptr<cricket::VoiceMediaChannel> channel(
       engine_->CreateMediaChannel(cricket::MediaChannel::Role::kBoth, &call_,
                                   cricket::MediaConfig(), options,
-                                  webrtc::CryptoOptions()));
+                                  webrtc::CryptoOptions(),
+                                  webrtc::AudioCodecPairId::Create()));
 
   channel->SetSend(true);
 }
@@ -3072,16 +3075,16 @@
 
   std::unique_ptr<cricket::WebRtcVoiceMediaChannel> channel1(
       static_cast<cricket::WebRtcVoiceMediaChannel*>(
-          engine_->CreateMediaChannel(cricket::MediaChannel::Role::kBoth,
-                                      &call_, cricket::MediaConfig(),
-                                      cricket::AudioOptions(),
-                                      webrtc::CryptoOptions())));
+          engine_->CreateMediaChannel(
+              cricket::MediaChannel::Role::kBoth, &call_,
+              cricket::MediaConfig(), cricket::AudioOptions(),
+              webrtc::CryptoOptions(), webrtc::AudioCodecPairId::Create())));
   std::unique_ptr<cricket::WebRtcVoiceMediaChannel> channel2(
       static_cast<cricket::WebRtcVoiceMediaChannel*>(
-          engine_->CreateMediaChannel(cricket::MediaChannel::Role::kBoth,
-                                      &call_, cricket::MediaConfig(),
-                                      cricket::AudioOptions(),
-                                      webrtc::CryptoOptions())));
+          engine_->CreateMediaChannel(
+              cricket::MediaChannel::Role::kBoth, &call_,
+              cricket::MediaConfig(), cricket::AudioOptions(),
+              webrtc::CryptoOptions(), webrtc::AudioCodecPairId::Create())));
 
   // Have to add a stream to make SetSend work.
   cricket::StreamParams stream1;
@@ -3193,7 +3196,8 @@
   channel.reset(static_cast<cricket::WebRtcVoiceMediaChannel*>(
       engine_->CreateMediaChannel(cricket::MediaChannel::Role::kBoth, &call_,
                                   config, cricket::AudioOptions(),
-                                  webrtc::CryptoOptions())));
+                                  webrtc::CryptoOptions(),
+                                  webrtc::AudioCodecPairId::Create())));
   channel->SetInterface(&network_interface);
   // Default value when DSCP is disabled should be DSCP_DEFAULT.
   EXPECT_EQ(rtc::DSCP_DEFAULT, network_interface.dscp());
@@ -3203,7 +3207,8 @@
   channel.reset(static_cast<cricket::WebRtcVoiceMediaChannel*>(
       engine_->CreateMediaChannel(cricket::MediaChannel::Role::kBoth, &call_,
                                   config, cricket::AudioOptions(),
-                                  webrtc::CryptoOptions())));
+                                  webrtc::CryptoOptions(),
+                                  webrtc::AudioCodecPairId::Create())));
   channel->SetInterface(&network_interface);
   EXPECT_EQ(rtc::DSCP_DEFAULT, network_interface.dscp());
 
@@ -3233,7 +3238,8 @@
   channel.reset(static_cast<cricket::WebRtcVoiceMediaChannel*>(
       engine_->CreateMediaChannel(cricket::MediaChannel::Role::kBoth, &call_,
                                   config, cricket::AudioOptions(),
-                                  webrtc::CryptoOptions())));
+                                  webrtc::CryptoOptions(),
+                                  webrtc::AudioCodecPairId::Create())));
   channel->SetInterface(&network_interface);
   // Default value when DSCP is disabled should be DSCP_DEFAULT.
   EXPECT_EQ(rtc::DSCP_DEFAULT, network_interface.dscp());
@@ -3644,7 +3650,8 @@
     auto call = absl::WrapUnique(webrtc::Call::Create(call_config));
     cricket::VoiceMediaChannel* channel = engine.CreateMediaChannel(
         cricket::MediaChannel::Role::kBoth, call.get(), cricket::MediaConfig(),
-        cricket::AudioOptions(), webrtc::CryptoOptions());
+        cricket::AudioOptions(), webrtc::CryptoOptions(),
+        webrtc::AudioCodecPairId::Create());
     EXPECT_TRUE(channel != nullptr);
     delete channel;
   }
@@ -3676,7 +3683,7 @@
       cricket::VoiceMediaChannel* channel = engine.CreateMediaChannel(
           cricket::MediaChannel::Role::kBoth, call.get(),
           cricket::MediaConfig(), cricket::AudioOptions(),
-          webrtc::CryptoOptions());
+          webrtc::CryptoOptions(), webrtc::AudioCodecPairId::Create());
       EXPECT_TRUE(channel != nullptr);
       delete channel;
     }
@@ -3766,7 +3773,7 @@
       cricket::VoiceMediaChannel* channel = engine.CreateMediaChannel(
           cricket::MediaChannel::Role::kBoth, call.get(),
           cricket::MediaConfig(), cricket::AudioOptions(),
-          webrtc::CryptoOptions());
+          webrtc::CryptoOptions(), webrtc::AudioCodecPairId::Create());
       if (!channel)
         break;
       channels[num_channels++] = channel;
@@ -3812,7 +3819,8 @@
     auto call = absl::WrapUnique(webrtc::Call::Create(call_config));
     cricket::WebRtcVoiceMediaChannel channel(
         cricket::MediaChannel::Role::kReceive, &engine, cricket::MediaConfig(),
-        cricket::AudioOptions(), webrtc::CryptoOptions(), call.get());
+        cricket::AudioOptions(), webrtc::CryptoOptions(), call.get(),
+        webrtc::AudioCodecPairId::Create());
     cricket::AudioRecvParameters parameters;
     parameters.codecs = engine.recv_codecs();
     EXPECT_TRUE(channel.SetRecvParameters(parameters));
@@ -3846,7 +3854,8 @@
   auto call = absl::WrapUnique(webrtc::Call::Create(call_config));
   cricket::WebRtcVoiceMediaChannel channel(
       cricket::MediaChannel::Role::kSend, &engine, cricket::MediaConfig(),
-      cricket::AudioOptions(), webrtc::CryptoOptions(), call.get());
+      cricket::AudioOptions(), webrtc::CryptoOptions(), call.get(),
+      webrtc::AudioCodecPairId::Create());
   {
     cricket::AudioSendParameters params;
     params.codecs.push_back(cricket::AudioCodec(1, "opus", 48000, 32000, 2));
diff --git a/pc/audio_rtp_receiver_unittest.cc b/pc/audio_rtp_receiver_unittest.cc
index eb77212..5a3383f 100644
--- a/pc/audio_rtp_receiver_unittest.cc
+++ b/pc/audio_rtp_receiver_unittest.cc
@@ -39,7 +39,8 @@
                                                     std::string(),
                                                     std::vector<std::string>(),
                                                     false)),
-        media_channel_(rtc::Thread::Current()) {
+        media_channel_(cricket::MediaChannel::Role::kReceive,
+                       rtc::Thread::Current()) {
     EXPECT_CALL(media_channel_, SetRawAudioSink(kSsrc, _));
     EXPECT_CALL(media_channel_, SetBaseMinimumPlayoutDelayMs(kSsrc, _));
   }
@@ -101,7 +102,8 @@
 TEST(AudioRtpReceiver, OnChangedNotificationsAfterConstruction) {
   webrtc::test::RunLoop loop;
   auto* thread = rtc::Thread::Current();  // Points to loop's thread.
-  cricket::MockVoiceMediaChannel media_channel(thread);
+  cricket::MockVoiceMediaChannel media_channel(
+      cricket::MediaChannel::Role::kReceive, thread);
   auto receiver = rtc::make_ref_counted<AudioRtpReceiver>(
       thread, std::string(), std::vector<std::string>(), true, &media_channel);
 
diff --git a/pc/channel.cc b/pc/channel.cc
index 6d261ec..21b8223 100644
--- a/pc/channel.cc
+++ b/pc/channel.cc
@@ -110,15 +110,19 @@
   send_params->extmap_allow_mixed = desc->extmap_allow_mixed();
 }
 
-BaseChannel::BaseChannel(rtc::Thread* worker_thread,
-                         rtc::Thread* network_thread,
-                         rtc::Thread* signaling_thread,
-                         std::unique_ptr<MediaChannel> media_channel,
-                         absl::string_view mid,
-                         bool srtp_required,
-                         webrtc::CryptoOptions crypto_options,
-                         UniqueRandomIdGenerator* ssrc_generator)
-    : worker_thread_(worker_thread),
+BaseChannel::BaseChannel(
+    rtc::Thread* worker_thread,
+    rtc::Thread* network_thread,
+    rtc::Thread* signaling_thread,
+    std::unique_ptr<MediaChannel> send_media_channel_impl,
+    std::unique_ptr<MediaChannel> receive_media_channel_impl,
+    absl::string_view mid,
+    bool srtp_required,
+    webrtc::CryptoOptions crypto_options,
+    UniqueRandomIdGenerator* ssrc_generator)
+    : media_send_channel_impl_(std::move(send_media_channel_impl)),
+      media_receive_channel_impl_(std::move(receive_media_channel_impl)),
+      worker_thread_(worker_thread),
       network_thread_(network_thread),
       signaling_thread_(signaling_thread),
       alive_(PendingTaskSafetyFlag::Create()),
@@ -127,11 +131,37 @@
           crypto_options.srtp.enable_encrypted_rtp_header_extensions
               ? webrtc::RtpExtension::kPreferEncryptedExtension
               : webrtc::RtpExtension::kDiscardEncryptedExtension),
-      media_channel_(std::move(media_channel)),
       demuxer_criteria_(mid),
       ssrc_generator_(ssrc_generator) {
   RTC_DCHECK_RUN_ON(worker_thread_);
-  RTC_DCHECK(media_channel_);
+  RTC_DCHECK(media_send_channel_impl_);
+  RTC_DCHECK(media_receive_channel_impl_);
+  RTC_DCHECK(ssrc_generator_);
+  RTC_DLOG(LS_INFO) << "Created channel: " << ToString();
+}
+
+BaseChannel::BaseChannel(rtc::Thread* worker_thread,
+                         rtc::Thread* network_thread,
+                         rtc::Thread* signaling_thread,
+                         std::unique_ptr<MediaChannel> media_channel_impl,
+                         absl::string_view mid,
+                         bool srtp_required,
+                         webrtc::CryptoOptions crypto_options,
+                         UniqueRandomIdGenerator* ssrc_generator)
+    : media_channel_impl_(std::move(media_channel_impl)),
+      worker_thread_(worker_thread),
+      network_thread_(network_thread),
+      signaling_thread_(signaling_thread),
+      alive_(PendingTaskSafetyFlag::Create()),
+      srtp_required_(srtp_required),
+      extensions_filter_(
+          crypto_options.srtp.enable_encrypted_rtp_header_extensions
+              ? webrtc::RtpExtension::kPreferEncryptedExtension
+              : webrtc::RtpExtension::kDiscardEncryptedExtension),
+      demuxer_criteria_(mid),
+      ssrc_generator_(ssrc_generator) {
+  RTC_DCHECK_RUN_ON(worker_thread_);
+  RTC_DCHECK(media_channel_impl_);
   RTC_DCHECK(ssrc_generator_);
   RTC_DLOG(LS_INFO) << "Created channel: " << ToString();
 }
@@ -148,8 +178,15 @@
 }
 
 std::string BaseChannel::ToString() const {
-  return StringFormat("{mid: %s, media_type: %s}", mid().c_str(),
-                      MediaTypeToString(media_channel_->media_type()).c_str());
+  if (media_send_channel_impl_) {
+    return StringFormat(
+        "{mid: %s, media_type: %s}", mid().c_str(),
+        MediaTypeToString(media_send_channel_impl_->media_type()).c_str());
+  } else {
+    return StringFormat(
+        "{mid: %s, media_type: %s}", mid().c_str(),
+        MediaTypeToString(media_channel_impl_->media_type()).c_str());
+  }
 }
 
 bool BaseChannel::ConnectToRtpTransport_n() {
@@ -181,7 +218,8 @@
   rtp_transport_->SignalWritableState.disconnect(this);
   rtp_transport_->SignalSentPacket.disconnect(this);
   rtp_transport_ = nullptr;
-  media_channel_->SetInterface(nullptr);
+  media_send_channel()->SetInterface(nullptr);
+  media_receive_channel()->SetInterface(nullptr);
 }
 
 bool BaseChannel::SetRtpTransport(webrtc::RtpTransportInternal* rtp_transport) {
@@ -206,10 +244,12 @@
       return false;
     }
 
-    RTC_DCHECK(!media_channel_->HasNetworkInterface());
-    media_channel_->SetInterface(this);
+    RTC_DCHECK(!media_send_channel()->HasNetworkInterface());
+    RTC_DCHECK(!media_receive_channel()->HasNetworkInterface());
+    media_send_channel()->SetInterface(this);
+    media_receive_channel()->SetInterface(this);
 
-    media_channel_->OnReadyToSend(rtp_transport_->IsReadyToSend());
+    media_send_channel()->OnReadyToSend(rtp_transport_->IsReadyToSend());
     UpdateWritableState_n();
 
     // Set the cached socket options.
@@ -338,7 +378,8 @@
   // use the same transport name and MediaChannel::OnNetworkRouteChanged cannot
   // work correctly. Intentionally leave it broken to simplify the code and
   // encourage the users to stop using non-muxing RTCP.
-  media_channel_->OnNetworkRouteChanged(transport_name(), new_route);
+  media_send_channel()->OnNetworkRouteChanged(transport_name(), new_route);
+  media_receive_channel()->OnNetworkRouteChanged(transport_name(), new_route);
 }
 
 void BaseChannel::SetFirstPacketReceivedCallback(
@@ -358,7 +399,7 @@
 void BaseChannel::OnTransportReadyToSend(bool ready) {
   RTC_DCHECK_RUN_ON(network_thread());
   RTC_DCHECK(network_initialized());
-  media_channel_->OnReadyToSend(ready);
+  media_send_channel()->OnReadyToSend(ready);
 }
 
 bool BaseChannel::SendPacket(bool rtcp,
@@ -433,7 +474,7 @@
                         << ToString();
     return;
   }
-  media_channel_->OnPacketReceived(parsed_packet);
+  media_receive_channel()->OnPacketReceived(parsed_packet);
 }
 
 bool BaseChannel::MaybeUpdateDemuxerAndRtpExtensions_w(
@@ -483,7 +524,7 @@
 }
 
 bool BaseChannel::RegisterRtpDemuxerSink_w() {
-  media_channel_->OnDemuxerCriteriaUpdatePending();
+  media_receive_channel()->OnDemuxerCriteriaUpdatePending();
   // Copy demuxer criteria, since they're a worker-thread variable
   // and we want to pass them to the network thread
   bool ret = network_thread_->BlockingCall(
@@ -501,7 +542,7 @@
         return rtp_transport_->RegisterRtpDemuxerSink(demuxer_criteria, this);
       });
 
-  media_channel_->OnDemuxerCriteriaUpdateComplete();
+  media_receive_channel()->OnDemuxerCriteriaUpdateComplete();
 
   return ret;
 }
@@ -671,6 +712,18 @@
     if (media_send_channel()->AddSendStream(new_stream)) {
       RTC_LOG(LS_INFO) << "Add send stream ssrc: " << new_stream.ssrcs[0]
                        << " into " << ToString();
+      // Must also tell the corresponding receive stream to listen for
+      // RRs coming in on the new stream's SSRC
+      if (media_send_channel_impl_) {
+        if (all_streams.size() == 1) {
+          if (!media_receive_channel()->SetLocalSsrc(new_stream)) {
+            error_desc = StringFormat(
+                "Failed to set local ssrc: %u into m-section with mid='%s'",
+                new_stream.first_ssrc(), mid().c_str());
+            ret = false;
+          }
+        }
+      }
     } else {
       error_desc = StringFormat(
           "Failed to add send stream ssrc: %u into m-section with mid='%s'",
@@ -807,24 +860,47 @@
   media_send_channel()->OnPacketSent(sent_packet);
 }
 
-VoiceChannel::VoiceChannel(rtc::Thread* worker_thread,
-                           rtc::Thread* network_thread,
-                           rtc::Thread* signaling_thread,
-                           std::unique_ptr<VoiceMediaChannel> media_channel,
-                           absl::string_view mid,
-                           bool srtp_required,
-                           webrtc::CryptoOptions crypto_options,
-                           UniqueRandomIdGenerator* ssrc_generator)
+VoiceChannel::VoiceChannel(
+    rtc::Thread* worker_thread,
+    rtc::Thread* network_thread,
+    rtc::Thread* signaling_thread,
+    std::unique_ptr<VoiceMediaChannel> media_send_channel_impl,
+    std::unique_ptr<VoiceMediaChannel> media_receive_channel_impl,
+    absl::string_view mid,
+    bool srtp_required,
+    webrtc::CryptoOptions crypto_options,
+    UniqueRandomIdGenerator* ssrc_generator)
     : BaseChannel(worker_thread,
                   network_thread,
                   signaling_thread,
-                  std::move(media_channel),
+                  std::move(media_send_channel_impl),
+                  std::move(media_receive_channel_impl),
                   mid,
                   srtp_required,
                   crypto_options,
                   ssrc_generator),
-      send_channel_(this->media_channel()->AsVoiceChannel()),
-      receive_channel_(this->media_channel()->AsVoiceChannel()) {}
+      send_channel_(media_send_channel_impl_->AsVoiceChannel()),
+      receive_channel_(media_receive_channel_impl_->AsVoiceChannel()) {}
+
+VoiceChannel::VoiceChannel(
+    rtc::Thread* worker_thread,
+    rtc::Thread* network_thread,
+    rtc::Thread* signaling_thread,
+    std::unique_ptr<VoiceMediaChannel> media_channel_impl,
+    absl::string_view mid,
+    bool srtp_required,
+    webrtc::CryptoOptions crypto_options,
+    UniqueRandomIdGenerator* ssrc_generator)
+    : BaseChannel(worker_thread,
+                  network_thread,
+                  signaling_thread,
+                  std::move(media_channel_impl),
+                  mid,
+                  srtp_required,
+                  crypto_options,
+                  ssrc_generator),
+      send_channel_(media_channel_impl_->AsVoiceChannel()),
+      receive_channel_(media_channel_impl_->AsVoiceChannel()) {}
 
 VoiceChannel::~VoiceChannel() {
   TRACE_EVENT0("webrtc", "VoiceChannel::~VoiceChannel");
@@ -928,29 +1004,69 @@
         mid().c_str());
     return false;
   }
+  // Update Receive channel based on Send channel's codec information.
+  // TODO(bugs.webrtc.org/14911): This is silly. Stop doing it.
+  media_receive_channel()->SetReceiveNackEnabled(
+      media_send_channel()->SenderNackEnabled());
+  media_receive_channel()->SetReceiveNonSenderRttEnabled(
+      media_send_channel()->SenderNonSenderRttEnabled());
   last_send_params_ = send_params;
 
   return UpdateRemoteStreams_w(content, type, error_desc);
 }
 
-VideoChannel::VideoChannel(rtc::Thread* worker_thread,
-                           rtc::Thread* network_thread,
-                           rtc::Thread* signaling_thread,
-                           std::unique_ptr<VideoMediaChannel> media_channel,
-                           absl::string_view mid,
-                           bool srtp_required,
-                           webrtc::CryptoOptions crypto_options,
-                           UniqueRandomIdGenerator* ssrc_generator)
+VideoChannel::VideoChannel(
+    rtc::Thread* worker_thread,
+    rtc::Thread* network_thread,
+    rtc::Thread* signaling_thread,
+    std::unique_ptr<VideoMediaChannel> media_send_channel_impl,
+    std::unique_ptr<VideoMediaChannel> media_receive_channel_impl,
+    absl::string_view mid,
+    bool srtp_required,
+    webrtc::CryptoOptions crypto_options,
+    UniqueRandomIdGenerator* ssrc_generator)
     : BaseChannel(worker_thread,
                   network_thread,
                   signaling_thread,
-                  std::move(media_channel),
+                  std::move(media_send_channel_impl),
+                  std::move(media_receive_channel_impl),
                   mid,
                   srtp_required,
                   crypto_options,
                   ssrc_generator),
-      send_channel_(this->media_channel()->AsVideoChannel()),
-      receive_channel_(this->media_channel()->AsVideoChannel()) {}
+      send_channel_(media_send_channel_impl_->AsVideoChannel()),
+      receive_channel_(media_receive_channel_impl_->AsVideoChannel()) {
+  // TODO(bugs.webrtc.org/13931): Remove when values are set
+  // in a more sensible fashion
+  media_send_channel_impl_->AsVideoChannel()->SetSendCodecChangedCallback(
+      [this]() {
+        // Adjust receive streams based on send codec.
+        media_receive_channel()->SetReceiverFeedbackParameters(
+            media_send_channel()->SendCodecHasLntf(),
+            media_send_channel()->SendCodecHasNack(),
+            media_send_channel()->SendCodecRtcpMode(),
+            media_send_channel()->SendCodecRtxTime());
+      });
+}
+VideoChannel::VideoChannel(
+    rtc::Thread* worker_thread,
+    rtc::Thread* network_thread,
+    rtc::Thread* signaling_thread,
+    std::unique_ptr<VideoMediaChannel> media_channel_impl,
+    absl::string_view mid,
+    bool srtp_required,
+    webrtc::CryptoOptions crypto_options,
+    UniqueRandomIdGenerator* ssrc_generator)
+    : BaseChannel(worker_thread,
+                  network_thread,
+                  signaling_thread,
+                  std::move(media_channel_impl),
+                  mid,
+                  srtp_required,
+                  crypto_options,
+                  ssrc_generator),
+      send_channel_(media_channel_impl_->AsVideoChannel()),
+      receive_channel_(media_channel_impl_->AsVideoChannel()) {}
 
 VideoChannel::~VideoChannel() {
   TRACE_EVENT0("webrtc", "VideoChannel::~VideoChannel");
@@ -1100,6 +1216,12 @@
         mid().c_str());
     return false;
   }
+  // adjust receive streams based on send codec
+  media_receive_channel()->SetReceiverFeedbackParameters(
+      media_send_channel()->SendCodecHasLntf(),
+      media_send_channel()->SendCodecHasNack(),
+      media_send_channel()->SendCodecRtcpMode(),
+      media_send_channel()->SendCodecRtxTime());
   last_send_params_ = send_params;
 
   if (needs_recv_params_update) {
diff --git a/pc/channel.h b/pc/channel.h
index b3020e3..a84a842 100644
--- a/pc/channel.h
+++ b/pc/channel.h
@@ -82,10 +82,23 @@
   // responsibility of the user to ensure it outlives this object.
   // TODO(zhihuang:) Create a BaseChannel::Config struct for the parameter lists
   // which will make it easier to change the constructor.
+
+  // Constructor for use when the MediaChannels are split
   BaseChannel(rtc::Thread* worker_thread,
               rtc::Thread* network_thread,
               rtc::Thread* signaling_thread,
-              std::unique_ptr<MediaChannel> media_channel,
+              std::unique_ptr<MediaChannel> media_send_channel_impl,
+              std::unique_ptr<MediaChannel> media_receive_channel_impl,
+              absl::string_view mid,
+              bool srtp_required,
+              webrtc::CryptoOptions crypto_options,
+              rtc::UniqueRandomIdGenerator* ssrc_generator);
+  // Constructor for use when the MediaChannel is not split
+  // TODO(bugs.webrtc.org/13931): Delete when split channel project is complete.
+  BaseChannel(rtc::Thread* worker_thread,
+              rtc::Thread* network_thread,
+              rtc::Thread* signaling_thread,
+              std::unique_ptr<MediaChannel> media_channel_impl,
               absl::string_view mid,
               bool srtp_required,
               webrtc::CryptoOptions crypto_options,
@@ -158,8 +171,6 @@
   // RtpPacketSinkInterface overrides.
   void OnRtpPacket(const webrtc::RtpPacketReceived& packet) override;
 
-  MediaChannel* media_channel() override { return media_channel_.get(); }
-
   VideoMediaSendChannelInterface* video_media_send_channel() override {
     RTC_CHECK(false) << "Attempt to fetch video channel from non-video";
     return nullptr;
@@ -203,7 +214,7 @@
   }
 
   bool network_initialized() RTC_RUN_ON(network_thread()) {
-    return media_channel_->HasNetworkInterface();
+    return media_send_channel()->HasNetworkInterface();
   }
 
   bool enabled() const RTC_RUN_ON(worker_thread()) { return enabled_; }
@@ -304,6 +315,17 @@
   // Return description of media channel to facilitate logging
   std::string ToString() const;
 
+  // MediaChannel related members that should be accessed from the worker
+  // thread. These are used in initializing the subclasses and deleting
+  // the channels when exiting; they have no accessors.
+  // Either the media_channel_impl_ is set, or the media_send_channel_impl_
+  // and the media_receive_channel_impl_ is set.
+  // TODO(bugs.webrtc.org/13931): Delete when split channel project is complete.
+  const std::unique_ptr<MediaChannel> media_channel_impl_;
+
+  const std::unique_ptr<MediaChannel> media_send_channel_impl_;
+  const std::unique_ptr<MediaChannel> media_receive_channel_impl_;
+
  private:
   bool ConnectToRtpTransport_n() RTC_RUN_ON(network_thread());
   void DisconnectFromRtpTransport_n() RTC_RUN_ON(network_thread());
@@ -333,9 +355,6 @@
   // based on the supplied CryptoOptions.
   const webrtc::RtpExtension::Filter extensions_filter_;
 
-  // MediaChannel related members that should be accessed from the worker
-  // thread.
-  const std::unique_ptr<MediaChannel> media_channel_;
   // Currently the `enabled_` flag is accessed from the signaling thread as
   // well, but it can be changed only when signaling thread does a synchronous
   // call to the worker thread, so it should be safe.
@@ -370,11 +389,23 @@
   VoiceChannel(rtc::Thread* worker_thread,
                rtc::Thread* network_thread,
                rtc::Thread* signaling_thread,
-               std::unique_ptr<VoiceMediaChannel> channel,
+               std::unique_ptr<VoiceMediaChannel> send_channel_impl,
+               std::unique_ptr<VoiceMediaChannel> receive_channel_impl,
                absl::string_view mid,
                bool srtp_required,
                webrtc::CryptoOptions crypto_options,
                rtc::UniqueRandomIdGenerator* ssrc_generator);
+  // Constructor for use when the MediaChannel is not split
+  // TODO(bugs.webrtc.org/13931): Delete when split channel project is complete.
+  VoiceChannel(rtc::Thread* worker_thread,
+               rtc::Thread* network_thread,
+               rtc::Thread* signaling_thread,
+               std::unique_ptr<VoiceMediaChannel> media_channel_impl,
+               absl::string_view mid,
+               bool srtp_required,
+               webrtc::CryptoOptions crypto_options,
+               rtc::UniqueRandomIdGenerator* ssrc_generator);
+
   ~VoiceChannel();
 
   VideoChannel* AsVideoChannel() override {
@@ -431,7 +462,18 @@
   VideoChannel(rtc::Thread* worker_thread,
                rtc::Thread* network_thread,
                rtc::Thread* signaling_thread,
-               std::unique_ptr<VideoMediaChannel> media_channel,
+               std::unique_ptr<VideoMediaChannel> media_send_channel_impl,
+               std::unique_ptr<VideoMediaChannel> media_receive_channel_impl,
+               absl::string_view mid,
+               bool srtp_required,
+               webrtc::CryptoOptions crypto_options,
+               rtc::UniqueRandomIdGenerator* ssrc_generator);
+  // Constructor for use when the MediaChannel is not split
+  // TODO(bugs.webrtc.org/13931): Delete when split channel project is complete.
+  VideoChannel(rtc::Thread* worker_thread,
+               rtc::Thread* network_thread,
+               rtc::Thread* signaling_thread,
+               std::unique_ptr<VideoMediaChannel> media_channel_impl,
                absl::string_view mid,
                bool srtp_required,
                webrtc::CryptoOptions crypto_options,
diff --git a/pc/channel_interface.h b/pc/channel_interface.h
index 783755e..7495ad8 100644
--- a/pc/channel_interface.h
+++ b/pc/channel_interface.h
@@ -53,8 +53,6 @@
   virtual VideoChannel* AsVideoChannel() = 0;
   virtual VoiceChannel* AsVoiceChannel() = 0;
 
-  // Temporary fix while MediaChannel is being reconstructed
-  virtual MediaChannel* media_channel() = 0;
   virtual MediaSendChannelInterface* media_send_channel() = 0;
   // Typecasts of media_channel(). Will cause an exception if the
   // channel is of the wrong type.
diff --git a/pc/channel_unittest.cc b/pc/channel_unittest.cc
index 1635ea5..c6ad9e8 100644
--- a/pc/channel_unittest.cc
+++ b/pc/channel_unittest.cc
@@ -151,15 +151,23 @@
 
   void CreateChannels(int flags1, int flags2) {
     CreateChannels(std::make_unique<typename T::MediaChannel>(
-                       cricket::MediaChannel::Role::kBoth, nullptr,
+                       cricket::MediaChannel::Role::kSend, nullptr,
                        typename T::Options(), network_thread_),
                    std::make_unique<typename T::MediaChannel>(
-                       cricket::MediaChannel::Role::kBoth, nullptr,
+                       cricket::MediaChannel::Role::kReceive, nullptr,
+                       typename T::Options(), network_thread_),
+                   std::make_unique<typename T::MediaChannel>(
+                       cricket::MediaChannel::Role::kSend, nullptr,
+                       typename T::Options(), network_thread_),
+                   std::make_unique<typename T::MediaChannel>(
+                       cricket::MediaChannel::Role::kReceive, nullptr,
                        typename T::Options(), network_thread_),
                    flags1, flags2);
   }
-  void CreateChannels(std::unique_ptr<typename T::MediaChannel> ch1,
-                      std::unique_ptr<typename T::MediaChannel> ch2,
+  void CreateChannels(std::unique_ptr<typename T::MediaChannel> ch1s,
+                      std::unique_ptr<typename T::MediaChannel> ch1r,
+                      std::unique_ptr<typename T::MediaChannel> ch2s,
+                      std::unique_ptr<typename T::MediaChannel> ch2r,
                       int flags1,
                       int flags2) {
     RTC_DCHECK(!channel1_);
@@ -237,10 +245,10 @@
         fake_rtp_dtls_transport2_.get(), fake_rtcp_dtls_transport2_.get(),
         flags2);
 
-    channel1_ = CreateChannel(worker_thread, network_thread_, std::move(ch1),
-                              rtp_transport1_.get(), flags1);
-    channel2_ = CreateChannel(worker_thread, network_thread_, std::move(ch2),
-                              rtp_transport2_.get(), flags2);
+    channel1_ = CreateChannel(worker_thread, network_thread_, std::move(ch1s),
+                              std::move(ch1r), rtp_transport1_.get(), flags1);
+    channel2_ = CreateChannel(worker_thread, network_thread_, std::move(ch2s),
+                              std::move(ch2r), rtp_transport2_.get(), flags2);
     CreateContent(flags1, kPcmuCodec, kH264Codec, &local_media_content1_);
     CreateContent(flags2, kPcmuCodec, kH264Codec, &local_media_content2_);
     CopyContent(local_media_content1_, &remote_media_content1_);
@@ -263,7 +271,8 @@
   std::unique_ptr<typename T::Channel> CreateChannel(
       rtc::Thread* worker_thread,
       rtc::Thread* network_thread,
-      std::unique_ptr<typename T::MediaChannel> ch,
+      std::unique_ptr<typename T::MediaChannel> ch_send,
+      std::unique_ptr<typename T::MediaChannel> ch_receive,
       webrtc::RtpTransportInternal* rtp_transport,
       int flags);
 
@@ -439,7 +448,7 @@
   }
 
   void SendRtp1(rtc::Buffer data) {
-    SendRtp(media_channel1(), std::move(data));
+    SendRtp(media_send_channel1_impl(), std::move(data));
   }
 
   void SendRtp2() {
@@ -447,7 +456,7 @@
   }
 
   void SendRtp2(rtc::Buffer data) {
-    SendRtp(media_channel2(), std::move(data));
+    SendRtp(media_send_channel2_impl(), std::move(data));
   }
 
   // Methods to send custom data.
@@ -459,19 +468,21 @@
   }
 
   bool CheckRtp1() {
-    return media_channel1()->CheckRtp(rtp_packet_.data(), rtp_packet_.size());
+    return media_receive_channel1_impl()->CheckRtp(rtp_packet_.data(),
+                                                   rtp_packet_.size());
   }
   bool CheckRtp2() {
-    return media_channel2()->CheckRtp(rtp_packet_.data(), rtp_packet_.size());
+    return media_receive_channel2_impl()->CheckRtp(rtp_packet_.data(),
+                                                   rtp_packet_.size());
   }
   // Methods to check custom data.
   bool CheckCustomRtp1(uint32_t ssrc, int sequence_number, int pl_type = -1) {
     rtc::Buffer data = CreateRtpData(ssrc, sequence_number, pl_type);
-    return media_channel1()->CheckRtp(data.data(), data.size());
+    return media_receive_channel1_impl()->CheckRtp(data.data(), data.size());
   }
   bool CheckCustomRtp2(uint32_t ssrc, int sequence_number, int pl_type = -1) {
     rtc::Buffer data = CreateRtpData(ssrc, sequence_number, pl_type);
-    return media_channel2()->CheckRtp(data.data(), data.size());
+    return media_receive_channel2_impl()->CheckRtp(data.data(), data.size());
   }
   rtc::Buffer CreateRtpData(uint32_t ssrc, int sequence_number, int pl_type) {
     rtc::Buffer data(rtp_packet_.data(), rtp_packet_.size());
@@ -484,8 +495,8 @@
     return data;
   }
 
-  bool CheckNoRtp1() { return media_channel1()->CheckNoRtp(); }
-  bool CheckNoRtp2() { return media_channel2()->CheckNoRtp(); }
+  bool CheckNoRtp1() { return media_send_channel1_impl()->CheckNoRtp(); }
+  bool CheckNoRtp2() { return media_send_channel2_impl()->CheckNoRtp(); }
 
   void CreateContent(int flags,
                      const cricket::AudioCodec& audio_codec,
@@ -567,18 +578,20 @@
   void TestInit() {
     CreateChannels(0, 0);
     EXPECT_FALSE(IsSrtpActive(channel1_));
-    EXPECT_FALSE(media_channel1()->sending());
+    EXPECT_FALSE(media_send_channel1_impl()->sending());
     if (verify_playout_) {
-      EXPECT_FALSE(media_channel1()->playout());
+      EXPECT_FALSE(media_receive_channel1_impl()->playout());
     }
-    EXPECT_TRUE(media_channel1()->codecs().empty());
-    EXPECT_TRUE(media_channel1()->recv_streams().empty());
-    EXPECT_TRUE(media_channel1()->rtp_packets().empty());
+    EXPECT_TRUE(media_send_channel1_impl()->codecs().empty());
+    EXPECT_TRUE(media_receive_channel1_impl()->recv_streams().empty());
+    EXPECT_TRUE(media_send_channel1_impl()->rtp_packets().empty());
     // Basic sanity test for send and receive channel objects
     EXPECT_EQ(channel1_->media_send_channel()->media_type(),
-              media_channel1()->media_type());
+              media_send_channel1_impl()->media_type());
     EXPECT_EQ(channel1_->media_receive_channel()->media_type(),
-              media_channel1()->media_type());
+              media_receive_channel1_impl()->media_type());
+    EXPECT_EQ(channel1_->media_send_channel()->media_type(),
+              channel1_->media_receive_channel()->media_type());
   }
 
   // Test that SetLocalContent and SetRemoteContent properly configure
@@ -589,11 +602,11 @@
     CreateContent(0, kPcmuCodec, kH264Codec, &content);
     std::string err;
     EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kOffer, err));
-    EXPECT_EQ(0U, media_channel1()->codecs().size());
+    EXPECT_EQ(0U, media_send_channel1_impl()->codecs().size());
     EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer, err));
-    ASSERT_EQ(1U, media_channel1()->codecs().size());
-    EXPECT_TRUE(
-        CodecMatches(content.codecs()[0], media_channel1()->codecs()[0]));
+    ASSERT_EQ(1U, media_send_channel1_impl()->codecs().size());
+    EXPECT_TRUE(CodecMatches(content.codecs()[0],
+                             media_send_channel1_impl()->codecs()[0]));
   }
 
   // Test that SetLocalContent and SetRemoteContent properly configure
@@ -611,7 +624,7 @@
     EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kOffer, err));
     content.set_extmap_allow_mixed_enum(answer_enum);
     EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer, err));
-    EXPECT_EQ(answer, media_send_channel1()->ExtmapAllowMixed());
+    EXPECT_EQ(answer, media_send_channel1_impl()->ExtmapAllowMixed());
   }
   void TestSetContentsExtmapAllowMixedCallee(bool offer, bool answer) {
     // For a callee, SetRemoteContent() is called first with an offer and next
@@ -637,11 +650,11 @@
     std::string err;
     EXPECT_TRUE(channel1_->SetLocalContent(&content, SdpType::kOffer, err));
     CreateContent(0, kPcmuCodec, kH264Codec, &content);
-    EXPECT_EQ(0U, media_channel1()->codecs().size());
+    EXPECT_EQ(0U, media_send_channel1_impl()->codecs().size());
     EXPECT_TRUE(channel1_->SetRemoteContent(&content, SdpType::kAnswer, err));
-    ASSERT_EQ(1U, media_channel1()->codecs().size());
-    EXPECT_TRUE(
-        CodecMatches(content.codecs()[0], media_channel1()->codecs()[0]));
+    ASSERT_EQ(1U, media_send_channel1_impl()->codecs().size());
+    EXPECT_TRUE(CodecMatches(content.codecs()[0],
+                             media_send_channel1_impl()->codecs()[0]));
   }
 
   // Test that SetLocalContent and SetRemoteContent properly set RTCP
@@ -683,20 +696,20 @@
     std::string err;
     EXPECT_TRUE(channel1_->SetLocalContent(&content1, SdpType::kOffer, err));
     channel1_->Enable(true);
-    EXPECT_EQ(1u, media_channel1()->send_streams().size());
+    EXPECT_EQ(1u, media_send_channel1_impl()->send_streams().size());
 
     EXPECT_TRUE(channel2_->SetRemoteContent(&content1, SdpType::kOffer, err));
-    EXPECT_EQ(1u, media_channel2()->recv_streams().size());
+    EXPECT_EQ(1u, media_receive_channel2_impl()->recv_streams().size());
     ConnectFakeTransports();
 
     // Channel 2 do not send anything.
     typename T::Content content2;
     CreateContent(0, kPcmuCodec, kH264Codec, &content2);
     EXPECT_TRUE(channel1_->SetRemoteContent(&content2, SdpType::kAnswer, err));
-    EXPECT_EQ(0u, media_channel1()->recv_streams().size());
+    EXPECT_EQ(0u, media_receive_channel1_impl()->recv_streams().size());
     EXPECT_TRUE(channel2_->SetLocalContent(&content2, SdpType::kAnswer, err));
     channel2_->Enable(true);
-    EXPECT_EQ(0u, media_channel2()->send_streams().size());
+    EXPECT_EQ(0u, media_send_channel2_impl()->send_streams().size());
 
     SendCustomRtp1(kSsrc1, 0);
     WaitForThreads();
@@ -707,21 +720,21 @@
     CreateContent(0, kPcmuCodec, kH264Codec, &content3);
     content3.AddStream(stream2);
     EXPECT_TRUE(channel2_->SetLocalContent(&content3, SdpType::kOffer, err));
-    ASSERT_EQ(1u, media_channel2()->send_streams().size());
-    EXPECT_EQ(stream2, media_channel2()->send_streams()[0]);
+    ASSERT_EQ(1u, media_send_channel2_impl()->send_streams().size());
+    EXPECT_EQ(stream2, media_send_channel2_impl()->send_streams()[0]);
 
     EXPECT_TRUE(channel1_->SetRemoteContent(&content3, SdpType::kOffer, err));
-    ASSERT_EQ(1u, media_channel1()->recv_streams().size());
-    EXPECT_EQ(stream2, media_channel1()->recv_streams()[0]);
+    ASSERT_EQ(1u, media_receive_channel1_impl()->recv_streams().size());
+    EXPECT_EQ(stream2, media_receive_channel1_impl()->recv_streams()[0]);
 
     // Channel 1 replies but stop sending stream1.
     typename T::Content content4;
     CreateContent(0, kPcmuCodec, kH264Codec, &content4);
     EXPECT_TRUE(channel1_->SetLocalContent(&content4, SdpType::kAnswer, err));
-    EXPECT_EQ(0u, media_channel1()->send_streams().size());
+    EXPECT_EQ(0u, media_send_channel1_impl()->send_streams().size());
 
     EXPECT_TRUE(channel2_->SetRemoteContent(&content4, SdpType::kAnswer, err));
-    EXPECT_EQ(0u, media_channel2()->recv_streams().size());
+    EXPECT_EQ(0u, media_receive_channel2_impl()->recv_streams().size());
 
     SendCustomRtp2(kSsrc2, 0);
     WaitForThreads();
@@ -732,59 +745,59 @@
   void TestPlayoutAndSendingStates() {
     CreateChannels(0, 0);
     if (verify_playout_) {
-      EXPECT_FALSE(media_channel1()->playout());
+      EXPECT_FALSE(media_receive_channel1_impl()->playout());
     }
-    EXPECT_FALSE(media_channel1()->sending());
+    EXPECT_FALSE(media_send_channel1_impl()->sending());
     if (verify_playout_) {
-      EXPECT_FALSE(media_channel2()->playout());
+      EXPECT_FALSE(media_receive_channel2_impl()->playout());
     }
-    EXPECT_FALSE(media_channel2()->sending());
+    EXPECT_FALSE(media_send_channel2_impl()->sending());
     channel1_->Enable(true);
     FlushCurrentThread();
     if (verify_playout_) {
-      EXPECT_FALSE(media_channel1()->playout());
+      EXPECT_FALSE(media_receive_channel1_impl()->playout());
     }
-    EXPECT_FALSE(media_channel1()->sending());
+    EXPECT_FALSE(media_send_channel1_impl()->sending());
     std::string err;
     EXPECT_TRUE(channel1_->SetLocalContent(&local_media_content1_,
                                            SdpType::kOffer, err));
     if (verify_playout_) {
-      EXPECT_TRUE(media_channel1()->playout());
+      EXPECT_TRUE(media_receive_channel1_impl()->playout());
     }
-    EXPECT_FALSE(media_channel1()->sending());
+    EXPECT_FALSE(media_send_channel1_impl()->sending());
     EXPECT_TRUE(channel2_->SetRemoteContent(&local_media_content1_,
                                             SdpType::kOffer, err));
     if (verify_playout_) {
-      EXPECT_FALSE(media_channel2()->playout());
+      EXPECT_FALSE(media_receive_channel2_impl()->playout());
     }
-    EXPECT_FALSE(media_channel2()->sending());
+    EXPECT_FALSE(media_send_channel2_impl()->sending());
     EXPECT_TRUE(channel2_->SetLocalContent(&local_media_content2_,
                                            SdpType::kAnswer, err));
     if (verify_playout_) {
-      EXPECT_FALSE(media_channel2()->playout());
+      EXPECT_FALSE(media_receive_channel2_impl()->playout());
     }
-    EXPECT_FALSE(media_channel2()->sending());
+    EXPECT_FALSE(media_send_channel2_impl()->sending());
     ConnectFakeTransports();
     if (verify_playout_) {
-      EXPECT_TRUE(media_channel1()->playout());
+      EXPECT_TRUE(media_receive_channel1_impl()->playout());
     }
-    EXPECT_FALSE(media_channel1()->sending());
+    EXPECT_FALSE(media_send_channel1_impl()->sending());
     if (verify_playout_) {
-      EXPECT_FALSE(media_channel2()->playout());
+      EXPECT_FALSE(media_receive_channel2_impl()->playout());
     }
-    EXPECT_FALSE(media_channel2()->sending());
+    EXPECT_FALSE(media_send_channel2_impl()->sending());
     channel2_->Enable(true);
     FlushCurrentThread();
     if (verify_playout_) {
-      EXPECT_TRUE(media_channel2()->playout());
+      EXPECT_TRUE(media_receive_channel2_impl()->playout());
     }
-    EXPECT_TRUE(media_channel2()->sending());
+    EXPECT_TRUE(media_send_channel2_impl()->sending());
     EXPECT_TRUE(channel1_->SetRemoteContent(&local_media_content2_,
                                             SdpType::kAnswer, err));
     if (verify_playout_) {
-      EXPECT_TRUE(media_channel1()->playout());
+      EXPECT_TRUE(media_receive_channel1_impl()->playout());
     }
-    EXPECT_TRUE(media_channel1()->sending());
+    EXPECT_TRUE(media_send_channel1_impl()->sending());
   }
 
   // Test that changing the MediaContentDirection in the local and remote
@@ -802,13 +815,13 @@
     channel2_->Enable(true);
     FlushCurrentThread();
     if (verify_playout_) {
-      EXPECT_FALSE(media_channel1()->playout());
+      EXPECT_FALSE(media_receive_channel1_impl()->playout());
     }
-    EXPECT_FALSE(media_channel1()->sending());
+    EXPECT_FALSE(media_send_channel1_impl()->sending());
     if (verify_playout_) {
-      EXPECT_FALSE(media_channel2()->playout());
+      EXPECT_FALSE(media_receive_channel2_impl()->playout());
     }
-    EXPECT_FALSE(media_channel2()->sending());
+    EXPECT_FALSE(media_send_channel2_impl()->sending());
 
     std::string err;
     EXPECT_TRUE(channel1_->SetLocalContent(&content1, SdpType::kOffer, err));
@@ -819,13 +832,13 @@
     ConnectFakeTransports();
 
     if (verify_playout_) {
-      EXPECT_TRUE(media_channel1()->playout());
+      EXPECT_TRUE(media_receive_channel1_impl()->playout());
     }
-    EXPECT_FALSE(media_channel1()->sending());  // remote InActive
+    EXPECT_FALSE(media_send_channel1_impl()->sending());  // remote InActive
     if (verify_playout_) {
-      EXPECT_FALSE(media_channel2()->playout());  // local InActive
+      EXPECT_FALSE(media_receive_channel2_impl()->playout());  // local InActive
     }
-    EXPECT_FALSE(media_channel2()->sending());  // local InActive
+    EXPECT_FALSE(media_send_channel2_impl()->sending());  // local InActive
 
     // Update `content2` to be RecvOnly.
     content2.set_direction(RtpTransceiverDirection::kRecvOnly);
@@ -834,13 +847,13 @@
         channel1_->SetRemoteContent(&content2, SdpType::kPrAnswer, err));
 
     if (verify_playout_) {
-      EXPECT_TRUE(media_channel1()->playout());
+      EXPECT_TRUE(media_receive_channel1_impl()->playout());
     }
-    EXPECT_TRUE(media_channel1()->sending());
+    EXPECT_TRUE(media_send_channel1_impl()->sending());
     if (verify_playout_) {
-      EXPECT_TRUE(media_channel2()->playout());  // local RecvOnly
+      EXPECT_TRUE(media_receive_channel2_impl()->playout());  // local RecvOnly
     }
-    EXPECT_FALSE(media_channel2()->sending());  // local RecvOnly
+    EXPECT_FALSE(media_send_channel2_impl()->sending());  // local RecvOnly
 
     // Update `content2` to be SendRecv.
     content2.set_direction(RtpTransceiverDirection::kSendRecv);
@@ -848,13 +861,13 @@
     EXPECT_TRUE(channel1_->SetRemoteContent(&content2, SdpType::kAnswer, err));
 
     if (verify_playout_) {
-      EXPECT_TRUE(media_channel1()->playout());
+      EXPECT_TRUE(media_receive_channel1_impl()->playout());
     }
-    EXPECT_TRUE(media_channel1()->sending());
+    EXPECT_TRUE(media_send_channel1_impl()->sending());
     if (verify_playout_) {
-      EXPECT_TRUE(media_channel2()->playout());
+      EXPECT_TRUE(media_receive_channel2_impl()->playout());
     }
-    EXPECT_TRUE(media_channel2()->sending());
+    EXPECT_TRUE(media_send_channel2_impl()->sending());
   }
 
   // Tests that when the transport channel signals a candidate pair change
@@ -870,14 +883,15 @@
     CreateChannels(DTLS, DTLS);
     SendInitiate();
 
-    typename T::MediaChannel* media_channel1 = this->media_channel1();
-    ASSERT_TRUE(media_channel1);
+    typename T::MediaChannel* media_send_channel1_impl =
+        this->media_send_channel1_impl();
+    ASSERT_TRUE(media_send_channel1_impl);
 
     // Need to wait for the threads before calling
     // `set_num_network_route_changes` because the network route would be set
     // when creating the channel.
     WaitForThreads();
-    media_channel1->set_num_network_route_changes(0);
+    media_send_channel1_impl->set_num_network_route_changes(0);
     SendTask(network_thread_, [this] {
       rtc::NetworkRoute network_route;
       // The transport channel becomes disconnected.
@@ -885,9 +899,9 @@
           absl::optional<rtc::NetworkRoute>(network_route));
     });
     WaitForThreads();
-    EXPECT_EQ(1, media_channel1->num_network_route_changes());
-    EXPECT_FALSE(media_channel1->last_network_route().connected);
-    media_channel1->set_num_network_route_changes(0);
+    EXPECT_EQ(1, media_send_channel1_impl->num_network_route_changes());
+    EXPECT_FALSE(media_send_channel1_impl->last_network_route().connected);
+    media_send_channel1_impl->set_num_network_route_changes(0);
 
     SendTask(network_thread_, [this] {
       rtc::NetworkRoute network_route;
@@ -904,16 +918,19 @@
           absl::optional<rtc::NetworkRoute>(network_route));
     });
     WaitForThreads();
-    EXPECT_EQ(1, media_channel1->num_network_route_changes());
-    EXPECT_TRUE(media_channel1->last_network_route().connected);
-    EXPECT_EQ(kLocalNetId,
-              media_channel1->last_network_route().local.network_id());
-    EXPECT_EQ(kRemoteNetId,
-              media_channel1->last_network_route().remote.network_id());
-    EXPECT_EQ(kLastPacketId,
-              media_channel1->last_network_route().last_sent_packet_id);
+    EXPECT_EQ(1, media_send_channel1_impl->num_network_route_changes());
+    EXPECT_TRUE(media_send_channel1_impl->last_network_route().connected);
+    EXPECT_EQ(
+        kLocalNetId,
+        media_send_channel1_impl->last_network_route().local.network_id());
+    EXPECT_EQ(
+        kRemoteNetId,
+        media_send_channel1_impl->last_network_route().remote.network_id());
+    EXPECT_EQ(
+        kLastPacketId,
+        media_send_channel1_impl->last_network_route().last_sent_packet_id);
     EXPECT_EQ(kTransportOverheadPerPacket + kSrtpOverheadPerPacket,
-              media_channel1->transport_overhead_per_packet());
+              media_send_channel1_impl->transport_overhead_per_packet());
   }
 
   // Test setting up a call.
@@ -922,18 +939,18 @@
     EXPECT_FALSE(IsSrtpActive(channel1_));
     EXPECT_TRUE(SendInitiate());
     if (verify_playout_) {
-      EXPECT_TRUE(media_channel1()->playout());
+      EXPECT_TRUE(media_receive_channel1_impl()->playout());
     }
-    EXPECT_FALSE(media_channel1()->sending());
+    EXPECT_FALSE(media_send_channel1_impl()->sending());
     EXPECT_TRUE(SendAccept());
     EXPECT_FALSE(IsSrtpActive(channel1_));
-    EXPECT_TRUE(media_channel1()->sending());
-    EXPECT_EQ(1U, media_channel1()->codecs().size());
+    EXPECT_TRUE(media_send_channel1_impl()->sending());
+    EXPECT_EQ(1U, media_send_channel1_impl()->codecs().size());
     if (verify_playout_) {
-      EXPECT_TRUE(media_channel2()->playout());
+      EXPECT_TRUE(media_receive_channel2_impl()->playout());
     }
-    EXPECT_TRUE(media_channel2()->sending());
-    EXPECT_EQ(1U, media_channel2()->codecs().size());
+    EXPECT_TRUE(media_send_channel2_impl()->sending());
+    EXPECT_EQ(1U, media_send_channel2_impl()->codecs().size());
   }
 
   // Send voice RTP data to the other side and ensure it gets there.
@@ -1060,7 +1077,7 @@
     // Regain writability
     SendTask(network_thread_,
              [this] { fake_rtp_dtls_transport1_->SetWritable(true); });
-    EXPECT_TRUE(media_channel1()->sending());
+    EXPECT_TRUE(media_send_channel1_impl()->sending());
     SendRtp1();
     SendRtp2();
     WaitForThreads();
@@ -1074,7 +1091,7 @@
       bool asymmetric = true;
       fake_rtp_dtls_transport1_->SetDestination(nullptr, asymmetric);
     });
-    EXPECT_TRUE(media_channel1()->sending());
+    EXPECT_TRUE(media_send_channel1_impl()->sending());
 
     // Should fail also.
     SendRtp1();
@@ -1090,7 +1107,7 @@
       fake_rtp_dtls_transport1_->SetDestination(fake_rtp_dtls_transport2_.get(),
                                                 asymmetric);
     });
-    EXPECT_TRUE(media_channel1()->sending());
+    EXPECT_TRUE(media_send_channel1_impl()->sending());
     SendRtp1();
     SendRtp2();
     WaitForThreads();
@@ -1143,17 +1160,17 @@
     std::unique_ptr<typename T::Content> content(
         CreateMediaContentWithStream(1));
 
-    media_channel1()->set_fail_set_recv_codecs(true);
+    media_receive_channel1_impl()->set_fail_set_recv_codecs(true);
     EXPECT_FALSE(
         channel1_->SetLocalContent(content.get(), SdpType::kOffer, err));
     EXPECT_FALSE(
         channel1_->SetLocalContent(content.get(), SdpType::kAnswer, err));
 
-    media_channel1()->set_fail_set_send_codecs(true);
+    media_send_channel1_impl()->set_fail_set_send_codecs(true);
     EXPECT_FALSE(
         channel1_->SetRemoteContent(content.get(), SdpType::kOffer, err));
 
-    media_channel1()->set_fail_set_send_codecs(true);
+    media_send_channel1_impl()->set_fail_set_send_codecs(true);
     EXPECT_FALSE(
         channel1_->SetRemoteContent(content.get(), SdpType::kAnswer, err));
   }
@@ -1166,14 +1183,14 @@
         CreateMediaContentWithStream(1));
     EXPECT_TRUE(
         channel1_->SetLocalContent(content1.get(), SdpType::kOffer, err));
-    EXPECT_TRUE(media_channel1()->HasSendStream(1));
+    EXPECT_TRUE(media_send_channel1_impl()->HasSendStream(1));
 
     std::unique_ptr<typename T::Content> content2(
         CreateMediaContentWithStream(2));
     EXPECT_TRUE(
         channel1_->SetLocalContent(content2.get(), SdpType::kOffer, err));
-    EXPECT_FALSE(media_channel1()->HasSendStream(1));
-    EXPECT_TRUE(media_channel1()->HasSendStream(2));
+    EXPECT_FALSE(media_send_channel1_impl()->HasSendStream(1));
+    EXPECT_TRUE(media_send_channel1_impl()->HasSendStream(2));
   }
 
   void TestReceiveTwoOffers() {
@@ -1184,14 +1201,14 @@
         CreateMediaContentWithStream(1));
     EXPECT_TRUE(
         channel1_->SetRemoteContent(content1.get(), SdpType::kOffer, err));
-    EXPECT_TRUE(media_channel1()->HasRecvStream(1));
+    EXPECT_TRUE(media_receive_channel1_impl()->HasRecvStream(1));
 
     std::unique_ptr<typename T::Content> content2(
         CreateMediaContentWithStream(2));
     EXPECT_TRUE(
         channel1_->SetRemoteContent(content2.get(), SdpType::kOffer, err));
-    EXPECT_FALSE(media_channel1()->HasRecvStream(1));
-    EXPECT_TRUE(media_channel1()->HasRecvStream(2));
+    EXPECT_FALSE(media_receive_channel1_impl()->HasRecvStream(1));
+    EXPECT_TRUE(media_receive_channel1_impl()->HasRecvStream(2));
   }
 
   void TestSendPrAnswer() {
@@ -1203,24 +1220,24 @@
         CreateMediaContentWithStream(1));
     EXPECT_TRUE(
         channel1_->SetRemoteContent(content1.get(), SdpType::kOffer, err));
-    EXPECT_TRUE(media_channel1()->HasRecvStream(1));
+    EXPECT_TRUE(media_receive_channel1_impl()->HasRecvStream(1));
 
     // Send PR answer
     std::unique_ptr<typename T::Content> content2(
         CreateMediaContentWithStream(2));
     EXPECT_TRUE(
         channel1_->SetLocalContent(content2.get(), SdpType::kPrAnswer, err));
-    EXPECT_TRUE(media_channel1()->HasRecvStream(1));
-    EXPECT_TRUE(media_channel1()->HasSendStream(2));
+    EXPECT_TRUE(media_receive_channel1_impl()->HasRecvStream(1));
+    EXPECT_TRUE(media_send_channel1_impl()->HasSendStream(2));
 
     // Send answer
     std::unique_ptr<typename T::Content> content3(
         CreateMediaContentWithStream(3));
     EXPECT_TRUE(
         channel1_->SetLocalContent(content3.get(), SdpType::kAnswer, err));
-    EXPECT_TRUE(media_channel1()->HasRecvStream(1));
-    EXPECT_FALSE(media_channel1()->HasSendStream(2));
-    EXPECT_TRUE(media_channel1()->HasSendStream(3));
+    EXPECT_TRUE(media_receive_channel1_impl()->HasRecvStream(1));
+    EXPECT_FALSE(media_send_channel1_impl()->HasSendStream(2));
+    EXPECT_TRUE(media_send_channel1_impl()->HasSendStream(3));
   }
 
   void TestReceivePrAnswer() {
@@ -1232,39 +1249,39 @@
         CreateMediaContentWithStream(1));
     EXPECT_TRUE(
         channel1_->SetLocalContent(content1.get(), SdpType::kOffer, err));
-    EXPECT_TRUE(media_channel1()->HasSendStream(1));
+    EXPECT_TRUE(media_send_channel1_impl()->HasSendStream(1));
 
     // Receive PR answer
     std::unique_ptr<typename T::Content> content2(
         CreateMediaContentWithStream(2));
     EXPECT_TRUE(
         channel1_->SetRemoteContent(content2.get(), SdpType::kPrAnswer, err));
-    EXPECT_TRUE(media_channel1()->HasSendStream(1));
-    EXPECT_TRUE(media_channel1()->HasRecvStream(2));
+    EXPECT_TRUE(media_send_channel1_impl()->HasSendStream(1));
+    EXPECT_TRUE(media_receive_channel1_impl()->HasRecvStream(2));
 
     // Receive answer
     std::unique_ptr<typename T::Content> content3(
         CreateMediaContentWithStream(3));
     EXPECT_TRUE(
         channel1_->SetRemoteContent(content3.get(), SdpType::kAnswer, err));
-    EXPECT_TRUE(media_channel1()->HasSendStream(1));
-    EXPECT_FALSE(media_channel1()->HasRecvStream(2));
-    EXPECT_TRUE(media_channel1()->HasRecvStream(3));
+    EXPECT_TRUE(media_send_channel1_impl()->HasSendStream(1));
+    EXPECT_FALSE(media_receive_channel1_impl()->HasRecvStream(2));
+    EXPECT_TRUE(media_receive_channel1_impl()->HasRecvStream(3));
   }
 
   void TestOnTransportReadyToSend() {
     CreateChannels(0, 0);
-    EXPECT_FALSE(media_channel1()->ready_to_send());
+    EXPECT_FALSE(media_send_channel1_impl()->ready_to_send());
 
     network_thread_->PostTask(
         [this] { channel1_->OnTransportReadyToSend(true); });
     WaitForThreads();
-    EXPECT_TRUE(media_channel1()->ready_to_send());
+    EXPECT_TRUE(media_send_channel1_impl()->ready_to_send());
 
     network_thread_->PostTask(
         [this] { channel1_->OnTransportReadyToSend(false); });
     WaitForThreads();
-    EXPECT_FALSE(media_channel1()->ready_to_send());
+    EXPECT_FALSE(media_send_channel1_impl()->ready_to_send());
   }
 
   bool SetRemoteContentWithBitrateLimit(int remote_limit) {
@@ -1293,7 +1310,7 @@
     std::string err;
     EXPECT_TRUE(channel1_->SetLocalContent(&local_media_content1_,
                                            SdpType::kOffer, err));
-    EXPECT_EQ(media_channel1()->max_bps(), -1);
+    EXPECT_EQ(media_send_channel1_impl()->max_bps(), -1);
     VerifyMaxBitrate(media_send_channel1()->GetRtpSendParameters(kSsrc1),
                      absl::nullopt);
   }
@@ -1411,31 +1428,49 @@
     ProcessThreadQueue(rtc::Thread::Current());
   }
 
+  // Accessors that return the standard VideoMedia{Send|Receive}ChannelInterface
+  typename T::MediaSendChannel* media_send_channel1() {
+    return channel1_->media_send_channel();
+  }
+  typename T::MediaSendChannel* media_send_channel2() {
+    return channel2_->media_send_channel();
+  }
+  typename T::MediaReceiveChannel* media_receive_channel1() {
+    return channel1_->media_receive_channel();
+  }
+  typename T::MediaReceiveChannel* media_receive_channel2() {
+    return channel2_->media_receive_channel();
+  }
+
   // Accessors that return the FakeMedia<type>Channel object.
   // Note that these depend on getting the object back that was
   // passed to the channel constructor.
-  typename T::MediaChannel* media_channel1() {
-    RTC_DCHECK(channel1_);
-    RTC_DCHECK(channel1_->media_channel());
-    return static_cast<typename T::MediaChannel*>(channel1_->media_channel());
-  }
-
-  typename T::MediaChannel* media_channel2() {
-    RTC_DCHECK(channel2_);
-    RTC_DCHECK(channel2_->media_channel());
-    return static_cast<typename T::MediaChannel*>(channel2_->media_channel());
-  }
-
-  typename T::MediaSendChannel* media_send_channel1() {
+  // T::MediaChannel is either FakeVoiceMediaChannel or FakeVideoMediaChannel.
+  typename T::MediaChannel* media_send_channel1_impl() {
     RTC_DCHECK(channel1_);
     RTC_DCHECK(channel1_->media_send_channel());
-    return channel1_->media_send_channel();
+    return static_cast<typename T::MediaChannel*>(
+        channel1_->media_send_channel()->ImplForTesting());
   }
 
-  typename T::MediaSendChannel* media_send_channel2() {
+  typename T::MediaChannel* media_send_channel2_impl() {
     RTC_DCHECK(channel2_);
     RTC_DCHECK(channel2_->media_send_channel());
-    return channel2_->media_send_channel();
+    return static_cast<typename T::MediaChannel*>(
+        channel2_->media_send_channel()->ImplForTesting());
+  }
+  typename T::MediaChannel* media_receive_channel1_impl() {
+    RTC_DCHECK(channel1_);
+    RTC_DCHECK(channel1_->media_receive_channel());
+    return static_cast<typename T::MediaChannel*>(
+        channel1_->media_receive_channel()->ImplForTesting());
+  }
+
+  typename T::MediaChannel* media_receive_channel2_impl() {
+    RTC_DCHECK(channel2_);
+    RTC_DCHECK(channel2_->media_receive_channel());
+    return static_cast<typename T::MediaChannel*>(
+        channel2_->media_receive_channel()->ImplForTesting());
   }
 
   rtc::AutoThread main_thread_;
@@ -1476,14 +1511,15 @@
 std::unique_ptr<cricket::VoiceChannel> ChannelTest<VoiceTraits>::CreateChannel(
     rtc::Thread* worker_thread,
     rtc::Thread* network_thread,
-    std::unique_ptr<cricket::FakeVoiceMediaChannel> ch,
+    std::unique_ptr<cricket::FakeVoiceMediaChannel> send_ch,
+    std::unique_ptr<cricket::FakeVoiceMediaChannel> receive_ch,
     webrtc::RtpTransportInternal* rtp_transport,
     int flags) {
   rtc::Thread* signaling_thread = rtc::Thread::Current();
   auto channel = std::make_unique<cricket::VoiceChannel>(
-      worker_thread, network_thread, signaling_thread, std::move(ch),
-      cricket::CN_AUDIO, (flags & DTLS) != 0, webrtc::CryptoOptions(),
-      &ssrc_generator_);
+      worker_thread, network_thread, signaling_thread, std::move(send_ch),
+      std::move(receive_ch), cricket::CN_AUDIO, (flags & DTLS) != 0,
+      webrtc::CryptoOptions(), &ssrc_generator_);
   SendTask(network_thread, [&]() {
     RTC_DCHECK_RUN_ON(channel->network_thread());
     channel->SetRtpTransport(rtp_transport);
@@ -1562,14 +1598,15 @@
 std::unique_ptr<cricket::VideoChannel> ChannelTest<VideoTraits>::CreateChannel(
     rtc::Thread* worker_thread,
     rtc::Thread* network_thread,
-    std::unique_ptr<cricket::FakeVideoMediaChannel> ch,
+    std::unique_ptr<cricket::FakeVideoMediaChannel> send_ch,
+    std::unique_ptr<cricket::FakeVideoMediaChannel> receive_ch,
     webrtc::RtpTransportInternal* rtp_transport,
     int flags) {
   rtc::Thread* signaling_thread = rtc::Thread::Current();
   auto channel = std::make_unique<cricket::VideoChannel>(
-      worker_thread, network_thread, signaling_thread, std::move(ch),
-      cricket::CN_VIDEO, (flags & DTLS) != 0, webrtc::CryptoOptions(),
-      &ssrc_generator_);
+      worker_thread, network_thread, signaling_thread, std::move(send_ch),
+      std::move(receive_ch), cricket::CN_VIDEO, (flags & DTLS) != 0,
+      webrtc::CryptoOptions(), &ssrc_generator_);
   SendTask(network_thread, [&]() {
     RTC_DCHECK_RUN_ON(channel->network_thread());
     channel->SetRtpTransport(rtp_transport);
@@ -1624,8 +1661,8 @@
 
 TEST_F(VoiceChannelSingleThreadTest, TestInit) {
   Base::TestInit();
-  EXPECT_FALSE(media_channel1()->IsStreamMuted(0));
-  EXPECT_TRUE(media_channel1()->dtmf_info_queue().empty());
+  EXPECT_FALSE(media_send_channel1_impl()->IsStreamMuted(0));
+  EXPECT_TRUE(media_send_channel1_impl()->dtmf_info_queue().empty());
 }
 
 TEST_F(VoiceChannelSingleThreadTest, TestDeinit) {
@@ -1761,8 +1798,8 @@
 // VoiceChannelDoubleThreadTest
 TEST_F(VoiceChannelDoubleThreadTest, TestInit) {
   Base::TestInit();
-  EXPECT_FALSE(media_channel1()->IsStreamMuted(0));
-  EXPECT_TRUE(media_channel1()->dtmf_info_queue().empty());
+  EXPECT_FALSE(media_send_channel1_impl()->IsStreamMuted(0));
+  EXPECT_TRUE(media_send_channel1_impl()->dtmf_info_queue().empty());
 }
 
 TEST_F(VoiceChannelDoubleThreadTest, TestDeinit) {
@@ -2045,14 +2082,15 @@
 
   std::string err;
   EXPECT_TRUE(channel1_->SetLocalContent(&video, SdpType::kOffer, err));
-  EXPECT_THAT(media_channel1()->send_codecs(), testing::IsEmpty());
-  ASSERT_THAT(media_channel1()->recv_codecs(), testing::SizeIs(2));
-  EXPECT_TRUE(
-      media_channel1()->recv_codecs()[0].Matches(kVp8Codec, &field_trials_));
-  EXPECT_EQ(media_channel1()->recv_codecs()[0].packetization, absl::nullopt);
-  EXPECT_TRUE(
-      media_channel1()->recv_codecs()[1].Matches(vp9_codec, &field_trials_));
-  EXPECT_EQ(media_channel1()->recv_codecs()[1].packetization,
+  EXPECT_THAT(media_send_channel1_impl()->send_codecs(), testing::IsEmpty());
+  ASSERT_THAT(media_receive_channel1_impl()->recv_codecs(), testing::SizeIs(2));
+  EXPECT_TRUE(media_receive_channel1_impl()->recv_codecs()[0].Matches(
+      kVp8Codec, &field_trials_));
+  EXPECT_EQ(media_receive_channel1_impl()->recv_codecs()[0].packetization,
+            absl::nullopt);
+  EXPECT_TRUE(media_receive_channel1_impl()->recv_codecs()[1].Matches(
+      vp9_codec, &field_trials_));
+  EXPECT_EQ(media_receive_channel1_impl()->recv_codecs()[1].packetization,
             cricket::kPacketizationParamRaw);
 }
 
@@ -2068,14 +2106,15 @@
   std::string err;
   EXPECT_TRUE(channel1_->SetRemoteContent(&video, SdpType::kOffer, err));
   EXPECT_TRUE(err.empty());
-  EXPECT_THAT(media_channel1()->recv_codecs(), testing::IsEmpty());
-  ASSERT_THAT(media_channel1()->send_codecs(), testing::SizeIs(2));
-  EXPECT_TRUE(
-      media_channel1()->send_codecs()[0].Matches(kVp8Codec, &field_trials_));
-  EXPECT_EQ(media_channel1()->send_codecs()[0].packetization, absl::nullopt);
-  EXPECT_TRUE(
-      media_channel1()->send_codecs()[1].Matches(vp9_codec, &field_trials_));
-  EXPECT_EQ(media_channel1()->send_codecs()[1].packetization,
+  EXPECT_THAT(media_receive_channel1_impl()->recv_codecs(), testing::IsEmpty());
+  ASSERT_THAT(media_send_channel1_impl()->send_codecs(), testing::SizeIs(2));
+  EXPECT_TRUE(media_send_channel1_impl()->send_codecs()[0].Matches(
+      kVp8Codec, &field_trials_));
+  EXPECT_EQ(media_send_channel1_impl()->send_codecs()[0].packetization,
+            absl::nullopt);
+  EXPECT_TRUE(media_send_channel1_impl()->send_codecs()[1].Matches(
+      vp9_codec, &field_trials_));
+  EXPECT_EQ(media_send_channel1_impl()->send_codecs()[1].packetization,
             cricket::kPacketizationParamRaw);
 }
 
@@ -2093,21 +2132,23 @@
   EXPECT_TRUE(err.empty());
   EXPECT_TRUE(channel1_->SetRemoteContent(&video, SdpType::kAnswer, err));
   EXPECT_TRUE(err.empty());
-  ASSERT_THAT(media_channel1()->recv_codecs(), testing::SizeIs(2));
-  EXPECT_TRUE(
-      media_channel1()->recv_codecs()[0].Matches(kVp8Codec, &field_trials_));
-  EXPECT_EQ(media_channel1()->recv_codecs()[0].packetization, absl::nullopt);
-  EXPECT_TRUE(
-      media_channel1()->recv_codecs()[1].Matches(vp9_codec, &field_trials_));
-  EXPECT_EQ(media_channel1()->recv_codecs()[1].packetization,
+  ASSERT_THAT(media_receive_channel1_impl()->recv_codecs(), testing::SizeIs(2));
+  EXPECT_TRUE(media_receive_channel1_impl()->recv_codecs()[0].Matches(
+      kVp8Codec, &field_trials_));
+  EXPECT_EQ(media_receive_channel1_impl()->recv_codecs()[0].packetization,
+            absl::nullopt);
+  EXPECT_TRUE(media_receive_channel1_impl()->recv_codecs()[1].Matches(
+      vp9_codec, &field_trials_));
+  EXPECT_EQ(media_receive_channel1_impl()->recv_codecs()[1].packetization,
             cricket::kPacketizationParamRaw);
-  EXPECT_THAT(media_channel1()->send_codecs(), testing::SizeIs(2));
-  EXPECT_TRUE(
-      media_channel1()->send_codecs()[0].Matches(kVp8Codec, &field_trials_));
-  EXPECT_EQ(media_channel1()->send_codecs()[0].packetization, absl::nullopt);
-  EXPECT_TRUE(
-      media_channel1()->send_codecs()[1].Matches(vp9_codec, &field_trials_));
-  EXPECT_EQ(media_channel1()->send_codecs()[1].packetization,
+  EXPECT_THAT(media_send_channel1_impl()->send_codecs(), testing::SizeIs(2));
+  EXPECT_TRUE(media_send_channel1_impl()->send_codecs()[0].Matches(
+      kVp8Codec, &field_trials_));
+  EXPECT_EQ(media_send_channel1_impl()->send_codecs()[0].packetization,
+            absl::nullopt);
+  EXPECT_TRUE(media_send_channel1_impl()->send_codecs()[1].Matches(
+      vp9_codec, &field_trials_));
+  EXPECT_EQ(media_send_channel1_impl()->send_codecs()[1].packetization,
             cricket::kPacketizationParamRaw);
 }
 
@@ -2125,10 +2166,12 @@
   std::string err;
   EXPECT_TRUE(channel1_->SetRemoteContent(&remote_video, SdpType::kOffer, err));
   EXPECT_TRUE(channel1_->SetLocalContent(&local_video, SdpType::kAnswer, err));
-  ASSERT_THAT(media_channel1()->recv_codecs(), testing::SizeIs(1));
-  EXPECT_EQ(media_channel1()->recv_codecs()[0].packetization, absl::nullopt);
-  ASSERT_THAT(media_channel1()->send_codecs(), testing::SizeIs(1));
-  EXPECT_EQ(media_channel1()->send_codecs()[0].packetization, absl::nullopt);
+  ASSERT_THAT(media_receive_channel1_impl()->recv_codecs(), testing::SizeIs(1));
+  EXPECT_EQ(media_receive_channel1_impl()->recv_codecs()[0].packetization,
+            absl::nullopt);
+  ASSERT_THAT(media_send_channel1_impl()->send_codecs(), testing::SizeIs(1));
+  EXPECT_EQ(media_send_channel1_impl()->send_codecs()[0].packetization,
+            absl::nullopt);
 }
 
 TEST_F(VideoChannelSingleThreadTest, TestSetRemoteAnswerWithoutPacketization) {
@@ -2146,10 +2189,12 @@
   EXPECT_TRUE(channel1_->SetLocalContent(&local_video, SdpType::kOffer, err));
   EXPECT_TRUE(
       channel1_->SetRemoteContent(&remote_video, SdpType::kAnswer, err));
-  ASSERT_THAT(media_channel1()->recv_codecs(), testing::SizeIs(1));
-  EXPECT_EQ(media_channel1()->recv_codecs()[0].packetization, absl::nullopt);
-  ASSERT_THAT(media_channel1()->send_codecs(), testing::SizeIs(1));
-  EXPECT_EQ(media_channel1()->send_codecs()[0].packetization, absl::nullopt);
+  ASSERT_THAT(media_receive_channel1_impl()->recv_codecs(), testing::SizeIs(1));
+  EXPECT_EQ(media_receive_channel1_impl()->recv_codecs()[0].packetization,
+            absl::nullopt);
+  ASSERT_THAT(media_send_channel1_impl()->send_codecs(), testing::SizeIs(1));
+  EXPECT_EQ(media_send_channel1_impl()->send_codecs()[0].packetization,
+            absl::nullopt);
 }
 
 TEST_F(VideoChannelSingleThreadTest,
@@ -2171,10 +2216,10 @@
   EXPECT_FALSE(
       channel1_->SetRemoteContent(&remote_video, SdpType::kAnswer, err));
   EXPECT_FALSE(err.empty());
-  ASSERT_THAT(media_channel1()->recv_codecs(), testing::SizeIs(1));
-  EXPECT_EQ(media_channel1()->recv_codecs()[0].packetization,
+  ASSERT_THAT(media_receive_channel1_impl()->recv_codecs(), testing::SizeIs(1));
+  EXPECT_EQ(media_receive_channel1_impl()->recv_codecs()[0].packetization,
             cricket::kPacketizationParamRaw);
-  EXPECT_THAT(media_channel1()->send_codecs(), testing::IsEmpty());
+  EXPECT_THAT(media_send_channel1_impl()->send_codecs(), testing::IsEmpty());
 }
 
 TEST_F(VideoChannelSingleThreadTest,
@@ -2194,9 +2239,10 @@
   EXPECT_TRUE(err.empty());
   EXPECT_FALSE(channel1_->SetLocalContent(&local_video, SdpType::kAnswer, err));
   EXPECT_FALSE(err.empty());
-  EXPECT_THAT(media_channel1()->recv_codecs(), testing::IsEmpty());
-  ASSERT_THAT(media_channel1()->send_codecs(), testing::SizeIs(1));
-  EXPECT_EQ(media_channel1()->send_codecs()[0].packetization, absl::nullopt);
+  EXPECT_THAT(media_receive_channel1_impl()->recv_codecs(), testing::IsEmpty());
+  ASSERT_THAT(media_send_channel1_impl()->send_codecs(), testing::SizeIs(1));
+  EXPECT_EQ(media_send_channel1_impl()->send_codecs()[0].packetization,
+            absl::nullopt);
 }
 
 // VideoChannelDoubleThreadTest
diff --git a/pc/legacy_stats_collector_unittest.cc b/pc/legacy_stats_collector_unittest.cc
index 3033172..6d3fd9f 100644
--- a/pc/legacy_stats_collector_unittest.cc
+++ b/pc/legacy_stats_collector_unittest.cc
@@ -952,8 +952,9 @@
   VoiceMediaInfo voice_info;
   voice_info.senders.push_back(voice_sender_info);
 
-  auto* voice_media_channel = pc->AddVoiceChannel("audio", "transport");
-  voice_media_channel->SetStats(voice_info);
+  auto voice_media_channels = pc->AddVoiceChannel("audio", "transport");
+  voice_media_channels.first->SetStats(voice_info);
+  voice_media_channels.second->SetStats(voice_info);
 
   AddOutgoingAudioTrack(pc.get(), stats.get());
 
@@ -1526,8 +1527,9 @@
   voice_info.senders.push_back(voice_sender_info);
   voice_info.receivers.push_back(voice_receiver_info);
 
-  auto* voice_media_channel = pc->AddVoiceChannel("voice", "transport");
-  voice_media_channel->SetStats(voice_info);
+  auto voice_media_channels = pc->AddVoiceChannel("voice", "transport");
+  voice_media_channels.first->SetStats(voice_info);
+  voice_media_channels.second->SetStats(voice_info);
 
   stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
 
@@ -1578,8 +1580,9 @@
   VoiceMediaInfo voice_info;
   voice_info.senders.push_back(voice_sender_info);
 
-  auto* voice_media_channel = pc->AddVoiceChannel("audio", "transport");
-  voice_media_channel->SetStats(voice_info);
+  auto voice_media_channels = pc->AddVoiceChannel("audio", "transport");
+  voice_media_channels.first->SetStats(voice_info);
+  voice_media_channels.second->SetStats(voice_info);
 
   StatsReports reports;  // returned values.
   VerifyAudioTrackStats(audio_track_.get(), stats.get(), voice_info, &reports);
@@ -1605,8 +1608,9 @@
   VoiceMediaInfo voice_info;
   voice_info.receivers.push_back(voice_receiver_info);
 
-  auto* voice_media_channel = pc->AddVoiceChannel("audio", "transport");
-  voice_media_channel->SetStats(voice_info);
+  auto voice_media_channels = pc->AddVoiceChannel("audio", "transport");
+  voice_media_channels.first->SetStats(voice_info);
+  voice_media_channels.second->SetStats(voice_info);
 
   StatsReports reports;  // returned values.
   VerifyAudioTrackStats(audio_track_.get(), stats.get(), voice_info, &reports);
@@ -1626,8 +1630,9 @@
   VoiceMediaInfo voice_info;
   voice_info.senders.push_back(voice_sender_info);
 
-  auto* voice_media_channel = pc->AddVoiceChannel("audio", "transport");
-  voice_media_channel->SetStats(voice_info);
+  auto voice_media_channels = pc->AddVoiceChannel("audio", "transport");
+  voice_media_channels.first->SetStats(voice_info);
+  voice_media_channels.second->SetStats(voice_info);
 
   stats->RemoveLocalAudioTrack(audio_track_.get(), kSsrcOfTrack);
 
@@ -1688,8 +1693,9 @@
   voice_info.receivers.push_back(voice_receiver_info);
 
   // Instruct the session to return stats containing the transport channel.
-  auto* voice_media_channel = pc->AddVoiceChannel("audio", "transport");
-  voice_media_channel->SetStats(voice_info);
+  auto voice_media_channels = pc->AddVoiceChannel("audio", "transport");
+  voice_media_channels.first->SetStats(voice_info);
+  voice_media_channels.second->SetStats(voice_info);
 
   stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
 
@@ -1744,8 +1750,9 @@
   VoiceMediaInfo voice_info;
   voice_info.senders.push_back(voice_sender_info);
 
-  auto* voice_media_channel = pc->AddVoiceChannel("voice", "transport");
-  voice_media_channel->SetStats(voice_info);
+  auto voice_media_channels = pc->AddVoiceChannel("voice", "transport");
+  voice_media_channels.first->SetStats(voice_info);
+  voice_media_channels.second->SetStats(voice_info);
 
   StatsReports reports;  // returned values.
   VerifyAudioTrackStats(audio_track_.get(), stats.get(), voice_info, &reports);
@@ -1771,7 +1778,8 @@
                                       &new_voice_sender_info, false);
   VoiceMediaInfo new_voice_info;
   new_voice_info.senders.push_back(new_voice_sender_info);
-  voice_media_channel->SetStats(new_voice_info);
+  voice_media_channels.first->SetStats(new_voice_info);
+  voice_media_channels.second->SetStats(new_voice_info);
 
   reports.clear();
   VerifyAudioTrackStats(new_audio_track.get(), stats.get(), new_voice_info,
@@ -1809,8 +1817,9 @@
   voice_info.senders.push_back(first_sender_info);
   voice_info.senders.push_back(second_sender_info);
 
-  auto* voice_media_channel = pc->AddVoiceChannel("voice", "transport");
-  voice_media_channel->SetStats(voice_info);
+  auto voice_media_channels = pc->AddVoiceChannel("voice", "transport");
+  voice_media_channels.first->SetStats(voice_info);
+  voice_media_channels.second->SetStats(voice_info);
 
   stats->UpdateStats(PeerConnectionInterface::kStatsOutputLevelStandard);
 
diff --git a/pc/peer_connection_media_unittest.cc b/pc/peer_connection_media_unittest.cc
index 322540f..56ff8ff 100644
--- a/pc/peer_connection_media_unittest.cc
+++ b/pc/peer_connection_media_unittest.cc
@@ -75,6 +75,7 @@
 using ::testing::Bool;
 using ::testing::Combine;
 using ::testing::ElementsAre;
+using ::testing::NotNull;
 using ::testing::Values;
 
 class PeerConnectionWrapperForMediaTest : public PeerConnectionWrapper {
@@ -289,28 +290,36 @@
   ASSERT_TRUE(
       caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
 
-  auto* caller_voice = caller->media_engine()->GetVoiceChannel(0);
-  EXPECT_THAT(GetIds(caller_voice->recv_streams()),
+  auto* caller_voice_send = caller->media_engine()->GetVoiceSendChannel(0);
+  auto* caller_voice_receive =
+      caller->media_engine()->GetVoiceReceiveChannel(0);
+  EXPECT_THAT(GetIds(caller_voice_receive->recv_streams()),
               ElementsAre(kCalleeAudioId));
-  EXPECT_THAT(GetIds(caller_voice->send_streams()),
+  EXPECT_THAT(GetIds(caller_voice_send->send_streams()),
               ElementsAre(kCallerAudioId));
 
-  auto* caller_video = caller->media_engine()->GetVideoChannel(0);
-  EXPECT_THAT(GetIds(caller_video->recv_streams()),
+  auto* caller_video_send = caller->media_engine()->GetVideoSendChannel(0);
+  auto* caller_video_receive =
+      caller->media_engine()->GetVideoReceiveChannel(0);
+  EXPECT_THAT(GetIds(caller_video_receive->recv_streams()),
               ElementsAre(kCalleeVideoId));
-  EXPECT_THAT(GetIds(caller_video->send_streams()),
+  EXPECT_THAT(GetIds(caller_video_send->send_streams()),
               ElementsAre(kCallerVideoId));
 
-  auto* callee_voice = callee->media_engine()->GetVoiceChannel(0);
-  EXPECT_THAT(GetIds(callee_voice->recv_streams()),
+  auto* callee_voice_send = callee->media_engine()->GetVoiceSendChannel(0);
+  auto* callee_voice_receive =
+      callee->media_engine()->GetVoiceReceiveChannel(0);
+  EXPECT_THAT(GetIds(callee_voice_receive->recv_streams()),
               ElementsAre(kCallerAudioId));
-  EXPECT_THAT(GetIds(callee_voice->send_streams()),
+  EXPECT_THAT(GetIds(callee_voice_send->send_streams()),
               ElementsAre(kCalleeAudioId));
 
-  auto* callee_video = callee->media_engine()->GetVideoChannel(0);
-  EXPECT_THAT(GetIds(callee_video->recv_streams()),
+  auto* callee_video_send = callee->media_engine()->GetVideoSendChannel(0);
+  auto* callee_video_receive =
+      callee->media_engine()->GetVideoReceiveChannel(0);
+  EXPECT_THAT(GetIds(callee_video_receive->recv_streams()),
               ElementsAre(kCallerVideoId));
-  EXPECT_THAT(GetIds(callee_video->send_streams()),
+  EXPECT_THAT(GetIds(callee_video_send->send_streams()),
               ElementsAre(kCalleeVideoId));
 }
 
@@ -333,8 +342,10 @@
 
   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
 
-  ASSERT_FALSE(callee->media_engine()->GetVoiceChannel(0));
-  ASSERT_FALSE(callee->media_engine()->GetVideoChannel(0));
+  ASSERT_FALSE(callee->media_engine()->GetVoiceSendChannel(0));
+  ASSERT_FALSE(callee->media_engine()->GetVideoSendChannel(0));
+  ASSERT_FALSE(callee->media_engine()->GetVoiceReceiveChannel(0));
+  ASSERT_FALSE(callee->media_engine()->GetVideoReceiveChannel(0));
 }
 
 // Test that removing streams from a subsequent offer causes the receive streams
@@ -354,12 +365,14 @@
 
   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
 
-  auto callee_voice = callee->media_engine()->GetVoiceChannel(0);
-  auto callee_video = callee->media_engine()->GetVideoChannel(0);
-  EXPECT_EQ(1u, callee_voice->send_streams().size());
-  EXPECT_EQ(0u, callee_voice->recv_streams().size());
-  EXPECT_EQ(1u, callee_video->send_streams().size());
-  EXPECT_EQ(0u, callee_video->recv_streams().size());
+  auto callee_voice_send = callee->media_engine()->GetVoiceSendChannel(0);
+  auto callee_video_send = callee->media_engine()->GetVideoSendChannel(0);
+  auto callee_voice_receive = callee->media_engine()->GetVoiceReceiveChannel(0);
+  auto callee_video_receive = callee->media_engine()->GetVideoReceiveChannel(0);
+  EXPECT_EQ(1u, callee_voice_send->send_streams().size());
+  EXPECT_EQ(0u, callee_voice_receive->recv_streams().size());
+  EXPECT_EQ(1u, callee_video_send->send_streams().size());
+  EXPECT_EQ(0u, callee_video_receive->recv_streams().size());
 }
 
 // Test enabling of simulcast with Plan B semantics.
@@ -431,8 +444,8 @@
 
   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
 
-  EXPECT_FALSE(callee->media_engine()->GetVoiceChannel(0));
-  EXPECT_FALSE(callee->media_engine()->GetVideoChannel(0));
+  EXPECT_FALSE(callee->media_engine()->GetVoiceReceiveChannel(0));
+  EXPECT_FALSE(callee->media_engine()->GetVideoReceiveChannel(0));
 }
 
 // Test that removing streams from a subsequent answer causes the send streams
@@ -452,12 +465,14 @@
 
   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
 
-  auto callee_voice = callee->media_engine()->GetVoiceChannel(0);
-  auto callee_video = callee->media_engine()->GetVideoChannel(0);
-  EXPECT_EQ(0u, callee_voice->send_streams().size());
-  EXPECT_EQ(1u, callee_voice->recv_streams().size());
-  EXPECT_EQ(0u, callee_video->send_streams().size());
-  EXPECT_EQ(1u, callee_video->recv_streams().size());
+  auto callee_voice_send = callee->media_engine()->GetVoiceSendChannel(0);
+  auto callee_voice_receive = callee->media_engine()->GetVoiceReceiveChannel(0);
+  auto callee_video_send = callee->media_engine()->GetVideoSendChannel(0);
+  auto callee_video_receive = callee->media_engine()->GetVideoReceiveChannel(0);
+  EXPECT_EQ(0u, callee_voice_send->send_streams().size());
+  EXPECT_EQ(1u, callee_voice_receive->recv_streams().size());
+  EXPECT_EQ(0u, callee_video_send->send_streams().size());
+  EXPECT_EQ(1u, callee_video_receive->recv_streams().size());
 }
 
 // Test that a new stream in a subsequent offer causes a new receive stream to
@@ -474,10 +489,10 @@
 
   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
 
-  auto a1 = callee->media_engine()->GetVoiceChannel(0);
-  auto a2 = callee->media_engine()->GetVoiceChannel(1);
-  auto v1 = callee->media_engine()->GetVideoChannel(0);
-  auto v2 = callee->media_engine()->GetVideoChannel(1);
+  auto a1 = callee->media_engine()->GetVoiceReceiveChannel(0);
+  auto a2 = callee->media_engine()->GetVoiceReceiveChannel(1);
+  auto v1 = callee->media_engine()->GetVideoReceiveChannel(0);
+  auto v2 = callee->media_engine()->GetVideoReceiveChannel(1);
   if (IsUnifiedPlan()) {
     ASSERT_TRUE(a1);
     EXPECT_EQ(1u, a1->recv_streams().size());
@@ -520,9 +535,9 @@
   ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get(), offer_options,
                                               answer_options));
 
-  auto callee_voice = callee->media_engine()->GetVoiceChannel(0);
+  auto callee_voice = callee->media_engine()->GetVoiceSendChannel(0);
   ASSERT_TRUE(callee_voice);
-  auto callee_video = callee->media_engine()->GetVideoChannel(0);
+  auto callee_video = callee->media_engine()->GetVideoSendChannel(0);
   ASSERT_TRUE(callee_video);
 
   if (IsUnifiedPlan()) {
@@ -1095,11 +1110,12 @@
   ASSERT_TRUE(caller->SetRemoteDescription(
       callee->CreateAnswerAndSetAsLocal(options_reject_video)));
 
-  auto caller_voice = caller->media_engine()->GetVoiceChannel(0);
-  ASSERT_TRUE(caller_voice);
-  EXPECT_EQ(0u, caller_voice->recv_streams().size());
-  EXPECT_EQ(1u, caller_voice->send_streams().size());
-  auto caller_video = caller->media_engine()->GetVideoChannel(0);
+  auto caller_voice_send = caller->media_engine()->GetVoiceSendChannel(0);
+  auto caller_voice_receive = caller->media_engine()->GetVoiceReceiveChannel(0);
+  ASSERT_TRUE(caller_voice_send && caller_voice_receive);
+  EXPECT_EQ(0u, caller_voice_receive->recv_streams().size());
+  EXPECT_EQ(1u, caller_voice_send->send_streams().size());
+  auto caller_video = caller->media_engine()->GetVideoSendChannel(0);
   EXPECT_FALSE(caller_video);
 
   // Callee adds its own audio/video stream and offers to receive audio/video
@@ -1110,14 +1126,16 @@
   ASSERT_TRUE(
       caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
 
-  auto callee_voice = callee->media_engine()->GetVoiceChannel(0);
-  ASSERT_TRUE(callee_voice);
-  EXPECT_EQ(1u, callee_voice->recv_streams().size());
-  EXPECT_EQ(1u, callee_voice->send_streams().size());
-  auto callee_video = callee->media_engine()->GetVideoChannel(0);
-  ASSERT_TRUE(callee_video);
-  EXPECT_EQ(1u, callee_video->recv_streams().size());
-  EXPECT_EQ(1u, callee_video->send_streams().size());
+  auto callee_voice_send = callee->media_engine()->GetVoiceSendChannel(0);
+  auto callee_voice_receive = callee->media_engine()->GetVoiceReceiveChannel(0);
+  ASSERT_TRUE(callee_voice_send && callee_voice_receive);
+  EXPECT_EQ(1u, callee_voice_receive->recv_streams().size());
+  EXPECT_EQ(1u, callee_voice_send->send_streams().size());
+  auto callee_video_send = callee->media_engine()->GetVideoSendChannel(0);
+  auto callee_video_receive = callee->media_engine()->GetVideoReceiveChannel(0);
+  ASSERT_TRUE(callee_video_send && callee_video_receive);
+  EXPECT_EQ(1u, callee_video_receive->recv_streams().size());
+  EXPECT_EQ(1u, callee_video_send->send_streams().size());
 
   // Callee removes video but keeps audio and rejects the video once again.
   callee->pc()->RemoveTrackOrError(callee_video_track);
@@ -1125,11 +1143,12 @@
   ASSERT_TRUE(
       callee->SetLocalDescription(callee->CreateAnswer(options_reject_video)));
 
-  callee_voice = callee->media_engine()->GetVoiceChannel(0);
-  ASSERT_TRUE(callee_voice);
-  EXPECT_EQ(1u, callee_voice->recv_streams().size());
-  EXPECT_EQ(1u, callee_voice->send_streams().size());
-  callee_video = callee->media_engine()->GetVideoChannel(0);
+  callee_voice_send = callee->media_engine()->GetVoiceSendChannel(0);
+  callee_voice_receive = callee->media_engine()->GetVoiceReceiveChannel(0);
+  ASSERT_TRUE(callee_voice_send && callee_voice_receive);
+  EXPECT_EQ(1u, callee_voice_receive->recv_streams().size());
+  EXPECT_EQ(1u, callee_voice_send->send_streams().size());
+  auto callee_video = callee->media_engine()->GetVideoSendChannel(0);
   EXPECT_FALSE(callee_video);
 }
 
@@ -1165,12 +1184,13 @@
   ASSERT_TRUE(caller->SetRemoteDescription(
       callee->CreateAnswerAndSetAsLocal(options_reject_audio)));
 
-  auto caller_voice = caller->media_engine()->GetVoiceChannel(0);
+  auto caller_voice = caller->media_engine()->GetVoiceSendChannel(0);
   EXPECT_FALSE(caller_voice);
-  auto caller_video = caller->media_engine()->GetVideoChannel(0);
-  ASSERT_TRUE(caller_video);
-  EXPECT_EQ(0u, caller_video->recv_streams().size());
-  EXPECT_EQ(1u, caller_video->send_streams().size());
+  auto caller_video_send = caller->media_engine()->GetVideoSendChannel(0);
+  auto caller_video_receive = caller->media_engine()->GetVideoReceiveChannel(0);
+  ASSERT_TRUE(caller_video_send && caller_video_receive);
+  EXPECT_EQ(0u, caller_video_receive->recv_streams().size());
+  EXPECT_EQ(1u, caller_video_send->send_streams().size());
 
   // Callee adds its own audio/video stream and offers to receive audio/video
   // too.
@@ -1180,14 +1200,16 @@
   ASSERT_TRUE(caller->SetRemoteDescription(
       callee->CreateAnswerAndSetAsLocal(options_no_bundle)));
 
-  auto callee_voice = callee->media_engine()->GetVoiceChannel(0);
-  ASSERT_TRUE(callee_voice);
-  EXPECT_EQ(1u, callee_voice->recv_streams().size());
-  EXPECT_EQ(1u, callee_voice->send_streams().size());
-  auto callee_video = callee->media_engine()->GetVideoChannel(0);
-  ASSERT_TRUE(callee_video);
-  EXPECT_EQ(1u, callee_video->recv_streams().size());
-  EXPECT_EQ(1u, callee_video->send_streams().size());
+  auto callee_voice_send = callee->media_engine()->GetVoiceSendChannel(0);
+  auto callee_voice_receive = callee->media_engine()->GetVoiceReceiveChannel(0);
+  ASSERT_TRUE(callee_voice_send && callee_voice_receive);
+  EXPECT_EQ(1u, callee_voice_receive->recv_streams().size());
+  EXPECT_EQ(1u, callee_voice_send->send_streams().size());
+  auto callee_video_send = callee->media_engine()->GetVideoSendChannel(0);
+  auto callee_video_receive = callee->media_engine()->GetVideoReceiveChannel(0);
+  ASSERT_TRUE(callee_video_send && callee_video_receive);
+  EXPECT_EQ(1u, callee_video_receive->recv_streams().size());
+  EXPECT_EQ(1u, callee_video_send->send_streams().size());
 
   // Callee removes audio but keeps video and rejects the audio once again.
   callee->pc()->RemoveTrackOrError(callee_audio_track);
@@ -1195,12 +1217,13 @@
   ASSERT_TRUE(
       callee->SetLocalDescription(callee->CreateAnswer(options_reject_audio)));
 
-  callee_voice = callee->media_engine()->GetVoiceChannel(0);
+  auto callee_voice = callee->media_engine()->GetVoiceReceiveChannel(0);
   EXPECT_FALSE(callee_voice);
-  callee_video = callee->media_engine()->GetVideoChannel(0);
-  ASSERT_TRUE(callee_video);
-  EXPECT_EQ(1u, callee_video->recv_streams().size());
-  EXPECT_EQ(1u, callee_video->send_streams().size());
+  callee_video_send = callee->media_engine()->GetVideoSendChannel(0);
+  callee_video_receive = callee->media_engine()->GetVideoReceiveChannel(0);
+  ASSERT_TRUE(callee_video_send && callee_video_receive);
+  EXPECT_EQ(1u, callee_video_receive->recv_streams().size());
+  EXPECT_EQ(1u, callee_video_send->send_streams().size());
 }
 
 // Tests that if the underlying video encoder fails to be initialized (signaled
@@ -1212,7 +1235,7 @@
 
   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
 
-  auto video_channel = caller->media_engine()->GetVideoChannel(0);
+  auto video_channel = caller->media_engine()->GetVideoSendChannel(0);
   video_channel->set_fail_set_send_codecs(true);
 
   std::string error;
@@ -1235,7 +1258,7 @@
 
   ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
 
-  auto video_channel = caller->media_engine()->GetVideoChannel(0);
+  auto video_channel = caller->media_engine()->GetVideoSendChannel(0);
   video_channel->set_fail_set_send_codecs(true);
 
   EXPECT_FALSE(
@@ -1333,7 +1356,7 @@
 
   ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
 
-  auto caller_voice = caller->media_engine()->GetVoiceChannel(0);
+  auto caller_voice = caller->media_engine()->GetVoiceSendChannel(0);
   ASSERT_TRUE(caller_voice);
   const cricket::AudioOptions& audio_options = caller_voice->options();
   EXPECT_EQ(config.combined_audio_video_bwe,
@@ -2236,6 +2259,7 @@
   EXPECT_TRUE(video_transceiver->SetCodecPreferences(capabilities.codecs).ok());
 
   auto reoffer = caller->CreateOffer(options);
+  ASSERT_THAT(reoffer, NotNull());
 
   EXPECT_FALSE(HasPayloadTypeConflict(reoffer->description()));
   // Sanity check that we got the primary codec and RTX.
diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc
index 8c161a2..dded283 100644
--- a/pc/rtc_stats_collector_unittest.cc
+++ b/pc/rtc_stats_collector_unittest.cc
@@ -814,6 +814,11 @@
     EXPECT_EQ(*outbound_rtp.codec_id, graph.send_codec_id);
     EXPECT_EQ(*outbound_rtp.track_id, graph.sender_track_id);
     EXPECT_EQ(*outbound_rtp.transport_id, graph.transport_id);
+    EXPECT_TRUE(graph.full_report->Get(graph.inbound_rtp_id));
+    // We can't use an ASSERT in a function returning non-void, so just return.
+    if (!graph.full_report->Get(graph.inbound_rtp_id)) {
+      return graph;
+    }
     const auto& inbound_rtp = graph.full_report->Get(graph.inbound_rtp_id)
                                   ->cast_to<RTCInboundRTPStreamStats>();
     EXPECT_EQ(*inbound_rtp.codec_id, graph.recv_codec_id);
@@ -931,6 +936,11 @@
     EXPECT_EQ(*outbound_rtp.codec_id, graph.send_codec_id);
     EXPECT_EQ(*outbound_rtp.track_id, graph.sender_track_id);
     EXPECT_EQ(*outbound_rtp.transport_id, graph.transport_id);
+    EXPECT_TRUE(graph.full_report->Get(graph.inbound_rtp_id));
+    // We can't use ASSERT in a function with a return value.
+    if (!graph.full_report->Get(graph.inbound_rtp_id)) {
+      return graph;
+    }
     const auto& inbound_rtp = graph.full_report->Get(graph.inbound_rtp_id)
                                   ->cast_to<RTCInboundRTPStreamStats>();
     EXPECT_EQ(*inbound_rtp.codec_id, graph.recv_codec_id);
@@ -1177,9 +1187,9 @@
   outbound_video_info.codec_payload_type = 4;
   video_media_info.senders.push_back(outbound_video_info);
 
-  FakeVoiceMediaChannelForStats* audio_channel =
+  auto audio_channels =
       pc_->AddVoiceChannel("AudioMid", "TransportName", voice_media_info);
-  FakeVideoMediaChannelForStats* video_channel =
+  auto video_channels =
       pc_->AddVideoChannel("VideoMid", "TransportName", video_media_info);
 
   rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
@@ -1244,10 +1254,12 @@
   // being exposed, despite `send_codecs` and `receive_codecs` still being set.
   voice_media_info.senders.clear();
   voice_media_info.receivers.clear();
-  audio_channel->SetStats(voice_media_info);
+  audio_channels.first->SetStats(voice_media_info);
+  audio_channels.second->SetStats(voice_media_info);
   video_media_info.senders.clear();
   video_media_info.receivers.clear();
-  video_channel->SetStats(video_media_info);
+  video_channels.first->SetStats(video_media_info);
+  video_channels.second->SetStats(video_media_info);
   stats_->stats_collector()->ClearCachedStatsReport();
   report = stats_->GetStatsReport();
   EXPECT_FALSE(report->Get(expected_inbound_audio_codec.id()));
@@ -2495,7 +2507,7 @@
   voice_media_info.receive_codecs.insert(
       std::make_pair(codec_parameters.payload_type, codec_parameters));
 
-  auto* voice_media_channel =
+  auto voice_media_channels =
       pc_->AddVoiceChannel("AudioMid", "TransportName", voice_media_info);
   stats_->SetupRemoteTrackAndReceiver(
       cricket::MEDIA_TYPE_AUDIO, "RemoteAudioTrackID", "RemoteStreamId", 1);
@@ -2561,7 +2573,8 @@
   expected_audio.last_packet_received_timestamp = 3000.0;
   voice_media_info.receivers[0].estimated_playout_ntp_timestamp_ms = 4567;
   expected_audio.estimated_playout_timestamp = 4567;
-  voice_media_channel->SetStats(voice_media_info);
+  voice_media_channels.first->SetStats(voice_media_info);
+  voice_media_channels.second->SetStats(voice_media_info);
 
   report = stats_->GetFreshStatsReport();
 
@@ -2673,7 +2686,7 @@
   video_media_info.receive_codecs.insert(
       std::make_pair(codec_parameters.payload_type, codec_parameters));
 
-  auto* video_media_channel =
+  auto video_media_channels =
       pc_->AddVideoChannel("VideoMid", "TransportName", video_media_info);
   stats_->SetupRemoteTrackAndReceiver(
       cricket::MEDIA_TYPE_VIDEO, "RemoteVideoTrackID", "RemoteStreamId", 1);
@@ -2743,7 +2756,8 @@
   expected_video.decoder_implementation = "libfoodecoder";
   video_media_info.receivers[0].power_efficient_decoder = true;
   expected_video.power_efficient_decoder = true;
-  video_media_channel->SetStats(video_media_info);
+  video_media_channels.first->SetStats(video_media_info);
+  video_media_channels.second->SetStats(video_media_info);
 
   report = stats_->GetFreshStatsReport();
 
@@ -2932,7 +2946,7 @@
   video_media_info.send_codecs.insert(
       std::make_pair(codec_parameters.payload_type, codec_parameters));
 
-  auto* video_media_channel =
+  auto video_media_channels =
       pc_->AddVideoChannel("VideoMid", "TransportName", video_media_info);
   stats_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_VIDEO,
                                    "LocalVideoTrackID", 1, true,
@@ -3002,7 +3016,8 @@
   expected_video.encoder_implementation = "libfooencoder";
   video_media_info.senders[0].power_efficient_encoder = true;
   expected_video.power_efficient_encoder = true;
-  video_media_channel->SetStats(video_media_info);
+  video_media_channels.first->SetStats(video_media_info);
+  video_media_channels.second->SetStats(video_media_info);
 
   report = stats_->GetFreshStatsReport();
 
diff --git a/pc/rtp_sender_receiver_unittest.cc b/pc/rtp_sender_receiver_unittest.cc
index 5e7c1f6..8be3a0a 100644
--- a/pc/rtp_sender_receiver_unittest.cc
+++ b/pc/rtp_sender_receiver_unittest.cc
@@ -119,45 +119,60 @@
     // Create the channels, discard the result; we get them later.
     // Fake media channels are owned by the media engine.
     media_engine_->voice().CreateMediaChannel(
-        cricket::MediaChannel::Role::kBoth, &fake_call_, cricket::MediaConfig(),
+        cricket::MediaChannel::Role::kSend, &fake_call_, cricket::MediaConfig(),
         cricket::AudioOptions(), webrtc::CryptoOptions(),
         webrtc::AudioCodecPairId::Create());
     media_engine_->video().CreateMediaChannel(
-        cricket::MediaChannel::Role::kBoth, &fake_call_, cricket::MediaConfig(),
+        cricket::MediaChannel::Role::kSend, &fake_call_, cricket::MediaConfig(),
         cricket::VideoOptions(), webrtc::CryptoOptions(),
         video_bitrate_allocator_factory_.get());
-    // TODO(hta): Split into sender and receiver channels
+    media_engine_->voice().CreateMediaChannel(
+        cricket::MediaChannel::Role::kReceive, &fake_call_,
+        cricket::MediaConfig(), cricket::AudioOptions(),
+        webrtc::CryptoOptions(), webrtc::AudioCodecPairId::Create());
+    media_engine_->video().CreateMediaChannel(
+        cricket::MediaChannel::Role::kReceive, &fake_call_,
+        cricket::MediaConfig(), cricket::VideoOptions(),
+        webrtc::CryptoOptions(), video_bitrate_allocator_factory_.get());
 
-    voice_media_channel_ = absl::WrapUnique(media_engine_->GetVoiceChannel(0));
-    video_media_channel_ = absl::WrapUnique(media_engine_->GetVideoChannel(0));
+    voice_media_send_channel_ =
+        absl::WrapUnique(media_engine_->GetVoiceSendChannel(0));
+    video_media_send_channel_ =
+        absl::WrapUnique(media_engine_->GetVideoSendChannel(0));
+    voice_media_receive_channel_ =
+        absl::WrapUnique(media_engine_->GetVoiceReceiveChannel(0));
+    video_media_receive_channel_ =
+        absl::WrapUnique(media_engine_->GetVideoReceiveChannel(0));
 
-    RTC_CHECK(voice_media_channel());
-    RTC_CHECK(video_media_channel());
+    RTC_CHECK(voice_media_send_channel());
+    RTC_CHECK(video_media_send_channel());
+    RTC_CHECK(voice_media_receive_channel());
+    RTC_CHECK(video_media_receive_channel());
     // Create sender channel objects
-    voice_send_channel_ =
-        std::make_unique<cricket::VoiceMediaSendChannel>(voice_media_channel());
-    video_send_channel_ =
-        std::make_unique<cricket::VideoMediaSendChannel>(video_media_channel());
+    voice_send_channel_ = std::make_unique<cricket::VoiceMediaSendChannel>(
+        voice_media_send_channel());
+    video_send_channel_ = std::make_unique<cricket::VideoMediaSendChannel>(
+        video_media_send_channel());
 
     // Create streams for predefined SSRCs. Streams need to exist in order
     // for the senders and receievers to apply parameters to them.
     // Normally these would be created by SetLocalDescription and
     // SetRemoteDescription.
-    voice_media_channel_->AddSendStream(
+    voice_media_send_channel_->AddSendStream(
         cricket::StreamParams::CreateLegacy(kAudioSsrc));
-    voice_media_channel_->AddRecvStream(
+    voice_media_receive_channel_->AddRecvStream(
         cricket::StreamParams::CreateLegacy(kAudioSsrc));
-    voice_media_channel_->AddSendStream(
+    voice_media_send_channel_->AddSendStream(
         cricket::StreamParams::CreateLegacy(kAudioSsrc2));
-    voice_media_channel_->AddRecvStream(
+    voice_media_receive_channel_->AddRecvStream(
         cricket::StreamParams::CreateLegacy(kAudioSsrc2));
-    video_media_channel_->AddSendStream(
+    video_media_send_channel_->AddSendStream(
         cricket::StreamParams::CreateLegacy(kVideoSsrc));
-    video_media_channel_->AddRecvStream(
+    video_media_receive_channel_->AddRecvStream(
         cricket::StreamParams::CreateLegacy(kVideoSsrc));
-    video_media_channel_->AddSendStream(
+    video_media_send_channel_->AddSendStream(
         cricket::StreamParams::CreateLegacy(kVideoSsrc2));
-    video_media_channel_->AddRecvStream(
+    video_media_receive_channel_->AddRecvStream(
         cricket::StreamParams::CreateLegacy(kVideoSsrc2));
   }
 
@@ -185,7 +200,7 @@
     const cricket::AudioCodec kTelephoneEventCodec(106, "telephone-event", 8000,
                                                    0, 1);
     params.codecs.push_back(kTelephoneEventCodec);
-    voice_media_channel()->SetSendParameters(params);
+    voice_media_send_channel()->SetSendParameters(params);
   }
 
   void AddVideoTrack() { AddVideoTrack(false); }
@@ -221,7 +236,7 @@
     audio_rtp_sender_ =
         AudioRtpSender::Create(worker_thread_, /*id=*/"", nullptr, nullptr);
     audio_rtp_sender_->SetMediaChannel(
-        voice_media_channel()->AsVoiceSendChannel());
+        voice_media_send_channel()->AsVoiceSendChannel());
   }
 
   void CreateVideoRtpSender(uint32_t ssrc) {
@@ -240,7 +255,7 @@
   }
 
   uint32_t CreateVideoRtpSender(const cricket::StreamParams& stream_params) {
-    video_media_channel_->AddSendStream(stream_params);
+    video_media_send_channel_->AddSendStream(stream_params);
     uint32_t primary_ssrc = stream_params.first_ssrc();
     CreateVideoRtpSender(primary_ssrc);
     return primary_ssrc;
@@ -274,7 +289,7 @@
     EXPECT_CALL(*set_streams_observer, OnSetStreams());
     video_rtp_sender_->SetStreams({local_stream_->id()});
     video_rtp_sender_->SetMediaChannel(
-        video_media_channel()->AsVideoSendChannel());
+        video_media_send_channel()->AsVideoSendChannel());
     video_rtp_sender_->SetSsrc(ssrc);
     VerifyVideoChannelInput(ssrc);
   }
@@ -282,7 +297,7 @@
     video_rtp_sender_ =
         VideoRtpSender::Create(worker_thread_, /*id=*/"", nullptr);
     video_rtp_sender_->SetMediaChannel(
-        video_media_channel()->AsVideoSendChannel());
+        video_media_send_channel()->AsVideoSendChannel());
   }
 
   void DestroyAudioRtpSender() {
@@ -301,7 +316,7 @@
         rtc::Thread::Current(), kAudioTrackId, streams,
         /*is_unified_plan=*/true);
     audio_rtp_receiver_->SetMediaChannel(
-        voice_media_channel()->AsVoiceReceiveChannel());
+        voice_media_receive_channel()->AsVoiceReceiveChannel());
     audio_rtp_receiver_->SetupMediaChannel(kAudioSsrc);
     audio_track_ = audio_rtp_receiver_->audio_track();
     VerifyVoiceChannelOutput();
@@ -312,7 +327,7 @@
     video_rtp_receiver_ = rtc::make_ref_counted<VideoRtpReceiver>(
         rtc::Thread::Current(), kVideoTrackId, streams);
     video_rtp_receiver_->SetMediaChannel(
-        video_media_channel()->AsVideoReceiveChannel());
+        video_media_receive_channel()->AsVideoReceiveChannel());
     video_rtp_receiver_->SetupMediaChannel(kVideoSsrc);
     video_track_ = video_rtp_receiver_->video_track();
     VerifyVideoChannelOutput();
@@ -327,13 +342,13 @@
       ssrcs.push_back(kVideoSsrcSimulcast + i);
     cricket::StreamParams stream_params =
         cricket::CreateSimStreamParams("cname", ssrcs);
-    video_media_channel_->AddRecvStream(stream_params);
+    video_media_receive_channel_->AddRecvStream(stream_params);
     uint32_t primary_ssrc = stream_params.first_ssrc();
 
     video_rtp_receiver_ = rtc::make_ref_counted<VideoRtpReceiver>(
         rtc::Thread::Current(), kVideoTrackId, streams);
     video_rtp_receiver_->SetMediaChannel(
-        video_media_channel()->AsVideoReceiveChannel());
+        video_media_receive_channel()->AsVideoReceiveChannel());
     video_rtp_receiver_->SetupMediaChannel(primary_ssrc);
     video_track_ = video_rtp_receiver_->video_track();
   }
@@ -360,53 +375,55 @@
   void VerifyVoiceChannelInput(uint32_t ssrc) {
     // Verify that the media channel has an audio source, and the stream isn't
     // muted.
-    EXPECT_TRUE(voice_media_channel()->HasSource(ssrc));
-    EXPECT_FALSE(voice_media_channel()->IsStreamMuted(ssrc));
+    EXPECT_TRUE(voice_media_send_channel()->HasSource(ssrc));
+    EXPECT_FALSE(voice_media_send_channel()->IsStreamMuted(ssrc));
   }
 
   void VerifyVideoChannelInput() { VerifyVideoChannelInput(kVideoSsrc); }
 
   void VerifyVideoChannelInput(uint32_t ssrc) {
     // Verify that the media channel has a video source,
-    EXPECT_TRUE(video_media_channel_->HasSource(ssrc));
+    EXPECT_TRUE(video_media_send_channel_->HasSource(ssrc));
   }
 
   void VerifyVoiceChannelNoInput() { VerifyVoiceChannelNoInput(kAudioSsrc); }
 
   void VerifyVoiceChannelNoInput(uint32_t ssrc) {
     // Verify that the media channel's source is reset.
-    EXPECT_FALSE(voice_media_channel()->HasSource(ssrc));
+    EXPECT_FALSE(voice_media_receive_channel()->HasSource(ssrc));
   }
 
   void VerifyVideoChannelNoInput() { VerifyVideoChannelNoInput(kVideoSsrc); }
 
   void VerifyVideoChannelNoInput(uint32_t ssrc) {
     // Verify that the media channel's source is reset.
-    EXPECT_FALSE(video_media_channel_->HasSource(ssrc));
+    EXPECT_FALSE(video_media_receive_channel_->HasSource(ssrc));
   }
 
   void VerifyVoiceChannelOutput() {
     // Verify that the volume is initialized to 1.
     double volume;
-    EXPECT_TRUE(voice_media_channel()->GetOutputVolume(kAudioSsrc, &volume));
+    EXPECT_TRUE(
+        voice_media_receive_channel()->GetOutputVolume(kAudioSsrc, &volume));
     EXPECT_EQ(1, volume);
   }
 
   void VerifyVideoChannelOutput() {
     // Verify that the media channel has a sink.
-    EXPECT_TRUE(video_media_channel_->HasSink(kVideoSsrc));
+    EXPECT_TRUE(video_media_receive_channel_->HasSink(kVideoSsrc));
   }
 
   void VerifyVoiceChannelNoOutput() {
     // Verify that the volume is reset to 0.
     double volume;
-    EXPECT_TRUE(voice_media_channel()->GetOutputVolume(kAudioSsrc, &volume));
+    EXPECT_TRUE(
+        voice_media_receive_channel()->GetOutputVolume(kAudioSsrc, &volume));
     EXPECT_EQ(0, volume);
   }
 
   void VerifyVideoChannelNoOutput() {
     // Verify that the media channel's sink is reset.
-    EXPECT_FALSE(video_media_channel_->HasSink(kVideoSsrc));
+    EXPECT_FALSE(video_media_receive_channel_->HasSink(kVideoSsrc));
   }
 
   // Verifies that the encoding layers contain the specified RIDs.
@@ -483,7 +500,8 @@
     RunDisableEncodingLayersTest(all_layers, disabled_layers,
                                  video_rtp_sender_.get());
 
-    auto channel_parameters = video_media_channel_->GetRtpSendParameters(ssrc);
+    auto channel_parameters =
+        video_media_send_channel_->GetRtpSendParameters(ssrc);
     ASSERT_EQ(channel_parameters.encodings.size(), all_layers.size());
     for (size_t i = 0; i < all_layers.size(); ++i) {
       EXPECT_EQ(all_layers[i], channel_parameters.encodings[i].rid);
@@ -506,11 +524,17 @@
   }
 
  protected:
-  cricket::FakeVideoMediaChannel* video_media_channel() {
-    return video_media_channel_.get();
+  cricket::FakeVideoMediaChannel* video_media_send_channel() {
+    return video_media_send_channel_.get();
   }
-  cricket::FakeVoiceMediaChannel* voice_media_channel() {
-    return voice_media_channel_.get();
+  cricket::FakeVoiceMediaChannel* voice_media_send_channel() {
+    return voice_media_send_channel_.get();
+  }
+  cricket::FakeVideoMediaChannel* video_media_receive_channel() {
+    return video_media_receive_channel_.get();
+  }
+  cricket::FakeVoiceMediaChannel* voice_media_receive_channel() {
+    return voice_media_receive_channel_.get();
   }
 
   test::RunLoop run_loop_;
@@ -526,8 +550,10 @@
   std::unique_ptr<cricket::FakeMediaEngine> media_engine_;
   rtc::UniqueRandomIdGenerator ssrc_generator_;
   cricket::FakeCall fake_call_;
-  std::unique_ptr<cricket::FakeVoiceMediaChannel> voice_media_channel_;
-  std::unique_ptr<cricket::FakeVideoMediaChannel> video_media_channel_;
+  std::unique_ptr<cricket::FakeVoiceMediaChannel> voice_media_send_channel_;
+  std::unique_ptr<cricket::FakeVideoMediaChannel> video_media_send_channel_;
+  std::unique_ptr<cricket::FakeVoiceMediaChannel> voice_media_receive_channel_;
+  std::unique_ptr<cricket::FakeVideoMediaChannel> video_media_receive_channel_;
   std::unique_ptr<cricket::VoiceMediaSendChannel> voice_send_channel_;
   std::unique_ptr<cricket::VideoMediaSendChannel> video_send_channel_;
   rtc::scoped_refptr<AudioRtpSender> audio_rtp_sender_;
@@ -585,7 +611,7 @@
   auto source = LocalAudioSource::Create(&options);
   CreateAudioRtpSender(source);
 
-  EXPECT_EQ(true, voice_media_channel()->options().echo_cancellation);
+  EXPECT_EQ(true, voice_media_send_channel()->options().echo_cancellation);
 
   DestroyAudioRtpSender();
 }
@@ -596,10 +622,10 @@
   CreateAudioRtpSender();
 
   audio_track_->set_enabled(false);
-  EXPECT_TRUE(voice_media_channel()->IsStreamMuted(kAudioSsrc));
+  EXPECT_TRUE(voice_media_send_channel()->IsStreamMuted(kAudioSsrc));
 
   audio_track_->set_enabled(true);
-  EXPECT_FALSE(voice_media_channel()->IsStreamMuted(kAudioSsrc));
+  EXPECT_FALSE(voice_media_send_channel()->IsStreamMuted(kAudioSsrc));
 
   DestroyAudioRtpSender();
 }
@@ -610,19 +636,22 @@
   CreateAudioRtpReceiver();
 
   double volume;
-  EXPECT_TRUE(voice_media_channel()->GetOutputVolume(kAudioSsrc, &volume));
+  EXPECT_TRUE(
+      voice_media_receive_channel()->GetOutputVolume(kAudioSsrc, &volume));
   EXPECT_EQ(1, volume);
 
   // Handling of enable/disable is applied asynchronously.
   audio_track_->set_enabled(false);
   run_loop_.Flush();
 
-  EXPECT_TRUE(voice_media_channel()->GetOutputVolume(kAudioSsrc, &volume));
+  EXPECT_TRUE(
+      voice_media_receive_channel()->GetOutputVolume(kAudioSsrc, &volume));
   EXPECT_EQ(0, volume);
 
   audio_track_->set_enabled(true);
   run_loop_.Flush();
-  EXPECT_TRUE(voice_media_channel()->GetOutputVolume(kAudioSsrc, &volume));
+  EXPECT_TRUE(
+      voice_media_receive_channel()->GetOutputVolume(kAudioSsrc, &volume));
   EXPECT_EQ(1, volume);
 
   DestroyAudioRtpReceiver();
@@ -677,7 +706,8 @@
   double volume;
   audio_track_->GetSource()->SetVolume(0.5);
   run_loop_.Flush();
-  EXPECT_TRUE(voice_media_channel()->GetOutputVolume(kAudioSsrc, &volume));
+  EXPECT_TRUE(
+      voice_media_receive_channel()->GetOutputVolume(kAudioSsrc, &volume));
   EXPECT_EQ(0.5, volume);
 
   // Disable the audio track, this should prevent setting the volume.
@@ -685,19 +715,22 @@
   RTC_DCHECK_EQ(worker_thread_, run_loop_.task_queue());
   run_loop_.Flush();
   audio_track_->GetSource()->SetVolume(0.8);
-  EXPECT_TRUE(voice_media_channel()->GetOutputVolume(kAudioSsrc, &volume));
+  EXPECT_TRUE(
+      voice_media_receive_channel()->GetOutputVolume(kAudioSsrc, &volume));
   EXPECT_EQ(0, volume);
 
   // When the track is enabled, the previously set volume should take effect.
   audio_track_->set_enabled(true);
   run_loop_.Flush();
-  EXPECT_TRUE(voice_media_channel()->GetOutputVolume(kAudioSsrc, &volume));
+  EXPECT_TRUE(
+      voice_media_receive_channel()->GetOutputVolume(kAudioSsrc, &volume));
   EXPECT_EQ(0.8, volume);
 
   // Try changing volume one more time.
   audio_track_->GetSource()->SetVolume(0.9);
   run_loop_.Flush();
-  EXPECT_TRUE(voice_media_channel()->GetOutputVolume(kAudioSsrc, &volume));
+  EXPECT_TRUE(
+      voice_media_receive_channel()->GetOutputVolume(kAudioSsrc, &volume));
   EXPECT_EQ(0.9, volume);
 
   DestroyAudioRtpReceiver();
@@ -706,16 +739,16 @@
 TEST_F(RtpSenderReceiverTest, AudioRtpReceiverDelay) {
   CreateAudioRtpReceiver();
   VerifyRtpReceiverDelayBehaviour(
-      voice_media_channel()->AsVoiceReceiveChannel(), audio_rtp_receiver_.get(),
-      kAudioSsrc);
+      voice_media_receive_channel()->AsVoiceReceiveChannel(),
+      audio_rtp_receiver_.get(), kAudioSsrc);
   DestroyAudioRtpReceiver();
 }
 
 TEST_F(RtpSenderReceiverTest, VideoRtpReceiverDelay) {
   CreateVideoRtpReceiver();
   VerifyRtpReceiverDelayBehaviour(
-      video_media_channel()->AsVideoReceiveChannel(), video_rtp_receiver_.get(),
-      kVideoSsrc);
+      video_media_receive_channel()->AsVideoReceiveChannel(),
+      video_rtp_receiver_.get(), kVideoSsrc);
   DestroyVideoRtpReceiver();
 }
 
@@ -953,9 +986,9 @@
   std::vector<uint32_t> ssrcs(1, 1);
   cricket::StreamParams stream_params =
       cricket::CreateSimStreamParams("cname", ssrcs);
-  voice_media_channel()->AddSendStream(stream_params);
+  voice_media_send_channel()->AddSendStream(stream_params);
   audio_rtp_sender_->SetMediaChannel(
-      voice_media_channel()->AsVoiceSendChannel());
+      voice_media_send_channel()->AsVoiceSendChannel());
   audio_rtp_sender_->SetSsrc(1);
 
   params = audio_rtp_sender_->GetParameters();
@@ -1070,7 +1103,7 @@
 TEST_F(RtpSenderReceiverTest, SetAudioMaxSendBitrate) {
   CreateAudioRtpSender();
 
-  EXPECT_EQ(-1, voice_media_channel()->max_bps());
+  EXPECT_EQ(-1, voice_media_send_channel()->max_bps());
   webrtc::RtpParameters params = audio_rtp_sender_->GetParameters();
   EXPECT_EQ(1U, params.encodings.size());
   EXPECT_FALSE(params.encodings[0].max_bitrate_bps);
@@ -1083,12 +1116,12 @@
   EXPECT_EQ(1000, params.encodings[0].max_bitrate_bps);
 
   // Verify that the audio channel received the new parameters.
-  params = voice_media_channel()->GetRtpSendParameters(kAudioSsrc);
+  params = voice_media_send_channel()->GetRtpSendParameters(kAudioSsrc);
   EXPECT_EQ(1U, params.encodings.size());
   EXPECT_EQ(1000, params.encodings[0].max_bitrate_bps);
 
   // Verify that the global bitrate limit has not been changed.
-  EXPECT_EQ(-1, voice_media_channel()->max_bps());
+  EXPECT_EQ(-1, voice_media_send_channel()->max_bps());
 
   DestroyAudioRtpSender();
 }
@@ -1108,7 +1141,7 @@
   EXPECT_EQ(1U, params.encodings.size());
   EXPECT_EQ(new_bitrate_priority, params.encodings[0].bitrate_priority);
 
-  params = voice_media_channel()->GetRtpSendParameters(kAudioSsrc);
+  params = voice_media_send_channel()->GetRtpSendParameters(kAudioSsrc);
   EXPECT_EQ(1U, params.encodings.size());
   EXPECT_EQ(new_bitrate_priority, params.encodings[0].bitrate_priority);
 
@@ -1207,9 +1240,9 @@
     ssrcs.push_back(kVideoSsrcSimulcast + i);
   cricket::StreamParams stream_params =
       cricket::CreateSimStreamParams("cname", ssrcs);
-  video_media_channel()->AddSendStream(stream_params);
+  video_media_send_channel()->AddSendStream(stream_params);
   video_rtp_sender_->SetMediaChannel(
-      video_media_channel()->AsVideoSendChannel());
+      video_media_send_channel()->AsVideoSendChannel());
   video_rtp_sender_->SetSsrc(kVideoSsrcSimulcast);
 
   params = video_rtp_sender_->GetParameters();
@@ -1248,9 +1281,9 @@
     ssrcs.push_back(kVideoSsrcSimulcast + i);
   cricket::StreamParams stream_params =
       cricket::CreateSimStreamParams("cname", ssrcs);
-  video_media_channel()->AddSendStream(stream_params);
+  video_media_send_channel()->AddSendStream(stream_params);
   video_rtp_sender_->SetMediaChannel(
-      video_media_channel()->AsVideoSendChannel());
+      video_media_send_channel()->AsVideoSendChannel());
   video_rtp_sender_->SetSsrc(kVideoSsrcSimulcast);
 
   params = video_rtp_sender_->GetParameters();
@@ -1292,9 +1325,9 @@
     ssrcs.push_back(kVideoSsrcSimulcast + i);
   cricket::StreamParams stream_params =
       cricket::StreamParams::CreateLegacy(kVideoSsrc);
-  video_media_channel()->AddSendStream(stream_params);
+  video_media_send_channel()->AddSendStream(stream_params);
   video_rtp_sender_->SetMediaChannel(
-      video_media_channel()->AsVideoSendChannel());
+      video_media_send_channel()->AsVideoSendChannel());
   EXPECT_DEATH(video_rtp_sender_->SetSsrc(kVideoSsrcSimulcast), "");
 }
 #endif
@@ -1525,7 +1558,7 @@
 TEST_F(RtpSenderReceiverTest, SetVideoMinMaxSendBitrate) {
   CreateVideoRtpSender();
 
-  EXPECT_EQ(-1, video_media_channel()->max_bps());
+  EXPECT_EQ(-1, video_media_send_channel()->max_bps());
   webrtc::RtpParameters params = video_rtp_sender_->GetParameters();
   EXPECT_EQ(1U, params.encodings.size());
   EXPECT_FALSE(params.encodings[0].min_bitrate_bps);
@@ -1541,13 +1574,13 @@
   EXPECT_EQ(1000, params.encodings[0].max_bitrate_bps);
 
   // Verify that the video channel received the new parameters.
-  params = video_media_channel()->GetRtpSendParameters(kVideoSsrc);
+  params = video_media_send_channel()->GetRtpSendParameters(kVideoSsrc);
   EXPECT_EQ(1U, params.encodings.size());
   EXPECT_EQ(100, params.encodings[0].min_bitrate_bps);
   EXPECT_EQ(1000, params.encodings[0].max_bitrate_bps);
 
   // Verify that the global bitrate limit has not been changed.
-  EXPECT_EQ(-1, video_media_channel()->max_bps());
+  EXPECT_EQ(-1, video_media_send_channel()->max_bps());
 
   DestroyVideoRtpSender();
 }
@@ -1565,7 +1598,8 @@
   EXPECT_TRUE(video_rtp_sender_->SetParameters(params).ok());
 
   // Verify that the video channel received the new parameters.
-  params = video_media_channel()->GetRtpSendParameters(kVideoSsrcSimulcast);
+  params =
+      video_media_send_channel()->GetRtpSendParameters(kVideoSsrcSimulcast);
   EXPECT_EQ(kVideoSimulcastLayerCount, params.encodings.size());
   EXPECT_EQ(100, params.encodings[0].min_bitrate_bps);
   EXPECT_EQ(1000, params.encodings[0].max_bitrate_bps);
@@ -1590,7 +1624,7 @@
   EXPECT_EQ(1U, params.encodings.size());
   EXPECT_EQ(new_bitrate_priority, params.encodings[0].bitrate_priority);
 
-  params = video_media_channel()->GetRtpSendParameters(kVideoSsrc);
+  params = video_media_send_channel()->GetRtpSendParameters(kVideoSsrc);
   EXPECT_EQ(1U, params.encodings.size());
   EXPECT_EQ(new_bitrate_priority, params.encodings[0].bitrate_priority);
 
@@ -1644,23 +1678,23 @@
   video_track_->set_enabled(true);
 
   // `video_track_` is not screencast by default.
-  EXPECT_EQ(false, video_media_channel()->options().is_screencast);
+  EXPECT_EQ(false, video_media_send_channel()->options().is_screencast);
   // No content hint should be set by default.
   EXPECT_EQ(VideoTrackInterface::ContentHint::kNone,
             video_track_->content_hint());
   // Setting detailed should turn a non-screencast source into screencast mode.
   video_track_->set_content_hint(VideoTrackInterface::ContentHint::kDetailed);
-  EXPECT_EQ(true, video_media_channel()->options().is_screencast);
+  EXPECT_EQ(true, video_media_send_channel()->options().is_screencast);
   // Removing the content hint should turn the track back into non-screencast
   // mode.
   video_track_->set_content_hint(VideoTrackInterface::ContentHint::kNone);
-  EXPECT_EQ(false, video_media_channel()->options().is_screencast);
+  EXPECT_EQ(false, video_media_send_channel()->options().is_screencast);
   // Setting fluid should remain in non-screencast mode (its default).
   video_track_->set_content_hint(VideoTrackInterface::ContentHint::kFluid);
-  EXPECT_EQ(false, video_media_channel()->options().is_screencast);
+  EXPECT_EQ(false, video_media_send_channel()->options().is_screencast);
   // Setting text should have the same effect as Detailed
   video_track_->set_content_hint(VideoTrackInterface::ContentHint::kText);
-  EXPECT_EQ(true, video_media_channel()->options().is_screencast);
+  EXPECT_EQ(true, video_media_send_channel()->options().is_screencast);
 
   DestroyVideoRtpSender();
 }
@@ -1674,22 +1708,22 @@
   video_track_->set_enabled(true);
 
   // `video_track_` with a screencast source should be screencast by default.
-  EXPECT_EQ(true, video_media_channel()->options().is_screencast);
+  EXPECT_EQ(true, video_media_send_channel()->options().is_screencast);
   // No content hint should be set by default.
   EXPECT_EQ(VideoTrackInterface::ContentHint::kNone,
             video_track_->content_hint());
   // Setting fluid should turn a screencast source into non-screencast mode.
   video_track_->set_content_hint(VideoTrackInterface::ContentHint::kFluid);
-  EXPECT_EQ(false, video_media_channel()->options().is_screencast);
+  EXPECT_EQ(false, video_media_send_channel()->options().is_screencast);
   // Removing the content hint should turn the track back into screencast mode.
   video_track_->set_content_hint(VideoTrackInterface::ContentHint::kNone);
-  EXPECT_EQ(true, video_media_channel()->options().is_screencast);
+  EXPECT_EQ(true, video_media_send_channel()->options().is_screencast);
   // Setting detailed should still remain in screencast mode (its default).
   video_track_->set_content_hint(VideoTrackInterface::ContentHint::kDetailed);
-  EXPECT_EQ(true, video_media_channel()->options().is_screencast);
+  EXPECT_EQ(true, video_media_send_channel()->options().is_screencast);
   // Setting text should have the same effect as Detailed
   video_track_->set_content_hint(VideoTrackInterface::ContentHint::kText);
-  EXPECT_EQ(true, video_media_channel()->options().is_screencast);
+  EXPECT_EQ(true, video_media_send_channel()->options().is_screencast);
 
   DestroyVideoRtpSender();
 }
@@ -1710,21 +1744,21 @@
   EXPECT_CALL(*set_streams_observer, OnSetStreams());
   video_rtp_sender_->SetStreams({local_stream_->id()});
   video_rtp_sender_->SetMediaChannel(
-      video_media_channel()->AsVideoSendChannel());
+      video_media_send_channel()->AsVideoSendChannel());
   video_track_->set_enabled(true);
 
   // Sender is not ready to send (no SSRC) so no option should have been set.
-  EXPECT_EQ(absl::nullopt, video_media_channel()->options().is_screencast);
+  EXPECT_EQ(absl::nullopt, video_media_send_channel()->options().is_screencast);
 
   // Verify that the content hint is accounted for when video_rtp_sender_ does
   // get enabled.
   video_rtp_sender_->SetSsrc(kVideoSsrc);
-  EXPECT_EQ(true, video_media_channel()->options().is_screencast);
+  EXPECT_EQ(true, video_media_send_channel()->options().is_screencast);
 
   // And removing the hint should go back to false (to verify that false was
   // default correctly).
   video_track_->set_content_hint(VideoTrackInterface::ContentHint::kNone);
-  EXPECT_EQ(false, video_media_channel()->options().is_screencast);
+  EXPECT_EQ(false, video_media_send_channel()->options().is_screencast);
 
   DestroyVideoRtpSender();
 }
@@ -1763,22 +1797,22 @@
   auto dtmf_sender = audio_rtp_sender_->GetDtmfSender();
   ASSERT_NE(nullptr, dtmf_sender);
 
-  EXPECT_EQ(0U, voice_media_channel()->dtmf_info_queue().size());
+  EXPECT_EQ(0U, voice_media_send_channel()->dtmf_info_queue().size());
 
   // Insert DTMF
   const int expected_duration = 90;
   dtmf_sender->InsertDtmf("012", expected_duration, 100);
 
   // Verify
-  ASSERT_EQ_WAIT(3U, voice_media_channel()->dtmf_info_queue().size(),
+  ASSERT_EQ_WAIT(3U, voice_media_send_channel()->dtmf_info_queue().size(),
                  kDefaultTimeout);
   const uint32_t send_ssrc =
-      voice_media_channel()->send_streams()[0].first_ssrc();
-  EXPECT_TRUE(CompareDtmfInfo(voice_media_channel()->dtmf_info_queue()[0],
+      voice_media_send_channel()->send_streams()[0].first_ssrc();
+  EXPECT_TRUE(CompareDtmfInfo(voice_media_send_channel()->dtmf_info_queue()[0],
                               send_ssrc, 0, expected_duration));
-  EXPECT_TRUE(CompareDtmfInfo(voice_media_channel()->dtmf_info_queue()[1],
+  EXPECT_TRUE(CompareDtmfInfo(voice_media_send_channel()->dtmf_info_queue()[1],
                               send_ssrc, 1, expected_duration));
-  EXPECT_TRUE(CompareDtmfInfo(voice_media_channel()->dtmf_info_queue()[2],
+  EXPECT_TRUE(CompareDtmfInfo(voice_media_send_channel()->dtmf_info_queue()[2],
                               send_ssrc, 2, expected_duration));
 }
 
diff --git a/pc/rtp_transceiver.cc b/pc/rtp_transceiver.cc
index 9bc24be..a0faf6e 100644
--- a/pc/rtp_transceiver.cc
+++ b/pc/rtp_transceiver.cc
@@ -197,6 +197,8 @@
     return RTCError(RTCErrorType::INTERNAL_ERROR,
                     "No media engine for mid=" + std::string(mid));
   }
+  bool use_split_media_channel =
+      context()->field_trials().IsEnabled("WebRTC-SplitMediaChannel");
   std::unique_ptr<cricket::ChannelInterface> new_channel;
   if (media_type() == cricket::MEDIA_TYPE_AUDIO) {
     // TODO(bugs.webrtc.org/11992): CreateVideoChannel internally switches to
@@ -210,18 +212,44 @@
     context()->worker_thread()->BlockingCall([&] {
       RTC_DCHECK_RUN_ON(context()->worker_thread());
 
-      cricket::VoiceMediaChannel* media_channel =
-          media_engine()->voice().CreateMediaChannel(
-              cricket::MediaChannel::Role::kBoth, call_ptr, media_config,
-              audio_options, crypto_options, AudioCodecPairId::Create());
-      if (!media_channel) {
-        return;
-      }
+      AudioCodecPairId codec_pair_id = AudioCodecPairId::Create();
 
-      new_channel = std::make_unique<cricket::VoiceChannel>(
-          context()->worker_thread(), context()->network_thread(),
-          context()->signaling_thread(), absl::WrapUnique(media_channel), mid,
-          srtp_required, crypto_options, context()->ssrc_generator());
+      if (use_split_media_channel) {
+        std::unique_ptr<cricket::VoiceMediaChannel> media_send_channel =
+            absl::WrapUnique(media_engine()->voice().CreateMediaChannel(
+                cricket::MediaChannel::Role::kSend, call_ptr, media_config,
+                audio_options, crypto_options, codec_pair_id));
+        if (!media_send_channel) {
+          // TODO(bugs.webrtc.org/14912): Consider CHECK or reporting failure
+          return;
+        }
+        std::unique_ptr<cricket::VoiceMediaChannel> media_receive_channel =
+            absl::WrapUnique(media_engine()->voice().CreateMediaChannel(
+                cricket::MediaChannel::Role::kReceive, call_ptr, media_config,
+                audio_options, crypto_options, codec_pair_id));
+        if (!media_receive_channel) {
+          return;
+        }
+
+        new_channel = std::make_unique<cricket::VoiceChannel>(
+            context()->worker_thread(), context()->network_thread(),
+            context()->signaling_thread(), std::move(media_send_channel),
+            std::move(media_receive_channel), mid, srtp_required,
+            crypto_options, context()->ssrc_generator());
+      } else {
+        cricket::VoiceMediaChannel* media_channel =
+            media_engine()->voice().CreateMediaChannel(
+                cricket::MediaChannel::Role::kBoth, call_ptr, media_config,
+                audio_options, crypto_options, AudioCodecPairId::Create());
+        if (!media_channel) {
+          return;
+        }
+
+        new_channel = std::make_unique<cricket::VoiceChannel>(
+            context()->worker_thread(), context()->network_thread(),
+            context()->signaling_thread(), absl::WrapUnique(media_channel), mid,
+            srtp_required, crypto_options, context()->ssrc_generator());
+      }
     });
   } else {
     RTC_DCHECK_EQ(cricket::MEDIA_TYPE_VIDEO, media_type());
@@ -231,18 +259,45 @@
     // simply be on the worker thread and use `call_` (update upstream code).
     context()->worker_thread()->BlockingCall([&] {
       RTC_DCHECK_RUN_ON(context()->worker_thread());
-      cricket::VideoMediaChannel* media_channel =
-          media_engine()->video().CreateMediaChannel(
-              cricket::MediaChannel::Role::kBoth, call_ptr, media_config,
-              video_options, crypto_options, video_bitrate_allocator_factory);
-      if (!media_channel) {
-        return;
-      }
 
-      new_channel = std::make_unique<cricket::VideoChannel>(
-          context()->worker_thread(), context()->network_thread(),
-          context()->signaling_thread(), absl::WrapUnique(media_channel), mid,
-          srtp_required, crypto_options, context()->ssrc_generator());
+      if (use_split_media_channel) {
+        std::unique_ptr<cricket::VideoMediaChannel> media_send_channel =
+            absl::WrapUnique(media_engine()->video().CreateMediaChannel(
+                cricket::MediaChannel::Role::kSend, call_ptr, media_config,
+                video_options, crypto_options,
+                video_bitrate_allocator_factory));
+        if (!media_send_channel) {
+          return;
+        }
+
+        std::unique_ptr<cricket::VideoMediaChannel> media_receive_channel =
+            absl::WrapUnique(media_engine()->video().CreateMediaChannel(
+                cricket::MediaChannel::Role::kReceive, call_ptr, media_config,
+                video_options, crypto_options,
+                video_bitrate_allocator_factory));
+        if (!media_receive_channel) {
+          return;
+        }
+
+        new_channel = std::make_unique<cricket::VideoChannel>(
+            context()->worker_thread(), context()->network_thread(),
+            context()->signaling_thread(), std::move(media_send_channel),
+            std::move(media_receive_channel), mid, srtp_required,
+            crypto_options, context()->ssrc_generator());
+      } else {
+        cricket::VideoMediaChannel* media_channel =
+            media_engine()->video().CreateMediaChannel(
+                cricket::MediaChannel::Role::kBoth, call_ptr, media_config,
+                video_options, crypto_options, video_bitrate_allocator_factory);
+        if (!media_channel) {
+          return;
+        }
+
+        new_channel = std::make_unique<cricket::VideoChannel>(
+            context()->worker_thread(), context()->network_thread(),
+            context()->signaling_thread(), absl::WrapUnique(media_channel), mid,
+            srtp_required, crypto_options, context()->ssrc_generator());
+      }
     });
   }
   if (!new_channel) {
diff --git a/pc/test/fake_peer_connection_for_stats.h b/pc/test/fake_peer_connection_for_stats.h
index 49f9dd6..8d70308 100644
--- a/pc/test/fake_peer_connection_for_stats.h
+++ b/pc/test/fake_peer_connection_for_stats.h
@@ -29,8 +29,9 @@
 // Fake VoiceMediaChannel where the result of GetStats can be configured.
 class FakeVoiceMediaChannelForStats : public cricket::FakeVoiceMediaChannel {
  public:
-  explicit FakeVoiceMediaChannelForStats(TaskQueueBase* network_thread)
-      : cricket::FakeVoiceMediaChannel(MediaChannel::Role::kBoth,
+  explicit FakeVoiceMediaChannelForStats(MediaChannel::Role role,
+                                         TaskQueueBase* network_thread)
+      : cricket::FakeVoiceMediaChannel(role,
                                        nullptr,
                                        cricket::AudioOptions(),
                                        network_thread) {}
@@ -47,6 +48,7 @@
 
   // VoiceMediaChannel overrides.
   bool GetSendStats(cricket::VoiceMediaSendInfo* info) override {
+    RTC_DCHECK(role() == MediaChannel::Role::kSend);
     if (send_stats_) {
       *info = *send_stats_;
       return true;
@@ -55,6 +57,7 @@
   }
   bool GetReceiveStats(cricket::VoiceMediaReceiveInfo* info,
                        bool get_and_clear_legacy_stats) override {
+    RTC_DCHECK(role() == MediaChannel::Role::kReceive);
     if (receive_stats_) {
       *info = *receive_stats_;
       return true;
@@ -70,24 +73,35 @@
 // Fake VideoMediaChannel where the result of GetStats can be configured.
 class FakeVideoMediaChannelForStats : public cricket::FakeVideoMediaChannel {
  public:
-  explicit FakeVideoMediaChannelForStats(TaskQueueBase* network_thread)
-      : cricket::FakeVideoMediaChannel(MediaChannel::Role::kBoth,
+  explicit FakeVideoMediaChannelForStats(cricket::MediaChannel::Role role,
+                                         TaskQueueBase* network_thread)
+      : cricket::FakeVideoMediaChannel(role,
                                        nullptr,
                                        cricket::VideoOptions(),
                                        network_thread) {}
 
   void SetStats(const cricket::VideoMediaInfo& video_info) {
-    send_stats_ = cricket::VideoMediaSendInfo();
-    send_stats_->senders = video_info.senders;
-    send_stats_->aggregated_senders = video_info.aggregated_senders;
-    send_stats_->send_codecs = video_info.send_codecs;
-    receive_stats_ = cricket::VideoMediaReceiveInfo();
-    receive_stats_->receivers = video_info.receivers;
-    receive_stats_->receive_codecs = video_info.receive_codecs;
+    switch (role()) {
+      case MediaChannel::Role::kSend:
+        send_stats_ = cricket::VideoMediaSendInfo();
+        send_stats_->senders = video_info.senders;
+        send_stats_->aggregated_senders = video_info.aggregated_senders;
+        send_stats_->send_codecs = video_info.send_codecs;
+        break;
+      case MediaChannel::Role::kReceive:
+        receive_stats_ = cricket::VideoMediaReceiveInfo();
+        receive_stats_->receivers = video_info.receivers;
+        receive_stats_->receive_codecs = video_info.receive_codecs;
+        break;
+      default:
+        RTC_CHECK_NOTREACHED();
+    }
   }
 
   // VideoMediaChannel overrides.
   bool GetSendStats(cricket::VideoMediaSendInfo* info) override {
+    RTC_DCHECK(role() == MediaChannel::Role::kSend);
+
     if (send_stats_) {
       *info = *send_stats_;
       return true;
@@ -95,6 +109,7 @@
     return false;
   }
   bool GetReceiveStats(cricket::VideoMediaReceiveInfo* info) override {
+    RTC_DCHECK(role() == MediaChannel::Role::kReceive);
     if (receive_stats_) {
       *info = *receive_stats_;
       return true;
@@ -112,19 +127,22 @@
 
 class VoiceChannelForTesting : public cricket::VoiceChannel {
  public:
-  VoiceChannelForTesting(rtc::Thread* worker_thread,
-                         rtc::Thread* network_thread,
-                         rtc::Thread* signaling_thread,
-                         std::unique_ptr<cricket::VoiceMediaChannel> channel,
-                         const std::string& content_name,
-                         bool srtp_required,
-                         webrtc::CryptoOptions crypto_options,
-                         rtc::UniqueRandomIdGenerator* ssrc_generator,
-                         std::string transport_name)
+  VoiceChannelForTesting(
+      rtc::Thread* worker_thread,
+      rtc::Thread* network_thread,
+      rtc::Thread* signaling_thread,
+      std::unique_ptr<cricket::VoiceMediaChannel> send_channel,
+      std::unique_ptr<cricket::VoiceMediaChannel> receive_channel,
+      const std::string& content_name,
+      bool srtp_required,
+      webrtc::CryptoOptions crypto_options,
+      rtc::UniqueRandomIdGenerator* ssrc_generator,
+      std::string transport_name)
       : VoiceChannel(worker_thread,
                      network_thread,
                      signaling_thread,
-                     std::move(channel),
+                     std::move(send_channel),
+                     std::move(receive_channel),
                      content_name,
                      srtp_required,
                      std::move(crypto_options),
@@ -141,19 +159,22 @@
 
 class VideoChannelForTesting : public cricket::VideoChannel {
  public:
-  VideoChannelForTesting(rtc::Thread* worker_thread,
-                         rtc::Thread* network_thread,
-                         rtc::Thread* signaling_thread,
-                         std::unique_ptr<cricket::VideoMediaChannel> channel,
-                         const std::string& content_name,
-                         bool srtp_required,
-                         webrtc::CryptoOptions crypto_options,
-                         rtc::UniqueRandomIdGenerator* ssrc_generator,
-                         std::string transport_name)
+  VideoChannelForTesting(
+      rtc::Thread* worker_thread,
+      rtc::Thread* network_thread,
+      rtc::Thread* signaling_thread,
+      std::unique_ptr<cricket::VideoMediaChannel> send_channel,
+      std::unique_ptr<cricket::VideoMediaChannel> receive_channel,
+      const std::string& content_name,
+      bool srtp_required,
+      webrtc::CryptoOptions crypto_options,
+      rtc::UniqueRandomIdGenerator* ssrc_generator,
+      std::string transport_name)
       : VideoChannel(worker_thread,
                      network_thread,
                      signaling_thread,
-                     std::move(channel),
+                     std::move(send_channel),
+                     std::move(receive_channel),
                      content_name,
                      srtp_required,
                      std::move(crypto_options),
@@ -244,16 +265,23 @@
         ->RemoveReceiver(receiver.get());
   }
 
-  FakeVoiceMediaChannelForStats* AddVoiceChannel(
+  std::pair<FakeVoiceMediaChannelForStats*, FakeVoiceMediaChannelForStats*>
+  AddVoiceChannel(
       const std::string& mid,
       const std::string& transport_name,
       cricket::VoiceMediaInfo initial_stats = cricket::VoiceMediaInfo()) {
-    auto voice_media_channel =
-        std::make_unique<FakeVoiceMediaChannelForStats>(network_thread_);
-    auto* voice_media_channel_ptr = voice_media_channel.get();
+    auto voice_media_send_channel =
+        std::make_unique<FakeVoiceMediaChannelForStats>(
+            cricket::MediaChannel::Role::kSend, network_thread_);
+    auto voice_media_receive_channel =
+        std::make_unique<FakeVoiceMediaChannelForStats>(
+            cricket::MediaChannel::Role::kReceive, network_thread_);
+    auto* voice_media_send_channel_ptr = voice_media_send_channel.get();
+    auto* voice_media_receive_channel_ptr = voice_media_receive_channel.get();
     auto voice_channel = std::make_unique<VoiceChannelForTesting>(
         worker_thread_, network_thread_, signaling_thread_,
-        std::move(voice_media_channel), mid, kDefaultSrtpRequired,
+        std::move(voice_media_send_channel),
+        std::move(voice_media_receive_channel), mid, kDefaultSrtpRequired,
         webrtc::CryptoOptions(), context_->ssrc_generator(), transport_name);
     auto transceiver =
         GetOrCreateFirstTransceiverOfType(cricket::MEDIA_TYPE_AUDIO)
@@ -266,20 +294,29 @@
     RTC_DCHECK(!transceiver->channel());
     transceiver->SetChannel(std::move(voice_channel),
                             [](const std::string&) { return nullptr; });
-    voice_media_channel_ptr->SetStats(initial_stats);
-    return voice_media_channel_ptr;
+    voice_media_send_channel_ptr->SetStats(initial_stats);
+    voice_media_receive_channel_ptr->SetStats(initial_stats);
+    return std::make_pair(voice_media_send_channel_ptr,
+                          voice_media_receive_channel_ptr);
   }
 
-  FakeVideoMediaChannelForStats* AddVideoChannel(
+  std::pair<FakeVideoMediaChannelForStats*, FakeVideoMediaChannelForStats*>
+  AddVideoChannel(
       const std::string& mid,
       const std::string& transport_name,
       cricket::VideoMediaInfo initial_stats = cricket::VideoMediaInfo()) {
-    auto video_media_channel =
-        std::make_unique<FakeVideoMediaChannelForStats>(network_thread_);
-    auto video_media_channel_ptr = video_media_channel.get();
+    auto video_media_send_channel =
+        std::make_unique<FakeVideoMediaChannelForStats>(
+            cricket::MediaChannel::Role::kSend, network_thread_);
+    auto video_media_receive_channel =
+        std::make_unique<FakeVideoMediaChannelForStats>(
+            cricket::MediaChannel::Role::kReceive, network_thread_);
+    auto video_media_send_channel_ptr = video_media_send_channel.get();
+    auto video_media_receive_channel_ptr = video_media_receive_channel.get();
     auto video_channel = std::make_unique<VideoChannelForTesting>(
         worker_thread_, network_thread_, signaling_thread_,
-        std::move(video_media_channel), mid, kDefaultSrtpRequired,
+        std::move(video_media_send_channel),
+        std::move(video_media_receive_channel), mid, kDefaultSrtpRequired,
         webrtc::CryptoOptions(), context_->ssrc_generator(), transport_name);
     auto transceiver =
         GetOrCreateFirstTransceiverOfType(cricket::MEDIA_TYPE_VIDEO)
@@ -292,8 +329,10 @@
     RTC_DCHECK(!transceiver->channel());
     transceiver->SetChannel(std::move(video_channel),
                             [](const std::string&) { return nullptr; });
-    video_media_channel_ptr->SetStats(initial_stats);
-    return video_media_channel_ptr;
+    video_media_send_channel_ptr->SetStats(initial_stats);
+    video_media_receive_channel_ptr->SetStats(initial_stats);
+    return std::make_pair(video_media_send_channel_ptr,
+                          video_media_receive_channel_ptr);
   }
 
   void AddSctpDataChannel(const std::string& label) {
diff --git a/pc/test/mock_channel_interface.h b/pc/test/mock_channel_interface.h
index 41c142d..3d82beb 100644
--- a/pc/test/mock_channel_interface.h
+++ b/pc/test/mock_channel_interface.h
@@ -27,7 +27,6 @@
   MOCK_METHOD(cricket::MediaType, media_type, (), (const, override));
   MOCK_METHOD(VideoChannel*, AsVideoChannel, (), (override));
   MOCK_METHOD(VoiceChannel*, AsVoiceChannel, (), (override));
-  MOCK_METHOD(MediaChannel*, media_channel, (), (override));
   MOCK_METHOD(MediaChannel*, media_send_channel, (), (override));
   MOCK_METHOD(VoiceMediaChannel*, voice_media_send_channel, (), (override));
   MOCK_METHOD(VideoMediaChannel*, video_media_send_channel, (), (override));
diff --git a/pc/test/mock_voice_media_channel.h b/pc/test/mock_voice_media_channel.h
index 71f7a18..feab3c0 100644
--- a/pc/test/mock_voice_media_channel.h
+++ b/pc/test/mock_voice_media_channel.h
@@ -28,8 +28,9 @@
 namespace cricket {
 class MockVoiceMediaChannel : public VoiceMediaChannel {
  public:
-  explicit MockVoiceMediaChannel(webrtc::TaskQueueBase* network_thread)
-      : VoiceMediaChannel(MediaChannel::Role::kBoth, network_thread) {}
+  MockVoiceMediaChannel(MediaChannel::Role role,
+                        webrtc::TaskQueueBase* network_thread)
+      : VoiceMediaChannel(role, network_thread) {}
 
   MOCK_METHOD(void,
               SetInterface,
@@ -161,6 +162,10 @@
               GetBaseMinimumPlayoutDelayMs,
               (uint32_t ssrc),
               (const, override));
+  MOCK_METHOD(bool, SenderNackEnabled, (), (const, override));
+  MOCK_METHOD(bool, SenderNonSenderRttEnabled, (), (const, override));
+  MOCK_METHOD(void, SetReceiveNackEnabled, (bool enabled), (override));
+  MOCK_METHOD(void, SetReceiveNonSenderRttEnabled, (bool enabled), (override));
 };
 }  // namespace cricket