Don't create channel_manager when media_engine is not set

Also remove a bunch of functions in ChannelManager that were just
forwarding to MediaEngineInterface.

Bug: webrtc:13931
Change-Id: Ia38591fd22c665cace16d032f5c1e384e413cded
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/261304
Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36801}
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 1edbd24..4796de3 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -377,6 +377,7 @@
     "../rtc_base:checks",
     "../rtc_base:logging",
     "../rtc_base:stringutils",
+    "../rtc_base/memory:always_valid_pointer",
     "../rtc_base/third_party/base64",
   ]
   absl_deps = [
diff --git a/pc/channel_manager.cc b/pc/channel_manager.cc
index e13db54..9ee2bca 100644
--- a/pc/channel_manager.cc
+++ b/pc/channel_manager.cc
@@ -31,6 +31,7 @@
     bool enable_rtx,
     rtc::Thread* worker_thread,
     rtc::Thread* network_thread) {
+  RTC_CHECK(media_engine);
   RTC_DCHECK(network_thread);
   RTC_DCHECK(worker_thread);
 
@@ -51,12 +52,11 @@
   RTC_DCHECK_RUN_ON(signaling_thread_);
   RTC_DCHECK(worker_thread_);
   RTC_DCHECK(network_thread_);
+  RTC_CHECK(media_engine_);
 
-  if (media_engine_) {
-    // TODO(tommi): Change VoiceEngine to do ctor time initialization so that
-    // this isn't necessary.
-    worker_thread_->Invoke<void>(RTC_FROM_HERE, [&] { media_engine_->Init(); });
-  }
+  // TODO(tommi): Change VoiceEngine to do ctor time initialization so that
+  // this isn't necessary.
+  worker_thread_->Invoke<void>(RTC_FROM_HERE, [&] { media_engine_->Init(); });
 }
 
 ChannelManager::~ChannelManager() {
@@ -71,27 +71,8 @@
   });
 }
 
-void ChannelManager::GetSupportedAudioSendCodecs(
-    std::vector<AudioCodec>* codecs) const {
-  if (!media_engine_) {
-    return;
-  }
-  *codecs = media_engine_->voice().send_codecs();
-}
-
-void ChannelManager::GetSupportedAudioReceiveCodecs(
-    std::vector<AudioCodec>* codecs) const {
-  if (!media_engine_) {
-    return;
-  }
-  *codecs = media_engine_->voice().recv_codecs();
-}
-
 void ChannelManager::GetSupportedVideoSendCodecs(
     std::vector<VideoCodec>* codecs) const {
-  if (!media_engine_) {
-    return;
-  }
   codecs->clear();
 
   std::vector<VideoCodec> video_codecs = media_engine_->video().send_codecs();
@@ -106,9 +87,6 @@
 
 void ChannelManager::GetSupportedVideoReceiveCodecs(
     std::vector<VideoCodec>* codecs) const {
-  if (!media_engine_) {
-    return;
-  }
   codecs->clear();
 
   std::vector<VideoCodec> video_codecs = media_engine_->video().recv_codecs();
@@ -123,32 +101,14 @@
 
 RtpHeaderExtensions ChannelManager::GetDefaultEnabledAudioRtpHeaderExtensions()
     const {
-  if (!media_engine_)
-    return {};
   return GetDefaultEnabledRtpHeaderExtensions(media_engine_->voice());
 }
 
-std::vector<webrtc::RtpHeaderExtensionCapability>
-ChannelManager::GetSupportedAudioRtpHeaderExtensions() const {
-  if (!media_engine_)
-    return {};
-  return media_engine_->voice().GetRtpHeaderExtensions();
-}
-
 RtpHeaderExtensions ChannelManager::GetDefaultEnabledVideoRtpHeaderExtensions()
     const {
-  if (!media_engine_)
-    return {};
   return GetDefaultEnabledRtpHeaderExtensions(media_engine_->video());
 }
 
-std::vector<webrtc::RtpHeaderExtensionCapability>
-ChannelManager::GetSupportedVideoRtpHeaderExtensions() const {
-  if (!media_engine_)
-    return {};
-  return media_engine_->video().GetRtpHeaderExtensions();
-}
-
 std::unique_ptr<VoiceChannel> ChannelManager::CreateVoiceChannel(
     webrtc::Call* call,
     const MediaConfig& media_config,
diff --git a/pc/channel_manager.h b/pc/channel_manager.h
index 8d1ec28..23adac3 100644
--- a/pc/channel_manager.h
+++ b/pc/channel_manager.h
@@ -47,7 +47,7 @@
 class ChannelManager : public ChannelFactoryInterface {
  public:
   // Returns an initialized instance of ChannelManager.
-  // If media_engine is non-nullptr, then the returned ChannelManager instance
+  // `media_engine` cannot be null. The returned ChannelManager instance
   // will own that reference and media engine initialization
   static std::unique_ptr<ChannelManager> Create(
       std::unique_ptr<MediaEngineInterface> media_engine,
@@ -63,18 +63,11 @@
   MediaEngineInterface* media_engine() { return media_engine_.get(); }
   rtc::UniqueRandomIdGenerator& ssrc_generator() { return ssrc_generator_; }
 
-  // Retrieves the list of supported audio & video codec types.
-  // Can be called before starting the media engine.
-  void GetSupportedAudioSendCodecs(std::vector<AudioCodec>* codecs) const;
-  void GetSupportedAudioReceiveCodecs(std::vector<AudioCodec>* codecs) const;
+  // Retrieves the list of supported video codec types.
   void GetSupportedVideoSendCodecs(std::vector<VideoCodec>* codecs) const;
   void GetSupportedVideoReceiveCodecs(std::vector<VideoCodec>* codecs) const;
   RtpHeaderExtensions GetDefaultEnabledAudioRtpHeaderExtensions() const;
-  std::vector<webrtc::RtpHeaderExtensionCapability>
-  GetSupportedAudioRtpHeaderExtensions() const;
   RtpHeaderExtensions GetDefaultEnabledVideoRtpHeaderExtensions() const;
-  std::vector<webrtc::RtpHeaderExtensionCapability>
-  GetSupportedVideoRtpHeaderExtensions() const;
 
   // The operations below all occur on the worker thread.
   // The caller is responsible for ensuring that destruction happens
@@ -123,7 +116,7 @@
   void DestroyVideoChannel(VideoChannel* video_channel);
 
  private:
-  const std::unique_ptr<MediaEngineInterface> media_engine_;  // Nullable.
+  const std::unique_ptr<MediaEngineInterface> media_engine_;
   rtc::Thread* const signaling_thread_;
   rtc::Thread* const worker_thread_;
   rtc::Thread* const network_thread_;
diff --git a/pc/connection_context.cc b/pc/connection_context.cc
index 818283d..e2e0666 100644
--- a/pc/connection_context.cc
+++ b/pc/connection_context.cc
@@ -144,9 +144,11 @@
   default_socket_factory_ =
       std::make_unique<rtc::BasicPacketSocketFactory>(socket_factory);
 
-  channel_manager_ = cricket::ChannelManager::Create(
-      std::move(dependencies->media_engine),
-      /*enable_rtx=*/true, worker_thread(), network_thread());
+  if (dependencies->media_engine) {
+    channel_manager_ = cricket::ChannelManager::Create(
+        std::move(dependencies->media_engine),
+        /*enable_rtx=*/true, worker_thread(), network_thread());
+  }
 
   // Set warning levels on the threads, to give warnings when response
   // may be slower than is expected of the thread.
diff --git a/pc/media_session.cc b/pc/media_session.cc
index 71a6d0a..91a0b88 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -1557,21 +1557,22 @@
     const TransportDescriptionFactory* transport_desc_factory,
     rtc::UniqueRandomIdGenerator* ssrc_generator)
     : ssrc_generator_(ssrc_generator),
-      transport_desc_factory_(transport_desc_factory) {
-  RTC_DCHECK(ssrc_generator_);
-}
+      transport_desc_factory_(transport_desc_factory) {}
 
 MediaSessionDescriptionFactory::MediaSessionDescriptionFactory(
     ChannelManager* channel_manager,
     const TransportDescriptionFactory* transport_desc_factory)
-    : MediaSessionDescriptionFactory(transport_desc_factory,
-                                     &channel_manager->ssrc_generator()) {
-  channel_manager->GetSupportedAudioSendCodecs(&audio_send_codecs_);
-  channel_manager->GetSupportedAudioReceiveCodecs(&audio_recv_codecs_);
-  channel_manager->GetSupportedVideoSendCodecs(&video_send_codecs_);
-  channel_manager->GetSupportedVideoReceiveCodecs(&video_recv_codecs_);
-  ComputeAudioCodecsIntersectionAndUnion();
-  ComputeVideoCodecsIntersectionAndUnion();
+    : MediaSessionDescriptionFactory(
+          transport_desc_factory,
+          channel_manager ? &channel_manager->ssrc_generator() : nullptr) {
+  if (channel_manager) {
+    audio_send_codecs_ = channel_manager->media_engine()->voice().send_codecs();
+    audio_recv_codecs_ = channel_manager->media_engine()->voice().recv_codecs();
+    channel_manager->GetSupportedVideoSendCodecs(&video_send_codecs_);
+    channel_manager->GetSupportedVideoReceiveCodecs(&video_recv_codecs_);
+    ComputeAudioCodecsIntersectionAndUnion();
+    ComputeVideoCodecsIntersectionAndUnion();
+  }
 }
 
 const AudioCodecs& MediaSessionDescriptionFactory::audio_sendrecv_codecs()
@@ -2354,7 +2355,7 @@
   if (!CreateMediaContentOffer(
           media_description_options, session_options, filtered_codecs,
           sdes_policy, GetCryptos(current_content), crypto_suites,
-          audio_rtp_extensions, ssrc_generator_, current_streams, audio.get(),
+          audio_rtp_extensions, ssrc_generator(), current_streams, audio.get(),
           transport_desc_factory_->trials())) {
     return false;
   }
@@ -2466,7 +2467,7 @@
   if (!CreateMediaContentOffer(
           media_description_options, session_options, filtered_codecs,
           sdes_policy, GetCryptos(current_content), crypto_suites,
-          video_rtp_extensions, ssrc_generator_, current_streams, video.get(),
+          video_rtp_extensions, ssrc_generator(), current_streams, video.get(),
           transport_desc_factory_->trials())) {
     return false;
   }
@@ -2519,8 +2520,8 @@
 
   if (!CreateContentOffer(media_description_options, session_options,
                           sdes_policy, GetCryptos(current_content),
-                          crypto_suites, RtpHeaderExtensions(), ssrc_generator_,
-                          current_streams, data.get())) {
+                          crypto_suites, RtpHeaderExtensions(),
+                          ssrc_generator(), current_streams, data.get())) {
     return false;
   }
 
@@ -2654,7 +2655,7 @@
       audio_transport->secure() ? cricket::SEC_DISABLED : secure();
   if (!SetCodecsInAnswer(offer_audio_description, filtered_codecs,
                          media_description_options, session_options,
-                         ssrc_generator_, current_streams, audio_answer.get(),
+                         ssrc_generator(), current_streams, audio_answer.get(),
                          transport_desc_factory_->trials())) {
     return false;
   }
@@ -2662,7 +2663,7 @@
           offer_audio_description, media_description_options, session_options,
           sdes_policy, GetCryptos(current_content),
           filtered_rtp_header_extensions(default_audio_rtp_header_extensions),
-          ssrc_generator_, enable_encrypted_rtp_header_extensions_,
+          ssrc_generator(), enable_encrypted_rtp_header_extensions_,
           current_streams, bundle_enabled, audio_answer.get())) {
     return false;  // Fails the session setup.
   }
@@ -2783,7 +2784,7 @@
       video_transport->secure() ? cricket::SEC_DISABLED : secure();
   if (!SetCodecsInAnswer(offer_video_description, filtered_codecs,
                          media_description_options, session_options,
-                         ssrc_generator_, current_streams, video_answer.get(),
+                         ssrc_generator(), current_streams, video_answer.get(),
                          transport_desc_factory_->trials())) {
     return false;
   }
@@ -2791,7 +2792,7 @@
           offer_video_description, media_description_options, session_options,
           sdes_policy, GetCryptos(current_content),
           filtered_rtp_header_extensions(default_video_rtp_header_extensions),
-          ssrc_generator_, enable_encrypted_rtp_header_extensions_,
+          ssrc_generator(), enable_encrypted_rtp_header_extensions_,
           current_streams, bundle_enabled, video_answer.get())) {
     return false;  // Failed the sessin setup.
   }
@@ -2864,7 +2865,7 @@
     if (!CreateMediaContentAnswer(
             offer_data_description, media_description_options, session_options,
             sdes_policy, GetCryptos(current_content), RtpHeaderExtensions(),
-            ssrc_generator_, enable_encrypted_rtp_header_extensions_,
+            ssrc_generator(), enable_encrypted_rtp_header_extensions_,
             current_streams, bundle_enabled, data_answer.get())) {
       return false;  // Fails the session setup.
     }
diff --git a/pc/media_session.h b/pc/media_session.h
index 9a67ad1..8f97538 100644
--- a/pc/media_session.h
+++ b/pc/media_session.h
@@ -34,6 +34,7 @@
 #include "pc/media_protocol_names.h"
 #include "pc/session_description.h"
 #include "pc/simulcast_description.h"
+#include "rtc_base/memory/always_valid_pointer.h"
 #include "rtc_base/unique_id_generator.h"
 
 namespace webrtc {
@@ -328,6 +329,10 @@
 
   void ComputeVideoCodecsIntersectionAndUnion();
 
+  rtc::UniqueRandomIdGenerator* ssrc_generator() const {
+    return ssrc_generator_.get();
+  }
+
   bool is_unified_plan_ = false;
   AudioCodecs audio_send_codecs_;
   AudioCodecs audio_recv_codecs_;
@@ -341,8 +346,9 @@
   VideoCodecs video_sendrecv_codecs_;
   // Union of send and recv.
   VideoCodecs all_video_codecs_;
-  // This object is not owned by the channel so it must outlive it.
-  rtc::UniqueRandomIdGenerator* const ssrc_generator_;
+  // This object may or may not be owned by this class.
+  webrtc::AlwaysValidPointer<rtc::UniqueRandomIdGenerator> const
+      ssrc_generator_;
   bool enable_encrypted_rtp_header_extensions_ = false;
   // TODO(zhihuang): Rename secure_ to sdec_policy_; rename the related getter
   // and setter.
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index f914fc8..c139d0a 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -646,25 +646,27 @@
   sdp_handler_ = SdpOfferAnswerHandler::Create(this, configuration,
                                                dependencies, context_.get());
 
-  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();
-      });
+  if (ConfiguredForMedia()) {
+    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();
+        });
 
-  // Add default audio/video transceivers for Plan B SDP.
-  if (!IsUnifiedPlan()) {
-    rtp_manager()->transceivers()->Add(
-        RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
-            signaling_thread(),
-            rtc::make_ref_counted<RtpTransceiver>(cricket::MEDIA_TYPE_AUDIO,
-                                                  channel_manager())));
-    rtp_manager()->transceivers()->Add(
-        RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
-            signaling_thread(),
-            rtc::make_ref_counted<RtpTransceiver>(cricket::MEDIA_TYPE_VIDEO,
-                                                  channel_manager())));
+    // Add default audio/video transceivers for Plan B SDP.
+    if (!IsUnifiedPlan()) {
+      rtp_manager()->transceivers()->Add(
+          RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
+              signaling_thread(),
+              rtc::make_ref_counted<RtpTransceiver>(cricket::MEDIA_TYPE_AUDIO,
+                                                    channel_manager())));
+      rtp_manager()->transceivers()->Add(
+          RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
+              signaling_thread(),
+              rtc::make_ref_counted<RtpTransceiver>(cricket::MEDIA_TYPE_VIDEO,
+                                                    channel_manager())));
+    }
   }
 
   int delay_ms = configuration.report_usage_pattern_delay_ms
@@ -1178,8 +1180,10 @@
     const {
   RTC_DCHECK_RUN_ON(signaling_thread());
   std::vector<rtc::scoped_refptr<RtpSenderInterface>> ret;
-  for (const auto& sender : rtp_manager()->GetSendersInternal()) {
-    ret.push_back(sender);
+  if (ConfiguredForMedia()) {
+    for (const auto& sender : rtp_manager()->GetSendersInternal()) {
+      ret.push_back(sender);
+    }
   }
   return ret;
 }
@@ -1188,8 +1192,10 @@
 PeerConnection::GetReceivers() const {
   RTC_DCHECK_RUN_ON(signaling_thread());
   std::vector<rtc::scoped_refptr<RtpReceiverInterface>> ret;
-  for (const auto& receiver : rtp_manager()->GetReceiversInternal()) {
-    ret.push_back(receiver);
+  if (ConfiguredForMedia()) {
+    for (const auto& receiver : rtp_manager()->GetReceiversInternal()) {
+      ret.push_back(receiver);
+    }
   }
   return ret;
 }
@@ -1200,8 +1206,10 @@
   RTC_CHECK(IsUnifiedPlan())
       << "GetTransceivers is only supported with Unified Plan SdpSemantics.";
   std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> all_transceivers;
-  for (const auto& transceiver : rtp_manager()->transceivers()->List()) {
-    all_transceivers.push_back(transceiver);
+  if (ConfiguredForMedia()) {
+    for (const auto& transceiver : rtp_manager()->transceivers()->List()) {
+      all_transceivers.push_back(transceiver);
+    }
   }
   return all_transceivers;
 }
@@ -1680,8 +1688,7 @@
         RTC_FROM_HERE, [this, playout] { SetAudioPlayout(playout); });
     return;
   }
-  auto audio_state =
-      context_->channel_manager()->media_engine()->voice().GetAudioState();
+  auto audio_state = media_engine()->voice().GetAudioState();
   audio_state->SetPlayout(playout);
 }
 
@@ -1691,8 +1698,7 @@
         RTC_FROM_HERE, [this, recording] { SetAudioRecording(recording); });
     return;
   }
-  auto audio_state =
-      context_->channel_manager()->media_engine()->voice().GetAudioState();
+  auto audio_state = media_engine()->voice().GetAudioState();
   audio_state->SetRecording(recording);
 }
 
@@ -1712,7 +1718,7 @@
 }
 
 bool PeerConnection::ConfiguredForMedia() const {
-  return context_->channel_manager()->media_engine();
+  return context_->channel_manager();
 }
 
 bool PeerConnection::StartRtcEventLog(std::unique_ptr<RtcEventLogOutput> output,
@@ -1822,12 +1828,13 @@
 
   NoteUsageEvent(UsageEvent::CLOSE_CALLED);
 
-  for (const auto& transceiver : rtp_manager()->transceivers()->List()) {
-    transceiver->internal()->SetPeerConnectionClosed();
-    if (!transceiver->stopped())
-      transceiver->StopInternal();
+  if (ConfiguredForMedia()) {
+    for (const auto& transceiver : rtp_manager()->transceivers()->List()) {
+      transceiver->internal()->SetPeerConnectionClosed();
+      if (!transceiver->stopped())
+        transceiver->StopInternal();
+    }
   }
-
   // Ensure that all asynchronous stats requests are completed before destroying
   // the transport controller below.
   if (stats_collector_) {
@@ -1844,7 +1851,9 @@
   // WebRTC session description factory, the session description factory would
   // call the transport controller.
   sdp_handler_->ResetSessionDescFactory();
-  rtp_manager_->Close();
+  if (ConfiguredForMedia()) {
+    rtp_manager_->Close();
+  }
 
   network_thread()->Invoke<void>(RTC_FROM_HERE, [this] {
     // Data channels will already have been unset via the DestroyAllChannels()
@@ -2735,12 +2744,15 @@
   rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
   std::map<std::string, std::set<cricket::MediaType>>
       media_types_by_transport_name;
-  for (const auto& transceiver : rtp_manager()->transceivers()->UnsafeList()) {
-    if (transceiver->internal()->channel()) {
-      std::string transport_name(
-          transceiver->internal()->channel()->transport_name());
-      media_types_by_transport_name[transport_name].insert(
-          transceiver->media_type());
+  if (ConfiguredForMedia()) {
+    for (const auto& transceiver :
+         rtp_manager()->transceivers()->UnsafeList()) {
+      if (transceiver->internal()->channel()) {
+        std::string transport_name(
+            transceiver->internal()->channel()->transport_name());
+        media_types_by_transport_name[transport_name].insert(
+            transceiver->media_type());
+      }
     }
   }
 
@@ -2888,10 +2900,13 @@
     DataChannelTransportInterface* data_channel_transport) {
   RTC_DCHECK_RUN_ON(network_thread());
   bool ret = true;
-  for (const auto& transceiver : rtp_manager()->transceivers()->UnsafeList()) {
-    cricket::ChannelInterface* channel = transceiver->internal()->channel();
-    if (channel && channel->mid() == mid) {
-      ret = channel->SetRtpTransport(rtp_transport);
+  if (ConfiguredForMedia()) {
+    for (const auto& transceiver :
+         rtp_manager()->transceivers()->UnsafeList()) {
+      cricket::ChannelInterface* channel = transceiver->internal()->channel();
+      if (channel && channel->mid() == mid) {
+        ret = channel->SetRtpTransport(rtp_transport);
+      }
     }
   }
 
@@ -2976,4 +2991,10 @@
   };
 }
 
+cricket::MediaEngineInterface* PeerConnection::media_engine() {
+  RTC_DCHECK(context_);
+  RTC_DCHECK(context_->channel_manager());
+  return context_->channel_manager()->media_engine();
+}
+
 }  // namespace webrtc
diff --git a/pc/peer_connection.h b/pc/peer_connection.h
index 2907cb5..8c578d5 100644
--- a/pc/peer_connection.h
+++ b/pc/peer_connection.h
@@ -273,6 +273,9 @@
       rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
   GetTransceiversInternal() const override {
     RTC_DCHECK_RUN_ON(signaling_thread());
+    if (!ConfiguredForMedia()) {
+      return {};
+    }
     return rtp_manager()->transceivers()->List();
   }
 
@@ -596,6 +599,7 @@
   cricket::ChannelManager* channel_manager() {
     return context_->channel_manager();
   }
+  cricket::MediaEngineInterface* media_engine();
 
   const rtc::scoped_refptr<ConnectionContext> context_;
   // Field trials active for this PeerConnection is the first of:
diff --git a/pc/peer_connection_factory.cc b/pc/peer_connection_factory.cc
index 6e6a72c..3380358 100644
--- a/pc/peer_connection_factory.cc
+++ b/pc/peer_connection_factory.cc
@@ -129,14 +129,14 @@
   switch (kind) {
     case cricket::MEDIA_TYPE_AUDIO: {
       cricket::AudioCodecs cricket_codecs;
-      channel_manager()->GetSupportedAudioSendCodecs(&cricket_codecs);
+      cricket_codecs = media_engine()->voice().send_codecs();
       return ToRtpCapabilities(
           cricket_codecs,
           channel_manager()->GetDefaultEnabledAudioRtpHeaderExtensions());
     }
     case cricket::MEDIA_TYPE_VIDEO: {
       cricket::VideoCodecs cricket_codecs;
-      channel_manager()->GetSupportedVideoSendCodecs(&cricket_codecs);
+      cricket_codecs = media_engine()->video().send_codecs();
       return ToRtpCapabilities(
           cricket_codecs,
           channel_manager()->GetDefaultEnabledVideoRtpHeaderExtensions());
@@ -156,7 +156,7 @@
   switch (kind) {
     case cricket::MEDIA_TYPE_AUDIO: {
       cricket::AudioCodecs cricket_codecs;
-      channel_manager()->GetSupportedAudioReceiveCodecs(&cricket_codecs);
+      cricket_codecs = media_engine()->voice().recv_codecs();
       return ToRtpCapabilities(
           cricket_codecs,
           channel_manager()->GetDefaultEnabledAudioRtpHeaderExtensions());
@@ -195,6 +195,12 @@
   channel_manager()->StopAecDump();
 }
 
+cricket::MediaEngineInterface* PeerConnectionFactory::media_engine() const {
+  RTC_DCHECK(context_);
+  RTC_DCHECK(context_->channel_manager());
+  return context_->channel_manager()->media_engine();
+}
+
 RTCErrorOr<rtc::scoped_refptr<PeerConnectionInterface>>
 PeerConnectionFactory::CreatePeerConnectionOrError(
     const PeerConnectionInterface::RTCConfiguration& configuration,
@@ -312,7 +318,7 @@
   RTC_DCHECK_RUN_ON(worker_thread());
 
   webrtc::Call::Config call_config(event_log, network_thread());
-  if (!channel_manager()->media_engine() || !context_->call_factory()) {
+  if (!channel_manager() || !context_->call_factory()) {
     return nullptr;
   }
   call_config.audio_state =
diff --git a/pc/peer_connection_factory.h b/pc/peer_connection_factory.h
index ff3d515..917f054 100644
--- a/pc/peer_connection_factory.h
+++ b/pc/peer_connection_factory.h
@@ -120,6 +120,8 @@
     return context_->field_trials();
   }
 
+  cricket::MediaEngineInterface* media_engine() const;
+
  protected:
   // Constructor used by the static Create() method. Modifies the dependencies.
   PeerConnectionFactory(rtc::scoped_refptr<ConnectionContext> context,
diff --git a/pc/rtp_transceiver.cc b/pc/rtp_transceiver.cc
index caa9ba5..1f6f879 100644
--- a/pc/rtp_transceiver.cc
+++ b/pc/rtp_transceiver.cc
@@ -22,6 +22,7 @@
 #include "api/sequence_checker.h"
 #include "media/base/codec.h"
 #include "media/base/media_constants.h"
+#include "media/base/media_engine.h"
 #include "pc/channel.h"
 #include "pc/channel_manager.h"
 #include "pc/rtp_media_utils.h"
@@ -562,7 +563,6 @@
 RTCError RtpTransceiver::SetCodecPreferences(
     rtc::ArrayView<RtpCodecCapability> codec_capabilities) {
   RTC_DCHECK(unified_plan_);
-
   // 3. If codecs is an empty list, set transceiver's [[PreferredCodecs]] slot
   // to codecs and abort these steps.
   if (codec_capabilities.empty()) {
@@ -581,15 +581,14 @@
   RTCError result;
   if (media_type_ == cricket::MEDIA_TYPE_AUDIO) {
     std::vector<cricket::AudioCodec> recv_codecs, send_codecs;
-    channel_manager_->GetSupportedAudioReceiveCodecs(&recv_codecs);
-    channel_manager_->GetSupportedAudioSendCodecs(&send_codecs);
+    recv_codecs = media_engine()->voice().recv_codecs();
+    send_codecs = media_engine()->voice().send_codecs();
 
     result = VerifyCodecPreferences(codecs, send_codecs, recv_codecs);
   } else if (media_type_ == cricket::MEDIA_TYPE_VIDEO) {
     std::vector<cricket::VideoCodec> recv_codecs, send_codecs;
     channel_manager_->GetSupportedVideoReceiveCodecs(&recv_codecs);
     channel_manager_->GetSupportedVideoSendCodecs(&send_codecs);
-
     result = VerifyCodecPreferences(codecs, send_codecs, recv_codecs);
   }
 
@@ -672,4 +671,8 @@
   is_pc_closed_ = true;
 }
 
+cricket::MediaEngineInterface* RtpTransceiver::media_engine() const {
+  return channel_manager_->media_engine();
+}
+
 }  // namespace webrtc
diff --git a/pc/rtp_transceiver.h b/pc/rtp_transceiver.h
index b61ca75..bbcbf23 100644
--- a/pc/rtp_transceiver.h
+++ b/pc/rtp_transceiver.h
@@ -47,6 +47,7 @@
 
 namespace cricket {
 class ChannelManager;
+class MediaEngineInterface;
 }
 
 namespace webrtc {
@@ -338,6 +339,7 @@
       RTC_GUARDED_BY(thread_);
 
   const std::function<void()> on_negotiation_needed_;
+  cricket::MediaEngineInterface* media_engine() const;
 };
 
 BEGIN_PRIMARY_PROXY_MAP(RtpTransceiver)
diff --git a/pc/rtp_transceiver_unittest.cc b/pc/rtp_transceiver_unittest.cc
index ce2eefc..3c4b2c9 100644
--- a/pc/rtp_transceiver_unittest.cc
+++ b/pc/rtp_transceiver_unittest.cc
@@ -126,7 +126,7 @@
                 rtc::Thread::Current(),
                 receiver_),
             &channel_manager_,
-            channel_manager_.GetSupportedAudioRtpHeaderExtensions(),
+            channel_manager_.media_engine()->voice().GetRtpHeaderExtensions(),
             /* on_negotiation_needed= */ [] {})) {}
 
   static rtc::scoped_refptr<MockRtpReceiverInternal> MockReceiver() {
diff --git a/pc/rtp_transmission_manager.cc b/pc/rtp_transmission_manager.cc
index 6939a07..b2520d5 100644
--- a/pc/rtp_transmission_manager.cc
+++ b/pc/rtp_transmission_manager.cc
@@ -273,8 +273,8 @@
       rtc::make_ref_counted<RtpTransceiver>(
           sender, receiver, channel_manager(),
           sender->media_type() == cricket::MEDIA_TYPE_AUDIO
-              ? channel_manager()->GetSupportedAudioRtpHeaderExtensions()
-              : channel_manager()->GetSupportedVideoRtpHeaderExtensions(),
+              ? media_engine()->voice().GetRtpHeaderExtensions()
+              : media_engine()->video().GetRtpHeaderExtensions(),
           [this_weak_ptr = weak_ptr_factory_.GetWeakPtr()]() {
             if (this_weak_ptr) {
               this_weak_ptr->OnNegotiationNeeded();
@@ -690,4 +690,8 @@
   return nullptr;
 }
 
+cricket::MediaEngineInterface* RtpTransmissionManager::media_engine() const {
+  return channel_manager()->media_engine();
+}
+
 }  // namespace webrtc
diff --git a/pc/rtp_transmission_manager.h b/pc/rtp_transmission_manager.h
index 6e25f2d..532adcf 100644
--- a/pc/rtp_transmission_manager.h
+++ b/pc/rtp_transmission_manager.h
@@ -244,6 +244,8 @@
   PeerConnectionObserver* Observer() const;
   void OnNegotiationNeeded();
 
+  cricket::MediaEngineInterface* media_engine() const;
+
   TransceiverList transceivers_;
 
   // These lists store sender info seen in local/remote descriptions.
diff --git a/pc/sdp_offer_answer.cc b/pc/sdp_offer_answer.cc
index 1d34081..95f22b7 100644
--- a/pc/sdp_offer_answer.cc
+++ b/pc/sdp_offer_answer.cc
@@ -1258,10 +1258,17 @@
 }
 
 // ==================================================================
-// Access to pc_ variables
+// Access to pc_ and context_ variables
 cricket::ChannelManager* SdpOfferAnswerHandler::channel_manager() const {
   return context_->channel_manager();
 }
+
+cricket::MediaEngineInterface* SdpOfferAnswerHandler::media_engine() const {
+  RTC_DCHECK(context_);
+  RTC_DCHECK(context_->channel_manager());
+  return context_->channel_manager()->media_engine();
+}
+
 TransceiverList* SdpOfferAnswerHandler::transceivers() {
   if (!pc_->rtp_manager()) {
     return nullptr;
@@ -1557,56 +1564,60 @@
                         << ")";
       return error;
     }
-    std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> remove_list;
-    std::vector<rtc::scoped_refptr<MediaStreamInterface>> removed_streams;
-    for (const auto& transceiver_ext : transceivers()->List()) {
-      auto transceiver = transceiver_ext->internal();
-      if (transceiver->stopped()) {
-        continue;
-      }
-
-      // 2.2.7.1.1.(6-9): Set sender and receiver's transport slots.
-      // Note that code paths that don't set MID won't be able to use
-      // information about DTLS transports.
-      if (transceiver->mid()) {
-        auto dtls_transport = LookupDtlsTransportByMid(
-            context_->network_thread(), transport_controller_s(),
-            *transceiver->mid());
-        transceiver->sender_internal()->set_transport(dtls_transport);
-        transceiver->receiver_internal()->set_transport(dtls_transport);
-      }
-
-      const ContentInfo* content =
-          FindMediaSectionForTransceiver(transceiver, local_description());
-      if (!content) {
-        continue;
-      }
-      const MediaContentDescription* media_desc = content->media_description();
-      // 2.2.7.1.6: If description is of type "answer" or "pranswer", then run
-      // the following steps:
-      if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
-        // 2.2.7.1.6.1: If direction is "sendonly" or "inactive", and
-        // transceiver's [[FiredDirection]] slot is either "sendrecv" or
-        // "recvonly", process the removal of a remote track for the media
-        // description, given transceiver, removeList, and muteTracks.
-        if (!RtpTransceiverDirectionHasRecv(media_desc->direction()) &&
-            (transceiver->fired_direction() &&
-             RtpTransceiverDirectionHasRecv(*transceiver->fired_direction()))) {
-          ProcessRemovalOfRemoteTrack(transceiver_ext, &remove_list,
-                                      &removed_streams);
+    if (ConfiguredForMedia()) {
+      std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> remove_list;
+      std::vector<rtc::scoped_refptr<MediaStreamInterface>> removed_streams;
+      for (const auto& transceiver_ext : transceivers()->List()) {
+        auto transceiver = transceiver_ext->internal();
+        if (transceiver->stopped()) {
+          continue;
         }
-        // 2.2.7.1.6.2: Set transceiver's [[CurrentDirection]] and
-        // [[FiredDirection]] slots to direction.
-        transceiver->set_current_direction(media_desc->direction());
-        transceiver->set_fired_direction(media_desc->direction());
+
+        // 2.2.7.1.1.(6-9): Set sender and receiver's transport slots.
+        // Note that code paths that don't set MID won't be able to use
+        // information about DTLS transports.
+        if (transceiver->mid()) {
+          auto dtls_transport = LookupDtlsTransportByMid(
+              context_->network_thread(), transport_controller_s(),
+              *transceiver->mid());
+          transceiver->sender_internal()->set_transport(dtls_transport);
+          transceiver->receiver_internal()->set_transport(dtls_transport);
+        }
+
+        const ContentInfo* content =
+            FindMediaSectionForTransceiver(transceiver, local_description());
+        if (!content) {
+          continue;
+        }
+        const MediaContentDescription* media_desc =
+            content->media_description();
+        // 2.2.7.1.6: If description is of type "answer" or "pranswer", then run
+        // the following steps:
+        if (type == SdpType::kPrAnswer || type == SdpType::kAnswer) {
+          // 2.2.7.1.6.1: If direction is "sendonly" or "inactive", and
+          // transceiver's [[FiredDirection]] slot is either "sendrecv" or
+          // "recvonly", process the removal of a remote track for the media
+          // description, given transceiver, removeList, and muteTracks.
+          if (!RtpTransceiverDirectionHasRecv(media_desc->direction()) &&
+              (transceiver->fired_direction() &&
+               RtpTransceiverDirectionHasRecv(
+                   *transceiver->fired_direction()))) {
+            ProcessRemovalOfRemoteTrack(transceiver_ext, &remove_list,
+                                        &removed_streams);
+          }
+          // 2.2.7.1.6.2: Set transceiver's [[CurrentDirection]] and
+          // [[FiredDirection]] slots to direction.
+          transceiver->set_current_direction(media_desc->direction());
+          transceiver->set_fired_direction(media_desc->direction());
+        }
       }
-    }
-    auto observer = pc_->Observer();
-    for (const auto& transceiver : remove_list) {
-      observer->OnRemoveTrack(transceiver->receiver());
-    }
-    for (const auto& stream : removed_streams) {
-      observer->OnRemoveStream(stream);
+      auto observer = pc_->Observer();
+      for (const auto& transceiver : remove_list) {
+        observer->OnRemoveTrack(transceiver->receiver());
+      }
+      for (const auto& stream : removed_streams) {
+        observer->OnRemoveStream(stream);
+      }
     }
   } else {
     // Media channels will be created only when offer is set. These may use new
@@ -1650,35 +1661,39 @@
   }
 
   if (IsUnifiedPlan()) {
-    // We must use List and not ListInternal here because
-    // transceivers()->StableState() is indexed by the non-internal refptr.
-    for (const auto& transceiver_ext : transceivers()->List()) {
-      auto transceiver = transceiver_ext->internal();
-      if (transceiver->stopped()) {
-        continue;
-      }
-      const ContentInfo* content =
-          FindMediaSectionForTransceiver(transceiver, local_description());
-      if (!content) {
-        continue;
-      }
-      cricket::ChannelInterface* channel = transceiver->channel();
-      if (content->rejected || !channel || channel->local_streams().empty()) {
-        // 0 is a special value meaning "this sender has no associated send
-        // stream". Need to call this so the sender won't attempt to configure
-        // a no longer existing stream and run into DCHECKs in the lower
-        // layers.
-        transceiver->sender_internal()->SetSsrc(0);
-      } else {
-        // Get the StreamParams from the channel which could generate SSRCs.
-        const std::vector<StreamParams>& streams = channel->local_streams();
-        transceiver->sender_internal()->set_stream_ids(streams[0].stream_ids());
-        auto encodings = transceiver->sender_internal()->init_send_encodings();
-        transceiver->sender_internal()->SetSsrc(streams[0].first_ssrc());
-        if (!encodings.empty()) {
-          transceivers()
-              ->StableState(transceiver_ext)
-              ->SetInitSendEncodings(encodings);
+    if (ConfiguredForMedia()) {
+      // We must use List and not ListInternal here because
+      // transceivers()->StableState() is indexed by the non-internal refptr.
+      for (const auto& transceiver_ext : transceivers()->List()) {
+        auto transceiver = transceiver_ext->internal();
+        if (transceiver->stopped()) {
+          continue;
+        }
+        const ContentInfo* content =
+            FindMediaSectionForTransceiver(transceiver, local_description());
+        if (!content) {
+          continue;
+        }
+        cricket::ChannelInterface* channel = transceiver->channel();
+        if (content->rejected || !channel || channel->local_streams().empty()) {
+          // 0 is a special value meaning "this sender has no associated send
+          // stream". Need to call this so the sender won't attempt to configure
+          // a no longer existing stream and run into DCHECKs in the lower
+          // layers.
+          transceiver->sender_internal()->SetSsrc(0);
+        } else {
+          // Get the StreamParams from the channel which could generate SSRCs.
+          const std::vector<StreamParams>& streams = channel->local_streams();
+          transceiver->sender_internal()->set_stream_ids(
+              streams[0].stream_ids());
+          auto encodings =
+              transceiver->sender_internal()->init_send_encodings();
+          transceiver->sender_internal()->SetSsrc(streams[0].first_ssrc());
+          if (!encodings.empty()) {
+            transceivers()
+                ->StableState(transceiver_ext)
+                ->SetInitSendEncodings(encodings);
+          }
         }
       }
     }
@@ -1938,6 +1953,9 @@
     SdpType sdp_type) {
   RTC_DCHECK_RUN_ON(signaling_thread());
   RTC_DCHECK(IsUnifiedPlan());
+  if (!ConfiguredForMedia()) {
+    return;
+  }
   std::vector<rtc::scoped_refptr<RtpTransceiverInterface>>
       now_receiving_transceivers;
   std::vector<rtc::scoped_refptr<RtpTransceiverInterface>> remove_list;
@@ -2725,7 +2743,9 @@
   } else {
     RTC_DCHECK(type == SdpType::kAnswer);
     ChangeSignalingState(PeerConnectionInterface::kStable);
-    transceivers()->DiscardStableStates();
+    if (ConfiguredForMedia()) {
+      transceivers()->DiscardStableStates();
+    }
   }
 
   // Update internal objects according to the session description's media
@@ -3151,6 +3171,9 @@
     if (!cricket::GetFirstDataContent(description->description()->contents()))
       return true;
   }
+  if (!ConfiguredForMedia()) {
+    return false;
+  }
 
   // 5. For each transceiver in connection's set of transceivers, perform the
   // following checks:
@@ -3262,7 +3285,6 @@
       }
     }
   }
-
   // If all the preceding checks were performed and true was not returned,
   // nothing remains to be negotiated; return false.
   return false;
@@ -3841,38 +3863,43 @@
 void SdpOfferAnswerHandler::GetOptionsForPlanBOffer(
     const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
     cricket::MediaSessionOptions* session_options) {
-  // Figure out transceiver directional preferences.
-  bool send_audio =
-      !rtp_manager()->GetAudioTransceiver()->internal()->senders().empty();
-  bool send_video =
-      !rtp_manager()->GetVideoTransceiver()->internal()->senders().empty();
+  bool offer_new_data_description =
+      data_channel_controller()->HasDataChannels();
+  bool send_audio = false;
+  bool send_video = false;
+  bool recv_audio = false;
+  bool recv_video = false;
+  if (ConfiguredForMedia()) {
+    // Figure out transceiver directional preferences.
+    send_audio =
+        !rtp_manager()->GetAudioTransceiver()->internal()->senders().empty();
+    send_video =
+        !rtp_manager()->GetVideoTransceiver()->internal()->senders().empty();
 
-  // By default, generate sendrecv/recvonly m= sections.
-  bool recv_audio = true;
-  bool recv_video = true;
-
+    // By default, generate sendrecv/recvonly m= sections.
+    recv_audio = true;
+    recv_video = true;
+  }
   // By default, only offer a new m= section if we have media to send with it.
   bool offer_new_audio_description = send_audio;
   bool offer_new_video_description = send_video;
-  bool offer_new_data_description =
-      data_channel_controller()->HasDataChannels();
-
-  // The "offer_to_receive_X" options allow those defaults to be overridden.
-  if (offer_answer_options.offer_to_receive_audio !=
-      PeerConnectionInterface::RTCOfferAnswerOptions::kUndefined) {
-    recv_audio = (offer_answer_options.offer_to_receive_audio > 0);
-    offer_new_audio_description =
-        offer_new_audio_description ||
-        (offer_answer_options.offer_to_receive_audio > 0);
+  if (ConfiguredForMedia()) {
+    // The "offer_to_receive_X" options allow those defaults to be overridden.
+    if (offer_answer_options.offer_to_receive_audio !=
+        PeerConnectionInterface::RTCOfferAnswerOptions::kUndefined) {
+      recv_audio = (offer_answer_options.offer_to_receive_audio > 0);
+      offer_new_audio_description =
+          offer_new_audio_description ||
+          (offer_answer_options.offer_to_receive_audio > 0);
+    }
+    if (offer_answer_options.offer_to_receive_video !=
+        RTCOfferAnswerOptions::kUndefined) {
+      recv_video = (offer_answer_options.offer_to_receive_video > 0);
+      offer_new_video_description =
+          offer_new_video_description ||
+          (offer_answer_options.offer_to_receive_video > 0);
+    }
   }
-  if (offer_answer_options.offer_to_receive_video !=
-      RTCOfferAnswerOptions::kUndefined) {
-    recv_video = (offer_answer_options.offer_to_receive_video > 0);
-    offer_new_video_description =
-        offer_new_video_description ||
-        (offer_answer_options.offer_to_receive_video > 0);
-  }
-
   absl::optional<size_t> audio_index;
   absl::optional<size_t> video_index;
   absl::optional<size_t> data_index;
@@ -3887,42 +3914,44 @@
         &audio_index, &video_index, &data_index, session_options);
   }
 
-  // Add audio/video/data m= sections to the end if needed.
-  if (!audio_index && offer_new_audio_description) {
-    cricket::MediaDescriptionOptions options(
-        cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO,
-        RtpTransceiverDirectionFromSendRecv(send_audio, recv_audio), false);
-    options.header_extensions =
-        channel_manager()->GetSupportedAudioRtpHeaderExtensions();
-    session_options->media_description_options.push_back(options);
-    audio_index = session_options->media_description_options.size() - 1;
-  }
-  if (!video_index && offer_new_video_description) {
-    cricket::MediaDescriptionOptions options(
-        cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO,
-        RtpTransceiverDirectionFromSendRecv(send_video, recv_video), false);
-    options.header_extensions =
-        channel_manager()->GetSupportedVideoRtpHeaderExtensions();
-    session_options->media_description_options.push_back(options);
-    video_index = session_options->media_description_options.size() - 1;
+  if (ConfiguredForMedia()) {
+    // Add audio/video/data m= sections to the end if needed.
+    if (!audio_index && offer_new_audio_description) {
+      cricket::MediaDescriptionOptions options(
+          cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO,
+          RtpTransceiverDirectionFromSendRecv(send_audio, recv_audio), false);
+      options.header_extensions =
+          media_engine()->voice().GetRtpHeaderExtensions();
+      session_options->media_description_options.push_back(options);
+      audio_index = session_options->media_description_options.size() - 1;
+    }
+    if (!video_index && offer_new_video_description) {
+      cricket::MediaDescriptionOptions options(
+          cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO,
+          RtpTransceiverDirectionFromSendRecv(send_video, recv_video), false);
+      options.header_extensions =
+          media_engine()->video().GetRtpHeaderExtensions();
+      session_options->media_description_options.push_back(options);
+      video_index = session_options->media_description_options.size() - 1;
+    }
+    cricket::MediaDescriptionOptions* audio_media_description_options =
+        !audio_index
+            ? nullptr
+            : &session_options->media_description_options[*audio_index];
+    cricket::MediaDescriptionOptions* video_media_description_options =
+        !video_index
+            ? nullptr
+            : &session_options->media_description_options[*video_index];
+
+    AddPlanBRtpSenderOptions(rtp_manager()->GetSendersInternal(),
+                             audio_media_description_options,
+                             video_media_description_options,
+                             offer_answer_options.num_simulcast_layers);
   }
   if (!data_index && offer_new_data_description) {
     session_options->media_description_options.push_back(
         GetMediaDescriptionOptionsForActiveData(cricket::CN_DATA));
-    data_index = session_options->media_description_options.size() - 1;
   }
-
-  cricket::MediaDescriptionOptions* audio_media_description_options =
-      !audio_index ? nullptr
-                   : &session_options->media_description_options[*audio_index];
-  cricket::MediaDescriptionOptions* video_media_description_options =
-      !video_index ? nullptr
-                   : &session_options->media_description_options[*video_index];
-
-  AddPlanBRtpSenderOptions(rtp_manager()->GetSendersInternal(),
-                           audio_media_description_options,
-                           video_media_description_options,
-                           offer_answer_options.num_simulcast_layers);
 }
 
 void SdpOfferAnswerHandler::GetOptionsForUnifiedPlanOffer(
@@ -4028,27 +4057,29 @@
   // 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()->ListInternal()) {
-    if (transceiver->mid() || transceiver->stopping()) {
-      continue;
+  if (ConfiguredForMedia()) {
+    for (const auto& transceiver : transceivers()->ListInternal()) {
+      if (transceiver->mid() || transceiver->stopping()) {
+        continue;
+      }
+      size_t mline_index;
+      if (!recycleable_mline_indices.empty()) {
+        mline_index = recycleable_mline_indices.front();
+        recycleable_mline_indices.pop();
+        session_options->media_description_options[mline_index] =
+            GetMediaDescriptionOptionsForTransceiver(
+                transceiver, mid_generator_.GenerateString(),
+                /*is_create_offer=*/true);
+      } else {
+        mline_index = session_options->media_description_options.size();
+        session_options->media_description_options.push_back(
+            GetMediaDescriptionOptionsForTransceiver(
+                transceiver, mid_generator_.GenerateString(),
+                /*is_create_offer=*/true));
+      }
+      // See comment above for why CreateOffer changes the transceiver's state.
+      transceiver->set_mline_index(mline_index);
     }
-    size_t mline_index;
-    if (!recycleable_mline_indices.empty()) {
-      mline_index = recycleable_mline_indices.front();
-      recycleable_mline_indices.pop();
-      session_options->media_description_options[mline_index] =
-          GetMediaDescriptionOptionsForTransceiver(
-              transceiver, mid_generator_.GenerateString(),
-              /*is_create_offer=*/true);
-    } else {
-      mline_index = session_options->media_description_options.size();
-      session_options->media_description_options.push_back(
-          GetMediaDescriptionOptionsForTransceiver(
-              transceiver, mid_generator_.GenerateString(),
-              /*is_create_offer=*/true));
-    }
-    // See comment above for why CreateOffer changes the transceiver's state.
-    transceiver->set_mline_index(mline_index);
   }
   // Lastly, add a m-section if we have local data channels and an m section
   // does not already exist.
@@ -4088,25 +4119,32 @@
 void SdpOfferAnswerHandler::GetOptionsForPlanBAnswer(
     const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
     cricket::MediaSessionOptions* session_options) {
-  // Figure out transceiver directional preferences.
-  bool send_audio =
-      !rtp_manager()->GetAudioTransceiver()->internal()->senders().empty();
-  bool send_video =
-      !rtp_manager()->GetVideoTransceiver()->internal()->senders().empty();
+  bool send_audio = false;
+  bool recv_audio = false;
+  bool send_video = false;
+  bool recv_video = false;
 
-  // By default, generate sendrecv/recvonly m= sections. The direction is also
-  // restricted by the direction in the offer.
-  bool recv_audio = true;
-  bool recv_video = true;
+  if (ConfiguredForMedia()) {
+    // Figure out transceiver directional preferences.
+    send_audio =
+        !rtp_manager()->GetAudioTransceiver()->internal()->senders().empty();
+    send_video =
+        !rtp_manager()->GetVideoTransceiver()->internal()->senders().empty();
 
-  // The "offer_to_receive_X" options allow those defaults to be overridden.
-  if (offer_answer_options.offer_to_receive_audio !=
-      RTCOfferAnswerOptions::kUndefined) {
-    recv_audio = (offer_answer_options.offer_to_receive_audio > 0);
-  }
-  if (offer_answer_options.offer_to_receive_video !=
-      RTCOfferAnswerOptions::kUndefined) {
-    recv_video = (offer_answer_options.offer_to_receive_video > 0);
+    // By default, generate sendrecv/recvonly m= sections. The direction is also
+    // restricted by the direction in the offer.
+    recv_audio = true;
+    recv_video = true;
+
+    // The "offer_to_receive_X" options allow those defaults to be overridden.
+    if (offer_answer_options.offer_to_receive_audio !=
+        RTCOfferAnswerOptions::kUndefined) {
+      recv_audio = (offer_answer_options.offer_to_receive_audio > 0);
+    }
+    if (offer_answer_options.offer_to_receive_video !=
+        RTCOfferAnswerOptions::kUndefined) {
+      recv_video = (offer_answer_options.offer_to_receive_video > 0);
+    }
   }
 
   absl::optional<size_t> audio_index;
@@ -4129,10 +4167,12 @@
       !video_index ? nullptr
                    : &session_options->media_description_options[*video_index];
 
-  AddPlanBRtpSenderOptions(rtp_manager()->GetSendersInternal(),
-                           audio_media_description_options,
-                           video_media_description_options,
-                           offer_answer_options.num_simulcast_layers);
+  if (ConfiguredForMedia()) {
+    AddPlanBRtpSenderOptions(rtp_manager()->GetSendersInternal(),
+                             audio_media_description_options,
+                             video_media_description_options,
+                             offer_answer_options.num_simulcast_layers);
+  }
 }
 
 void SdpOfferAnswerHandler::GetOptionsForUnifiedPlanAnswer(
@@ -4477,6 +4517,9 @@
 void SdpOfferAnswerHandler::EnableSending() {
   TRACE_EVENT0("webrtc", "SdpOfferAnswerHandler::EnableSending");
   RTC_DCHECK_RUN_ON(signaling_thread());
+  if (!ConfiguredForMedia()) {
+    return;
+  }
   for (const auto& transceiver : transceivers()->ListInternal()) {
     cricket::ChannelInterface* channel = transceiver->channel();
     if (channel) {
@@ -4497,60 +4540,63 @@
   RTC_DCHECK_RUN_ON(signaling_thread());
   RTC_DCHECK(sdesc);
 
-  // Note: This will perform an Invoke over to the worker thread, which we'll
-  // also do in a loop below.
-  if (!UpdatePayloadTypeDemuxingState(source, bundle_groups_by_mid)) {
-    // Note that this is never expected to fail, since RtpDemuxer doesn't return
-    // an error when changing payload type demux criteria, which is all this
-    // does.
-    return RTCError(RTCErrorType::INTERNAL_ERROR,
-                    "Failed to update payload type demuxing state.");
-  }
-
-  // Push down the new SDP media section for each audio/video transceiver.
-  auto rtp_transceivers = transceivers()->ListInternal();
-  std::vector<
-      std::pair<cricket::ChannelInterface*, const MediaContentDescription*>>
-      channels;
-  for (const auto& transceiver : rtp_transceivers) {
-    const ContentInfo* content_info =
-        FindMediaSectionForTransceiver(transceiver, sdesc);
-    cricket::ChannelInterface* channel = transceiver->channel();
-    if (!channel || !content_info || content_info->rejected) {
-      continue;
-    }
-    const MediaContentDescription* content_desc =
-        content_info->media_description();
-    if (!content_desc) {
-      continue;
+  if (ConfiguredForMedia()) {
+    // Note: This will perform an Invoke over to the worker thread, which we'll
+    // also do in a loop below.
+    if (!UpdatePayloadTypeDemuxingState(source, bundle_groups_by_mid)) {
+      // Note that this is never expected to fail, since RtpDemuxer doesn't
+      // return an error when changing payload type demux criteria, which is all
+      // this does.
+      return RTCError(RTCErrorType::INTERNAL_ERROR,
+                      "Failed to update payload type demuxing state.");
     }
 
-    transceiver->OnNegotiationUpdate(type, content_desc);
-    channels.push_back(std::make_pair(channel, content_desc));
-  }
+    // Push down the new SDP media section for each audio/video transceiver.
+    auto rtp_transceivers = transceivers()->ListInternal();
+    std::vector<
+        std::pair<cricket::ChannelInterface*, const MediaContentDescription*>>
+        channels;
+    for (const auto& transceiver : rtp_transceivers) {
+      const ContentInfo* content_info =
+          FindMediaSectionForTransceiver(transceiver, sdesc);
+      cricket::ChannelInterface* channel = transceiver->channel();
+      if (!channel || !content_info || content_info->rejected) {
+        continue;
+      }
+      const MediaContentDescription* content_desc =
+          content_info->media_description();
+      if (!content_desc) {
+        continue;
+      }
 
-  // This for-loop of invokes helps audio impairment during re-negotiations.
-  // One of the causes is that downstairs decoder creation is synchronous at the
-  // moment, and that a decoder is created for each codec listed in the SDP.
-  //
-  // TODO(bugs.webrtc.org/12840): consider merging the invokes again after
-  // these projects have shipped:
-  // - bugs.webrtc.org/12462
-  // - crbug.com/1157227
-  // - crbug.com/1187289
-  for (const auto& entry : channels) {
-    std::string error;
-    bool success =
-        context_->worker_thread()->Invoke<bool>(RTC_FROM_HERE, [&]() {
-          return (source == cricket::CS_LOCAL)
-                     ? entry.first->SetLocalContent(entry.second, type, error)
-                     : entry.first->SetRemoteContent(entry.second, type, error);
-        });
-    if (!success) {
-      return RTCError(RTCErrorType::INVALID_PARAMETER, error);
+      transceiver->OnNegotiationUpdate(type, content_desc);
+      channels.push_back(std::make_pair(channel, content_desc));
+    }
+
+    // This for-loop of invokes helps audio impairment during re-negotiations.
+    // One of the causes is that downstairs decoder creation is synchronous at
+    // the moment, and that a decoder is created for each codec listed in the
+    // SDP.
+    //
+    // TODO(bugs.webrtc.org/12840): consider merging the invokes again after
+    // these projects have shipped:
+    // - bugs.webrtc.org/12462
+    // - crbug.com/1157227
+    // - crbug.com/1187289
+    for (const auto& entry : channels) {
+      std::string error;
+      bool success =
+          context_->worker_thread()->Invoke<bool>(RTC_FROM_HERE, [&]() {
+            return (source == cricket::CS_LOCAL)
+                       ? entry.first->SetLocalContent(entry.second, type, error)
+                       : entry.first->SetRemoteContent(entry.second, type,
+                                                       error);
+          });
+      if (!success) {
+        return RTCError(RTCErrorType::INVALID_PARAMETER, error);
+      }
     }
   }
-
   // Need complete offer/answer with an SCTP m= section before starting SCTP,
   // according to https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-19
   if (pc_->sctp_mid() && local_description() && remote_description()) {
@@ -4604,6 +4650,9 @@
   //           run the following steps:
   if (!IsUnifiedPlan())
     return;
+  if (!ConfiguredForMedia()) {
+    return;
+  }
   // Traverse a copy of the transceiver list.
   auto transceiver_list = transceivers()->List();
   for (auto transceiver : transceiver_list) {
@@ -4638,18 +4687,21 @@
 void SdpOfferAnswerHandler::RemoveUnusedChannels(
     const SessionDescription* desc) {
   RTC_DCHECK_RUN_ON(signaling_thread());
-  // Destroy video channel first since it may have a pointer to the
-  // voice channel.
-  const cricket::ContentInfo* video_info = cricket::GetFirstVideoContent(desc);
-  if (!video_info || video_info->rejected) {
-    rtp_manager()->GetVideoTransceiver()->internal()->ClearChannel();
-  }
+  if (ConfiguredForMedia()) {
+    // Destroy video channel first since it may have a pointer to the
+    // voice channel.
+    const cricket::ContentInfo* video_info =
+        cricket::GetFirstVideoContent(desc);
+    if (!video_info || video_info->rejected) {
+      rtp_manager()->GetVideoTransceiver()->internal()->ClearChannel();
+    }
 
-  const cricket::ContentInfo* audio_info = cricket::GetFirstAudioContent(desc);
-  if (!audio_info || audio_info->rejected) {
-    rtp_manager()->GetAudioTransceiver()->internal()->ClearChannel();
+    const cricket::ContentInfo* audio_info =
+        cricket::GetFirstAudioContent(desc);
+    if (!audio_info || audio_info->rejected) {
+      rtp_manager()->GetAudioTransceiver()->internal()->ClearChannel();
+    }
   }
-
   const cricket::ContentInfo* data_info = cricket::GetFirstDataContent(desc);
   if (!data_info) {
     RTCError error(RTCErrorType::OPERATION_ERROR_WITH_DATA,
@@ -4967,7 +5019,7 @@
         *audio_index = session_options->media_description_options.size() - 1;
       }
       session_options->media_description_options.back().header_extensions =
-          channel_manager()->GetSupportedAudioRtpHeaderExtensions();
+          media_engine()->voice().GetRtpHeaderExtensions();
     } else if (IsVideoContent(&content)) {
       // If we already have an video m= section, reject this extra one.
       if (*video_index) {
@@ -4984,7 +5036,7 @@
         *video_index = session_options->media_description_options.size() - 1;
       }
       session_options->media_description_options.back().header_extensions =
-          channel_manager()->GetSupportedVideoRtpHeaderExtensions();
+          media_engine()->video().GetRtpHeaderExtensions();
     } else if (IsUnsupportedContent(&content)) {
       session_options->media_description_options.push_back(
           cricket::MediaDescriptionOptions(cricket::MEDIA_TYPE_UNSUPPORTED,
@@ -5208,4 +5260,8 @@
       });
 }
 
+bool SdpOfferAnswerHandler::ConfiguredForMedia() const {
+  return context_->channel_manager();
+}
+
 }  // namespace webrtc
diff --git a/pc/sdp_offer_answer.h b/pc/sdp_offer_answer.h
index de814ec..6a0aaaa 100644
--- a/pc/sdp_offer_answer.h
+++ b/pc/sdp_offer_answer.h
@@ -478,7 +478,7 @@
   // This enables media to flow on all configured audio/video channels.
   void EnableSending();
   // Push the media parts of the local or remote session description
-  // down to all of the channels.
+  // down to all of the channels, and start SCTP if needed.
   RTCError PushdownMediaDescription(
       SdpType type,
       cricket::ContentSource source,
@@ -576,6 +576,7 @@
   // ==================================================================
   // Access to pc_ variables
   cricket::ChannelManager* channel_manager() const;
+  cricket::MediaEngineInterface* media_engine() const;
   TransceiverList* transceivers();
   const TransceiverList* transceivers() const;
   DataChannelController* data_channel_controller();
@@ -595,6 +596,7 @@
   // ===================================================================
   const cricket::AudioOptions& audio_options() { return audio_options_; }
   const cricket::VideoOptions& video_options() { return video_options_; }
+  bool ConfiguredForMedia() const;
 
   PeerConnectionSdpMethods* const pc_;
   ConnectionContext* const context_;
diff --git a/pc/test/fake_peer_connection_for_stats.h b/pc/test/fake_peer_connection_for_stats.h
index e612f0d..e7ed804 100644
--- a/pc/test/fake_peer_connection_for_stats.h
+++ b/pc/test/fake_peer_connection_for_stats.h
@@ -415,7 +415,10 @@
   class TestChannelManager : public cricket::ChannelManager {
    public:
     TestChannelManager(rtc::Thread* worker, rtc::Thread* network)
-        : cricket::ChannelManager(nullptr, true, worker, network) {}
+        : cricket::ChannelManager(std::make_unique<cricket::FakeMediaEngine>(),
+                                  true,
+                                  worker,
+                                  network) {}
   };
 
   rtc::Thread* const network_thread_;