Change MediaChannel to have a Role parameter

This allows MediaChannel to know whether it's being used
for sending, receiving, or both. This is a preparatory CL
for landing the split of MediaChannel usage into sending and
receiving objects.

Bug: webrtc:13931
Change-Id: If518c8b53d5256771200a42e1b5f2b3321d26d8c
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/292860
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#39283}
diff --git a/media/base/fake_media_engine.cc b/media/base/fake_media_engine.cc
index d4d24cd..f31cfaa 100644
--- a/media/base/fake_media_engine.cc
+++ b/media/base/fake_media_engine.cc
@@ -50,10 +50,11 @@
   return source_;
 }
 
-FakeVoiceMediaChannel::FakeVoiceMediaChannel(FakeVoiceEngine* engine,
+FakeVoiceMediaChannel::FakeVoiceMediaChannel(MediaChannel::Role role,
+                                             FakeVoiceEngine* engine,
                                              const AudioOptions& options,
                                              TaskQueueBase* network_thread)
-    : RtpHelper<VoiceMediaChannel>(network_thread),
+    : RtpHelper<VoiceMediaChannel>(role, network_thread),
       engine_(engine),
       max_bps_(-1) {
   output_scalings_[0] = 1.0;  // For default channel.
@@ -260,10 +261,11 @@
           info.ssrc == ssrc);
 }
 
-FakeVideoMediaChannel::FakeVideoMediaChannel(FakeVideoEngine* engine,
+FakeVideoMediaChannel::FakeVideoMediaChannel(MediaChannel::Role role,
+                                             FakeVideoEngine* engine,
                                              const VideoOptions& options,
                                              TaskQueueBase* network_thread)
-    : RtpHelper<VideoMediaChannel>(network_thread),
+    : RtpHelper<VideoMediaChannel>(role, network_thread),
       engine_(engine),
       max_bps_(-1) {
   SetOptions(options);
@@ -449,6 +451,7 @@
   return rtc::scoped_refptr<webrtc::AudioState>();
 }
 VoiceMediaChannel* FakeVoiceEngine::CreateMediaChannel(
+    MediaChannel::Role role,
     webrtc::Call* call,
     const MediaConfig& config,
     const AudioOptions& options,
@@ -458,7 +461,7 @@
   }
 
   FakeVoiceMediaChannel* ch =
-      new FakeVoiceMediaChannel(this, options, call->network_thread());
+      new FakeVoiceMediaChannel(role, this, options, call->network_thread());
   channels_.push_back(ch);
   return ch;
 }
@@ -519,6 +522,7 @@
   return true;
 }
 VideoMediaChannel* FakeVideoEngine::CreateMediaChannel(
+    MediaChannel::Role role,
     webrtc::Call* call,
     const MediaConfig& config,
     const VideoOptions& options,
@@ -529,7 +533,7 @@
   }
 
   FakeVideoMediaChannel* ch =
-      new FakeVideoMediaChannel(this, options, call->network_thread());
+      new FakeVideoMediaChannel(role, this, options, call->network_thread());
   channels_.emplace_back(ch);
   return ch;
 }
diff --git a/media/base/fake_media_engine.h b/media/base/fake_media_engine.h
index 082c7f3..7ec3818 100644
--- a/media/base/fake_media_engine.h
+++ b/media/base/fake_media_engine.h
@@ -45,8 +45,9 @@
 template <class Base>
 class RtpHelper : public Base {
  public:
-  explicit RtpHelper(webrtc::TaskQueueBase* network_thread)
-      : Base(network_thread),
+  explicit RtpHelper(MediaChannel::Role role,
+                     webrtc::TaskQueueBase* network_thread)
+      : Base(role, network_thread),
         sending_(false),
         playout_(false),
         fail_set_send_codecs_(false),
@@ -125,6 +126,7 @@
   virtual absl::optional<uint32_t> GetUnsignaledSsrc() const {
     return absl::nullopt;
   }
+  virtual bool SetLocalSsrc(const StreamParams& sp) { return true; }
   virtual void OnDemuxerCriteriaUpdatePending() {}
   virtual void OnDemuxerCriteriaUpdateComplete() {}
 
@@ -260,6 +262,14 @@
       rtc::scoped_refptr<webrtc::FrameTransformerInterface> frame_transformer)
       override {}
 
+  void SetInterface(MediaChannelNetworkInterface* iface) override {
+    network_interface_ = iface;
+    MediaChannel::SetInterface(iface);
+  }
+  bool HasNetworkInterface() const override {
+    return network_interface_ != nullptr;
+  }
+
  protected:
   bool MuteStream(uint32_t ssrc, bool mute) {
     if (!HasSendStream(ssrc) && ssrc != 0) {
@@ -337,6 +347,7 @@
   int transport_overhead_per_packet_;
   rtc::NetworkRoute last_network_route_;
   int num_network_route_changes_;
+  MediaChannelNetworkInterface* network_interface_ = nullptr;
 };
 
 class FakeVoiceMediaChannel : public RtpHelper<VoiceMediaChannel> {
@@ -347,7 +358,8 @@
     int event_code;
     int duration;
   };
-  FakeVoiceMediaChannel(FakeVoiceEngine* engine,
+  FakeVoiceMediaChannel(MediaChannel::Role role,
+                        FakeVoiceEngine* engine,
                         const AudioOptions& options,
                         webrtc::TaskQueueBase* network_thread);
   ~FakeVoiceMediaChannel();
@@ -442,7 +454,8 @@
 
 class FakeVideoMediaChannel : public RtpHelper<VideoMediaChannel> {
  public:
-  FakeVideoMediaChannel(FakeVideoEngine* engine,
+  FakeVideoMediaChannel(MediaChannel::Role role,
+                        FakeVideoEngine* engine,
                         const VideoOptions& options,
                         webrtc::TaskQueueBase* network_thread);
 
@@ -520,6 +533,7 @@
   rtc::scoped_refptr<webrtc::AudioState> GetAudioState() const override;
 
   VoiceMediaChannel* CreateMediaChannel(
+      MediaChannel::Role role,
       webrtc::Call* call,
       const MediaConfig& config,
       const AudioOptions& options,
@@ -559,6 +573,7 @@
   FakeVideoEngine();
   bool SetOptions(const VideoOptions& options);
   VideoMediaChannel* CreateMediaChannel(
+      MediaChannel::Role role,
       webrtc::Call* call,
       const MediaConfig& config,
       const VideoOptions& options,
diff --git a/media/base/media_channel.h b/media/base/media_channel.h
index 43e0929..1191986 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -73,6 +73,7 @@
 namespace cricket {
 
 class AudioSource;
+class MediaChannel;  // TODO(bugs.webrtc.org/13931): Delete when irrelevant
 class VideoCapturer;
 struct RtpHeader;
 struct VideoFormat;
@@ -212,6 +213,17 @@
   // worker_thread.
   virtual void SetExtmapAllowMixed(bool extmap_allow_mixed) = 0;
   virtual bool ExtmapAllowMixed() const = 0;
+
+  // Sets the abstract interface class for sending RTP/RTCP data.
+  virtual void SetInterface(MediaChannelNetworkInterface* iface) = 0;
+
+  // Returns `true` if a non-null MediaChannelNetworkInterface pointer is held.
+  // Must be called on the network thread.
+  virtual bool HasNetworkInterface() const = 0;
+
+  // Get the underlying send/receive implementation channel for testing.
+  // TODO(bugs.webrtc.org/13931): Remove method and the fakes that depend on it.
+  virtual MediaChannel* ImplForTesting() = 0;
 };
 
 class MediaSendChannelInterface
@@ -263,14 +275,9 @@
  public:
   virtual ~MediaReceiveChannelInterface() = default;
 
-  virtual VideoMediaReceiveChannelInterface* AsVideoReceiveChannel() {
-    RTC_CHECK_NOTREACHED();
-    return nullptr;
-  }
-  virtual VoiceMediaReceiveChannelInterface* AsVoiceReceiveChannel() {
-    RTC_CHECK_NOTREACHED();
-    return nullptr;
-  }
+  virtual VideoMediaReceiveChannelInterface* AsVideoReceiveChannel() = 0;
+  virtual VoiceMediaReceiveChannelInterface* AsVoiceReceiveChannel() = 0;
+
   // Creates a new incoming media stream with SSRCs, CNAME as described
   // by sp. In the case of a sp without SSRCs, the unsignaled sp is cached
   // to be used later for unsignaled streams received.
@@ -284,6 +291,9 @@
   virtual void ResetUnsignaledRecvStream() = 0;
   // Gets the current unsignaled receive stream's SSRC, if there is one.
   virtual absl::optional<uint32_t> GetUnsignaledSsrc() const = 0;
+  // Sets the local SSRC for listening to incoming RTCP reports.
+  virtual bool SetLocalSsrc(const StreamParams& sp) = 0;
+
   // This is currently a workaround because of the demuxer state being managed
   // across two separate threads. Once the state is consistently managed on
   // the same thread (network), this workaround can be removed.
diff --git a/media/base/media_channel_impl.cc b/media/base/media_channel_impl.cc
index a72b974..ca2a117 100644
--- a/media/base/media_channel_impl.cc
+++ b/media/base/media_channel_impl.cc
@@ -57,8 +57,11 @@
     : content_hint(VideoTrackInterface::ContentHint::kNone) {}
 VideoOptions::~VideoOptions() = default;
 
-MediaChannel::MediaChannel(TaskQueueBase* network_thread, bool enable_dscp)
-    : enable_dscp_(enable_dscp),
+MediaChannel::MediaChannel(Role role,
+                           TaskQueueBase* network_thread,
+                           bool enable_dscp)
+    : role_(role),
+      enable_dscp_(enable_dscp),
       network_safety_(PendingTaskSafetyFlag::CreateDetachedInactive()),
       network_thread_(network_thread) {}
 
diff --git a/media/base/media_channel_impl.h b/media/base/media_channel_impl.h
index 8142dd4..f18670f 100644
--- a/media/base/media_channel_impl.h
+++ b/media/base/media_channel_impl.h
@@ -71,10 +71,22 @@
 class MediaChannel : public MediaSendChannelInterface,
                      public MediaReceiveChannelInterface {
  public:
-  explicit MediaChannel(webrtc::TaskQueueBase* network_thread,
+  // Role of the channel. Used to describe which interface it supports.
+  // This is temporary until we stop using the same implementation for both
+  // interfaces.
+  enum class Role {
+    kSend,
+    kReceive,
+    kBoth  // Temporary value for non-converted test code
+  };
+
+  explicit MediaChannel(Role role,
+                        webrtc::TaskQueueBase* network_thread,
                         bool enable_dscp = false);
   virtual ~MediaChannel();
 
+  Role role() { return role_; }
+
   // Downcasting to the subclasses.
   virtual VideoMediaChannel* AsVideoChannel() {
     RTC_CHECK_NOTREACHED();
@@ -85,7 +97,6 @@
     RTC_CHECK_NOTREACHED();
     return nullptr;
   }
-
   // Must declare the methods inherited from the base interface template,
   // even when abstract, to tell the compiler that all instances of the name
   // referred to by subclasses of this share the same implementation.
@@ -97,8 +108,6 @@
                              const rtc::NetworkRoute& network_route) override =
       0;
 
-  // Sets the abstract interface class for sending RTP/RTCP data.
-  virtual void SetInterface(MediaChannelNetworkInterface* iface);
   // Returns the absolute sendtime extension id value from media channel.
   virtual int GetRtpSendTimeExtnId() const;
   // Base method to send packet using MediaChannelNetworkInterface.
@@ -119,9 +128,10 @@
   void SetExtmapAllowMixed(bool extmap_allow_mixed) override;
   bool ExtmapAllowMixed() const override;
 
+  void SetInterface(MediaChannelNetworkInterface* iface) override;
   // Returns `true` if a non-null MediaChannelNetworkInterface pointer is held.
   // Must be called on the network thread.
-  bool HasNetworkInterface() const;
+  bool HasNetworkInterface() const override;
 
   void SetFrameEncryptor(uint32_t ssrc,
                          rtc::scoped_refptr<webrtc::FrameEncryptorInterface>
@@ -170,6 +180,7 @@
                     bool rtcp,
                     const rtc::PacketOptions& options);
 
+  const Role role_;
   const bool enable_dscp_;
   const rtc::scoped_refptr<webrtc::PendingTaskSafetyFlag> network_safety_
       RTC_PT_GUARDED_BY(network_thread_);
@@ -187,9 +198,10 @@
                           public VideoMediaSendChannelInterface,
                           public VideoMediaReceiveChannelInterface {
  public:
-  explicit VideoMediaChannel(webrtc::TaskQueueBase* network_thread,
+  explicit VideoMediaChannel(MediaChannel::Role role,
+                             webrtc::TaskQueueBase* network_thread,
                              bool enable_dscp = false)
-      : MediaChannel(network_thread, enable_dscp) {}
+      : MediaChannel(role, network_thread, enable_dscp) {}
   ~VideoMediaChannel() override {}
 
   // Downcasting to the implemented interfaces.
@@ -202,6 +214,10 @@
   VideoMediaReceiveChannelInterface* AsVideoReceiveChannel() override {
     return this;
   }
+  VoiceMediaReceiveChannelInterface* AsVoiceReceiveChannel() override {
+    RTC_CHECK_NOTREACHED();
+    return nullptr;
+  }
   cricket::MediaType media_type() const override;
 
   // Downcasting to the subclasses.
@@ -213,6 +229,12 @@
   bool ExtmapAllowMixed() const override {
     return MediaChannel::ExtmapAllowMixed();
   }
+  void SetInterface(MediaChannelNetworkInterface* iface) override {
+    return MediaChannel::SetInterface(iface);
+  }
+  // Declared here in order to avoid "found by multiple paths" compile error
+  bool AddSendStream(const StreamParams& sp) override = 0;
+
   // This fills the "bitrate parts" (rtx, video bitrate) of the
   // BandwidthEstimationInfo, since that part that isn't possible to get
   // through webrtc::Call::GetStats, as they are statistics of the send
@@ -225,8 +247,6 @@
   // Gets quality stats for the channel.
   virtual bool GetSendStats(VideoMediaSendInfo* info) = 0;
   virtual bool GetReceiveStats(VideoMediaReceiveInfo* info) = 0;
-  // Enable network condition based codec switching.
-  void SetVideoCodecSwitchingEnabled(bool enabled) override;
 
  private:
   // Functions not implemented on this interface
@@ -238,6 +258,16 @@
     RTC_CHECK_NOTREACHED();
     return false;
   }
+  bool HasNetworkInterface() const override {
+    return MediaChannel::HasNetworkInterface();
+  }
+  // Enable network condition based codec switching.
+  void SetVideoCodecSwitchingEnabled(bool enabled) override;
+
+  MediaChannel* ImplForTesting() override {
+    // This class and its subclasses are not interface classes.
+    RTC_CHECK_NOTREACHED();
+  }
 };
 
 // Base class for implementation classes
@@ -246,9 +276,10 @@
                           public VoiceMediaReceiveChannelInterface {
  public:
   MediaType media_type() const override;
-  VoiceMediaChannel(webrtc::TaskQueueBase* network_thread,
+  VoiceMediaChannel(MediaChannel::Role role,
+                    webrtc::TaskQueueBase* network_thread,
                     bool enable_dscp = false)
-      : MediaChannel(network_thread, enable_dscp) {}
+      : MediaChannel(role, network_thread, enable_dscp) {}
   ~VoiceMediaChannel() override {}
 
   // Downcasting to the implemented interfaces.
@@ -264,6 +295,10 @@
     RTC_CHECK_NOTREACHED();
     return nullptr;
   }
+  VideoMediaReceiveChannelInterface* AsVideoReceiveChannel() override {
+    RTC_CHECK_NOTREACHED();
+    return nullptr;
+  }
 
   void SetExtmapAllowMixed(bool mixed) override {
     MediaChannel::SetExtmapAllowMixed(mixed);
@@ -271,6 +306,12 @@
   bool ExtmapAllowMixed() const override {
     return MediaChannel::ExtmapAllowMixed();
   }
+  void SetInterface(MediaChannelNetworkInterface* iface) override {
+    return MediaChannel::SetInterface(iface);
+  }
+  bool HasNetworkInterface() const override {
+    return MediaChannel::HasNetworkInterface();
+  }
 
   // Gets quality stats for the channel.
   virtual bool GetSendStats(VoiceMediaSendInfo* info) = 0;
@@ -288,6 +329,10 @@
     RTC_CHECK_NOTREACHED();
     return false;
   }
+  MediaChannel* ImplForTesting() override {
+    // This class and its subclasses are not interface classes.
+    RTC_CHECK_NOTREACHED();
+  }
 };
 
 // The externally exposed objects that support the Send and Receive interfaces.
@@ -320,6 +365,12 @@
     impl()->SetExtmapAllowMixed(extmap_allow_mixed);
   }
   bool ExtmapAllowMixed() const override { return impl()->ExtmapAllowMixed(); }
+  void SetInterface(MediaChannelNetworkInterface* iface) override {
+    return impl()->SetInterface(iface);
+  }
+  bool HasNetworkInterface() const override {
+    return impl()->HasNetworkInterface();
+  }
   // Implementation of MediaSendChannelInterface
   bool AddSendStream(const StreamParams& sp) override {
     return impl()->AddSendStream(sp);
@@ -372,6 +423,7 @@
   bool GetStats(VoiceMediaSendInfo* info) override {
     return impl_->GetSendStats(info);
   }
+  MediaChannel* ImplForTesting() override { return impl_; }
 
  private:
   VoiceMediaSendChannelInterface* impl() { return impl_; }
@@ -383,6 +435,14 @@
  public:
   explicit VoiceMediaReceiveChannel(VoiceMediaChannel* impl) : impl_(impl) {}
   virtual ~VoiceMediaReceiveChannel() {}
+
+  VoiceMediaReceiveChannelInterface* AsVoiceReceiveChannel() override {
+    return this;
+  }
+  VideoMediaReceiveChannelInterface* AsVideoReceiveChannel() override {
+    RTC_DCHECK_NOTREACHED();
+    return nullptr;
+  }
   // Implementation of MediaBaseChannelInterface
   cricket::MediaType media_type() const override { return MEDIA_TYPE_AUDIO; }
   void OnPacketReceived(const webrtc::RtpPacketReceived& packet) override {
@@ -400,6 +460,12 @@
     impl()->SetExtmapAllowMixed(extmap_allow_mixed);
   }
   bool ExtmapAllowMixed() const override { return impl()->ExtmapAllowMixed(); }
+  void SetInterface(MediaChannelNetworkInterface* iface) override {
+    return impl()->SetInterface(iface);
+  }
+  bool HasNetworkInterface() const override {
+    return impl()->HasNetworkInterface();
+  }
   // Implementation of Delayable
   bool SetBaseMinimumPlayoutDelayMs(uint32_t ssrc, int delay_ms) override {
     return impl()->SetBaseMinimumPlayoutDelayMs(ssrc, delay_ms);
@@ -418,6 +484,9 @@
   void ResetUnsignaledRecvStream() override {
     return impl()->ResetUnsignaledRecvStream();
   }
+  bool SetLocalSsrc(const StreamParams& sp) override {
+    return impl()->SetLocalSsrc(sp);
+  }
   absl::optional<uint32_t> GetUnsignaledSsrc() const override {
     return impl()->GetUnsignaledSsrc();
   }
@@ -470,6 +539,7 @@
   bool GetStats(VoiceMediaReceiveInfo* info, bool reset_legacy) override {
     return impl_->GetReceiveStats(info, reset_legacy);
   }
+  MediaChannel* ImplForTesting() override { return impl_; }
 
  private:
   VoiceMediaReceiveChannelInterface* impl() { return impl_; }
@@ -503,7 +573,13 @@
   void SetExtmapAllowMixed(bool extmap_allow_mixed) override {
     impl()->SetExtmapAllowMixed(extmap_allow_mixed);
   }
+  bool HasNetworkInterface() const override {
+    return impl()->HasNetworkInterface();
+  }
   bool ExtmapAllowMixed() const override { return impl()->ExtmapAllowMixed(); }
+  void SetInterface(MediaChannelNetworkInterface* iface) override {
+    return impl()->SetInterface(iface);
+  }
   // Implementation of MediaSendChannelInterface
   bool AddSendStream(const StreamParams& sp) override {
     return impl()->AddSendStream(sp);
@@ -566,6 +642,8 @@
     return impl_->FillBitrateInfo(bwe_info);
   }
 
+  MediaChannel* ImplForTesting() override { return impl_; }
+
  private:
   VideoMediaSendChannelInterface* impl() { return impl_; }
   const VideoMediaSendChannelInterface* impl() const { return impl_; }
@@ -575,6 +653,15 @@
 class VideoMediaReceiveChannel : public VideoMediaReceiveChannelInterface {
  public:
   explicit VideoMediaReceiveChannel(VideoMediaChannel* impl) : impl_(impl) {}
+
+  VideoMediaReceiveChannelInterface* AsVideoReceiveChannel() override {
+    return this;
+  }
+  VoiceMediaReceiveChannelInterface* AsVoiceReceiveChannel() override {
+    RTC_DCHECK_NOTREACHED();
+    return nullptr;
+  }
+
   // Implementation of MediaBaseChannelInterface
   cricket::MediaType media_type() const override { return MEDIA_TYPE_VIDEO; }
   void OnPacketReceived(const webrtc::RtpPacketReceived& packet) override {
@@ -592,6 +679,12 @@
     impl()->SetExtmapAllowMixed(extmap_allow_mixed);
   }
   bool ExtmapAllowMixed() const override { return impl()->ExtmapAllowMixed(); }
+  void SetInterface(MediaChannelNetworkInterface* iface) override {
+    return impl()->SetInterface(iface);
+  }
+  bool HasNetworkInterface() const override {
+    return impl()->HasNetworkInterface();
+  }
   // Implementation of Delayable
   bool SetBaseMinimumPlayoutDelayMs(uint32_t ssrc, int delay_ms) override {
     return impl()->SetBaseMinimumPlayoutDelayMs(ssrc, delay_ms);
@@ -601,9 +694,6 @@
     return impl()->GetBaseMinimumPlayoutDelayMs(ssrc);
   }
   // Implementation of MediaReceiveChannelInterface
-  VideoMediaReceiveChannelInterface* AsVideoReceiveChannel() override {
-    return this;
-  }
   bool AddRecvStream(const StreamParams& sp) override {
     return impl()->AddRecvStream(sp);
   }
@@ -616,6 +706,9 @@
   absl::optional<uint32_t> GetUnsignaledSsrc() const override {
     return impl()->GetUnsignaledSsrc();
   }
+  bool SetLocalSsrc(const StreamParams& sp) override {
+    return impl()->SetLocalSsrc(sp);
+  }
   void OnDemuxerCriteriaUpdatePending() override {
     impl()->OnDemuxerCriteriaUpdatePending();
   }
@@ -671,6 +764,7 @@
   bool GetStats(VideoMediaReceiveInfo* info) override {
     return impl_->GetReceiveStats(info);
   }
+  MediaChannel* ImplForTesting() override { return impl_; }
 
  private:
   VideoMediaReceiveChannelInterface* impl() { return impl_; }
diff --git a/media/base/media_engine.h b/media/base/media_engine.h
index cc7563a..8ab172f 100644
--- a/media/base/media_engine.h
+++ b/media/base/media_engine.h
@@ -24,6 +24,7 @@
 #include "call/audio_state.h"
 #include "media/base/codec.h"
 #include "media/base/media_channel.h"
+#include "media/base/media_channel_impl.h"
 #include "media/base/media_config.h"
 #include "media/base/video_common.h"
 #include "rtc_base/system/file_wrapper.h"
@@ -100,10 +101,25 @@
   // MediaChannel creation
   // Creates a voice media channel. Returns NULL on failure.
   virtual VoiceMediaChannel* CreateMediaChannel(
+      MediaChannel::Role role,
       webrtc::Call* call,
       const MediaConfig& config,
       const AudioOptions& options,
-      const webrtc::CryptoOptions& crypto_options) = 0;
+      const webrtc::CryptoOptions& crypto_options) {
+    // TODO(bugs.webrtc.org/13931): Remove default implementation
+    // when downstream has migrated to new API.
+    RTC_CHECK_NOTREACHED();
+  }
+
+  // Backwards compatible version
+  [[deprecated("Use version with role parameter")]] virtual VoiceMediaChannel*
+  CreateMediaChannel(webrtc::Call* call,
+                     const MediaConfig& config,
+                     const AudioOptions& options,
+                     const webrtc::CryptoOptions& crypto_options) {
+    return CreateMediaChannel(MediaChannel::Role::kBoth, call, config, options,
+                              crypto_options);
+  }
 
   virtual const std::vector<AudioCodec>& send_codecs() const = 0;
   virtual const std::vector<AudioCodec>& recv_codecs() const = 0;
@@ -129,15 +145,34 @@
   VideoEngineInterface(const VideoEngineInterface&) = delete;
   VideoEngineInterface& operator=(const VideoEngineInterface&) = delete;
 
-  // Creates a video media channel, paired with the specified voice channel.
+  // Creates a video media channel.
   // Returns NULL on failure.
   virtual VideoMediaChannel* CreateMediaChannel(
+      MediaChannel::Role role,
       webrtc::Call* call,
       const MediaConfig& config,
       const VideoOptions& options,
       const webrtc::CryptoOptions& crypto_options,
-      webrtc::VideoBitrateAllocatorFactory*
-          video_bitrate_allocator_factory) = 0;
+      webrtc::VideoBitrateAllocatorFactory* video_bitrate_allocator_factory) {
+    // TODO(bugs.webrtc.org/13931): Remove default implementation
+    // when downstream has migrated.
+    RTC_CHECK_NOTREACHED();
+    return nullptr;
+  }
+
+  // Creates a video media channel.
+  // Returns NULL on failure.
+  // TODO(bugs.webrtc.org/13931): Stop downstream usage of this function.
+  [[deprecated("Please specify the role")]] virtual VideoMediaChannel*
+  CreateMediaChannel(
+      webrtc::Call* call,
+      const MediaConfig& config,
+      const VideoOptions& options,
+      const webrtc::CryptoOptions& crypto_options,
+      webrtc::VideoBitrateAllocatorFactory* video_bitrate_allocator_factory) {
+    return CreateMediaChannel(MediaChannel::Role::kBoth, call, config, options,
+                              crypto_options, video_bitrate_allocator_factory);
+  }
 
   // Retrieve list of supported codecs.
   virtual std::vector<VideoCodec> send_codecs() const = 0;
diff --git a/media/engine/null_webrtc_video_engine.h b/media/engine/null_webrtc_video_engine.h
index ede0d1b..5efefd4 100644
--- a/media/engine/null_webrtc_video_engine.h
+++ b/media/engine/null_webrtc_video_engine.h
@@ -51,6 +51,7 @@
   }
 
   VideoMediaChannel* CreateMediaChannel(
+      MediaChannel::Role role,
       webrtc::Call* call,
       const MediaConfig& config,
       const VideoOptions& options,
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index 3791e10..edd6c38 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -600,13 +600,14 @@
 }
 
 VideoMediaChannel* WebRtcVideoEngine::CreateMediaChannel(
+    MediaChannel::Role role,
     webrtc::Call* call,
     const MediaConfig& config,
     const VideoOptions& options,
     const webrtc::CryptoOptions& crypto_options,
     webrtc::VideoBitrateAllocatorFactory* video_bitrate_allocator_factory) {
   RTC_LOG(LS_INFO) << "CreateMediaChannel. Options: " << options.ToString();
-  return new WebRtcVideoChannel(call, config, options, crypto_options,
+  return new WebRtcVideoChannel(role, call, config, options, crypto_options,
                                 encoder_factory_.get(), decoder_factory_.get(),
                                 video_bitrate_allocator_factory);
 }
@@ -664,6 +665,7 @@
 }
 
 WebRtcVideoChannel::WebRtcVideoChannel(
+    MediaChannel::Role role,
     webrtc::Call* call,
     const MediaConfig& config,
     const VideoOptions& options,
@@ -671,7 +673,7 @@
     webrtc::VideoEncoderFactory* encoder_factory,
     webrtc::VideoDecoderFactory* decoder_factory,
     webrtc::VideoBitrateAllocatorFactory* bitrate_allocator_factory)
-    : VideoMediaChannel(call->network_thread(), config.enable_dscp),
+    : VideoMediaChannel(role, call->network_thread(), config.enable_dscp),
       worker_thread_(call->worker_thread()),
       call_(call),
       default_sink_(nullptr),
@@ -1326,6 +1328,9 @@
 
 bool WebRtcVideoChannel::AddSendStream(const StreamParams& sp) {
   RTC_DCHECK_RUN_ON(&thread_checker_);
+  RTC_DCHECK(role() == MediaChannel::Role::kSend ||
+             role() == MediaChannel::Role::kBoth);
+
   RTC_LOG(LS_INFO) << "AddSendStream: " << sp.ToString();
   if (!ValidateStreamParams(sp))
     return false;
@@ -1364,8 +1369,12 @@
   RTC_DCHECK(ssrc != 0);
   send_streams_[ssrc] = stream;
 
-  if (rtcp_receiver_report_ssrc_ == kDefaultRtcpReceiverReportSsrc) {
-    SetReceiverReportSsrc(ssrc);
+  // If legacy kBoth mode, tell my receiver part about its SSRC.
+  // In kSend mode, this is the responsibility of the caller.
+  if (role() == MediaChannel::Role::kBoth) {
+    if (rtcp_receiver_report_ssrc_ == kDefaultRtcpReceiverReportSsrc) {
+      SetReceiverReportSsrc(ssrc);
+    }
   }
 
   if (sending_) {
@@ -1554,6 +1563,15 @@
   }
 }
 
+bool WebRtcVideoChannel::SetLocalSsrc(const StreamParams& sp) {
+  RTC_DCHECK_RUN_ON(&thread_checker_);
+  RTC_DCHECK(role() == MediaChannel::Role::kReceive);
+  if (rtcp_receiver_report_ssrc_ == kDefaultRtcpReceiverReportSsrc) {
+    SetReceiverReportSsrc(sp.first_ssrc());
+  }
+  return true;
+}
+
 absl::optional<uint32_t> WebRtcVideoChannel::GetUnsignaledSsrc() const {
   RTC_DCHECK_RUN_ON(&thread_checker_);
   absl::optional<uint32_t> ssrc;
@@ -1617,6 +1635,7 @@
     log_stats = true;
   }
 
+  info->Clear();
   FillSenderStats(info, log_stats);
   FillSendCodecStats(info);
   // TODO(holmer): We should either have rtt available as a metric on
diff --git a/media/engine/webrtc_video_engine.h b/media/engine/webrtc_video_engine.h
index ee22a7e..8a14ea4 100644
--- a/media/engine/webrtc_video_engine.h
+++ b/media/engine/webrtc_video_engine.h
@@ -74,6 +74,7 @@
   ~WebRtcVideoEngine() override;
 
   VideoMediaChannel* CreateMediaChannel(
+      MediaChannel::Role role,
       webrtc::Call* call,
       const MediaConfig& config,
       const VideoOptions& options,
@@ -105,6 +106,7 @@
                            public webrtc::EncoderSwitchRequestCallback {
  public:
   WebRtcVideoChannel(
+      MediaChannel::Role role,
       webrtc::Call* call,
       const MediaConfig& config,
       const VideoOptions& options,
@@ -137,6 +139,7 @@
   bool RemoveRecvStream(uint32_t ssrc) override;
   void ResetUnsignaledRecvStream() override;
   absl::optional<uint32_t> GetUnsignaledSsrc() const override;
+  bool SetLocalSsrc(const StreamParams& sp) override;
   void OnDemuxerCriteriaUpdatePending() override;
   void OnDemuxerCriteriaUpdateComplete() override;
   bool SetSink(uint32_t ssrc,
diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc
index 3cc1b3c..272b800 100644
--- a/media/engine/webrtc_video_engine_unittest.cc
+++ b/media/engine/webrtc_video_engine_unittest.cc
@@ -613,7 +613,8 @@
   AddSupportedVideoCodecType("VP8");
 
   std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
-      call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
+      cricket::MediaChannel::Role::kBoth, call_.get(), GetMediaConfig(),
+      VideoOptions(), webrtc::CryptoOptions(),
       video_bitrate_allocator_factory_.get()));
   auto send_channel = std::make_unique<VideoMediaSendChannel>(channel.get());
 
@@ -629,7 +630,8 @@
   AddSupportedVideoCodecType("VP8");
 
   std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
-      call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
+      cricket::MediaChannel::Role::kBoth, call_.get(), GetMediaConfig(),
+      VideoOptions(), webrtc::CryptoOptions(),
       video_bitrate_allocator_factory_.get()));
   auto send_channel = std::make_unique<VideoMediaSendChannel>(channel.get());
 
@@ -856,7 +858,8 @@
 VideoMediaChannel*
 WebRtcVideoEngineTest::SetSendParamsWithAllSupportedCodecs() {
   VideoMediaChannel* channel = engine_.CreateMediaChannel(
-      call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
+      cricket::MediaChannel::Role::kBoth, call_.get(), GetMediaConfig(),
+      VideoOptions(), webrtc::CryptoOptions(),
       video_bitrate_allocator_factory_.get());
   cricket::VideoSendParameters parameters;
   // We need to look up the codec in the engine to get the correct payload type.
@@ -876,7 +879,8 @@
 VideoMediaChannel* WebRtcVideoEngineTest::SetRecvParamsWithSupportedCodecs(
     const std::vector<VideoCodec>& codecs) {
   VideoMediaChannel* channel = engine_.CreateMediaChannel(
-      call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
+      cricket::MediaChannel::Role::kBoth, call_.get(), GetMediaConfig(),
+      VideoOptions(), webrtc::CryptoOptions(),
       video_bitrate_allocator_factory_.get());
   cricket::VideoRecvParameters parameters;
   parameters.codecs = codecs;
@@ -913,8 +917,8 @@
           }),
           Return(true)));
   std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
-      call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
-      video_bitrate_allocator_factory_.get()));
+      MediaChannel::Role::kBoth, call_.get(), GetMediaConfig(), VideoOptions(),
+      webrtc::CryptoOptions(), video_bitrate_allocator_factory_.get()));
   cricket::VideoRecvParameters parameters;
   parameters.codecs = supported_codecs;
   const int kTransportSeqExtensionId = 1;
@@ -952,8 +956,8 @@
   int rtx_payload_type = supported_codecs[1].id;
 
   std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
-      call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
-      video_bitrate_allocator_factory_.get()));
+      MediaChannel::Role::kBoth, call_.get(), GetMediaConfig(), VideoOptions(),
+      webrtc::CryptoOptions(), video_bitrate_allocator_factory_.get()));
   cricket::VideoRecvParameters parameters;
   parameters.codecs = supported_codecs;
   ASSERT_TRUE(channel->SetRecvParameters(parameters));
@@ -1034,7 +1038,8 @@
                                         rtc::kNumMicrosecsPerSec / 30);
 
   std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
-      call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
+      cricket::MediaChannel::Role::kBoth, call_.get(), GetMediaConfig(),
+      VideoOptions(), webrtc::CryptoOptions(),
       video_bitrate_allocator_factory_.get()));
   auto send_channel = std::make_unique<VideoMediaSendChannel>(channel.get());
   cricket::VideoSendParameters parameters;
@@ -1067,7 +1072,8 @@
   AddSupportedVideoCodecType("H264");
 
   std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
-      call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
+      cricket::MediaChannel::Role::kBoth, call_.get(), GetMediaConfig(),
+      VideoOptions(), webrtc::CryptoOptions(),
       video_bitrate_allocator_factory_.get()));
   auto send_channel = std::make_unique<VideoMediaSendChannel>(channel.get());
   cricket::VideoSendParameters parameters;
@@ -1106,7 +1112,8 @@
   AddSupportedVideoCodecType("H264");
 
   std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
-      call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
+      cricket::MediaChannel::Role::kBoth, call_.get(), GetMediaConfig(),
+      VideoOptions(), webrtc::CryptoOptions(),
       video_bitrate_allocator_factory_.get()));
   auto send_channel = std::make_unique<VideoMediaSendChannel>(channel.get());
   cricket::VideoSendParameters parameters;
@@ -1140,7 +1147,8 @@
   AddSupportedVideoCodecType("H264");
 
   std::unique_ptr<VideoMediaChannel> channel(engine_.CreateMediaChannel(
-      call_.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
+      cricket::MediaChannel::Role::kBoth, call_.get(), GetMediaConfig(),
+      VideoOptions(), webrtc::CryptoOptions(),
       video_bitrate_allocator_factory_.get()));
   auto send_channel = std::make_unique<VideoMediaSendChannel>(channel.get());
 
@@ -1441,8 +1449,8 @@
   // Create send channel.
   const int send_ssrc = 123;
   std::unique_ptr<VideoMediaChannel> send_channel(engine.CreateMediaChannel(
-      call.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
-      rate_allocator_factory.get()));
+      cricket::MediaChannel::Role::kBoth, call.get(), GetMediaConfig(),
+      VideoOptions(), webrtc::CryptoOptions(), rate_allocator_factory.get()));
   auto send_send_channel =
       std::make_unique<VideoMediaSendChannel>(send_channel.get());
 
@@ -1466,8 +1474,8 @@
   // Create recv channel.
   const int recv_ssrc = 321;
   std::unique_ptr<VideoMediaChannel> recv_channel(engine.CreateMediaChannel(
-      call.get(), GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
-      rate_allocator_factory.get()));
+      cricket::MediaChannel::Role::kBoth, call.get(), GetMediaConfig(),
+      VideoOptions(), webrtc::CryptoOptions(), rate_allocator_factory.get()));
   auto receive_channel =
       std::make_unique<VideoMediaReceiveChannel>(recv_channel.get());
 
@@ -1592,6 +1600,7 @@
             field_trials_),
         channel_(absl::WrapUnique(static_cast<cricket::WebRtcVideoChannel*>(
             engine_.CreateMediaChannel(
+                cricket::MediaChannel::Role::kBoth,
                 call_.get(),
                 cricket::MediaConfig(),
                 cricket::VideoOptions(),
@@ -1738,8 +1747,9 @@
     media_config.video.enable_cpu_adaptation = false;
     channel_.reset(
         static_cast<cricket::WebRtcVideoChannel*>(engine_.CreateMediaChannel(
-            call_.get(), media_config, cricket::VideoOptions(),
-            webrtc::CryptoOptions(), video_bitrate_allocator_factory_.get())));
+            cricket::MediaChannel::Role::kBoth, call_.get(), media_config,
+            cricket::VideoOptions(), webrtc::CryptoOptions(),
+            video_bitrate_allocator_factory_.get())));
     send_channel_ = std::make_unique<VideoMediaSendChannel>(channel_.get());
     receive_channel_ =
         std::make_unique<VideoMediaReceiveChannel>(channel_.get());
@@ -2604,8 +2614,9 @@
 
     fake_call_.reset(new FakeCall(&field_trials_));
     channel_.reset(engine_.CreateMediaChannel(
-        fake_call_.get(), GetMediaConfig(), VideoOptions(),
-        webrtc::CryptoOptions(), video_bitrate_allocator_factory_.get()));
+        cricket::MediaChannel::Role::kBoth, fake_call_.get(), GetMediaConfig(),
+        VideoOptions(), webrtc::CryptoOptions(),
+        video_bitrate_allocator_factory_.get()));
     send_channel_ =
         std::make_unique<cricket::VideoMediaSendChannel>(channel_.get());
     receive_channel_ =
@@ -3483,9 +3494,12 @@
   media_config.video.suspend_below_min_bitrate = true;
 
   channel_.reset(engine_.CreateMediaChannel(
-      fake_call_.get(), media_config, VideoOptions(), webrtc::CryptoOptions(),
+      cricket::MediaChannel::Role::kBoth, fake_call_.get(), media_config,
+      VideoOptions(), webrtc::CryptoOptions(),
       video_bitrate_allocator_factory_.get()));
   send_channel_.reset(new VideoMediaSendChannel(channel_.get()));
+  receive_channel_ =
+      std::make_unique<cricket::VideoMediaReceiveChannel>(channel_.get());
   send_channel_->OnReadyToSend(true);
 
   channel_->SetSendParameters(send_parameters_);
@@ -3495,9 +3509,12 @@
 
   media_config.video.suspend_below_min_bitrate = false;
   channel_.reset(engine_.CreateMediaChannel(
-      fake_call_.get(), media_config, VideoOptions(), webrtc::CryptoOptions(),
+      cricket::MediaChannel::Role::kBoth, fake_call_.get(), media_config,
+      VideoOptions(), webrtc::CryptoOptions(),
       video_bitrate_allocator_factory_.get()));
   send_channel_.reset(new VideoMediaSendChannel(channel_.get()));
+  receive_channel_ =
+      std::make_unique<cricket::VideoMediaReceiveChannel>(channel_.get());
   send_channel_->OnReadyToSend(true);
 
   channel_->SetSendParameters(send_parameters_);
@@ -4037,9 +4054,12 @@
   MediaConfig media_config = GetMediaConfig();
   media_config.video.enable_cpu_adaptation = true;
   channel_.reset(engine_.CreateMediaChannel(
-      fake_call_.get(), media_config, VideoOptions(), webrtc::CryptoOptions(),
+      cricket::MediaChannel::Role::kBoth, fake_call_.get(), media_config,
+      VideoOptions(), webrtc::CryptoOptions(),
       video_bitrate_allocator_factory_.get()));
   send_channel_.reset(new VideoMediaSendChannel(channel_.get()));
+  receive_channel_ =
+      std::make_unique<cricket::VideoMediaReceiveChannel>(channel_.get());
 
   send_channel_->OnReadyToSend(true);
   ASSERT_TRUE(channel_->SetSendParameters(parameters));
@@ -4089,9 +4109,12 @@
   MediaConfig media_config = GetMediaConfig();
   media_config.video.enable_cpu_adaptation = true;
   channel_.reset(engine_.CreateMediaChannel(
-      fake_call_.get(), media_config, VideoOptions(), webrtc::CryptoOptions(),
+      cricket::MediaChannel::Role::kBoth, fake_call_.get(), media_config,
+      VideoOptions(), webrtc::CryptoOptions(),
       video_bitrate_allocator_factory_.get()));
   send_channel_.reset(new VideoMediaSendChannel(channel_.get()));
+  receive_channel_ =
+      std::make_unique<cricket::VideoMediaReceiveChannel>(channel_.get());
   send_channel_->OnReadyToSend(true);
 
   EXPECT_TRUE(channel_->SetSendParameters(parameters));
@@ -4123,9 +4146,12 @@
     media_config.video.enable_cpu_adaptation = true;
   }
   channel_.reset(engine_.CreateMediaChannel(
-      fake_call_.get(), media_config, VideoOptions(), webrtc::CryptoOptions(),
+      cricket::MediaChannel::Role::kBoth, fake_call_.get(), media_config,
+      VideoOptions(), webrtc::CryptoOptions(),
       video_bitrate_allocator_factory_.get()));
   send_channel_.reset(new VideoMediaSendChannel(channel_.get()));
+  receive_channel_ =
+      std::make_unique<cricket::VideoMediaReceiveChannel>(channel_.get());
   send_channel_->OnReadyToSend(true);
 
   EXPECT_TRUE(channel_->SetSendParameters(parameters));
@@ -5455,7 +5481,8 @@
 
   channel.reset(
       static_cast<cricket::WebRtcVideoChannel*>(engine_.CreateMediaChannel(
-          call_.get(), config, VideoOptions(), webrtc::CryptoOptions(),
+          cricket::MediaChannel::Role::kBoth, call_.get(), config,
+          VideoOptions(), webrtc::CryptoOptions(),
           video_bitrate_allocator_factory_.get())));
   send_channel.reset(new VideoMediaSendChannel(channel_.get()));
 
@@ -5469,7 +5496,8 @@
   config.enable_dscp = true;
   channel.reset(
       static_cast<cricket::WebRtcVideoChannel*>(engine_.CreateMediaChannel(
-          call_.get(), config, VideoOptions(), webrtc::CryptoOptions(),
+          cricket::MediaChannel::Role::kBoth, call_.get(), config,
+          VideoOptions(), webrtc::CryptoOptions(),
           video_bitrate_allocator_factory_.get())));
   send_channel.reset(new VideoMediaSendChannel(channel.get()));
   channel->SetInterface(network_interface.get());
@@ -5502,7 +5530,8 @@
   config.enable_dscp = false;
   channel.reset(
       static_cast<cricket::WebRtcVideoChannel*>(engine_.CreateMediaChannel(
-          call_.get(), config, VideoOptions(), webrtc::CryptoOptions(),
+          cricket::MediaChannel::Role::kBoth, call_.get(), config,
+          VideoOptions(), webrtc::CryptoOptions(),
           video_bitrate_allocator_factory_.get())));
   channel->SetInterface(network_interface.get());
   EXPECT_EQ(rtc::DSCP_DEFAULT, network_interface->dscp());
@@ -9401,7 +9430,8 @@
     encoder_factory_->AddSupportedVideoCodecType("VP8");
     decoder_factory_->AddSupportedVideoCodecType("VP8");
     channel_.reset(engine_.CreateMediaChannel(
-        &fake_call_, GetMediaConfig(), VideoOptions(), webrtc::CryptoOptions(),
+        cricket::MediaChannel::Role::kBoth, &fake_call_, GetMediaConfig(),
+        VideoOptions(), webrtc::CryptoOptions(),
         mock_rate_allocator_factory_.get()));
     send_channel_ = std::make_unique<VideoMediaSendChannel>(channel_.get());
     receive_channel_ =
diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc
index e093c6f..3132cbc 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -409,13 +409,14 @@
 }
 
 VoiceMediaChannel* WebRtcVoiceEngine::CreateMediaChannel(
+    MediaChannel::Role role,
     webrtc::Call* call,
     const MediaConfig& config,
     const AudioOptions& options,
     const webrtc::CryptoOptions& crypto_options) {
   RTC_DCHECK_RUN_ON(call->worker_thread());
-  return new WebRtcVoiceMediaChannel(this, config, options, crypto_options,
-                                     call);
+  return new WebRtcVoiceMediaChannel(role, this, config, options,
+                                     crypto_options, call);
 }
 
 void WebRtcVoiceEngine::ApplyOptions(const AudioOptions& options_in) {
@@ -1249,12 +1250,13 @@
 };
 
 WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel(
+    MediaChannel::Role role,
     WebRtcVoiceEngine* engine,
     const MediaConfig& config,
     const AudioOptions& options,
     const webrtc::CryptoOptions& crypto_options,
     webrtc::Call* call)
-    : VoiceMediaChannel(call->network_thread(), config.enable_dscp),
+    : VoiceMediaChannel(role, call->network_thread(), config.enable_dscp),
       worker_thread_(call->worker_thread()),
       engine_(engine),
       call_(call),
@@ -1822,6 +1824,8 @@
   TRACE_EVENT0("webrtc", "WebRtcVoiceMediaChannel::AddSendStream");
   RTC_DCHECK_RUN_ON(worker_thread_);
   RTC_LOG(LS_INFO) << "AddSendStream: " << sp.ToString();
+  RTC_DCHECK(role() == MediaChannel::Role::kSend ||
+             role() == MediaChannel::Role::kBoth);
 
   uint32_t ssrc = sp.first_ssrc();
   RTC_DCHECK(0 != ssrc);
@@ -1840,14 +1844,19 @@
       call_, this, engine()->encoder_factory_, codec_pair_id_, nullptr,
       crypto_options_);
   send_streams_.insert(std::make_pair(ssrc, stream));
+  if (role() == MediaChannel::Role::kBoth) {
+    // In legacy kBoth mode, the MediaChannel takes the responsibility for
+    // telling the receiver about the local SSRC.
+    // In kSend mode, this is the caller's responsibility.
 
-  // At this point the stream's local SSRC has been updated. If it is the first
-  // send stream, make sure that all the receive streams are updated with the
-  // same SSRC in order to send receiver reports.
-  if (send_streams_.size() == 1) {
-    receiver_reports_ssrc_ = ssrc;
-    for (auto& kv : recv_streams_) {
-      call_->OnLocalSsrcUpdated(kv.second->stream(), ssrc);
+    // At this point the stream's local SSRC has been updated. If it is the
+    // first send stream, make sure that all the receive streams are updated
+    // with the same SSRC in order to send receiver reports.
+    if (send_streams_.size() == 1) {
+      receiver_reports_ssrc_ = ssrc;
+      for (auto& kv : recv_streams_) {
+        call_->OnLocalSsrcUpdated(kv.second->stream(), ssrc);
+      }
     }
   }
 
@@ -1884,6 +1893,8 @@
 bool WebRtcVoiceMediaChannel::AddRecvStream(const StreamParams& sp) {
   TRACE_EVENT0("webrtc", "WebRtcVoiceMediaChannel::AddRecvStream");
   RTC_DCHECK_RUN_ON(worker_thread_);
+  RTC_DCHECK(role() == MediaChannel::Role::kReceive ||
+             role() == MediaChannel::Role::kBoth);
   RTC_LOG(LS_INFO) << "AddRecvStream: " << sp.ToString();
 
   if (!sp.has_ssrcs()) {
@@ -1970,6 +1981,16 @@
   return unsignaled_recv_ssrcs_.back();
 }
 
+bool WebRtcVoiceMediaChannel::SetLocalSsrc(const StreamParams& sp) {
+  RTC_DCHECK(role() == MediaChannel::Role::kReceive);
+  uint32_t ssrc = sp.first_ssrc();
+  receiver_reports_ssrc_ = ssrc;
+  for (auto& kv : recv_streams_) {
+    call_->OnLocalSsrcUpdated(kv.second->stream(), ssrc);
+  }
+  return true;
+}
+
 // Not implemented.
 // TODO(https://crbug.com/webrtc/12676): Implement a fix for the unsignalled
 // SSRC race that can happen when an m= section goes from receiving to not
@@ -2282,7 +2303,10 @@
   RTC_DCHECK(info);
 
   // Get SSRC and stats for each sender.
-  RTC_DCHECK_EQ(info->senders.size(), 0U);
+  // With separate send and receive channels, we expect GetStats to be called on
+  // both, and accumulate info, but only one channel (the send one) should have
+  // senders.
+  RTC_DCHECK(info->senders.size() == 0U || send_streams_.size() == 0);
   for (const auto& stream : send_streams_) {
     webrtc::AudioSendStream::Stats stats =
         stream.second->GetStats(recv_streams_.size() > 0);
diff --git a/media/engine/webrtc_voice_engine.h b/media/engine/webrtc_voice_engine.h
index 1c06183..8fa7d7d 100644
--- a/media/engine/webrtc_voice_engine.h
+++ b/media/engine/webrtc_voice_engine.h
@@ -69,6 +69,7 @@
 
   rtc::scoped_refptr<webrtc::AudioState> GetAudioState() const override;
   VoiceMediaChannel* CreateMediaChannel(
+      MediaChannel::Role role,
       webrtc::Call* call,
       const MediaConfig& config,
       const AudioOptions& options,
@@ -139,7 +140,8 @@
 class WebRtcVoiceMediaChannel final : public VoiceMediaChannel,
                                       public webrtc::Transport {
  public:
-  WebRtcVoiceMediaChannel(WebRtcVoiceEngine* engine,
+  WebRtcVoiceMediaChannel(MediaChannel::Role role,
+                          WebRtcVoiceEngine* engine,
                           const MediaConfig& config,
                           const AudioOptions& options,
                           const webrtc::CryptoOptions& crypto_options,
@@ -175,6 +177,9 @@
   bool RemoveRecvStream(uint32_t ssrc) override;
   void ResetUnsignaledRecvStream() override;
   absl::optional<uint32_t> GetUnsignaledSsrc() const override;
+
+  bool SetLocalSsrc(const StreamParams& sp) override;
+
   void OnDemuxerCriteriaUpdatePending() override;
   void OnDemuxerCriteriaUpdateComplete() override;
 
diff --git a/media/engine/webrtc_voice_engine_unittest.cc b/media/engine/webrtc_voice_engine_unittest.cc
index 70b2cf8d..70cc461 100644
--- a/media/engine/webrtc_voice_engine_unittest.cc
+++ b/media/engine/webrtc_voice_engine_unittest.cc
@@ -230,9 +230,9 @@
   }
 
   bool SetupChannel() {
-    channel_ = engine_->CreateMediaChannel(&call_, cricket::MediaConfig(),
-                                           cricket::AudioOptions(),
-                                           webrtc::CryptoOptions());
+    channel_ = engine_->CreateMediaChannel(
+        cricket::MediaChannel::Role::kBoth, &call_, cricket::MediaConfig(),
+        cricket::AudioOptions(), webrtc::CryptoOptions());
     send_channel_ = std::make_unique<cricket::VoiceMediaSendChannel>(channel_);
     receive_channel_ =
         std::make_unique<cricket::VoiceMediaReceiveChannel>(channel_);
@@ -3029,9 +3029,9 @@
   EXPECT_CALL(*adm_, InitRecording()).Times(1);
 
   std::unique_ptr<cricket::VoiceMediaChannel> channel(
-      engine_->CreateMediaChannel(&call_, cricket::MediaConfig(),
-                                  cricket::AudioOptions(),
-                                  webrtc::CryptoOptions()));
+      engine_->CreateMediaChannel(
+          cricket::MediaChannel::Role::kBoth, &call_, cricket::MediaConfig(),
+          cricket::AudioOptions(), webrtc::CryptoOptions()));
 
   channel->SetSend(true);
 }
@@ -3045,7 +3045,8 @@
   options.init_recording_on_send = false;
 
   std::unique_ptr<cricket::VoiceMediaChannel> channel(
-      engine_->CreateMediaChannel(&call_, cricket::MediaConfig(), options,
+      engine_->CreateMediaChannel(cricket::MediaChannel::Role::kBoth, &call_,
+                                  cricket::MediaConfig(), options,
                                   webrtc::CryptoOptions()));
 
   channel->SetSend(true);
@@ -3071,12 +3072,14 @@
 
   std::unique_ptr<cricket::WebRtcVoiceMediaChannel> channel1(
       static_cast<cricket::WebRtcVoiceMediaChannel*>(
-          engine_->CreateMediaChannel(&call_, cricket::MediaConfig(),
+          engine_->CreateMediaChannel(cricket::MediaChannel::Role::kBoth,
+                                      &call_, cricket::MediaConfig(),
                                       cricket::AudioOptions(),
                                       webrtc::CryptoOptions())));
   std::unique_ptr<cricket::WebRtcVoiceMediaChannel> channel2(
       static_cast<cricket::WebRtcVoiceMediaChannel*>(
-          engine_->CreateMediaChannel(&call_, cricket::MediaConfig(),
+          engine_->CreateMediaChannel(cricket::MediaChannel::Role::kBoth,
+                                      &call_, cricket::MediaConfig(),
                                       cricket::AudioOptions(),
                                       webrtc::CryptoOptions())));
 
@@ -3188,7 +3191,8 @@
   webrtc::RtpParameters parameters;
 
   channel.reset(static_cast<cricket::WebRtcVoiceMediaChannel*>(
-      engine_->CreateMediaChannel(&call_, config, cricket::AudioOptions(),
+      engine_->CreateMediaChannel(cricket::MediaChannel::Role::kBoth, &call_,
+                                  config, cricket::AudioOptions(),
                                   webrtc::CryptoOptions())));
   channel->SetInterface(&network_interface);
   // Default value when DSCP is disabled should be DSCP_DEFAULT.
@@ -3197,7 +3201,8 @@
 
   config.enable_dscp = true;
   channel.reset(static_cast<cricket::WebRtcVoiceMediaChannel*>(
-      engine_->CreateMediaChannel(&call_, config, cricket::AudioOptions(),
+      engine_->CreateMediaChannel(cricket::MediaChannel::Role::kBoth, &call_,
+                                  config, cricket::AudioOptions(),
                                   webrtc::CryptoOptions())));
   channel->SetInterface(&network_interface);
   EXPECT_EQ(rtc::DSCP_DEFAULT, network_interface.dscp());
@@ -3226,7 +3231,8 @@
   // DiffServCodePoint.
   config.enable_dscp = false;
   channel.reset(static_cast<cricket::WebRtcVoiceMediaChannel*>(
-      engine_->CreateMediaChannel(&call_, config, cricket::AudioOptions(),
+      engine_->CreateMediaChannel(cricket::MediaChannel::Role::kBoth, &call_,
+                                  config, cricket::AudioOptions(),
                                   webrtc::CryptoOptions())));
   channel->SetInterface(&network_interface);
   // Default value when DSCP is disabled should be DSCP_DEFAULT.
@@ -3473,6 +3479,7 @@
   EXPECT_EQ(kSsrcY, GetRecvStreamConfig(kSsrcZ).rtp.local_ssrc);
   EXPECT_TRUE(send_channel_->AddSendStream(
       cricket::StreamParams::CreateLegacy(kSsrcW)));
+
   EXPECT_EQ(kSsrcY, GetRecvStreamConfig(kSsrcX).rtp.local_ssrc);
   EXPECT_EQ(kSsrcY, GetRecvStreamConfig(kSsrcZ).rtp.local_ssrc);
 }
@@ -3636,8 +3643,8 @@
     call_config.task_queue_factory = task_queue_factory.get();
     auto call = absl::WrapUnique(webrtc::Call::Create(call_config));
     cricket::VoiceMediaChannel* channel = engine.CreateMediaChannel(
-        call.get(), cricket::MediaConfig(), cricket::AudioOptions(),
-        webrtc::CryptoOptions());
+        cricket::MediaChannel::Role::kBoth, call.get(), cricket::MediaConfig(),
+        cricket::AudioOptions(), webrtc::CryptoOptions());
     EXPECT_TRUE(channel != nullptr);
     delete channel;
   }
@@ -3667,7 +3674,8 @@
       call_config.task_queue_factory = task_queue_factory.get();
       auto call = absl::WrapUnique(webrtc::Call::Create(call_config));
       cricket::VoiceMediaChannel* channel = engine.CreateMediaChannel(
-          call.get(), cricket::MediaConfig(), cricket::AudioOptions(),
+          cricket::MediaChannel::Role::kBoth, call.get(),
+          cricket::MediaConfig(), cricket::AudioOptions(),
           webrtc::CryptoOptions());
       EXPECT_TRUE(channel != nullptr);
       delete channel;
@@ -3756,7 +3764,8 @@
     size_t num_channels = 0;
     while (num_channels < arraysize(channels)) {
       cricket::VoiceMediaChannel* channel = engine.CreateMediaChannel(
-          call.get(), cricket::MediaConfig(), cricket::AudioOptions(),
+          cricket::MediaChannel::Role::kBoth, call.get(),
+          cricket::MediaConfig(), cricket::AudioOptions(),
           webrtc::CryptoOptions());
       if (!channel)
         break;
@@ -3802,8 +3811,8 @@
     call_config.task_queue_factory = task_queue_factory.get();
     auto call = absl::WrapUnique(webrtc::Call::Create(call_config));
     cricket::WebRtcVoiceMediaChannel channel(
-        &engine, cricket::MediaConfig(), cricket::AudioOptions(),
-        webrtc::CryptoOptions(), call.get());
+        cricket::MediaChannel::Role::kReceive, &engine, cricket::MediaConfig(),
+        cricket::AudioOptions(), webrtc::CryptoOptions(), call.get());
     cricket::AudioRecvParameters parameters;
     parameters.codecs = engine.recv_codecs();
     EXPECT_TRUE(channel.SetRecvParameters(parameters));
@@ -3835,9 +3844,9 @@
     call_config.audio_state = webrtc::AudioState::Create(config);
   }
   auto call = absl::WrapUnique(webrtc::Call::Create(call_config));
-  cricket::WebRtcVoiceMediaChannel channel(&engine, cricket::MediaConfig(),
-                                           cricket::AudioOptions(),
-                                           webrtc::CryptoOptions(), call.get());
+  cricket::WebRtcVoiceMediaChannel channel(
+      cricket::MediaChannel::Role::kSend, &engine, cricket::MediaConfig(),
+      cricket::AudioOptions(), webrtc::CryptoOptions(), call.get());
   {
     cricket::AudioSendParameters params;
     params.codecs.push_back(cricket::AudioCodec(1, "opus", 48000, 32000, 2));
diff --git a/pc/channel_unittest.cc b/pc/channel_unittest.cc
index 27ad96a..1635ea5 100644
--- a/pc/channel_unittest.cc
+++ b/pc/channel_unittest.cc
@@ -151,9 +151,11 @@
 
   void CreateChannels(int flags1, int flags2) {
     CreateChannels(std::make_unique<typename T::MediaChannel>(
-                       nullptr, typename T::Options(), network_thread_),
+                       cricket::MediaChannel::Role::kBoth, nullptr,
+                       typename T::Options(), network_thread_),
                    std::make_unique<typename T::MediaChannel>(
-                       nullptr, typename T::Options(), network_thread_),
+                       cricket::MediaChannel::Role::kBoth, nullptr,
+                       typename T::Options(), network_thread_),
                    flags1, flags2);
   }
   void CreateChannels(std::unique_ptr<typename T::MediaChannel> ch1,
diff --git a/pc/rtp_sender_receiver_unittest.cc b/pc/rtp_sender_receiver_unittest.cc
index 0404343..bcca2da 100644
--- a/pc/rtp_sender_receiver_unittest.cc
+++ b/pc/rtp_sender_receiver_unittest.cc
@@ -119,11 +119,13 @@
     // Create the channels, discard the result; we get them later.
     // Fake media channels are owned by the media engine.
     media_engine_->voice().CreateMediaChannel(
-        &fake_call_, cricket::MediaConfig(), cricket::AudioOptions(),
-        webrtc::CryptoOptions());
+        cricket::MediaChannel::Role::kBoth, &fake_call_, cricket::MediaConfig(),
+        cricket::AudioOptions(), webrtc::CryptoOptions());
     media_engine_->video().CreateMediaChannel(
-        &fake_call_, cricket::MediaConfig(), cricket::VideoOptions(),
-        webrtc::CryptoOptions(), video_bitrate_allocator_factory_.get());
+        cricket::MediaChannel::Role::kBoth, &fake_call_, cricket::MediaConfig(),
+        cricket::VideoOptions(), webrtc::CryptoOptions(),
+        video_bitrate_allocator_factory_.get());
+    // TODO(hta): Split into sender and receiver channels
 
     voice_media_channel_ = absl::WrapUnique(media_engine_->GetVoiceChannel(0));
     video_media_channel_ = absl::WrapUnique(media_engine_->GetVideoChannel(0));
diff --git a/pc/rtp_transceiver.cc b/pc/rtp_transceiver.cc
index 76549d1..791eb8c 100644
--- a/pc/rtp_transceiver.cc
+++ b/pc/rtp_transceiver.cc
@@ -211,7 +211,8 @@
 
       cricket::VoiceMediaChannel* media_channel =
           media_engine()->voice().CreateMediaChannel(
-              call_ptr, media_config, audio_options, crypto_options);
+              cricket::MediaChannel::Role::kBoth, call_ptr, media_config,
+              audio_options, crypto_options);
       if (!media_channel) {
         return;
       }
@@ -231,8 +232,8 @@
       RTC_DCHECK_RUN_ON(context()->worker_thread());
       cricket::VideoMediaChannel* media_channel =
           media_engine()->video().CreateMediaChannel(
-              call_ptr, media_config, video_options, crypto_options,
-              video_bitrate_allocator_factory);
+              cricket::MediaChannel::Role::kBoth, call_ptr, media_config,
+              video_options, crypto_options, video_bitrate_allocator_factory);
       if (!media_channel) {
         return;
       }
diff --git a/pc/test/fake_peer_connection_for_stats.h b/pc/test/fake_peer_connection_for_stats.h
index b771d45..d1914fd 100644
--- a/pc/test/fake_peer_connection_for_stats.h
+++ b/pc/test/fake_peer_connection_for_stats.h
@@ -30,7 +30,8 @@
 class FakeVoiceMediaChannelForStats : public cricket::FakeVoiceMediaChannel {
  public:
   explicit FakeVoiceMediaChannelForStats(TaskQueueBase* network_thread)
-      : cricket::FakeVoiceMediaChannel(nullptr,
+      : cricket::FakeVoiceMediaChannel(MediaChannel::Role::kBoth,
+                                       nullptr,
                                        cricket::AudioOptions(),
                                        network_thread) {}
 
@@ -70,7 +71,8 @@
 class FakeVideoMediaChannelForStats : public cricket::FakeVideoMediaChannel {
  public:
   explicit FakeVideoMediaChannelForStats(TaskQueueBase* network_thread)
-      : cricket::FakeVideoMediaChannel(nullptr,
+      : cricket::FakeVideoMediaChannel(MediaChannel::Role::kBoth,
+                                       nullptr,
                                        cricket::VideoOptions(),
                                        network_thread) {}
 
diff --git a/pc/test/mock_voice_media_channel.h b/pc/test/mock_voice_media_channel.h
index 2e5a8b5..71f7a18 100644
--- a/pc/test/mock_voice_media_channel.h
+++ b/pc/test/mock_voice_media_channel.h
@@ -29,7 +29,7 @@
 class MockVoiceMediaChannel : public VoiceMediaChannel {
  public:
   explicit MockVoiceMediaChannel(webrtc::TaskQueueBase* network_thread)
-      : VoiceMediaChannel(network_thread) {}
+      : VoiceMediaChannel(MediaChannel::Role::kBoth, network_thread) {}
 
   MOCK_METHOD(void,
               SetInterface,
@@ -49,6 +49,9 @@
               (absl::string_view transport_name,
                const rtc::NetworkRoute& network_route),
               (override));
+  MOCK_METHOD(void, SetExtmapAllowMixed, (bool extmap_allow_mixed), (override));
+  MOCK_METHOD(bool, ExtmapAllowMixed, (), (const, override));
+  MOCK_METHOD(bool, HasNetworkInterface, (), (const, override));
   MOCK_METHOD(bool, AddSendStream, (const StreamParams& sp), (override));
   MOCK_METHOD(bool, RemoveSendStream, (uint32_t ssrc), (override));
   MOCK_METHOD(bool, AddRecvStream, (const StreamParams& sp), (override));
@@ -58,6 +61,7 @@
               GetUnsignaledSsrc,
               (),
               (const, override));
+  MOCK_METHOD(bool, SetLocalSsrc, (const StreamParams& sp), (override));
   MOCK_METHOD(void, OnDemuxerCriteriaUpdatePending, (), (override));
   MOCK_METHOD(void, OnDemuxerCriteriaUpdateComplete, (), (override));
   MOCK_METHOD(int, GetRtpSendTimeExtnId, (), (const, override));
diff --git a/pc/video_rtp_receiver_unittest.cc b/pc/video_rtp_receiver_unittest.cc
index 3ec9a28..5efd6df 100644
--- a/pc/video_rtp_receiver_unittest.cc
+++ b/pc/video_rtp_receiver_unittest.cc
@@ -40,7 +40,10 @@
         cricket::FakeVideoEngine* engine,
         const cricket::VideoOptions& options,
         TaskQueueBase* network_thread = rtc::Thread::Current())
-        : FakeVideoMediaChannel(engine, options, network_thread) {}
+        : FakeVideoMediaChannel(cricket::MediaChannel::Role::kBoth,
+                                engine,
+                                options,
+                                network_thread) {}
     MOCK_METHOD(void,
                 SetRecordableEncodedFrameCallback,
                 (uint32_t, std::function<void(const RecordableEncodedFrame&)>),