Separate RTP object handling (senders, receivers, transceivers)

This is part of the PeerConnection disassembly project.

Bug: webrtc:11995
Change-Id: I4f207c8af39e267c4b5752c0828b84e221e1f080
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/188624
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Niels Moller <nisse@webrtc.org>
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#32443}
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 6e46301..821802f 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -195,8 +195,6 @@
     "stream_collection.h",
     "track_media_info_map.cc",
     "track_media_info_map.h",
-    "transceiver_list.cc",
-    "transceiver_list.h",
     "webrtc_sdp.cc",
     "webrtc_sdp.h",
     "webrtc_session_description_factory.cc",
@@ -220,7 +218,9 @@
     ":rtp_receiver",
     ":rtp_sender",
     ":rtp_transceiver",
+    ":rtp_transmission_manager",
     ":stats_collector_interface",
+    ":transceiver_list",
     ":usage_pattern",
     ":video_rtp_receiver",
     ":video_track",
@@ -365,6 +365,47 @@
   ]
 }
 
+rtc_library("rtp_transmission_manager") {
+  sources = [
+    "rtp_transmission_manager.cc",
+    "rtp_transmission_manager.h",
+  ]
+  deps = [
+    ":audio_rtp_receiver",
+    ":rtc_pc_base",
+    ":rtp_receiver",
+    ":rtp_sender",
+    ":rtp_transceiver",
+    ":stats_collector_interface",
+    ":transceiver_list",
+    ":usage_pattern",
+    ":video_rtp_receiver",
+    "../api:libjingle_peerconnection_api",
+    "../api:media_stream_interface",
+    "../api:rtc_error",
+    "../api:rtp_parameters",
+    "../api:rtp_transceiver_direction",
+    "../api:scoped_refptr",
+    "../media:rtc_media_base",
+    "../rtc_base",
+    "../rtc_base:checks",
+    "../rtc_base/third_party/sigslot",
+  ]
+  absl_deps = [
+    "//third_party/abseil-cpp/absl/algorithm:container",
+    "//third_party/abseil-cpp/absl/strings",
+    "//third_party/abseil-cpp/absl/types:optional",
+  ]
+}
+
+rtc_library("transceiver_list") {
+  sources = [
+    "transceiver_list.cc",
+    "transceiver_list.h",
+  ]
+  deps = [ ":rtp_transceiver" ]
+}
+
 rtc_library("rtp_receiver") {
   sources = [
     "rtp_receiver.cc",
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index 7123b7b..9f3f6ba 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -13,6 +13,10 @@
 #include <limits.h>
 #include <stddef.h>
 #include <algorithm>
+#include <limits>
+#include <memory>
+#include <queue>
+#include <set>
 #include <utility>
 
 #include "absl/algorithm/container.h"
@@ -83,9 +87,6 @@
 const char kSimulcastNumberOfEncodings[] =
     "WebRTC.PeerConnection.Simulcast.NumberOfSendEncodings";
 
-static const char kDefaultAudioSenderId[] = "defaulta0";
-static const char kDefaultVideoSenderId[] = "defaultv0";
-
 static const int REPORT_USAGE_PATTERN_DELAY_MS = 60000;
 
 
@@ -351,8 +352,10 @@
   // Need to stop transceivers before destroying the stats collector because
   // AudioRtpSender has a reference to the StatsCollector it will update when
   // stopping.
-  for (const auto& transceiver : transceivers_.List()) {
-    transceiver->StopInternal();
+  if (rtp_manager()) {
+    for (const auto& transceiver : rtp_manager()->transceivers()->List()) {
+      transceiver->StopInternal();
+    }
   }
 
   stats_.reset(nullptr);
@@ -547,13 +550,20 @@
         OnTransportControllerConnectionState(s);
       });
 
-  stats_.reset(new StatsCollector(this));
-  stats_collector_ = RTCStatsCollector::Create(this);
-
   configuration_ = configuration;
 
   transport_controller_->SetIceConfig(ParseIceConfig(configuration));
 
+  stats_ = std::make_unique<StatsCollector>(this);
+  stats_collector_ = RTCStatsCollector::Create(this);
+
+  rtp_manager_ = std::make_unique<RtpTransmissionManager>(
+      IsUnifiedPlan(), signaling_thread(), worker_thread(), channel_manager(),
+      &usage_pattern_, observer_, stats_.get(), [this]() {
+        RTC_DCHECK_RUN_ON(signaling_thread());
+        sdp_handler_.UpdateNegotiationNeeded();
+      });
+
   video_options_.screencast_min_bitrate_kbps =
       configuration.screencast_min_bitrate;
   audio_options_.combined_audio_video_bwe =
@@ -601,10 +611,12 @@
 
   // Add default audio/video transceivers for Plan B SDP.
   if (!IsUnifiedPlan()) {
-    transceivers_.Add(RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
-        signaling_thread(), new RtpTransceiver(cricket::MEDIA_TYPE_AUDIO)));
-    transceivers_.Add(RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
-        signaling_thread(), new RtpTransceiver(cricket::MEDIA_TYPE_VIDEO)));
+    rtp_manager()->transceivers()->Add(
+        RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
+            signaling_thread(), new RtpTransceiver(cricket::MEDIA_TYPE_AUDIO)));
+    rtp_manager()->transceivers()->Add(
+        RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
+            signaling_thread(), new RtpTransceiver(cricket::MEDIA_TYPE_VIDEO)));
   }
   int delay_ms =
       return_histogram_very_quickly_ ? 0 : REPORT_USAGE_PATTERN_DELAY_MS;
@@ -682,14 +694,12 @@
     LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_STATE,
                          "PeerConnection is closed.");
   }
-  if (FindSenderForTrack(track)) {
+  if (rtp_manager()->FindSenderForTrack(track)) {
     LOG_AND_RETURN_ERROR(
         RTCErrorType::INVALID_PARAMETER,
         "Sender already exists for track " + track->id() + ".");
   }
-  auto sender_or_error =
-      (IsUnifiedPlan() ? AddTrackUnifiedPlan(track, stream_ids)
-                       : AddTrackPlanB(track, stream_ids));
+  auto sender_or_error = rtp_manager()->AddTrack(track, stream_ids);
   if (sender_or_error.ok()) {
     sdp_handler_.UpdateNegotiationNeeded();
     stats_->AddTrack(track);
@@ -697,111 +707,6 @@
   return sender_or_error;
 }
 
-RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
-PeerConnection::AddTrackPlanB(
-    rtc::scoped_refptr<MediaStreamTrackInterface> track,
-    const std::vector<std::string>& stream_ids) {
-  if (stream_ids.size() > 1u) {
-    LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION,
-                         "AddTrack with more than one stream is not "
-                         "supported with Plan B semantics.");
-  }
-  std::vector<std::string> adjusted_stream_ids = stream_ids;
-  if (adjusted_stream_ids.empty()) {
-    adjusted_stream_ids.push_back(rtc::CreateRandomUuid());
-  }
-  cricket::MediaType media_type =
-      (track->kind() == MediaStreamTrackInterface::kAudioKind
-           ? cricket::MEDIA_TYPE_AUDIO
-           : cricket::MEDIA_TYPE_VIDEO);
-  auto new_sender =
-      CreateSender(media_type, track->id(), track, adjusted_stream_ids, {});
-  if (track->kind() == MediaStreamTrackInterface::kAudioKind) {
-    new_sender->internal()->SetMediaChannel(voice_media_channel());
-    GetAudioTransceiver()->internal()->AddSender(new_sender);
-    const RtpSenderInfo* sender_info =
-        FindSenderInfo(local_audio_sender_infos_,
-                       new_sender->internal()->stream_ids()[0], track->id());
-    if (sender_info) {
-      new_sender->internal()->SetSsrc(sender_info->first_ssrc);
-    }
-  } else {
-    RTC_DCHECK_EQ(MediaStreamTrackInterface::kVideoKind, track->kind());
-    new_sender->internal()->SetMediaChannel(video_media_channel());
-    GetVideoTransceiver()->internal()->AddSender(new_sender);
-    const RtpSenderInfo* sender_info =
-        FindSenderInfo(local_video_sender_infos_,
-                       new_sender->internal()->stream_ids()[0], track->id());
-    if (sender_info) {
-      new_sender->internal()->SetSsrc(sender_info->first_ssrc);
-    }
-  }
-  return rtc::scoped_refptr<RtpSenderInterface>(new_sender);
-}
-
-RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
-PeerConnection::AddTrackUnifiedPlan(
-    rtc::scoped_refptr<MediaStreamTrackInterface> track,
-    const std::vector<std::string>& stream_ids) {
-  auto transceiver = FindFirstTransceiverForAddedTrack(track);
-  if (transceiver) {
-    RTC_LOG(LS_INFO) << "Reusing an existing "
-                     << cricket::MediaTypeToString(transceiver->media_type())
-                     << " transceiver for AddTrack.";
-    if (transceiver->stopping()) {
-      LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
-                           "The existing transceiver is stopping.");
-    }
-
-    if (transceiver->direction() == RtpTransceiverDirection::kRecvOnly) {
-      transceiver->internal()->set_direction(
-          RtpTransceiverDirection::kSendRecv);
-    } else if (transceiver->direction() == RtpTransceiverDirection::kInactive) {
-      transceiver->internal()->set_direction(
-          RtpTransceiverDirection::kSendOnly);
-    }
-    transceiver->sender()->SetTrack(track);
-    transceiver->internal()->sender_internal()->set_stream_ids(stream_ids);
-    transceiver->internal()->set_reused_for_addtrack(true);
-  } else {
-    cricket::MediaType media_type =
-        (track->kind() == MediaStreamTrackInterface::kAudioKind
-             ? cricket::MEDIA_TYPE_AUDIO
-             : cricket::MEDIA_TYPE_VIDEO);
-    RTC_LOG(LS_INFO) << "Adding " << cricket::MediaTypeToString(media_type)
-                     << " transceiver in response to a call to AddTrack.";
-    std::string sender_id = track->id();
-    // Avoid creating a sender with an existing ID by generating a random ID.
-    // This can happen if this is the second time AddTrack has created a sender
-    // for this track.
-    if (FindSenderById(sender_id)) {
-      sender_id = rtc::CreateRandomUuid();
-    }
-    auto sender = CreateSender(media_type, sender_id, track, stream_ids, {});
-    auto receiver = CreateReceiver(media_type, rtc::CreateRandomUuid());
-    transceiver = CreateAndAddTransceiver(sender, receiver);
-    transceiver->internal()->set_created_by_addtrack(true);
-    transceiver->internal()->set_direction(RtpTransceiverDirection::kSendRecv);
-  }
-  return transceiver->sender();
-}
-
-rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-PeerConnection::FindFirstTransceiverForAddedTrack(
-    rtc::scoped_refptr<MediaStreamTrackInterface> track) {
-  RTC_DCHECK(track);
-  for (auto transceiver : transceivers_.List()) {
-    if (!transceiver->sender()->track() &&
-        cricket::MediaTypeToString(transceiver->media_type()) ==
-            track->kind() &&
-        !transceiver->internal()->has_ever_been_used_to_send() &&
-        !transceiver->stopped()) {
-      return transceiver;
-    }
-  }
-  return nullptr;
-}
-
 bool PeerConnection::RemoveTrack(RtpSenderInterface* sender) {
   TRACE_EVENT0("webrtc", "PeerConnection::RemoveTrack");
   return RemoveTrackNew(sender).ok();
@@ -833,10 +738,12 @@
   } else {
     bool removed;
     if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) {
-      removed = GetAudioTransceiver()->internal()->RemoveSender(sender);
+      removed = rtp_manager()->GetAudioTransceiver()->internal()->RemoveSender(
+          sender);
     } else {
       RTC_DCHECK_EQ(cricket::MEDIA_TYPE_VIDEO, sender->media_type());
-      removed = GetVideoTransceiver()->internal()->RemoveSender(sender);
+      removed = rtp_manager()->GetVideoTransceiver()->internal()->RemoveSender(
+          sender);
     }
     if (!removed) {
       LOG_AND_RETURN_ERROR(
@@ -851,7 +758,7 @@
 rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
 PeerConnection::FindTransceiverBySender(
     rtc::scoped_refptr<RtpSenderInterface> sender) {
-  return transceivers_.FindBySender(sender);
+  return rtp_manager()->transceivers()->FindBySender(sender);
 }
 
 RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
@@ -987,13 +894,14 @@
                    << " transceiver in response to a call to AddTransceiver.";
   // Set the sender ID equal to the track ID if the track is specified unless
   // that sender ID is already in use.
-  std::string sender_id =
-      (track && !FindSenderById(track->id()) ? track->id()
-                                             : rtc::CreateRandomUuid());
-  auto sender = CreateSender(media_type, sender_id, track, init.stream_ids,
-                             parameters.encodings);
-  auto receiver = CreateReceiver(media_type, rtc::CreateRandomUuid());
-  auto transceiver = CreateAndAddTransceiver(sender, receiver);
+  std::string sender_id = (track && !rtp_manager()->FindSenderById(track->id())
+                               ? track->id()
+                               : rtc::CreateRandomUuid());
+  auto sender = rtp_manager()->CreateSender(
+      media_type, sender_id, track, init.stream_ids, parameters.encodings);
+  auto receiver =
+      rtp_manager()->CreateReceiver(media_type, rtc::CreateRandomUuid());
+  auto transceiver = rtp_manager()->CreateAndAddTransceiver(sender, receiver);
   transceiver->internal()->set_direction(init.direction);
 
   if (update_negotiation_needed) {
@@ -1003,81 +911,6 @@
   return rtc::scoped_refptr<RtpTransceiverInterface>(transceiver);
 }
 
-rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
-PeerConnection::CreateSender(
-    cricket::MediaType media_type,
-    const std::string& id,
-    rtc::scoped_refptr<MediaStreamTrackInterface> track,
-    const std::vector<std::string>& stream_ids,
-    const std::vector<RtpEncodingParameters>& send_encodings) {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> sender;
-  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
-    RTC_DCHECK(!track ||
-               (track->kind() == MediaStreamTrackInterface::kAudioKind));
-    sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
-        signaling_thread(),
-        AudioRtpSender::Create(worker_thread(), id, stats_.get(), this));
-    NoteUsageEvent(UsageEvent::AUDIO_ADDED);
-  } else {
-    RTC_DCHECK_EQ(media_type, cricket::MEDIA_TYPE_VIDEO);
-    RTC_DCHECK(!track ||
-               (track->kind() == MediaStreamTrackInterface::kVideoKind));
-    sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
-        signaling_thread(), VideoRtpSender::Create(worker_thread(), id, this));
-    NoteUsageEvent(UsageEvent::VIDEO_ADDED);
-  }
-  bool set_track_succeeded = sender->SetTrack(track);
-  RTC_DCHECK(set_track_succeeded);
-  sender->internal()->set_stream_ids(stream_ids);
-  sender->internal()->set_init_send_encodings(send_encodings);
-  return sender;
-}
-
-rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
-PeerConnection::CreateReceiver(cricket::MediaType media_type,
-                               const std::string& receiver_id) {
-  rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
-      receiver;
-  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
-    receiver = RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
-        signaling_thread(), new AudioRtpReceiver(worker_thread(), receiver_id,
-                                                 std::vector<std::string>({})));
-    NoteUsageEvent(UsageEvent::AUDIO_ADDED);
-  } else {
-    RTC_DCHECK_EQ(media_type, cricket::MEDIA_TYPE_VIDEO);
-    receiver = RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
-        signaling_thread(), new VideoRtpReceiver(worker_thread(), receiver_id,
-                                                 std::vector<std::string>({})));
-    NoteUsageEvent(UsageEvent::VIDEO_ADDED);
-  }
-  return receiver;
-}
-
-rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-PeerConnection::CreateAndAddTransceiver(
-    rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> sender,
-    rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
-        receiver) {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  // Ensure that the new sender does not have an ID that is already in use by
-  // another sender.
-  // Allow receiver IDs to conflict since those come from remote SDP (which
-  // could be invalid, but should not cause a crash).
-  RTC_DCHECK(!FindSenderById(sender->id()));
-  auto transceiver = RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
-      signaling_thread(),
-      new RtpTransceiver(
-          sender, receiver, channel_manager(),
-          sender->media_type() == cricket::MEDIA_TYPE_AUDIO
-              ? channel_manager()->GetSupportedAudioRtpHeaderExtensions()
-              : channel_manager()->GetSupportedVideoRtpHeaderExtensions()));
-  transceivers_.Add(transceiver);
-  transceiver->internal()->SignalNegotiationNeeded.connect(
-      this, &PeerConnection::OnNegotiationNeeded);
-  return transceiver;
-}
-
 void PeerConnection::OnNegotiationNeeded() {
   RTC_DCHECK_RUN_ON(signaling_thread());
   RTC_DCHECK(!IsClosed());
@@ -1112,18 +945,18 @@
   rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> new_sender;
   if (kind == MediaStreamTrackInterface::kAudioKind) {
     auto audio_sender = AudioRtpSender::Create(
-        worker_thread(), rtc::CreateRandomUuid(), stats_.get(), this);
-    audio_sender->SetMediaChannel(voice_media_channel());
+        worker_thread(), rtc::CreateRandomUuid(), stats_.get(), rtp_manager());
+    audio_sender->SetMediaChannel(rtp_manager()->voice_media_channel());
     new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
         signaling_thread(), audio_sender);
-    GetAudioTransceiver()->internal()->AddSender(new_sender);
+    rtp_manager()->GetAudioTransceiver()->internal()->AddSender(new_sender);
   } else if (kind == MediaStreamTrackInterface::kVideoKind) {
-    auto video_sender =
-        VideoRtpSender::Create(worker_thread(), rtc::CreateRandomUuid(), this);
-    video_sender->SetMediaChannel(video_media_channel());
+    auto video_sender = VideoRtpSender::Create(
+        worker_thread(), rtc::CreateRandomUuid(), rtp_manager());
+    video_sender->SetMediaChannel(rtp_manager()->video_media_channel());
     new_sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
         signaling_thread(), video_sender);
-    GetVideoTransceiver()->internal()->AddSender(new_sender);
+    rtp_manager()->GetVideoTransceiver()->internal()->AddSender(new_sender);
   } else {
     RTC_LOG(LS_ERROR) << "CreateSender called with invalid kind: " << kind;
     return nullptr;
@@ -1137,61 +970,29 @@
     const {
   RTC_DCHECK_RUN_ON(signaling_thread());
   std::vector<rtc::scoped_refptr<RtpSenderInterface>> ret;
-  for (const auto& sender : GetSendersInternal()) {
+  for (const auto& sender : rtp_manager()->GetSendersInternal()) {
     ret.push_back(sender);
   }
   return ret;
 }
 
-std::vector<rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>>
-PeerConnection::GetSendersInternal() const {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  std::vector<rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>>
-      all_senders;
-  for (const auto& transceiver : transceivers_.List()) {
-    if (IsUnifiedPlan() && transceiver->internal()->stopped())
-      continue;
-
-    auto senders = transceiver->internal()->senders();
-    all_senders.insert(all_senders.end(), senders.begin(), senders.end());
-  }
-  return all_senders;
-}
-
 std::vector<rtc::scoped_refptr<RtpReceiverInterface>>
 PeerConnection::GetReceivers() const {
   RTC_DCHECK_RUN_ON(signaling_thread());
   std::vector<rtc::scoped_refptr<RtpReceiverInterface>> ret;
-  for (const auto& receiver : GetReceiversInternal()) {
+  for (const auto& receiver : rtp_manager()->GetReceiversInternal()) {
     ret.push_back(receiver);
   }
   return ret;
 }
 
-std::vector<
-    rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>>
-PeerConnection::GetReceiversInternal() const {
-  std::vector<
-      rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>>
-      all_receivers;
-  for (const auto& transceiver : transceivers_.List()) {
-    if (IsUnifiedPlan() && transceiver->internal()->stopped())
-      continue;
-
-    auto receivers = transceiver->internal()->receivers();
-    all_receivers.insert(all_receivers.end(), receivers.begin(),
-                         receivers.end());
-  }
-  return all_receivers;
-}
-
 std::vector<rtc::scoped_refptr<RtpTransceiverInterface>>
 PeerConnection::GetTransceivers() const {
   RTC_DCHECK_RUN_ON(signaling_thread());
   RTC_CHECK(IsUnifiedPlan())
       << "GetTransceivers is only supported with Unified Plan SdpSemantics.";
   std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> all_transceivers;
-  for (const auto& transceiver : transceivers_.List()) {
+  for (const auto& transceiver : rtp_manager()->transceivers()->List()) {
     all_transceivers.push_back(transceiver);
   }
   return all_transceivers;
@@ -1236,7 +1037,8 @@
   RTC_DCHECK(stats_collector_);
   rtc::scoped_refptr<RtpSenderInternal> internal_sender;
   if (selector) {
-    for (const auto& proxy_transceiver : transceivers_.List()) {
+    for (const auto& proxy_transceiver :
+         rtp_manager()->transceivers()->List()) {
       for (const auto& proxy_sender :
            proxy_transceiver->internal()->senders()) {
         if (proxy_sender == selector) {
@@ -1265,7 +1067,8 @@
   RTC_DCHECK(stats_collector_);
   rtc::scoped_refptr<RtpReceiverInternal> internal_receiver;
   if (selector) {
-    for (const auto& proxy_transceiver : transceivers_.List()) {
+    for (const auto& proxy_transceiver :
+         rtp_manager()->transceivers()->List()) {
       for (const auto& proxy_receiver :
            proxy_transceiver->internal()->receivers()) {
         if (proxy_receiver == selector) {
@@ -1552,7 +1355,7 @@
 
   if (modified_config.allow_codec_switching.has_value()) {
     std::vector<cricket::VideoMediaChannel*> channels;
-    for (const auto& transceiver : transceivers_.List()) {
+    for (const auto& transceiver : rtp_manager()->transceivers()->List()) {
       if (transceiver->media_type() != cricket::MEDIA_TYPE_VIDEO)
         continue;
 
@@ -1673,7 +1476,7 @@
 std::unique_ptr<rtc::SSLCertChain>
 PeerConnection::GetRemoteAudioSSLCertChain() {
   RTC_DCHECK_RUN_ON(signaling_thread());
-  auto audio_transceiver = GetFirstAudioTransceiver();
+  auto audio_transceiver = rtp_manager()->GetFirstAudioTransceiver();
   if (!audio_transceiver || !audio_transceiver->internal()->channel()) {
     return nullptr;
   }
@@ -1681,16 +1484,6 @@
       audio_transceiver->internal()->channel()->transport_name());
 }
 
-rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-PeerConnection::GetFirstAudioTransceiver() const {
-  for (auto transceiver : transceivers_.List()) {
-    if (transceiver->media_type() == cricket::MEDIA_TYPE_AUDIO) {
-      return transceiver;
-    }
-  }
-  return nullptr;
-}
-
 void PeerConnection::AddAdaptationResource(
     rtc::scoped_refptr<Resource> resource) {
   if (!worker_thread()->IsCurrent()) {
@@ -1807,7 +1600,7 @@
 
   NoteUsageEvent(UsageEvent::CLOSE_CALLED);
 
-  for (const auto& transceiver : transceivers_.List()) {
+  for (const auto& transceiver : rtp_manager()->transceivers()->List()) {
     transceiver->internal()->SetPeerConnectionClosed();
     if (!transceiver->stopped())
       transceiver->StopInternal();
@@ -1830,6 +1623,7 @@
   // call the transport controller.
   sdp_handler_.ResetSessionDescFactory();
   transport_controller_.reset();
+  rtp_manager_->Close();
 
   network_thread()->Invoke<void>(
       RTC_FROM_HERE, rtc::Bind(&cricket::PortAllocator::DiscardCandidatePool,
@@ -1848,176 +1642,6 @@
   observer_ = nullptr;
 }
 
-cricket::VoiceMediaChannel* PeerConnection::voice_media_channel() const {
-  RTC_DCHECK(!IsUnifiedPlan());
-  auto* voice_channel = static_cast<cricket::VoiceChannel*>(
-      GetAudioTransceiver()->internal()->channel());
-  if (voice_channel) {
-    return voice_channel->media_channel();
-  } else {
-    return nullptr;
-  }
-}
-
-cricket::VideoMediaChannel* PeerConnection::video_media_channel() const {
-  RTC_DCHECK(!IsUnifiedPlan());
-  auto* video_channel = static_cast<cricket::VideoChannel*>(
-      GetVideoTransceiver()->internal()->channel());
-  if (video_channel) {
-    return video_channel->media_channel();
-  } else {
-    return nullptr;
-  }
-}
-
-void PeerConnection::CreateAudioReceiver(
-    MediaStreamInterface* stream,
-    const RtpSenderInfo& remote_sender_info) {
-  std::vector<rtc::scoped_refptr<MediaStreamInterface>> streams;
-  streams.push_back(rtc::scoped_refptr<MediaStreamInterface>(stream));
-  // TODO(https://crbug.com/webrtc/9480): When we remove remote_streams(), use
-  // the constructor taking stream IDs instead.
-  auto* audio_receiver = new AudioRtpReceiver(
-      worker_thread(), remote_sender_info.sender_id, streams);
-  audio_receiver->SetMediaChannel(voice_media_channel());
-  if (remote_sender_info.sender_id == kDefaultAudioSenderId) {
-    audio_receiver->SetupUnsignaledMediaChannel();
-  } else {
-    audio_receiver->SetupMediaChannel(remote_sender_info.first_ssrc);
-  }
-  auto receiver = RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
-      signaling_thread(), audio_receiver);
-  GetAudioTransceiver()->internal()->AddReceiver(receiver);
-  Observer()->OnAddTrack(receiver, streams);
-  NoteUsageEvent(UsageEvent::AUDIO_ADDED);
-}
-
-void PeerConnection::CreateVideoReceiver(
-    MediaStreamInterface* stream,
-    const RtpSenderInfo& remote_sender_info) {
-  std::vector<rtc::scoped_refptr<MediaStreamInterface>> streams;
-  streams.push_back(rtc::scoped_refptr<MediaStreamInterface>(stream));
-  // TODO(https://crbug.com/webrtc/9480): When we remove remote_streams(), use
-  // the constructor taking stream IDs instead.
-  auto* video_receiver = new VideoRtpReceiver(
-      worker_thread(), remote_sender_info.sender_id, streams);
-  video_receiver->SetMediaChannel(video_media_channel());
-  if (remote_sender_info.sender_id == kDefaultVideoSenderId) {
-    video_receiver->SetupUnsignaledMediaChannel();
-  } else {
-    video_receiver->SetupMediaChannel(remote_sender_info.first_ssrc);
-  }
-  auto receiver = RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
-      signaling_thread(), video_receiver);
-  GetVideoTransceiver()->internal()->AddReceiver(receiver);
-  Observer()->OnAddTrack(receiver, streams);
-  NoteUsageEvent(UsageEvent::VIDEO_ADDED);
-}
-
-// TODO(deadbeef): Keep RtpReceivers around even if track goes away in remote
-// description.
-rtc::scoped_refptr<RtpReceiverInterface> PeerConnection::RemoveAndStopReceiver(
-    const RtpSenderInfo& remote_sender_info) {
-  auto receiver = FindReceiverById(remote_sender_info.sender_id);
-  if (!receiver) {
-    RTC_LOG(LS_WARNING) << "RtpReceiver for track with id "
-                        << remote_sender_info.sender_id << " doesn't exist.";
-    return nullptr;
-  }
-  if (receiver->media_type() == cricket::MEDIA_TYPE_AUDIO) {
-    GetAudioTransceiver()->internal()->RemoveReceiver(receiver);
-  } else {
-    GetVideoTransceiver()->internal()->RemoveReceiver(receiver);
-  }
-  return receiver;
-}
-
-void PeerConnection::AddAudioTrack(AudioTrackInterface* track,
-                                   MediaStreamInterface* stream) {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  RTC_DCHECK(!IsClosed());
-  RTC_DCHECK(track);
-  RTC_DCHECK(stream);
-  auto sender = FindSenderForTrack(track);
-  if (sender) {
-    // We already have a sender for this track, so just change the stream_id
-    // so that it's correct in the next call to CreateOffer.
-    sender->internal()->set_stream_ids({stream->id()});
-    return;
-  }
-
-  // Normal case; we've never seen this track before.
-  auto new_sender = CreateSender(cricket::MEDIA_TYPE_AUDIO, track->id(), track,
-                                 {stream->id()}, {});
-  new_sender->internal()->SetMediaChannel(voice_media_channel());
-  GetAudioTransceiver()->internal()->AddSender(new_sender);
-  // If the sender has already been configured in SDP, we call SetSsrc,
-  // which will connect the sender to the underlying transport. This can
-  // occur if a local session description that contains the ID of the sender
-  // is set before AddStream is called. It can also occur if the local
-  // session description is not changed and RemoveStream is called, and
-  // later AddStream is called again with the same stream.
-  const RtpSenderInfo* sender_info =
-      FindSenderInfo(local_audio_sender_infos_, stream->id(), track->id());
-  if (sender_info) {
-    new_sender->internal()->SetSsrc(sender_info->first_ssrc);
-  }
-}
-
-// TODO(deadbeef): Don't destroy RtpSenders here; they should be kept around
-// indefinitely, when we have unified plan SDP.
-void PeerConnection::RemoveAudioTrack(AudioTrackInterface* track,
-                                      MediaStreamInterface* stream) {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  RTC_DCHECK(!IsClosed());
-  auto sender = FindSenderForTrack(track);
-  if (!sender) {
-    RTC_LOG(LS_WARNING) << "RtpSender for track with id " << track->id()
-                        << " doesn't exist.";
-    return;
-  }
-  GetAudioTransceiver()->internal()->RemoveSender(sender);
-}
-
-void PeerConnection::AddVideoTrack(VideoTrackInterface* track,
-                                   MediaStreamInterface* stream) {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  RTC_DCHECK(!IsClosed());
-  RTC_DCHECK(track);
-  RTC_DCHECK(stream);
-  auto sender = FindSenderForTrack(track);
-  if (sender) {
-    // We already have a sender for this track, so just change the stream_id
-    // so that it's correct in the next call to CreateOffer.
-    sender->internal()->set_stream_ids({stream->id()});
-    return;
-  }
-
-  // Normal case; we've never seen this track before.
-  auto new_sender = CreateSender(cricket::MEDIA_TYPE_VIDEO, track->id(), track,
-                                 {stream->id()}, {});
-  new_sender->internal()->SetMediaChannel(video_media_channel());
-  GetVideoTransceiver()->internal()->AddSender(new_sender);
-  const RtpSenderInfo* sender_info =
-      FindSenderInfo(local_video_sender_infos_, stream->id(), track->id());
-  if (sender_info) {
-    new_sender->internal()->SetSsrc(sender_info->first_ssrc);
-  }
-}
-
-void PeerConnection::RemoveVideoTrack(VideoTrackInterface* track,
-                                      MediaStreamInterface* stream) {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  RTC_DCHECK(!IsClosed());
-  auto sender = FindSenderForTrack(track);
-  if (!sender) {
-    RTC_LOG(LS_WARNING) << "RtpSender for track with id " << track->id()
-                        << " doesn't exist.";
-    return;
-  }
-  GetVideoTransceiver()->internal()->RemoveSender(sender);
-}
-
 void PeerConnection::SetIceConnectionState(IceConnectionState new_state) {
   RTC_DCHECK_RUN_ON(signaling_thread());
   if (ice_connection_state_ == new_state) {
@@ -2126,7 +1750,7 @@
   if (IsClosed()) {
     return;
   }
-  AddAudioTrack(track, stream);
+  rtp_manager()->AddAudioTrack(track, stream);
   sdp_handler_.UpdateNegotiationNeeded();
 }
 
@@ -2135,7 +1759,7 @@
   if (IsClosed()) {
     return;
   }
-  RemoveAudioTrack(track, stream);
+  rtp_manager()->RemoveAudioTrack(track, stream);
   sdp_handler_.UpdateNegotiationNeeded();
 }
 
@@ -2144,7 +1768,7 @@
   if (IsClosed()) {
     return;
   }
-  AddVideoTrack(track, stream);
+  rtp_manager()->AddVideoTrack(track, stream);
   sdp_handler_.UpdateNegotiationNeeded();
 }
 
@@ -2153,7 +1777,7 @@
   if (IsClosed()) {
     return;
   }
-  RemoveVideoTrack(track, stream);
+  rtp_manager()->RemoveVideoTrack(track, stream);
   sdp_handler_.UpdateNegotiationNeeded();
 }
 
@@ -2172,104 +1796,6 @@
   }
 }
 
-void PeerConnection::OnRemoteSenderAdded(const RtpSenderInfo& sender_info,
-                                         MediaStreamInterface* stream,
-                                         cricket::MediaType media_type) {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  RTC_LOG(LS_INFO) << "Creating " << cricket::MediaTypeToString(media_type)
-                   << " receiver for track_id=" << sender_info.sender_id
-                   << " and stream_id=" << sender_info.stream_id;
-
-  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
-    CreateAudioReceiver(stream, sender_info);
-  } else if (media_type == cricket::MEDIA_TYPE_VIDEO) {
-    CreateVideoReceiver(stream, sender_info);
-  } else {
-    RTC_NOTREACHED() << "Invalid media type";
-  }
-}
-
-void PeerConnection::OnRemoteSenderRemoved(const RtpSenderInfo& sender_info,
-                                           MediaStreamInterface* stream,
-                                           cricket::MediaType media_type) {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  RTC_LOG(LS_INFO) << "Removing " << cricket::MediaTypeToString(media_type)
-                   << " receiver for track_id=" << sender_info.sender_id
-                   << " and stream_id=" << sender_info.stream_id;
-
-  rtc::scoped_refptr<RtpReceiverInterface> receiver;
-  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
-    // When the MediaEngine audio channel is destroyed, the RemoteAudioSource
-    // will be notified which will end the AudioRtpReceiver::track().
-    receiver = RemoveAndStopReceiver(sender_info);
-    rtc::scoped_refptr<AudioTrackInterface> audio_track =
-        stream->FindAudioTrack(sender_info.sender_id);
-    if (audio_track) {
-      stream->RemoveTrack(audio_track);
-    }
-  } else if (media_type == cricket::MEDIA_TYPE_VIDEO) {
-    // Stopping or destroying a VideoRtpReceiver will end the
-    // VideoRtpReceiver::track().
-    receiver = RemoveAndStopReceiver(sender_info);
-    rtc::scoped_refptr<VideoTrackInterface> video_track =
-        stream->FindVideoTrack(sender_info.sender_id);
-    if (video_track) {
-      // There's no guarantee the track is still available, e.g. the track may
-      // have been removed from the stream by an application.
-      stream->RemoveTrack(video_track);
-    }
-  } else {
-    RTC_NOTREACHED() << "Invalid media type";
-  }
-  if (receiver) {
-    Observer()->OnRemoveTrack(receiver);
-  }
-}
-
-void PeerConnection::OnLocalSenderAdded(const RtpSenderInfo& sender_info,
-                                        cricket::MediaType media_type) {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  RTC_DCHECK(!IsUnifiedPlan());
-  auto sender = FindSenderById(sender_info.sender_id);
-  if (!sender) {
-    RTC_LOG(LS_WARNING) << "An unknown RtpSender with id "
-                        << sender_info.sender_id
-                        << " has been configured in the local description.";
-    return;
-  }
-
-  if (sender->media_type() != media_type) {
-    RTC_LOG(LS_WARNING) << "An RtpSender has been configured in the local"
-                           " description with an unexpected media type.";
-    return;
-  }
-
-  sender->internal()->set_stream_ids({sender_info.stream_id});
-  sender->internal()->SetSsrc(sender_info.first_ssrc);
-}
-
-void PeerConnection::OnLocalSenderRemoved(const RtpSenderInfo& sender_info,
-                                          cricket::MediaType media_type) {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  auto sender = FindSenderById(sender_info.sender_id);
-  if (!sender) {
-    // This is the normal case. I.e., RemoveStream has been called and the
-    // SessionDescriptions has been renegotiated.
-    return;
-  }
-
-  // A sender has been removed from the SessionDescription but it's still
-  // associated with the PeerConnection. This only occurs if the SDP doesn't
-  // match with the calls to CreateSender, AddStream and RemoveStream.
-  if (sender->media_type() != media_type) {
-    RTC_LOG(LS_WARNING) << "An RtpSender has been configured in the local"
-                           " description with an unexpected media type.";
-    return;
-  }
-
-  sender->internal()->SetSsrc(0);
-}
-
 void PeerConnection::OnSctpDataChannelClosed(DataChannelInterface* channel) {
   // Since data_channel_controller doesn't do signals, this
   // signal is relayed here.
@@ -2277,102 +1803,6 @@
       static_cast<SctpDataChannel*>(channel));
 }
 
-rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-PeerConnection::GetAudioTransceiver() const {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  // This method only works with Plan B SDP, where there is a single
-  // audio/video transceiver.
-  RTC_DCHECK(!IsUnifiedPlan());
-  for (auto transceiver : transceivers_.List()) {
-    if (transceiver->media_type() == cricket::MEDIA_TYPE_AUDIO) {
-      return transceiver;
-    }
-  }
-  RTC_NOTREACHED();
-  return nullptr;
-}
-
-rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-PeerConnection::GetVideoTransceiver() const {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  // This method only works with Plan B SDP, where there is a single
-  // audio/video transceiver.
-  RTC_DCHECK(!IsUnifiedPlan());
-  for (auto transceiver : transceivers_.List()) {
-    if (transceiver->media_type() == cricket::MEDIA_TYPE_VIDEO) {
-      return transceiver;
-    }
-  }
-  RTC_NOTREACHED();
-  return nullptr;
-}
-
-rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
-PeerConnection::FindSenderForTrack(MediaStreamTrackInterface* track) const {
-  for (const auto& transceiver : transceivers_.List()) {
-    for (auto sender : transceiver->internal()->senders()) {
-      if (sender->track() == track) {
-        return sender;
-      }
-    }
-  }
-  return nullptr;
-}
-
-rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
-PeerConnection::FindSenderById(const std::string& sender_id) const {
-  for (const auto& transceiver : transceivers_.List()) {
-    for (auto sender : transceiver->internal()->senders()) {
-      if (sender->id() == sender_id) {
-        return sender;
-      }
-    }
-  }
-  return nullptr;
-}
-
-rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
-PeerConnection::FindReceiverById(const std::string& receiver_id) const {
-  for (const auto& transceiver : transceivers_.List()) {
-    for (auto receiver : transceiver->internal()->receivers()) {
-      if (receiver->id() == receiver_id) {
-        return receiver;
-      }
-    }
-  }
-  return nullptr;
-}
-
-std::vector<PeerConnection::RtpSenderInfo>*
-PeerConnection::GetRemoteSenderInfos(cricket::MediaType media_type) {
-  RTC_DCHECK(media_type == cricket::MEDIA_TYPE_AUDIO ||
-             media_type == cricket::MEDIA_TYPE_VIDEO);
-  return (media_type == cricket::MEDIA_TYPE_AUDIO)
-             ? &remote_audio_sender_infos_
-             : &remote_video_sender_infos_;
-}
-
-std::vector<PeerConnection::RtpSenderInfo>* PeerConnection::GetLocalSenderInfos(
-    cricket::MediaType media_type) {
-  RTC_DCHECK(media_type == cricket::MEDIA_TYPE_AUDIO ||
-             media_type == cricket::MEDIA_TYPE_VIDEO);
-  return (media_type == cricket::MEDIA_TYPE_AUDIO) ? &local_audio_sender_infos_
-                                                   : &local_video_sender_infos_;
-}
-
-const PeerConnection::RtpSenderInfo* PeerConnection::FindSenderInfo(
-    const std::vector<PeerConnection::RtpSenderInfo>& infos,
-    const std::string& stream_id,
-    const std::string sender_id) const {
-  for (const RtpSenderInfo& sender_info : infos) {
-    if (sender_info.stream_id == stream_id &&
-        sender_info.sender_id == sender_id) {
-      return &sender_info;
-    }
-  }
-  return nullptr;
-}
-
 SctpDataChannel* PeerConnection::FindDataChannelBySid(int sid) const {
   return data_channel_controller_.FindDataChannelBySid(sid);
 }
@@ -2497,7 +1927,7 @@
 
 cricket::ChannelInterface* PeerConnection::GetChannel(
     const std::string& content_name) {
-  for (const auto& transceiver : transceivers_.List()) {
+  for (const auto& transceiver : rtp_manager()->transceivers()->List()) {
     cricket::ChannelInterface* channel = transceiver->internal()->channel();
     if (channel && channel->content_name() == content_name) {
       return channel;
@@ -2641,7 +2071,7 @@
     const {
   RTC_DCHECK_RUN_ON(signaling_thread());
   std::map<std::string, std::string> transport_names_by_mid;
-  for (const auto& transceiver : transceivers_.List()) {
+  for (const auto& transceiver : rtp_manager()->transceivers()->List()) {
     cricket::ChannelInterface* channel = transceiver->internal()->channel();
     if (channel) {
       transport_names_by_mid[channel->content_name()] =
@@ -3007,7 +2437,7 @@
 void PeerConnection::ReportTransportStats() {
   std::map<std::string, std::set<cricket::MediaType>>
       media_types_by_transport_name;
-  for (const auto& transceiver : transceivers_.List()) {
+  for (const auto& transceiver : rtp_manager()->transceivers()->List()) {
     if (transceiver->internal()->channel()) {
       const std::string& transport_name =
           transceiver->internal()->channel()->transport_name();
@@ -3173,12 +2603,6 @@
   return ret;
 }
 
-void PeerConnection::OnSetStreams() {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  if (IsUnifiedPlan())
-    sdp_handler_.UpdateNegotiationNeeded();
-}
-
 PeerConnectionObserver* PeerConnection::Observer() const {
   RTC_DCHECK_RUN_ON(signaling_thread());
   RTC_DCHECK(observer_);
diff --git a/pc/peer_connection.h b/pc/peer_connection.h
index c2ea92f..582c76d 100644
--- a/pc/peer_connection.h
+++ b/pc/peer_connection.h
@@ -73,8 +73,10 @@
 #include "pc/rtp_receiver.h"
 #include "pc/rtp_sender.h"
 #include "pc/rtp_transceiver.h"
+#include "pc/rtp_transmission_manager.h"
 #include "pc/rtp_transport_internal.h"
 #include "pc/sctp_data_channel.h"
+#include "pc/sctp_transport.h"
 #include "pc/sdp_offer_answer.h"
 #include "pc/session_description.h"
 #include "pc/stats_collector.h"
@@ -114,7 +116,6 @@
 // - Generating stats.
 class PeerConnection : public PeerConnectionInternal,
                        public JsepTransportController::Observer,
-                       public RtpSenderBase::SetStreamsObserver,
                        public sigslot::has_slots<> {
  public:
   explicit PeerConnection(rtc::scoped_refptr<ConnectionContext> context,
@@ -291,7 +292,7 @@
       rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
   GetTransceiversInternal() const override {
     RTC_DCHECK_RUN_ON(signaling_thread());
-    return transceivers_.List();
+    return rtp_manager()->transceivers()->List();
   }
 
   sigslot::signal1<RtpDataChannel*>& SignalRtpDataChannelCreated() override {
@@ -366,6 +367,11 @@
     return &message_handler_;
   }
 
+  RtpTransmissionManager* rtp_manager() { return rtp_manager_.get(); }
+  const RtpTransmissionManager* rtp_manager() const {
+    return rtp_manager_.get();
+  }
+
   // Functions made public for testing.
   void ReturnHistogramVeryQuicklyForTesting() {
     RTC_DCHECK_RUN_ON(signaling_thread());
@@ -381,83 +387,6 @@
   // TOOD(https://bugs.webrtc.org/11995): Remove friendship.
   friend class SdpOfferAnswerHandler;
 
-  struct RtpSenderInfo {
-    RtpSenderInfo() : first_ssrc(0) {}
-    RtpSenderInfo(const std::string& stream_id,
-                  const std::string sender_id,
-                  uint32_t ssrc)
-        : stream_id(stream_id), sender_id(sender_id), first_ssrc(ssrc) {}
-    bool operator==(const RtpSenderInfo& other) {
-      return this->stream_id == other.stream_id &&
-             this->sender_id == other.sender_id &&
-             this->first_ssrc == other.first_ssrc;
-    }
-    std::string stream_id;
-    std::string sender_id;
-    // An RtpSender can have many SSRCs. The first one is used as a sort of ID
-    // for communicating with the lower layers.
-    uint32_t first_ssrc;
-  };
-
-  // Plan B helpers for getting the voice/video media channels for the single
-  // audio/video transceiver, if it exists.
-  cricket::VoiceMediaChannel* voice_media_channel() const
-      RTC_RUN_ON(signaling_thread());
-  cricket::VideoMediaChannel* video_media_channel() const
-      RTC_RUN_ON(signaling_thread());
-
-  std::vector<rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>>
-  GetSendersInternal() const;
-  std::vector<
-      rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>>
-  GetReceiversInternal() const RTC_RUN_ON(signaling_thread());
-
-  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-  GetAudioTransceiver() const;
-  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-  GetVideoTransceiver() const;
-
-  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-  GetFirstAudioTransceiver() const RTC_RUN_ON(signaling_thread());
-
-
-  void CreateAudioReceiver(MediaStreamInterface* stream,
-                           const RtpSenderInfo& remote_sender_info)
-      RTC_RUN_ON(signaling_thread());
-
-  void CreateVideoReceiver(MediaStreamInterface* stream,
-                           const RtpSenderInfo& remote_sender_info)
-      RTC_RUN_ON(signaling_thread());
-  rtc::scoped_refptr<RtpReceiverInterface> RemoveAndStopReceiver(
-      const RtpSenderInfo& remote_sender_info) RTC_RUN_ON(signaling_thread());
-
-  // May be called either by AddStream/RemoveStream, or when a track is
-  // added/removed from a stream previously added via AddStream.
-  void AddAudioTrack(AudioTrackInterface* track, MediaStreamInterface* stream);
-  void RemoveAudioTrack(AudioTrackInterface* track,
-                        MediaStreamInterface* stream);
-  void AddVideoTrack(VideoTrackInterface* track, MediaStreamInterface* stream);
-  void RemoveVideoTrack(VideoTrackInterface* track,
-                        MediaStreamInterface* stream);
-
-  // AddTrack implementation when Unified Plan is specified.
-  RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrackUnifiedPlan(
-      rtc::scoped_refptr<MediaStreamTrackInterface> track,
-      const std::vector<std::string>& stream_ids)
-      RTC_RUN_ON(signaling_thread());
-  // AddTrack implementation when Plan B is specified.
-  RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrackPlanB(
-      rtc::scoped_refptr<MediaStreamTrackInterface> track,
-      const std::vector<std::string>& stream_ids)
-      RTC_RUN_ON(signaling_thread());
-
-  // Returns the first RtpTransceiver suitable for a newly added track, if such
-  // transceiver is available.
-  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-  FindFirstTransceiverForAddedTrack(
-      rtc::scoped_refptr<MediaStreamTrackInterface> track)
-      RTC_RUN_ON(signaling_thread());
-
   rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
   FindTransceiverBySender(rtc::scoped_refptr<RtpSenderInterface> sender)
       RTC_RUN_ON(signaling_thread());
@@ -470,24 +399,6 @@
       const RtpTransceiverInit& init,
       bool fire_callback = true);
 
-  rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
-  CreateSender(cricket::MediaType media_type,
-               const std::string& id,
-               rtc::scoped_refptr<MediaStreamTrackInterface> track,
-               const std::vector<std::string>& stream_ids,
-               const std::vector<RtpEncodingParameters>& send_encodings);
-
-  rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
-  CreateReceiver(cricket::MediaType media_type, const std::string& receiver_id);
-
-  // Create a new RtpTransceiver of the given type and add it to the list of
-  // transceivers.
-  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-  CreateAndAddTransceiver(
-      rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> sender,
-      rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
-          receiver);
-
   void SetIceConnectionState(IceConnectionState new_state);
   void SetStandardizedIceConnectionState(
       PeerConnectionInterface::IceConnectionState new_state)
@@ -539,36 +450,6 @@
   // channels are configured this will return nullopt.
   absl::optional<std::string> GetDataMid() const;
 
-  // Triggered when a remote sender has been seen for the first time in a remote
-  // session description. It creates a remote MediaStreamTrackInterface
-  // implementation and triggers CreateAudioReceiver or CreateVideoReceiver.
-  void OnRemoteSenderAdded(const RtpSenderInfo& sender_info,
-                           MediaStreamInterface* stream,
-                           cricket::MediaType media_type);
-
-  // Triggered when a remote sender has been removed from a remote session
-  // description. It removes the remote sender with id |sender_id| from a remote
-  // MediaStream and triggers DestroyAudioReceiver or DestroyVideoReceiver.
-  void OnRemoteSenderRemoved(const RtpSenderInfo& sender_info,
-                             MediaStreamInterface* stream,
-                             cricket::MediaType media_type);
-
-  // Triggered when a local sender has been seen for the first time in a local
-  // session description.
-  // This method triggers CreateAudioSender or CreateVideoSender if the rtp
-  // streams in the local SessionDescription can be mapped to a MediaStreamTrack
-  // in a MediaStream in |local_streams_|
-  void OnLocalSenderAdded(const RtpSenderInfo& sender_info,
-                          cricket::MediaType media_type);
-
-  // Triggered when a local sender has been removed from a local session
-  // description.
-  // This method triggers DestroyAudioSender or DestroyVideoSender if a stream
-  // has been removed from the local SessionDescription and the stream can be
-  // mapped to a MediaStreamTrack in a MediaStream in |local_streams_|.
-  void OnLocalSenderRemoved(const RtpSenderInfo& sender_info,
-                            cricket::MediaType media_type);
-
   // Returns true if the PeerConnection is configured to use Unified Plan
   // semantics for creating offers/answers and setting local/remote
   // descriptions. If this is true the RtpTransceiver API will also be available
@@ -580,29 +461,6 @@
     return configuration_.sdp_semantics == SdpSemantics::kUnifiedPlan;
   }
 
-  // Return the RtpSender with the given track attached.
-  rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
-  FindSenderForTrack(MediaStreamTrackInterface* track) const
-      RTC_RUN_ON(signaling_thread());
-
-  // Return the RtpSender with the given id, or null if none exists.
-  rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
-  FindSenderById(const std::string& sender_id) const
-      RTC_RUN_ON(signaling_thread());
-
-  // Return the RtpReceiver with the given id, or null if none exists.
-  rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
-  FindReceiverById(const std::string& receiver_id) const
-      RTC_RUN_ON(signaling_thread());
-
-  std::vector<RtpSenderInfo>* GetRemoteSenderInfos(
-      cricket::MediaType media_type);
-  std::vector<RtpSenderInfo>* GetLocalSenderInfos(
-      cricket::MediaType media_type);
-  const RtpSenderInfo* FindSenderInfo(const std::vector<RtpSenderInfo>& infos,
-                                      const std::string& stream_id,
-                                      const std::string sender_id) const;
-
   // Returns the specified SCTP DataChannel in sctp_data_channels_,
   // or nullptr if not found.
   SctpDataChannel* FindDataChannelBySid(int sid) const
@@ -739,9 +597,6 @@
       rtc::scoped_refptr<DtlsTransport> dtls_transport,
       DataChannelTransportInterface* data_channel_transport) override;
 
-  // RtpSenderBase::SetStreamsObserver override.
-  void OnSetStreams() override;
-
   // Returns the CryptoOptions for this PeerConnection. This will always
   // return the RTCConfiguration.crypto_options if set and will only default
   // back to the PeerConnectionFactory settings if nothing was set.
@@ -806,16 +661,6 @@
       tls_cert_verifier_;  // TODO(bugs.webrtc.org/9987): Accessed on both
                            // signaling and network thread.
 
-  // These lists store sender info seen in local/remote descriptions.
-  std::vector<RtpSenderInfo> remote_audio_sender_infos_
-      RTC_GUARDED_BY(signaling_thread());
-  std::vector<RtpSenderInfo> remote_video_sender_infos_
-      RTC_GUARDED_BY(signaling_thread());
-  std::vector<RtpSenderInfo> local_audio_sender_infos_
-      RTC_GUARDED_BY(signaling_thread());
-  std::vector<RtpSenderInfo> local_video_sender_infos_
-      RTC_GUARDED_BY(signaling_thread());
-
   // The unique_ptr belongs to the worker thread, but the Call object manages
   // its own thread safety.
   std::unique_ptr<Call> call_ RTC_GUARDED_BY(worker_thread());
@@ -832,7 +677,6 @@
       RTC_GUARDED_BY(signaling_thread());  // A pointer is passed to senders_
   rtc::scoped_refptr<RTCStatsCollector> stats_collector_
       RTC_GUARDED_BY(signaling_thread());
-  TransceiverList transceivers_;
 
   std::string session_id_ RTC_GUARDED_BY(signaling_thread());
 
@@ -882,6 +726,10 @@
 
   // Machinery for handling messages posted to oneself
   PeerConnectionMessageHandler message_handler_;
+
+  // Administration of senders, receivers and transceivers
+  // Accessed on both signaling and network thread. Const after Initialize().
+  std::unique_ptr<RtpTransmissionManager> rtp_manager_;
 };
 
 }  // namespace webrtc
diff --git a/pc/rtp_transmission_manager.cc b/pc/rtp_transmission_manager.cc
new file mode 100644
index 0000000..5c0e4d9
--- /dev/null
+++ b/pc/rtp_transmission_manager.cc
@@ -0,0 +1,692 @@
+/*
+ *  Copyright 2020 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "pc/rtp_transmission_manager.h"
+
+#include <algorithm>
+
+#include "absl/types/optional.h"
+#include "api/peer_connection_interface.h"
+#include "api/rtp_transceiver_direction.h"
+#include "pc/audio_rtp_receiver.h"
+#include "pc/channel.h"
+#include "pc/stats_collector_interface.h"
+#include "pc/video_rtp_receiver.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/helpers.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+
+namespace {
+
+static const char kDefaultAudioSenderId[] = "defaulta0";
+static const char kDefaultVideoSenderId[] = "defaultv0";
+
+}  // namespace
+
+RtpTransmissionManager::RtpTransmissionManager(
+    bool is_unified_plan,
+    rtc::Thread* signaling_thread,
+    rtc::Thread* worker_thread,
+    cricket::ChannelManager* channel_manager,
+    UsagePattern* usage_pattern,
+    PeerConnectionObserver* observer,
+    StatsCollectorInterface* stats,
+    std::function<void()> on_negotiation_needed)
+    : is_unified_plan_(is_unified_plan),
+      signaling_thread_(signaling_thread),
+      worker_thread_(worker_thread),
+      channel_manager_(channel_manager),
+      usage_pattern_(usage_pattern),
+      observer_(observer),
+      stats_(stats),
+      on_negotiation_needed_(on_negotiation_needed) {}
+
+void RtpTransmissionManager::Close() {
+  closed_ = true;
+  observer_ = nullptr;
+}
+
+// Implementation of SetStreamsObserver
+void RtpTransmissionManager::OnSetStreams() {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  if (IsUnifiedPlan())
+    OnNegotiationNeeded();
+}
+
+// Function to call back to the PeerConnection when negotiation is needed
+void RtpTransmissionManager::OnNegotiationNeeded() {
+  on_negotiation_needed_();
+}
+
+// Function that returns the currently valid observer
+PeerConnectionObserver* RtpTransmissionManager::Observer() const {
+  RTC_DCHECK(!closed_);
+  RTC_DCHECK(observer_);
+  return observer_;
+}
+
+cricket::VoiceMediaChannel* RtpTransmissionManager::voice_media_channel()
+    const {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  RTC_DCHECK(!IsUnifiedPlan());
+  auto* voice_channel = static_cast<cricket::VoiceChannel*>(
+      GetAudioTransceiver()->internal()->channel());
+  if (voice_channel) {
+    return voice_channel->media_channel();
+  } else {
+    return nullptr;
+  }
+}
+
+cricket::VideoMediaChannel* RtpTransmissionManager::video_media_channel()
+    const {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  RTC_DCHECK(!IsUnifiedPlan());
+  auto* video_channel = static_cast<cricket::VideoChannel*>(
+      GetVideoTransceiver()->internal()->channel());
+  if (video_channel) {
+    return video_channel->media_channel();
+  } else {
+    return nullptr;
+  }
+}
+
+RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
+RtpTransmissionManager::AddTrack(
+    rtc::scoped_refptr<MediaStreamTrackInterface> track,
+    const std::vector<std::string>& stream_ids) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+
+  return (IsUnifiedPlan() ? AddTrackUnifiedPlan(track, stream_ids)
+                          : AddTrackPlanB(track, stream_ids));
+}
+
+RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
+RtpTransmissionManager::AddTrackPlanB(
+    rtc::scoped_refptr<MediaStreamTrackInterface> track,
+    const std::vector<std::string>& stream_ids) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  if (stream_ids.size() > 1u) {
+    LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_OPERATION,
+                         "AddTrack with more than one stream is not "
+                         "supported with Plan B semantics.");
+  }
+  std::vector<std::string> adjusted_stream_ids = stream_ids;
+  if (adjusted_stream_ids.empty()) {
+    adjusted_stream_ids.push_back(rtc::CreateRandomUuid());
+  }
+  cricket::MediaType media_type =
+      (track->kind() == MediaStreamTrackInterface::kAudioKind
+           ? cricket::MEDIA_TYPE_AUDIO
+           : cricket::MEDIA_TYPE_VIDEO);
+  auto new_sender =
+      CreateSender(media_type, track->id(), track, adjusted_stream_ids, {});
+  if (track->kind() == MediaStreamTrackInterface::kAudioKind) {
+    new_sender->internal()->SetMediaChannel(voice_media_channel());
+    GetAudioTransceiver()->internal()->AddSender(new_sender);
+    const RtpSenderInfo* sender_info =
+        FindSenderInfo(local_audio_sender_infos_,
+                       new_sender->internal()->stream_ids()[0], track->id());
+    if (sender_info) {
+      new_sender->internal()->SetSsrc(sender_info->first_ssrc);
+    }
+  } else {
+    RTC_DCHECK_EQ(MediaStreamTrackInterface::kVideoKind, track->kind());
+    new_sender->internal()->SetMediaChannel(video_media_channel());
+    GetVideoTransceiver()->internal()->AddSender(new_sender);
+    const RtpSenderInfo* sender_info =
+        FindSenderInfo(local_video_sender_infos_,
+                       new_sender->internal()->stream_ids()[0], track->id());
+    if (sender_info) {
+      new_sender->internal()->SetSsrc(sender_info->first_ssrc);
+    }
+  }
+  return rtc::scoped_refptr<RtpSenderInterface>(new_sender);
+}
+
+RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>>
+RtpTransmissionManager::AddTrackUnifiedPlan(
+    rtc::scoped_refptr<MediaStreamTrackInterface> track,
+    const std::vector<std::string>& stream_ids) {
+  auto transceiver = FindFirstTransceiverForAddedTrack(track);
+  if (transceiver) {
+    RTC_LOG(LS_INFO) << "Reusing an existing "
+                     << cricket::MediaTypeToString(transceiver->media_type())
+                     << " transceiver for AddTrack.";
+    if (transceiver->stopping()) {
+      LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
+                           "The existing transceiver is stopping.");
+    }
+
+    if (transceiver->direction() == RtpTransceiverDirection::kRecvOnly) {
+      transceiver->internal()->set_direction(
+          RtpTransceiverDirection::kSendRecv);
+    } else if (transceiver->direction() == RtpTransceiverDirection::kInactive) {
+      transceiver->internal()->set_direction(
+          RtpTransceiverDirection::kSendOnly);
+    }
+    transceiver->sender()->SetTrack(track);
+    transceiver->internal()->sender_internal()->set_stream_ids(stream_ids);
+    transceiver->internal()->set_reused_for_addtrack(true);
+  } else {
+    cricket::MediaType media_type =
+        (track->kind() == MediaStreamTrackInterface::kAudioKind
+             ? cricket::MEDIA_TYPE_AUDIO
+             : cricket::MEDIA_TYPE_VIDEO);
+    RTC_LOG(LS_INFO) << "Adding " << cricket::MediaTypeToString(media_type)
+                     << " transceiver in response to a call to AddTrack.";
+    std::string sender_id = track->id();
+    // Avoid creating a sender with an existing ID by generating a random ID.
+    // This can happen if this is the second time AddTrack has created a sender
+    // for this track.
+    if (FindSenderById(sender_id)) {
+      sender_id = rtc::CreateRandomUuid();
+    }
+    auto sender = CreateSender(media_type, sender_id, track, stream_ids, {});
+    auto receiver = CreateReceiver(media_type, rtc::CreateRandomUuid());
+    transceiver = CreateAndAddTransceiver(sender, receiver);
+    transceiver->internal()->set_created_by_addtrack(true);
+    transceiver->internal()->set_direction(RtpTransceiverDirection::kSendRecv);
+  }
+  return transceiver->sender();
+}
+
+rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
+RtpTransmissionManager::CreateSender(
+    cricket::MediaType media_type,
+    const std::string& id,
+    rtc::scoped_refptr<MediaStreamTrackInterface> track,
+    const std::vector<std::string>& stream_ids,
+    const std::vector<RtpEncodingParameters>& send_encodings) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> sender;
+  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    RTC_DCHECK(!track ||
+               (track->kind() == MediaStreamTrackInterface::kAudioKind));
+    sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
+        signaling_thread(),
+        AudioRtpSender::Create(worker_thread(), id, stats_, this));
+    NoteUsageEvent(UsageEvent::AUDIO_ADDED);
+  } else {
+    RTC_DCHECK_EQ(media_type, cricket::MEDIA_TYPE_VIDEO);
+    RTC_DCHECK(!track ||
+               (track->kind() == MediaStreamTrackInterface::kVideoKind));
+    sender = RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
+        signaling_thread(), VideoRtpSender::Create(worker_thread(), id, this));
+    NoteUsageEvent(UsageEvent::VIDEO_ADDED);
+  }
+  bool set_track_succeeded = sender->SetTrack(track);
+  RTC_DCHECK(set_track_succeeded);
+  sender->internal()->set_stream_ids(stream_ids);
+  sender->internal()->set_init_send_encodings(send_encodings);
+  return sender;
+}
+
+rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
+RtpTransmissionManager::CreateReceiver(cricket::MediaType media_type,
+                                       const std::string& receiver_id) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
+      receiver;
+  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    receiver = RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
+        signaling_thread(), new AudioRtpReceiver(worker_thread(), receiver_id,
+                                                 std::vector<std::string>({})));
+    NoteUsageEvent(UsageEvent::AUDIO_ADDED);
+  } else {
+    RTC_DCHECK_EQ(media_type, cricket::MEDIA_TYPE_VIDEO);
+    receiver = RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
+        signaling_thread(), new VideoRtpReceiver(worker_thread(), receiver_id,
+                                                 std::vector<std::string>({})));
+    NoteUsageEvent(UsageEvent::VIDEO_ADDED);
+  }
+  return receiver;
+}
+
+rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+RtpTransmissionManager::CreateAndAddTransceiver(
+    rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> sender,
+    rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
+        receiver) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  // Ensure that the new sender does not have an ID that is already in use by
+  // another sender.
+  // Allow receiver IDs to conflict since those come from remote SDP (which
+  // could be invalid, but should not cause a crash).
+  RTC_DCHECK(!FindSenderById(sender->id()));
+  auto transceiver = RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
+      signaling_thread(),
+      new RtpTransceiver(
+          sender, receiver, channel_manager(),
+          sender->media_type() == cricket::MEDIA_TYPE_AUDIO
+              ? channel_manager()->GetSupportedAudioRtpHeaderExtensions()
+              : channel_manager()->GetSupportedVideoRtpHeaderExtensions()));
+  transceivers()->Add(transceiver);
+  transceiver->internal()->SignalNegotiationNeeded.connect(
+      this, &RtpTransmissionManager::OnNegotiationNeeded);
+  return transceiver;
+}
+
+rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+RtpTransmissionManager::FindFirstTransceiverForAddedTrack(
+    rtc::scoped_refptr<MediaStreamTrackInterface> track) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  RTC_DCHECK(track);
+  for (auto transceiver : transceivers()->List()) {
+    if (!transceiver->sender()->track() &&
+        cricket::MediaTypeToString(transceiver->media_type()) ==
+            track->kind() &&
+        !transceiver->internal()->has_ever_been_used_to_send() &&
+        !transceiver->stopped()) {
+      return transceiver;
+    }
+  }
+  return nullptr;
+}
+
+std::vector<rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>>
+RtpTransmissionManager::GetSendersInternal() const {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  std::vector<rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>>
+      all_senders;
+  for (const auto& transceiver : transceivers_.List()) {
+    if (IsUnifiedPlan() && transceiver->internal()->stopped())
+      continue;
+
+    auto senders = transceiver->internal()->senders();
+    all_senders.insert(all_senders.end(), senders.begin(), senders.end());
+  }
+  return all_senders;
+}
+
+std::vector<
+    rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>>
+RtpTransmissionManager::GetReceiversInternal() const {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  std::vector<
+      rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>>
+      all_receivers;
+  for (const auto& transceiver : transceivers_.List()) {
+    if (IsUnifiedPlan() && transceiver->internal()->stopped())
+      continue;
+
+    auto receivers = transceiver->internal()->receivers();
+    all_receivers.insert(all_receivers.end(), receivers.begin(),
+                         receivers.end());
+  }
+  return all_receivers;
+}
+
+rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+RtpTransmissionManager::GetAudioTransceiver() const {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  // This method only works with Plan B SDP, where there is a single
+  // audio/video transceiver.
+  RTC_DCHECK(!IsUnifiedPlan());
+  for (auto transceiver : transceivers_.List()) {
+    if (transceiver->media_type() == cricket::MEDIA_TYPE_AUDIO) {
+      return transceiver;
+    }
+  }
+  RTC_NOTREACHED();
+  return nullptr;
+}
+
+rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+RtpTransmissionManager::GetVideoTransceiver() const {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  // This method only works with Plan B SDP, where there is a single
+  // audio/video transceiver.
+  RTC_DCHECK(!IsUnifiedPlan());
+  for (auto transceiver : transceivers_.List()) {
+    if (transceiver->media_type() == cricket::MEDIA_TYPE_VIDEO) {
+      return transceiver;
+    }
+  }
+  RTC_NOTREACHED();
+  return nullptr;
+}
+
+void RtpTransmissionManager::AddAudioTrack(AudioTrackInterface* track,
+                                           MediaStreamInterface* stream) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  RTC_DCHECK(track);
+  RTC_DCHECK(stream);
+  auto sender = FindSenderForTrack(track);
+  if (sender) {
+    // We already have a sender for this track, so just change the stream_id
+    // so that it's correct in the next call to CreateOffer.
+    sender->internal()->set_stream_ids({stream->id()});
+    return;
+  }
+
+  // Normal case; we've never seen this track before.
+  auto new_sender = CreateSender(cricket::MEDIA_TYPE_AUDIO, track->id(), track,
+                                 {stream->id()}, {});
+  new_sender->internal()->SetMediaChannel(voice_media_channel());
+  GetAudioTransceiver()->internal()->AddSender(new_sender);
+  // If the sender has already been configured in SDP, we call SetSsrc,
+  // which will connect the sender to the underlying transport. This can
+  // occur if a local session description that contains the ID of the sender
+  // is set before AddStream is called. It can also occur if the local
+  // session description is not changed and RemoveStream is called, and
+  // later AddStream is called again with the same stream.
+  const RtpSenderInfo* sender_info =
+      FindSenderInfo(local_audio_sender_infos_, stream->id(), track->id());
+  if (sender_info) {
+    new_sender->internal()->SetSsrc(sender_info->first_ssrc);
+  }
+}
+
+// TODO(deadbeef): Don't destroy RtpSenders here; they should be kept around
+// indefinitely, when we have unified plan SDP.
+void RtpTransmissionManager::RemoveAudioTrack(AudioTrackInterface* track,
+                                              MediaStreamInterface* stream) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  RTC_DCHECK(!IsUnifiedPlan());
+  auto sender = FindSenderForTrack(track);
+  if (!sender) {
+    RTC_LOG(LS_WARNING) << "RtpSender for track with id " << track->id()
+                        << " doesn't exist.";
+    return;
+  }
+  GetAudioTransceiver()->internal()->RemoveSender(sender);
+}
+
+void RtpTransmissionManager::AddVideoTrack(VideoTrackInterface* track,
+                                           MediaStreamInterface* stream) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  RTC_DCHECK(track);
+  RTC_DCHECK(stream);
+  auto sender = FindSenderForTrack(track);
+  if (sender) {
+    // We already have a sender for this track, so just change the stream_id
+    // so that it's correct in the next call to CreateOffer.
+    sender->internal()->set_stream_ids({stream->id()});
+    return;
+  }
+
+  // Normal case; we've never seen this track before.
+  auto new_sender = CreateSender(cricket::MEDIA_TYPE_VIDEO, track->id(), track,
+                                 {stream->id()}, {});
+  new_sender->internal()->SetMediaChannel(video_media_channel());
+  GetVideoTransceiver()->internal()->AddSender(new_sender);
+  const RtpSenderInfo* sender_info =
+      FindSenderInfo(local_video_sender_infos_, stream->id(), track->id());
+  if (sender_info) {
+    new_sender->internal()->SetSsrc(sender_info->first_ssrc);
+  }
+}
+
+void RtpTransmissionManager::RemoveVideoTrack(VideoTrackInterface* track,
+                                              MediaStreamInterface* stream) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  RTC_DCHECK(!IsUnifiedPlan());
+  auto sender = FindSenderForTrack(track);
+  if (!sender) {
+    RTC_LOG(LS_WARNING) << "RtpSender for track with id " << track->id()
+                        << " doesn't exist.";
+    return;
+  }
+  GetVideoTransceiver()->internal()->RemoveSender(sender);
+}
+
+rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+RtpTransmissionManager::GetFirstAudioTransceiver() const {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  for (auto transceiver : transceivers_.List()) {
+    if (transceiver->media_type() == cricket::MEDIA_TYPE_AUDIO) {
+      return transceiver;
+    }
+  }
+  return nullptr;
+}
+
+void RtpTransmissionManager::CreateAudioReceiver(
+    MediaStreamInterface* stream,
+    const RtpSenderInfo& remote_sender_info) {
+  RTC_DCHECK(!closed_);
+  std::vector<rtc::scoped_refptr<MediaStreamInterface>> streams;
+  streams.push_back(rtc::scoped_refptr<MediaStreamInterface>(stream));
+  // TODO(https://crbug.com/webrtc/9480): When we remove remote_streams(), use
+  // the constructor taking stream IDs instead.
+  auto* audio_receiver = new AudioRtpReceiver(
+      worker_thread(), remote_sender_info.sender_id, streams);
+  audio_receiver->SetMediaChannel(voice_media_channel());
+  if (remote_sender_info.sender_id == kDefaultAudioSenderId) {
+    audio_receiver->SetupUnsignaledMediaChannel();
+  } else {
+    audio_receiver->SetupMediaChannel(remote_sender_info.first_ssrc);
+  }
+  auto receiver = RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
+      signaling_thread(), audio_receiver);
+  GetAudioTransceiver()->internal()->AddReceiver(receiver);
+  Observer()->OnAddTrack(receiver, streams);
+  NoteUsageEvent(UsageEvent::AUDIO_ADDED);
+}
+
+void RtpTransmissionManager::CreateVideoReceiver(
+    MediaStreamInterface* stream,
+    const RtpSenderInfo& remote_sender_info) {
+  RTC_DCHECK(!closed_);
+  std::vector<rtc::scoped_refptr<MediaStreamInterface>> streams;
+  streams.push_back(rtc::scoped_refptr<MediaStreamInterface>(stream));
+  // TODO(https://crbug.com/webrtc/9480): When we remove remote_streams(), use
+  // the constructor taking stream IDs instead.
+  auto* video_receiver = new VideoRtpReceiver(
+      worker_thread(), remote_sender_info.sender_id, streams);
+  video_receiver->SetMediaChannel(video_media_channel());
+  if (remote_sender_info.sender_id == kDefaultVideoSenderId) {
+    video_receiver->SetupUnsignaledMediaChannel();
+  } else {
+    video_receiver->SetupMediaChannel(remote_sender_info.first_ssrc);
+  }
+  auto receiver = RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
+      signaling_thread(), video_receiver);
+  GetVideoTransceiver()->internal()->AddReceiver(receiver);
+  Observer()->OnAddTrack(receiver, streams);
+  NoteUsageEvent(UsageEvent::VIDEO_ADDED);
+}
+
+// TODO(deadbeef): Keep RtpReceivers around even if track goes away in remote
+// description.
+rtc::scoped_refptr<RtpReceiverInterface>
+RtpTransmissionManager::RemoveAndStopReceiver(
+    const RtpSenderInfo& remote_sender_info) {
+  auto receiver = FindReceiverById(remote_sender_info.sender_id);
+  if (!receiver) {
+    RTC_LOG(LS_WARNING) << "RtpReceiver for track with id "
+                        << remote_sender_info.sender_id << " doesn't exist.";
+    return nullptr;
+  }
+  if (receiver->media_type() == cricket::MEDIA_TYPE_AUDIO) {
+    GetAudioTransceiver()->internal()->RemoveReceiver(receiver);
+  } else {
+    GetVideoTransceiver()->internal()->RemoveReceiver(receiver);
+  }
+  return receiver;
+}
+
+void RtpTransmissionManager::OnRemoteSenderAdded(
+    const RtpSenderInfo& sender_info,
+    MediaStreamInterface* stream,
+    cricket::MediaType media_type) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  RTC_LOG(LS_INFO) << "Creating " << cricket::MediaTypeToString(media_type)
+                   << " receiver for track_id=" << sender_info.sender_id
+                   << " and stream_id=" << sender_info.stream_id;
+
+  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    CreateAudioReceiver(stream, sender_info);
+  } else if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+    CreateVideoReceiver(stream, sender_info);
+  } else {
+    RTC_NOTREACHED() << "Invalid media type";
+  }
+}
+
+void RtpTransmissionManager::OnRemoteSenderRemoved(
+    const RtpSenderInfo& sender_info,
+    MediaStreamInterface* stream,
+    cricket::MediaType media_type) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  RTC_LOG(LS_INFO) << "Removing " << cricket::MediaTypeToString(media_type)
+                   << " receiver for track_id=" << sender_info.sender_id
+                   << " and stream_id=" << sender_info.stream_id;
+
+  rtc::scoped_refptr<RtpReceiverInterface> receiver;
+  if (media_type == cricket::MEDIA_TYPE_AUDIO) {
+    // When the MediaEngine audio channel is destroyed, the RemoteAudioSource
+    // will be notified which will end the AudioRtpReceiver::track().
+    receiver = RemoveAndStopReceiver(sender_info);
+    rtc::scoped_refptr<AudioTrackInterface> audio_track =
+        stream->FindAudioTrack(sender_info.sender_id);
+    if (audio_track) {
+      stream->RemoveTrack(audio_track);
+    }
+  } else if (media_type == cricket::MEDIA_TYPE_VIDEO) {
+    // Stopping or destroying a VideoRtpReceiver will end the
+    // VideoRtpReceiver::track().
+    receiver = RemoveAndStopReceiver(sender_info);
+    rtc::scoped_refptr<VideoTrackInterface> video_track =
+        stream->FindVideoTrack(sender_info.sender_id);
+    if (video_track) {
+      // There's no guarantee the track is still available, e.g. the track may
+      // have been removed from the stream by an application.
+      stream->RemoveTrack(video_track);
+    }
+  } else {
+    RTC_NOTREACHED() << "Invalid media type";
+  }
+  if (receiver) {
+    RTC_DCHECK(!closed_);
+    Observer()->OnRemoveTrack(receiver);
+  }
+}
+
+void RtpTransmissionManager::OnLocalSenderAdded(
+    const RtpSenderInfo& sender_info,
+    cricket::MediaType media_type) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  RTC_DCHECK(!IsUnifiedPlan());
+  auto sender = FindSenderById(sender_info.sender_id);
+  if (!sender) {
+    RTC_LOG(LS_WARNING) << "An unknown RtpSender with id "
+                        << sender_info.sender_id
+                        << " has been configured in the local description.";
+    return;
+  }
+
+  if (sender->media_type() != media_type) {
+    RTC_LOG(LS_WARNING) << "An RtpSender has been configured in the local"
+                           " description with an unexpected media type.";
+    return;
+  }
+
+  sender->internal()->set_stream_ids({sender_info.stream_id});
+  sender->internal()->SetSsrc(sender_info.first_ssrc);
+}
+
+void RtpTransmissionManager::OnLocalSenderRemoved(
+    const RtpSenderInfo& sender_info,
+    cricket::MediaType media_type) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  auto sender = FindSenderById(sender_info.sender_id);
+  if (!sender) {
+    // This is the normal case. I.e., RemoveStream has been called and the
+    // SessionDescriptions has been renegotiated.
+    return;
+  }
+
+  // A sender has been removed from the SessionDescription but it's still
+  // associated with the PeerConnection. This only occurs if the SDP doesn't
+  // match with the calls to CreateSender, AddStream and RemoveStream.
+  if (sender->media_type() != media_type) {
+    RTC_LOG(LS_WARNING) << "An RtpSender has been configured in the local"
+                           " description with an unexpected media type.";
+    return;
+  }
+
+  sender->internal()->SetSsrc(0);
+}
+
+std::vector<RtpSenderInfo>* RtpTransmissionManager::GetRemoteSenderInfos(
+    cricket::MediaType media_type) {
+  RTC_DCHECK(media_type == cricket::MEDIA_TYPE_AUDIO ||
+             media_type == cricket::MEDIA_TYPE_VIDEO);
+  return (media_type == cricket::MEDIA_TYPE_AUDIO)
+             ? &remote_audio_sender_infos_
+             : &remote_video_sender_infos_;
+}
+
+std::vector<RtpSenderInfo>* RtpTransmissionManager::GetLocalSenderInfos(
+    cricket::MediaType media_type) {
+  RTC_DCHECK(media_type == cricket::MEDIA_TYPE_AUDIO ||
+             media_type == cricket::MEDIA_TYPE_VIDEO);
+  return (media_type == cricket::MEDIA_TYPE_AUDIO) ? &local_audio_sender_infos_
+                                                   : &local_video_sender_infos_;
+}
+
+const RtpSenderInfo* RtpTransmissionManager::FindSenderInfo(
+    const std::vector<RtpSenderInfo>& infos,
+    const std::string& stream_id,
+    const std::string sender_id) const {
+  for (const RtpSenderInfo& sender_info : infos) {
+    if (sender_info.stream_id == stream_id &&
+        sender_info.sender_id == sender_id) {
+      return &sender_info;
+    }
+  }
+  return nullptr;
+}
+
+rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
+RtpTransmissionManager::FindSenderForTrack(
+    MediaStreamTrackInterface* track) const {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  for (const auto& transceiver : transceivers_.List()) {
+    for (auto sender : transceiver->internal()->senders()) {
+      if (sender->track() == track) {
+        return sender;
+      }
+    }
+  }
+  return nullptr;
+}
+
+rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
+RtpTransmissionManager::FindSenderById(const std::string& sender_id) const {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  for (const auto& transceiver : transceivers_.List()) {
+    for (auto sender : transceiver->internal()->senders()) {
+      if (sender->id() == sender_id) {
+        return sender;
+      }
+    }
+  }
+  return nullptr;
+}
+
+rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
+RtpTransmissionManager::FindReceiverById(const std::string& receiver_id) const {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  for (const auto& transceiver : transceivers_.List()) {
+    for (auto receiver : transceiver->internal()->receivers()) {
+      if (receiver->id() == receiver_id) {
+        return receiver;
+      }
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace webrtc
diff --git a/pc/rtp_transmission_manager.h b/pc/rtp_transmission_manager.h
new file mode 100644
index 0000000..7aa410c
--- /dev/null
+++ b/pc/rtp_transmission_manager.h
@@ -0,0 +1,270 @@
+/*
+ *  Copyright 2020 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef PC_RTP_TRANSMISSION_MANAGER_H_
+#define PC_RTP_TRANSMISSION_MANAGER_H_
+
+#include <stdint.h>
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "api/media_stream_interface.h"
+#include "api/media_types.h"
+#include "api/peer_connection_interface.h"
+#include "api/rtc_error.h"
+#include "api/rtp_parameters.h"
+#include "api/rtp_receiver_interface.h"
+#include "api/rtp_sender_interface.h"
+#include "api/scoped_refptr.h"
+#include "media/base/media_channel.h"
+#include "pc/channel_manager.h"
+#include "pc/rtp_receiver.h"
+#include "pc/rtp_sender.h"
+#include "pc/rtp_transceiver.h"
+#include "pc/stats_collector_interface.h"
+#include "pc/transceiver_list.h"
+#include "pc/usage_pattern.h"
+#include "rtc_base/synchronization/sequence_checker.h"
+#include "rtc_base/third_party/sigslot/sigslot.h"
+#include "rtc_base/thread.h"
+#include "rtc_base/thread_annotations.h"
+
+namespace rtc {
+class Thread;
+}
+
+namespace webrtc {
+
+// This class contains information about
+// an RTPSender, used for things like looking it up by SSRC.
+struct RtpSenderInfo {
+  RtpSenderInfo() : first_ssrc(0) {}
+  RtpSenderInfo(const std::string& stream_id,
+                const std::string sender_id,
+                uint32_t ssrc)
+      : stream_id(stream_id), sender_id(sender_id), first_ssrc(ssrc) {}
+  bool operator==(const RtpSenderInfo& other) {
+    return this->stream_id == other.stream_id &&
+           this->sender_id == other.sender_id &&
+           this->first_ssrc == other.first_ssrc;
+  }
+  std::string stream_id;
+  std::string sender_id;
+  // An RtpSender can have many SSRCs. The first one is used as a sort of ID
+  // for communicating with the lower layers.
+  uint32_t first_ssrc;
+};
+
+// The RtpTransmissionManager class is responsible for managing the lifetime
+// and relationships between objects of type RtpSender, RtpReceiver and
+// RtpTransceiver.
+class RtpTransmissionManager : public RtpSenderBase::SetStreamsObserver,
+                               public sigslot::has_slots<> {
+ public:
+  RtpTransmissionManager(bool is_unified_plan,
+                         rtc::Thread* signaling_thread,
+                         rtc::Thread* worker_thread,
+                         cricket::ChannelManager* channel_manager,
+                         UsagePattern* usage_pattern,
+                         PeerConnectionObserver* observer,
+                         StatsCollectorInterface* stats_,
+                         std::function<void()> on_negotiation_needed);
+
+  // No move or copy permitted.
+  RtpTransmissionManager(const RtpTransmissionManager&) = delete;
+  RtpTransmissionManager& operator=(const RtpTransmissionManager&) = delete;
+
+  // Stop activity. In particular, don't call observer_ any more.
+  void Close();
+
+  // RtpSenderBase::SetStreamsObserver override.
+  void OnSetStreams() override;
+
+  // Add a new track, creating transceiver if required.
+  RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrack(
+      rtc::scoped_refptr<MediaStreamTrackInterface> track,
+      const std::vector<std::string>& stream_ids);
+
+  // Create a new RTP sender. Does not associate with a transceiver.
+  rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
+  CreateSender(cricket::MediaType media_type,
+               const std::string& id,
+               rtc::scoped_refptr<MediaStreamTrackInterface> track,
+               const std::vector<std::string>& stream_ids,
+               const std::vector<RtpEncodingParameters>& send_encodings);
+
+  // Create a new RTP receiver. Does not associate with a transceiver.
+  rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
+  CreateReceiver(cricket::MediaType media_type, const std::string& receiver_id);
+
+  // Create a new RtpTransceiver of the given type and add it to the list of
+  // registered transceivers.
+  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+  CreateAndAddTransceiver(
+      rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> sender,
+      rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
+          receiver);
+
+  // Returns the first RtpTransceiver suitable for a newly added track, if such
+  // transceiver is available.
+  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+  FindFirstTransceiverForAddedTrack(
+      rtc::scoped_refptr<MediaStreamTrackInterface> track);
+
+  // Returns the list of senders currently associated with some
+  // registered transceiver
+  std::vector<rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>>
+  GetSendersInternal() const;
+
+  // Returns the list of receivers currently associated with a transceiver
+  std::vector<
+      rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>>
+  GetReceiversInternal() const;
+
+  // Plan B: Get the transceiver containing all audio senders and receivers
+  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+  GetAudioTransceiver() const;
+  // Plan B: Get the transceiver containing all video senders and receivers
+  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+  GetVideoTransceiver() const;
+
+  // Gets the first audio transceiver.
+  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+  GetFirstAudioTransceiver() const;
+
+  // Add an audio track, reusing or creating the sender.
+  void AddAudioTrack(AudioTrackInterface* track, MediaStreamInterface* stream);
+  // Plan B: Remove an audio track, removing the sender.
+  void RemoveAudioTrack(AudioTrackInterface* track,
+                        MediaStreamInterface* stream);
+  // Add a video track, reusing or creating the sender.
+  void AddVideoTrack(VideoTrackInterface* track, MediaStreamInterface* stream);
+  // Plan B: Remove a video track, removing the sender.
+  void RemoveVideoTrack(VideoTrackInterface* track,
+                        MediaStreamInterface* stream);
+
+  // Triggered when a remote sender has been seen for the first time in a remote
+  // session description. It creates a remote MediaStreamTrackInterface
+  // implementation and triggers CreateAudioReceiver or CreateVideoReceiver.
+  void OnRemoteSenderAdded(const RtpSenderInfo& sender_info,
+                           MediaStreamInterface* stream,
+                           cricket::MediaType media_type);
+
+  // Triggered when a remote sender has been removed from a remote session
+  // description. It removes the remote sender with id |sender_id| from a remote
+  // MediaStream and triggers DestroyAudioReceiver or DestroyVideoReceiver.
+  void OnRemoteSenderRemoved(const RtpSenderInfo& sender_info,
+                             MediaStreamInterface* stream,
+                             cricket::MediaType media_type);
+
+  // Triggered when a local sender has been seen for the first time in a local
+  // session description.
+  // This method triggers CreateAudioSender or CreateVideoSender if the rtp
+  // streams in the local SessionDescription can be mapped to a MediaStreamTrack
+  // in a MediaStream in |local_streams_|
+  void OnLocalSenderAdded(const RtpSenderInfo& sender_info,
+                          cricket::MediaType media_type);
+
+  // Triggered when a local sender has been removed from a local session
+  // description.
+  // This method triggers DestroyAudioSender or DestroyVideoSender if a stream
+  // has been removed from the local SessionDescription and the stream can be
+  // mapped to a MediaStreamTrack in a MediaStream in |local_streams_|.
+  void OnLocalSenderRemoved(const RtpSenderInfo& sender_info,
+                            cricket::MediaType media_type);
+
+  std::vector<RtpSenderInfo>* GetRemoteSenderInfos(
+      cricket::MediaType media_type);
+  std::vector<RtpSenderInfo>* GetLocalSenderInfos(
+      cricket::MediaType media_type);
+  const RtpSenderInfo* FindSenderInfo(const std::vector<RtpSenderInfo>& infos,
+                                      const std::string& stream_id,
+                                      const std::string sender_id) const;
+
+  // Return the RtpSender with the given track attached.
+  rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
+  FindSenderForTrack(MediaStreamTrackInterface* track) const;
+
+  // Return the RtpSender with the given id, or null if none exists.
+  rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
+  FindSenderById(const std::string& sender_id) const;
+
+  // Return the RtpReceiver with the given id, or null if none exists.
+  rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
+  FindReceiverById(const std::string& receiver_id) const;
+
+  TransceiverList* transceivers() { return &transceivers_; }
+  const TransceiverList* transceivers() const { return &transceivers_; }
+
+  // Plan B helpers for getting the voice/video media channels for the single
+  // audio/video transceiver, if it exists.
+  cricket::VoiceMediaChannel* voice_media_channel() const;
+  cricket::VideoMediaChannel* video_media_channel() const;
+
+ private:
+  rtc::Thread* signaling_thread() const { return signaling_thread_; }
+  rtc::Thread* worker_thread() const { return worker_thread_; }
+  cricket::ChannelManager* channel_manager() const { return channel_manager_; }
+  bool IsUnifiedPlan() const { return is_unified_plan_; }
+  void NoteUsageEvent(UsageEvent event) {
+    usage_pattern_->NoteUsageEvent(event);
+  }
+
+  // AddTrack implementation when Unified Plan is specified.
+  RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrackUnifiedPlan(
+      rtc::scoped_refptr<MediaStreamTrackInterface> track,
+      const std::vector<std::string>& stream_ids);
+  // AddTrack implementation when Plan B is specified.
+  RTCErrorOr<rtc::scoped_refptr<RtpSenderInterface>> AddTrackPlanB(
+      rtc::scoped_refptr<MediaStreamTrackInterface> track,
+      const std::vector<std::string>& stream_ids);
+
+  // Create an RtpReceiver that sources an audio track.
+  void CreateAudioReceiver(MediaStreamInterface* stream,
+                           const RtpSenderInfo& remote_sender_info)
+      RTC_RUN_ON(signaling_thread());
+
+  // Create an RtpReceiver that sources a video track.
+  void CreateVideoReceiver(MediaStreamInterface* stream,
+                           const RtpSenderInfo& remote_sender_info)
+      RTC_RUN_ON(signaling_thread());
+  rtc::scoped_refptr<RtpReceiverInterface> RemoveAndStopReceiver(
+      const RtpSenderInfo& remote_sender_info) RTC_RUN_ON(signaling_thread());
+
+  PeerConnectionObserver* Observer() const;
+  void OnNegotiationNeeded();
+
+  TransceiverList transceivers_;
+
+  // These lists store sender info seen in local/remote descriptions.
+  std::vector<RtpSenderInfo> remote_audio_sender_infos_
+      RTC_GUARDED_BY(signaling_thread());
+  std::vector<RtpSenderInfo> remote_video_sender_infos_
+      RTC_GUARDED_BY(signaling_thread());
+  std::vector<RtpSenderInfo> local_audio_sender_infos_
+      RTC_GUARDED_BY(signaling_thread());
+  std::vector<RtpSenderInfo> local_video_sender_infos_
+      RTC_GUARDED_BY(signaling_thread());
+
+  bool closed_ = false;
+  bool const is_unified_plan_;
+  rtc::Thread* signaling_thread_;
+  rtc::Thread* worker_thread_;
+  cricket::ChannelManager* channel_manager_;
+  UsagePattern* usage_pattern_;
+  PeerConnectionObserver* observer_;
+  StatsCollectorInterface* const stats_;
+  std::function<void()> on_negotiation_needed_;
+};
+
+}  // namespace webrtc
+
+#endif  // PC_RTP_TRANSMISSION_MANAGER_H_
diff --git a/pc/sdp_offer_answer.cc b/pc/sdp_offer_answer.cc
index d7863d7..5197a69 100644
--- a/pc/sdp_offer_answer.cc
+++ b/pc/sdp_offer_answer.cc
@@ -947,11 +947,17 @@
 cricket::ChannelManager* SdpOfferAnswerHandler::channel_manager() const {
   return pc_->channel_manager();
 }
-TransceiverList& SdpOfferAnswerHandler::transceivers() {
-  return pc_->transceivers_;
+TransceiverList* SdpOfferAnswerHandler::transceivers() {
+  if (!pc_->rtp_manager()) {
+    return nullptr;
+  }
+  return pc_->rtp_manager()->transceivers();
 }
-const TransceiverList& SdpOfferAnswerHandler::transceivers() const {
-  return pc_->transceivers_;
+const TransceiverList* SdpOfferAnswerHandler::transceivers() const {
+  if (!pc_->rtp_manager()) {
+    return nullptr;
+  }
+  return pc_->rtp_manager()->transceivers();
 }
 JsepTransportController* SdpOfferAnswerHandler::transport_controller() {
   return pc_->transport_controller_.get();
@@ -969,6 +975,12 @@
 const cricket::PortAllocator* SdpOfferAnswerHandler::port_allocator() const {
   return pc_->port_allocator_.get();
 }
+RtpTransmissionManager* SdpOfferAnswerHandler::rtp_manager() {
+  return pc_->rtp_manager();
+}
+const RtpTransmissionManager* SdpOfferAnswerHandler::rtp_manager() const {
+  return pc_->rtp_manager();
+}
 
 // ===================================================================
 
@@ -1211,7 +1223,7 @@
     }
     std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> remove_list;
     std::vector<rtc::scoped_refptr<MediaStreamInterface>> removed_streams;
-    for (const auto& transceiver : transceivers().List()) {
+    for (const auto& transceiver : transceivers()->List()) {
       if (transceiver->stopped()) {
         continue;
       }
@@ -1300,7 +1312,7 @@
   }
 
   if (IsUnifiedPlan()) {
-    for (const auto& transceiver : transceivers().List()) {
+    for (const auto& transceiver : transceivers()->List()) {
       if (transceiver->stopped()) {
         continue;
       }
@@ -1577,7 +1589,7 @@
     std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> remove_list;
     std::vector<rtc::scoped_refptr<MediaStreamInterface>> added_streams;
     std::vector<rtc::scoped_refptr<MediaStreamInterface>> removed_streams;
-    for (const auto& transceiver : transceivers().List()) {
+    for (const auto& transceiver : transceivers()->List()) {
       const ContentInfo* content =
           FindMediaSectionForTransceiver(transceiver, remote_description());
       if (!content) {
@@ -1597,7 +1609,7 @@
           stream_ids = media_desc->streams()[0].stream_ids();
         }
         transceivers()
-            .StableState(transceiver)
+            ->StableState(transceiver)
             ->SetRemoteStreamIdsIfUnset(transceiver->receiver()->stream_ids());
 
         RTC_LOG(LS_INFO) << "Processing the MSIDs for MID=" << content->name
@@ -2401,7 +2413,7 @@
   } else {
     RTC_DCHECK(type == SdpType::kAnswer);
     ChangeSignalingState(PeerConnectionInterface::kStable);
-    transceivers().DiscardStableStates();
+    transceivers()->DiscardStableStates();
     have_pending_rtp_data_channel_ = false;
   }
 
@@ -2492,10 +2504,10 @@
   stream_observers_.push_back(std::unique_ptr<MediaStreamObserver>(observer));
 
   for (const auto& track : local_stream->GetAudioTracks()) {
-    pc_->AddAudioTrack(track.get(), local_stream);
+    rtp_manager()->AddAudioTrack(track.get(), local_stream);
   }
   for (const auto& track : local_stream->GetVideoTracks()) {
-    pc_->AddVideoTrack(track.get(), local_stream);
+    rtp_manager()->AddVideoTrack(track.get(), local_stream);
   }
 
   pc_->stats()->AddStream(local_stream);
@@ -2511,10 +2523,10 @@
   TRACE_EVENT0("webrtc", "PeerConnection::RemoveStream");
   if (!pc_->IsClosed()) {
     for (const auto& track : local_stream->GetAudioTracks()) {
-      pc_->RemoveAudioTrack(track.get(), local_stream);
+      rtp_manager()->RemoveAudioTrack(track.get(), local_stream);
     }
     for (const auto& track : local_stream->GetVideoTracks()) {
-      pc_->RemoveVideoTrack(track.get(), local_stream);
+      rtp_manager()->RemoveVideoTrack(track.get(), local_stream);
     }
   }
   local_streams_->RemoveStream(local_stream);
@@ -2546,7 +2558,7 @@
   std::vector<rtc::scoped_refptr<MediaStreamInterface>> all_removed_streams;
   std::vector<rtc::scoped_refptr<RtpReceiverInterface>> removed_receivers;
 
-  for (auto&& transceivers_stable_state_pair : transceivers().StableStates()) {
+  for (auto&& transceivers_stable_state_pair : transceivers()->StableStates()) {
     auto transceiver = transceivers_stable_state_pair.first;
     auto state = transceivers_stable_state_pair.second;
 
@@ -2577,7 +2589,7 @@
       if (transceiver->internal()->reused_for_addtrack()) {
         transceiver->internal()->set_created_by_addtrack(true);
       } else {
-        transceivers().Remove(transceiver);
+        transceivers()->Remove(transceiver);
       }
     }
     transceiver->internal()->sender_internal()->set_transport(nullptr);
@@ -2590,7 +2602,7 @@
     DestroyDataChannelTransport();
     have_pending_rtp_data_channel_ = false;
   }
-  transceivers().DiscardStableStates();
+  transceivers()->DiscardStableStates();
   pending_local_description_.reset();
   pending_remote_description_.reset();
   ChangeSignalingState(PeerConnectionInterface::kStable);
@@ -2742,7 +2754,7 @@
 
   // 5. For each transceiver in connection's set of transceivers, perform the
   // following checks:
-  for (const auto& transceiver : transceivers().List()) {
+  for (const auto& transceiver : transceivers()->List()) {
     const ContentInfo* current_local_msection =
         FindTransceiverMSection(transceiver.get(), description);
 
@@ -3060,27 +3072,27 @@
   // If this is an offer then the m= section might be recycled. If the m=
   // section is being recycled (defined as: rejected in the current local or
   // remote description and not rejected in new description), the transceiver
-  // should have been removed by RemoveStoppedTransceivers().
+  // should have been removed by RemoveStoppedtransceivers()->
   if (IsMediaSectionBeingRecycled(type, content, old_local_content,
                                   old_remote_content)) {
     const std::string& old_mid =
         (old_local_content && old_local_content->rejected)
             ? old_local_content->name
             : old_remote_content->name;
-    auto old_transceiver = transceivers().FindByMid(old_mid);
+    auto old_transceiver = transceivers()->FindByMid(old_mid);
     // The transceiver should be disassociated in RemoveStoppedTransceivers()
     RTC_DCHECK(!old_transceiver);
   }
 #endif
 
   const MediaContentDescription* media_desc = content.media_description();
-  auto transceiver = transceivers().FindByMid(content.name);
+  auto transceiver = transceivers()->FindByMid(content.name);
   if (source == cricket::CS_LOCAL) {
     // Find the RtpTransceiver that corresponds to this m= section, using the
     // mapping between transceivers and m= section indices established when
     // creating the offer.
     if (!transceiver) {
-      transceiver = transceivers().FindByMLineIndex(mline_index);
+      transceiver = transceivers()->FindByMLineIndex(mline_index);
     }
     if (!transceiver) {
       // This may happen normally when media sections are rejected.
@@ -3109,20 +3121,21 @@
       std::string sender_id = rtc::CreateRandomUuid();
       std::vector<RtpEncodingParameters> send_encodings =
           GetSendEncodingsFromRemoteDescription(*media_desc);
-      auto sender = pc_->CreateSender(media_desc->type(), sender_id, nullptr,
-                                      {}, send_encodings);
+      auto sender = rtp_manager()->CreateSender(media_desc->type(), sender_id,
+                                                nullptr, {}, send_encodings);
       std::string receiver_id;
       if (!media_desc->streams().empty()) {
         receiver_id = media_desc->streams()[0].id;
       } else {
         receiver_id = rtc::CreateRandomUuid();
       }
-      auto receiver = pc_->CreateReceiver(media_desc->type(), receiver_id);
-      transceiver = pc_->CreateAndAddTransceiver(sender, receiver);
+      auto receiver =
+          rtp_manager()->CreateReceiver(media_desc->type(), receiver_id);
+      transceiver = rtp_manager()->CreateAndAddTransceiver(sender, receiver);
       transceiver->internal()->set_direction(
           RtpTransceiverDirection::kRecvOnly);
       if (type == SdpType::kOffer) {
-        transceivers().StableState(transceiver)->set_newly_created();
+        transceivers()->StableState(transceiver)->set_newly_created();
       }
     }
 
@@ -3166,7 +3179,7 @@
                          transceiver->internal()->mline_index() != mline_index;
     if (state_changes) {
       transceivers()
-          .StableState(transceiver)
+          ->StableState(transceiver)
           ->SetMSectionIfUnset(transceiver->internal()->mid(),
                                transceiver->internal()->mline_index());
     }
@@ -3337,7 +3350,7 @@
   // the same type that were added to the PeerConnection by addTrack and are not
   // associated with any m= section and are not stopped, find the first such
   // RtpTransceiver.
-  for (auto transceiver : transceivers().List()) {
+  for (auto transceiver : transceivers()->List()) {
     if (transceiver->media_type() == media_type &&
         transceiver->internal()->created_by_addtrack() && !transceiver->mid() &&
         !transceiver->stopped()) {
@@ -3421,8 +3434,10 @@
     const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
     cricket::MediaSessionOptions* session_options) {
   // Figure out transceiver directional preferences.
-  bool send_audio = !pc_->GetAudioTransceiver()->internal()->senders().empty();
-  bool send_video = !pc_->GetVideoTransceiver()->internal()->senders().empty();
+  bool send_audio =
+      !rtp_manager()->GetAudioTransceiver()->internal()->senders().empty();
+  bool send_video =
+      !rtp_manager()->GetVideoTransceiver()->internal()->senders().empty();
 
   // By default, generate sendrecv/recvonly m= sections.
   bool recv_audio = true;
@@ -3496,7 +3511,7 @@
       !video_index ? nullptr
                    : &session_options->media_description_options[*video_index];
 
-  AddPlanBRtpSenderOptions(pc_->GetSendersInternal(),
+  AddPlanBRtpSenderOptions(rtp_manager()->GetSendersInternal(),
                            audio_media_description_options,
                            video_media_description_options,
                            offer_answer_options.num_simulcast_layers);
@@ -3545,7 +3560,7 @@
         media_type == cricket::MEDIA_TYPE_VIDEO) {
       // A media section is considered eligible for recycling if it is marked as
       // rejected in either the current local or current remote description.
-      auto transceiver = transceivers().FindByMid(mid);
+      auto transceiver = transceivers()->FindByMid(mid);
       if (!transceiver) {
         // No associated transceiver. The media section has been stopped.
         recycleable_mline_indices.push(i);
@@ -3599,7 +3614,7 @@
   // and not associated). Reuse media sections marked as recyclable first,
   // otherwise append to the end of the offer. New media sections should be
   // added in the order they were added to the PeerConnection.
-  for (const auto& transceiver : transceivers().List()) {
+  for (const auto& transceiver : transceivers()->List()) {
     if (transceiver->mid() || transceiver->stopping()) {
       continue;
     }
@@ -3670,8 +3685,10 @@
     const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
     cricket::MediaSessionOptions* session_options) {
   // Figure out transceiver directional preferences.
-  bool send_audio = !pc_->GetAudioTransceiver()->internal()->senders().empty();
-  bool send_video = !pc_->GetVideoTransceiver()->internal()->senders().empty();
+  bool send_audio =
+      !rtp_manager()->GetAudioTransceiver()->internal()->senders().empty();
+  bool send_video =
+      !rtp_manager()->GetVideoTransceiver()->internal()->senders().empty();
 
   // By default, generate sendrecv/recvonly m= sections. The direction is also
   // restricted by the direction in the offer.
@@ -3708,7 +3725,7 @@
       !video_index ? nullptr
                    : &session_options->media_description_options[*video_index];
 
-  AddPlanBRtpSenderOptions(pc_->GetSendersInternal(),
+  AddPlanBRtpSenderOptions(rtp_manager()->GetSendersInternal(),
                            audio_media_description_options,
                            video_media_description_options,
                            offer_answer_options.num_simulcast_layers);
@@ -3726,7 +3743,7 @@
     cricket::MediaType media_type = content.media_description()->type();
     if (media_type == cricket::MEDIA_TYPE_AUDIO ||
         media_type == cricket::MEDIA_TYPE_VIDEO) {
-      auto transceiver = transceivers().FindByMid(content.name);
+      auto transceiver = transceivers()->FindByMid(content.name);
       if (transceiver) {
         session_options->media_description_options.push_back(
             GetMediaDescriptionOptionsForTransceiver(
@@ -3855,7 +3872,7 @@
   std::vector<
       rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
       receiving_transceivers;
-  for (const auto& transceiver : transceivers().List()) {
+  for (const auto& transceiver : transceivers()->List()) {
     if (!transceiver->stopped() && transceiver->media_type() == media_type &&
         RtpTransceiverDirectionHasRecv(transceiver->direction())) {
       receiving_transceivers.push_back(transceiver);
@@ -3907,20 +3924,20 @@
     const std::vector<cricket::StreamParams>& streams,
     cricket::MediaType media_type) {
   RTC_DCHECK_RUN_ON(signaling_thread());
-  std::vector<PeerConnection::RtpSenderInfo>* current_senders =
-      pc_->GetLocalSenderInfos(media_type);
+  std::vector<RtpSenderInfo>* current_senders =
+      rtp_manager()->GetLocalSenderInfos(media_type);
 
   // Find removed tracks. I.e., tracks where the track id, stream id or ssrc
   // don't match the new StreamParam.
   for (auto sender_it = current_senders->begin();
        sender_it != current_senders->end();
        /* incremented manually */) {
-    const PeerConnection::RtpSenderInfo& info = *sender_it;
+    const RtpSenderInfo& info = *sender_it;
     const cricket::StreamParams* params =
         cricket::GetStreamBySsrc(streams, info.first_ssrc);
     if (!params || params->id != info.sender_id ||
         params->first_stream_id() != info.stream_id) {
-      pc_->OnLocalSenderRemoved(info, media_type);
+      rtp_manager()->OnLocalSenderRemoved(info, media_type);
       sender_it = current_senders->erase(sender_it);
     } else {
       ++sender_it;
@@ -3934,12 +3951,11 @@
     const std::string& stream_id = params.first_stream_id();
     const std::string& sender_id = params.id;
     uint32_t ssrc = params.first_ssrc();
-    const PeerConnection::RtpSenderInfo* sender_info =
-        pc_->FindSenderInfo(*current_senders, stream_id, sender_id);
+    const RtpSenderInfo* sender_info =
+        rtp_manager()->FindSenderInfo(*current_senders, stream_id, sender_id);
     if (!sender_info) {
-      current_senders->push_back(
-          PeerConnection::RtpSenderInfo(stream_id, sender_id, ssrc));
-      pc_->OnLocalSenderAdded(current_senders->back(), media_type);
+      current_senders->push_back(RtpSenderInfo(stream_id, sender_id, ssrc));
+      rtp_manager()->OnLocalSenderAdded(current_senders->back(), media_type);
     }
   }
 }
@@ -3952,15 +3968,15 @@
   RTC_DCHECK_RUN_ON(signaling_thread());
   RTC_DCHECK(!IsUnifiedPlan());
 
-  std::vector<PeerConnection::RtpSenderInfo>* current_senders =
-      pc_->GetRemoteSenderInfos(media_type);
+  std::vector<RtpSenderInfo>* current_senders =
+      rtp_manager()->GetRemoteSenderInfos(media_type);
 
   // Find removed senders. I.e., senders where the sender id or ssrc don't match
   // the new StreamParam.
   for (auto sender_it = current_senders->begin();
        sender_it != current_senders->end();
        /* incremented manually */) {
-    const PeerConnection::RtpSenderInfo& info = *sender_it;
+    const RtpSenderInfo& info = *sender_it;
     const cricket::StreamParams* params =
         cricket::GetStreamBySsrc(streams, info.first_ssrc);
     std::string params_stream_id;
@@ -3976,8 +3992,8 @@
         sender_exists) {
       ++sender_it;
     } else {
-      pc_->OnRemoteSenderRemoved(info, remote_streams_->find(info.stream_id),
-                                 media_type);
+      rtp_manager()->OnRemoteSenderRemoved(
+          info, remote_streams_->find(info.stream_id), media_type);
       sender_it = current_senders->erase(sender_it);
     }
   }
@@ -4013,12 +4029,12 @@
       new_streams->AddStream(stream);
     }
 
-    const PeerConnection::RtpSenderInfo* sender_info =
-        pc_->FindSenderInfo(*current_senders, stream_id, sender_id);
+    const RtpSenderInfo* sender_info =
+        rtp_manager()->FindSenderInfo(*current_senders, stream_id, sender_id);
     if (!sender_info) {
-      current_senders->push_back(
-          PeerConnection::RtpSenderInfo(stream_id, sender_id, ssrc));
-      pc_->OnRemoteSenderAdded(current_senders->back(), stream, media_type);
+      current_senders->push_back(RtpSenderInfo(stream_id, sender_id, ssrc));
+      rtp_manager()->OnRemoteSenderAdded(current_senders->back(), stream,
+                                         media_type);
     }
   }
 
@@ -4036,21 +4052,20 @@
     std::string default_sender_id = (media_type == cricket::MEDIA_TYPE_AUDIO)
                                         ? kDefaultAudioSenderId
                                         : kDefaultVideoSenderId;
-    const PeerConnection::RtpSenderInfo* default_sender_info =
-        pc_->FindSenderInfo(*current_senders, kDefaultStreamId,
-                            default_sender_id);
+    const RtpSenderInfo* default_sender_info = rtp_manager()->FindSenderInfo(
+        *current_senders, kDefaultStreamId, default_sender_id);
     if (!default_sender_info) {
-      current_senders->push_back(PeerConnection::RtpSenderInfo(
-          kDefaultStreamId, default_sender_id, /*ssrc=*/0));
-      pc_->OnRemoteSenderAdded(current_senders->back(), default_stream,
-                               media_type);
+      current_senders->push_back(
+          RtpSenderInfo(kDefaultStreamId, default_sender_id, /*ssrc=*/0));
+      rtp_manager()->OnRemoteSenderAdded(current_senders->back(),
+                                         default_stream, media_type);
     }
   }
 }
 
 void SdpOfferAnswerHandler::EnableSending() {
   RTC_DCHECK_RUN_ON(signaling_thread());
-  for (const auto& transceiver : transceivers().List()) {
+  for (const auto& transceiver : transceivers()->List()) {
     cricket::ChannelInterface* channel = transceiver->internal()->channel();
     if (channel && !channel->enabled()) {
       channel->Enable(true);
@@ -4081,7 +4096,7 @@
   }
 
   // Push down the new SDP media section for each audio/video transceiver.
-  for (const auto& transceiver : transceivers().List()) {
+  for (const auto& transceiver : transceivers()->List()) {
     const ContentInfo* content_info =
         FindMediaSectionForTransceiver(transceiver, sdesc);
     cricket::ChannelInterface* channel = transceiver->internal()->channel();
@@ -4178,7 +4193,7 @@
   if (!IsUnifiedPlan())
     return;
   // Traverse a copy of the transceiver list.
-  auto transceiver_list = transceivers().List();
+  auto transceiver_list = transceivers()->List();
   for (auto transceiver : transceiver_list) {
     // 3.2.10.1.1: If transceiver is stopped, associated with an m= section
     //             and the associated m= section is rejected in
@@ -4198,7 +4213,7 @@
                        << " since the media section is being recycled.";
       transceiver->internal()->set_mid(absl::nullopt);
       transceiver->internal()->set_mline_index(absl::nullopt);
-      transceivers().Remove(transceiver);
+      transceivers()->Remove(transceiver);
       continue;
     }
     if (!local_content && !remote_content) {
@@ -4206,7 +4221,7 @@
       // See https://github.com/w3c/webrtc-pc/issues/2576
       RTC_LOG(LS_INFO)
           << "Dropping stopped transceiver that was never associated";
-      transceivers().Remove(transceiver);
+      transceivers()->Remove(transceiver);
       continue;
     }
   }
@@ -4219,12 +4234,12 @@
   // voice channel.
   const cricket::ContentInfo* video_info = cricket::GetFirstVideoContent(desc);
   if (!video_info || video_info->rejected) {
-    DestroyTransceiverChannel(pc_->GetVideoTransceiver());
+    DestroyTransceiverChannel(rtp_manager()->GetVideoTransceiver());
   }
 
   const cricket::ContentInfo* audio_info = cricket::GetFirstAudioContent(desc);
   if (!audio_info || audio_info->rejected) {
-    DestroyTransceiverChannel(pc_->GetAudioTransceiver());
+    DestroyTransceiverChannel(rtp_manager()->GetAudioTransceiver());
   }
 
   const cricket::ContentInfo* data_info = cricket::GetFirstDataContent(desc);
@@ -4432,24 +4447,24 @@
   RTC_DCHECK_RUN_ON(signaling_thread());
   const cricket::ContentInfo* voice = cricket::GetFirstAudioContent(&desc);
   if (voice && !voice->rejected &&
-      !pc_->GetAudioTransceiver()->internal()->channel()) {
+      !rtp_manager()->GetAudioTransceiver()->internal()->channel()) {
     cricket::VoiceChannel* voice_channel = CreateVoiceChannel(voice->name);
     if (!voice_channel) {
       LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
                            "Failed to create voice channel.");
     }
-    pc_->GetAudioTransceiver()->internal()->SetChannel(voice_channel);
+    rtp_manager()->GetAudioTransceiver()->internal()->SetChannel(voice_channel);
   }
 
   const cricket::ContentInfo* video = cricket::GetFirstVideoContent(&desc);
   if (video && !video->rejected &&
-      !pc_->GetVideoTransceiver()->internal()->channel()) {
+      !rtp_manager()->GetVideoTransceiver()->internal()->channel()) {
     cricket::VideoChannel* video_channel = CreateVideoChannel(video->name);
     if (!video_channel) {
       LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
                            "Failed to create video channel.");
     }
-    pc_->GetVideoTransceiver()->internal()->SetChannel(video_channel);
+    rtp_manager()->GetVideoTransceiver()->internal()->SetChannel(video_channel);
   }
 
   const cricket::ContentInfo* data = cricket::GetFirstDataContent(&desc);
@@ -4627,14 +4642,17 @@
 
 void SdpOfferAnswerHandler::DestroyAllChannels() {
   RTC_DCHECK_RUN_ON(signaling_thread());
+  if (!transceivers()) {
+    return;
+  }
   // Destroy video channels first since they may have a pointer to a voice
   // channel.
-  for (const auto& transceiver : transceivers().List()) {
+  for (const auto& transceiver : transceivers()->List()) {
     if (transceiver->media_type() == cricket::MEDIA_TYPE_VIDEO) {
       DestroyTransceiverChannel(transceiver);
     }
   }
-  for (const auto& transceiver : transceivers().List()) {
+  for (const auto& transceiver : transceivers()->List()) {
     if (transceiver->media_type() == cricket::MEDIA_TYPE_AUDIO) {
       DestroyTransceiverChannel(transceiver);
     }
@@ -4819,7 +4837,7 @@
   // single Invoke; necessary due to thread guards.
   std::vector<std::pair<RtpTransceiverDirection, cricket::ChannelInterface*>>
       channels_to_update;
-  for (const auto& transceiver : transceivers().List()) {
+  for (const auto& transceiver : transceivers()->List()) {
     cricket::ChannelInterface* channel = transceiver->internal()->channel();
     const ContentInfo* content =
         FindMediaSectionForTransceiver(transceiver, sdesc);
diff --git a/pc/sdp_offer_answer.h b/pc/sdp_offer_answer.h
index 56f6175..da8b3a2 100644
--- a/pc/sdp_offer_answer.h
+++ b/pc/sdp_offer_answer.h
@@ -74,6 +74,7 @@
 class PeerConnection;
 class VideoRtpReceiver;
 class RtcEventLog;
+class RtpTransmissionManager;
 class TransceiverList;
 
 // SdpOfferAnswerHandler is a component
@@ -525,13 +526,15 @@
   // ==================================================================
   // Access to pc_ variables
   cricket::ChannelManager* channel_manager() const;
-  TransceiverList& transceivers();
-  const TransceiverList& transceivers() const;
+  TransceiverList* transceivers();
+  const TransceiverList* transceivers() const;
   JsepTransportController* transport_controller();
   DataChannelController* data_channel_controller();
   const DataChannelController* data_channel_controller() const;
   cricket::PortAllocator* port_allocator();
   const cricket::PortAllocator* port_allocator() const;
+  RtpTransmissionManager* rtp_manager();
+  const RtpTransmissionManager* rtp_manager() const;
   // ===================================================================
 
   PeerConnection* const pc_;