Move more functions from PeerConnection to SdpOfferAnswer

These are functions that are called only from SdpOfferAnswer,
or that logically belong in the SdpOfferAnswer class.

Bug: webrtc:11995
Change-Id: I92136ee84e20e50957814c21b041ca152a2acca4
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/186268
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Björn Terelius <terelius@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#32271}
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index 2b258cd..4196359 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -149,30 +149,6 @@
   return true;
 }
 
-// Add options to |[audio/video]_media_description_options| from |senders|.
-void AddPlanBRtpSenderOptions(
-    const std::vector<rtc::scoped_refptr<
-        RtpSenderProxyWithInternal<RtpSenderInternal>>>& senders,
-    cricket::MediaDescriptionOptions* audio_media_description_options,
-    cricket::MediaDescriptionOptions* video_media_description_options,
-    int num_sim_layers) {
-  for (const auto& sender : senders) {
-    if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) {
-      if (audio_media_description_options) {
-        audio_media_description_options->AddAudioSender(
-            sender->id(), sender->internal()->stream_ids());
-      }
-    } else {
-      RTC_DCHECK(sender->media_type() == cricket::MEDIA_TYPE_VIDEO);
-      if (video_media_description_options) {
-        video_media_description_options->AddVideoSender(
-            sender->id(), sender->internal()->stream_ids(), {},
-            SimulcastLayerList(), num_sim_layers);
-      }
-    }
-  }
-}
-
 // Add options to |session_options| from |rtp_data_channels|.
 void AddRtpDataChannelOptions(
     const std::map<std::string, rtc::scoped_refptr<RtpDataChannel>>&
@@ -445,17 +421,6 @@
   return cname;
 }
 
-// From |rtc_options|, fill parts of |session_options| shared by all generated
-// m= sectionss (in other words, nothing that involves a map/array).
-void ExtractSharedMediaSessionOptions(
-    const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
-    cricket::MediaSessionOptions* session_options) {
-  session_options->vad_enabled = rtc_options.voice_activity_detection;
-  session_options->bundle_enabled = rtc_options.use_rtp_mux;
-  session_options->raw_packetization_for_video =
-      rtc_options.raw_packetization_for_video;
-}
-
 PeerConnection::PeerConnection(PeerConnectionFactory* factory,
                                std::unique_ptr<RtcEventLog> event_log,
                                std::unique_ptr<Call> call)
@@ -1348,6 +1313,7 @@
 
 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()) {
@@ -1698,9 +1664,11 @@
       continue;
     }
     const ContentInfo* local_content =
-        FindMediaSectionForTransceiver(transceiver, local_description());
+        sdp_handler_.FindMediaSectionForTransceiver(transceiver,
+                                                    local_description());
     const ContentInfo* remote_content =
-        FindMediaSectionForTransceiver(transceiver, remote_description());
+        sdp_handler_.FindMediaSectionForTransceiver(transceiver,
+                                                    remote_description());
     if ((local_content && local_content->rejected) ||
         (remote_content && remote_content->rejected)) {
       RTC_LOG(LS_INFO) << "Dissociating transceiver"
@@ -1721,64 +1689,6 @@
   }
 }
 
-// The SDP parser used to populate these values by default for the 'content
-// name' if an a=mid line was absent.
-static absl::string_view GetDefaultMidForPlanB(cricket::MediaType media_type) {
-  switch (media_type) {
-    case cricket::MEDIA_TYPE_AUDIO:
-      return cricket::CN_AUDIO;
-    case cricket::MEDIA_TYPE_VIDEO:
-      return cricket::CN_VIDEO;
-    case cricket::MEDIA_TYPE_DATA:
-      return cricket::CN_DATA;
-  }
-  RTC_NOTREACHED();
-  return "";
-}
-
-void PeerConnection::FillInMissingRemoteMids(
-    cricket::SessionDescription* new_remote_description) {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  RTC_DCHECK(new_remote_description);
-  const cricket::ContentInfos no_infos;
-  const cricket::ContentInfos& local_contents =
-      (local_description() ? local_description()->description()->contents()
-                           : no_infos);
-  const cricket::ContentInfos& remote_contents =
-      (remote_description() ? remote_description()->description()->contents()
-                            : no_infos);
-  for (size_t i = 0; i < new_remote_description->contents().size(); ++i) {
-    cricket::ContentInfo& content = new_remote_description->contents()[i];
-    if (!content.name.empty()) {
-      continue;
-    }
-    std::string new_mid;
-    absl::string_view source_explanation;
-    if (IsUnifiedPlan()) {
-      if (i < local_contents.size()) {
-        new_mid = local_contents[i].name;
-        source_explanation = "from the matching local media section";
-      } else if (i < remote_contents.size()) {
-        new_mid = remote_contents[i].name;
-        source_explanation = "from the matching previous remote media section";
-      } else {
-        new_mid = mid_generator_();
-        source_explanation = "generated just now";
-      }
-    } else {
-      new_mid = std::string(
-          GetDefaultMidForPlanB(content.media_description()->type()));
-      source_explanation = "to match pre-existing behavior";
-    }
-    RTC_DCHECK(!new_mid.empty());
-    content.name = new_mid;
-    new_remote_description->transport_infos()[i].content_name = new_mid;
-    RTC_LOG(LS_INFO) << "SetRemoteDescription: Remote media section at i=" << i
-                     << " is missing an a=mid line. Filling in the value '"
-                     << new_mid << "' " << source_explanation << ".";
-  }
-}
-
 void PeerConnection::SetRemoteDescription(
     SetSessionDescriptionObserver* observer,
     SessionDescriptionInterface* desc_ptr) {
@@ -1839,48 +1749,6 @@
   return transceivers_.FindByMLineIndex(mline_index);
 }
 
-rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-PeerConnection::FindAvailableTransceiverToReceive(
-    cricket::MediaType media_type) const {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  RTC_DCHECK(IsUnifiedPlan());
-  // From JSEP section 5.10 (Applying a Remote Description):
-  // If the m= section is sendrecv or recvonly, and there are RtpTransceivers of
-  // 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()) {
-    if (transceiver->media_type() == media_type &&
-        transceiver->internal()->created_by_addtrack() && !transceiver->mid() &&
-        !transceiver->stopped()) {
-      return transceiver;
-    }
-  }
-  return nullptr;
-}
-
-const cricket::ContentInfo* PeerConnection::FindMediaSectionForTransceiver(
-    rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-        transceiver,
-    const SessionDescriptionInterface* sdesc) const {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  RTC_DCHECK(transceiver);
-  RTC_DCHECK(sdesc);
-  if (IsUnifiedPlan()) {
-    if (!transceiver->internal()->mid()) {
-      // This transceiver is not associated with a media section yet.
-      return nullptr;
-    }
-    return sdesc->description()->GetContentByName(
-        *transceiver->internal()->mid());
-  } else {
-    // Plan B only allows at most one audio and one video section, so use the
-    // first media section of that type.
-    return cricket::GetFirstMediaContent(sdesc->description()->contents(),
-                                         transceiver->media_type());
-  }
-}
-
 PeerConnectionInterface::RTCConfiguration PeerConnection::GetConfiguration() {
   RTC_DCHECK_RUN_ON(signaling_thread());
   return configuration_;
@@ -2687,465 +2555,6 @@
                            MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg);
 }
 
-void PeerConnection::GetOptionsForOffer(
-    const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
-    cricket::MediaSessionOptions* session_options) {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  ExtractSharedMediaSessionOptions(offer_answer_options, session_options);
-
-  if (IsUnifiedPlan()) {
-    GetOptionsForUnifiedPlanOffer(offer_answer_options, session_options);
-  } else {
-    GetOptionsForPlanBOffer(offer_answer_options, session_options);
-  }
-
-  // Intentionally unset the data channel type for RTP data channel with the
-  // second condition. Otherwise the RTP data channels would be successfully
-  // negotiated by default and the unit tests in WebRtcDataBrowserTest will fail
-  // when building with chromium. We want to leave RTP data channels broken, so
-  // people won't try to use them.
-  if (data_channel_controller_.HasRtpDataChannels() ||
-      data_channel_type() != cricket::DCT_RTP) {
-    session_options->data_channel_type = data_channel_type();
-  }
-
-  // Apply ICE restart flag and renomination flag.
-  bool ice_restart =
-      offer_answer_options.ice_restart || sdp_handler_.HasNewIceCredentials();
-  for (auto& options : session_options->media_description_options) {
-    options.transport_options.ice_restart = ice_restart;
-    options.transport_options.enable_ice_renomination =
-        configuration_.enable_ice_renomination;
-  }
-
-  session_options->rtcp_cname = rtcp_cname_;
-  session_options->crypto_options = GetCryptoOptions();
-  session_options->pooled_ice_credentials =
-      network_thread()->Invoke<std::vector<cricket::IceParameters>>(
-          RTC_FROM_HERE,
-          rtc::Bind(&cricket::PortAllocator::GetPooledIceCredentials,
-                    port_allocator_.get()));
-  session_options->offer_extmap_allow_mixed =
-      configuration_.offer_extmap_allow_mixed;
-
-  // Allow fallback for using obsolete SCTP syntax.
-  // Note that the default in |session_options| is true, while
-  // the default in |options| is false.
-  session_options->use_obsolete_sctp_sdp =
-      offer_answer_options.use_obsolete_sctp_sdp;
-}
-
-void PeerConnection::GetOptionsForPlanBOffer(
-    const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
-    cricket::MediaSessionOptions* session_options) {
-  // Figure out transceiver directional preferences.
-  bool send_audio = !GetAudioTransceiver()->internal()->senders().empty();
-  bool send_video = !GetVideoTransceiver()->internal()->senders().empty();
-
-  // By default, generate sendrecv/recvonly m= sections.
-  bool recv_audio = true;
-  bool 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 !=
-      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);
-  }
-
-  absl::optional<size_t> audio_index;
-  absl::optional<size_t> video_index;
-  absl::optional<size_t> data_index;
-  // If a current description exists, generate m= sections in the same order,
-  // using the first audio/video/data section that appears and rejecting
-  // extraneous ones.
-  if (local_description()) {
-    GenerateMediaDescriptionOptions(
-        local_description(),
-        RtpTransceiverDirectionFromSendRecv(send_audio, recv_audio),
-        RtpTransceiverDirectionFromSendRecv(send_video, recv_video),
-        &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 (!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(GetSendersInternal(),
-                           audio_media_description_options,
-                           video_media_description_options,
-                           offer_answer_options.num_simulcast_layers);
-}
-
-static cricket::MediaDescriptionOptions
-GetMediaDescriptionOptionsForTransceiver(
-    rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-        transceiver,
-    const std::string& mid,
-    bool is_create_offer) {
-  // NOTE: a stopping transceiver should be treated as a stopped one in
-  // createOffer as specified in
-  // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer.
-  bool stopped =
-      is_create_offer ? transceiver->stopping() : transceiver->stopped();
-  cricket::MediaDescriptionOptions media_description_options(
-      transceiver->media_type(), mid, transceiver->direction(), stopped);
-  media_description_options.codec_preferences =
-      transceiver->codec_preferences();
-  media_description_options.header_extensions =
-      transceiver->HeaderExtensionsToOffer();
-  // This behavior is specified in JSEP. The gist is that:
-  // 1. The MSID is included if the RtpTransceiver's direction is sendonly or
-  //    sendrecv.
-  // 2. If the MSID is included, then it must be included in any subsequent
-  //    offer/answer exactly the same until the RtpTransceiver is stopped.
-  if (stopped || (!RtpTransceiverDirectionHasSend(transceiver->direction()) &&
-                  !transceiver->internal()->has_ever_been_used_to_send())) {
-    return media_description_options;
-  }
-
-  cricket::SenderOptions sender_options;
-  sender_options.track_id = transceiver->sender()->id();
-  sender_options.stream_ids = transceiver->sender()->stream_ids();
-
-  // The following sets up RIDs and Simulcast.
-  // RIDs are included if Simulcast is requested or if any RID was specified.
-  RtpParameters send_parameters =
-      transceiver->internal()->sender_internal()->GetParametersInternal();
-  bool has_rids = std::any_of(send_parameters.encodings.begin(),
-                              send_parameters.encodings.end(),
-                              [](const RtpEncodingParameters& encoding) {
-                                return !encoding.rid.empty();
-                              });
-
-  std::vector<RidDescription> send_rids;
-  SimulcastLayerList send_layers;
-  for (const RtpEncodingParameters& encoding : send_parameters.encodings) {
-    if (encoding.rid.empty()) {
-      continue;
-    }
-    send_rids.push_back(RidDescription(encoding.rid, RidDirection::kSend));
-    send_layers.AddLayer(SimulcastLayer(encoding.rid, !encoding.active));
-  }
-
-  if (has_rids) {
-    sender_options.rids = send_rids;
-  }
-
-  sender_options.simulcast_layers = send_layers;
-  // When RIDs are configured, we must set num_sim_layers to 0 to.
-  // Otherwise, num_sim_layers must be 1 because either there is no
-  // simulcast, or simulcast is acheived by munging the SDP.
-  sender_options.num_sim_layers = has_rids ? 0 : 1;
-  media_description_options.sender_options.push_back(sender_options);
-
-  return media_description_options;
-}
-
-// Returns the ContentInfo at mline index |i|, or null if none exists.
-static const ContentInfo* GetContentByIndex(
-    const SessionDescriptionInterface* sdesc,
-    size_t i) {
-  if (!sdesc) {
-    return nullptr;
-  }
-  const ContentInfos& contents = sdesc->description()->contents();
-  return (i < contents.size() ? &contents[i] : nullptr);
-}
-
-void PeerConnection::GetOptionsForUnifiedPlanOffer(
-    const RTCOfferAnswerOptions& offer_answer_options,
-    cricket::MediaSessionOptions* session_options) {
-  // Rules for generating an offer are dictated by JSEP sections 5.2.1 (Initial
-  // Offers) and 5.2.2 (Subsequent Offers).
-  RTC_DCHECK_EQ(session_options->media_description_options.size(), 0);
-  const ContentInfos no_infos;
-  const ContentInfos& local_contents =
-      (local_description() ? local_description()->description()->contents()
-                           : no_infos);
-  const ContentInfos& remote_contents =
-      (remote_description() ? remote_description()->description()->contents()
-                            : no_infos);
-  // The mline indices that can be recycled. New transceivers should reuse these
-  // slots first.
-  std::queue<size_t> recycleable_mline_indices;
-  // First, go through each media section that exists in either the local or
-  // remote description and generate a media section in this offer for the
-  // associated transceiver. If a media section can be recycled, generate a
-  // default, rejected media section here that can be later overwritten.
-  for (size_t i = 0;
-       i < std::max(local_contents.size(), remote_contents.size()); ++i) {
-    // Either |local_content| or |remote_content| is non-null.
-    const ContentInfo* local_content =
-        (i < local_contents.size() ? &local_contents[i] : nullptr);
-    const ContentInfo* current_local_content =
-        GetContentByIndex(current_local_description(), i);
-    const ContentInfo* remote_content =
-        (i < remote_contents.size() ? &remote_contents[i] : nullptr);
-    const ContentInfo* current_remote_content =
-        GetContentByIndex(current_remote_description(), i);
-    bool had_been_rejected =
-        (current_local_content && current_local_content->rejected) ||
-        (current_remote_content && current_remote_content->rejected);
-    const std::string& mid =
-        (local_content ? local_content->name : remote_content->name);
-    cricket::MediaType media_type =
-        (local_content ? local_content->media_description()->type()
-                       : remote_content->media_description()->type());
-    if (media_type == cricket::MEDIA_TYPE_AUDIO ||
-        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 = GetAssociatedTransceiver(mid);
-      if (!transceiver) {
-        // No associated transceiver. The media section has been stopped.
-        recycleable_mline_indices.push(i);
-        session_options->media_description_options.push_back(
-            cricket::MediaDescriptionOptions(media_type, mid,
-                                             RtpTransceiverDirection::kInactive,
-                                             /*stopped=*/true));
-      } else {
-        // NOTE: a stopping transceiver should be treated as a stopped one in
-        // createOffer as specified in
-        // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer.
-        if (had_been_rejected && transceiver->stopping()) {
-          session_options->media_description_options.push_back(
-              cricket::MediaDescriptionOptions(
-                  transceiver->media_type(), mid,
-                  RtpTransceiverDirection::kInactive,
-                  /*stopped=*/true));
-          recycleable_mline_indices.push(i);
-        } else {
-          session_options->media_description_options.push_back(
-              GetMediaDescriptionOptionsForTransceiver(
-                  transceiver, mid,
-                  /*is_create_offer=*/true));
-          // CreateOffer shouldn't really cause any state changes in
-          // PeerConnection, but we need a way to match new transceivers to new
-          // media sections in SetLocalDescription and JSEP specifies this is
-          // done by recording the index of the media section generated for the
-          // transceiver in the offer.
-          transceiver->internal()->set_mline_index(i);
-        }
-      }
-    } else {
-      RTC_CHECK_EQ(cricket::MEDIA_TYPE_DATA, media_type);
-      if (had_been_rejected) {
-        session_options->media_description_options.push_back(
-            GetMediaDescriptionOptionsForRejectedData(mid));
-      } else {
-        RTC_CHECK(GetDataMid());
-        if (mid == *GetDataMid()) {
-          session_options->media_description_options.push_back(
-              GetMediaDescriptionOptionsForActiveData(mid));
-        } else {
-          session_options->media_description_options.push_back(
-              GetMediaDescriptionOptionsForRejectedData(mid));
-        }
-      }
-    }
-  }
-
-  // Next, look for transceivers that are newly added (that is, are not stopped
-  // 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()) {
-    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_(), /*is_create_offer=*/true);
-    } else {
-      mline_index = session_options->media_description_options.size();
-      session_options->media_description_options.push_back(
-          GetMediaDescriptionOptionsForTransceiver(
-              transceiver, mid_generator_(), /*is_create_offer=*/true));
-    }
-    // See comment above for why CreateOffer changes the transceiver's state.
-    transceiver->internal()->set_mline_index(mline_index);
-  }
-  // Lastly, add a m-section if we have local data channels and an m section
-  // does not already exist.
-  if (!GetDataMid() && data_channel_controller_.HasDataChannels()) {
-    session_options->media_description_options.push_back(
-        GetMediaDescriptionOptionsForActiveData(mid_generator_()));
-  }
-}
-
-void PeerConnection::GetOptionsForAnswer(
-    const RTCOfferAnswerOptions& offer_answer_options,
-    cricket::MediaSessionOptions* session_options) {
-  RTC_DCHECK_RUN_ON(signaling_thread());
-  ExtractSharedMediaSessionOptions(offer_answer_options, session_options);
-
-  if (IsUnifiedPlan()) {
-    GetOptionsForUnifiedPlanAnswer(offer_answer_options, session_options);
-  } else {
-    GetOptionsForPlanBAnswer(offer_answer_options, session_options);
-  }
-
-  // Intentionally unset the data channel type for RTP data channel. Otherwise
-  // the RTP data channels would be successfully negotiated by default and the
-  // unit tests in WebRtcDataBrowserTest will fail when building with chromium.
-  // We want to leave RTP data channels broken, so people won't try to use them.
-  if (data_channel_controller_.HasRtpDataChannels() ||
-      data_channel_type() != cricket::DCT_RTP) {
-    session_options->data_channel_type = data_channel_type();
-  }
-
-  // Apply ICE renomination flag.
-  for (auto& options : session_options->media_description_options) {
-    options.transport_options.enable_ice_renomination =
-        configuration_.enable_ice_renomination;
-  }
-
-  session_options->rtcp_cname = rtcp_cname_;
-  session_options->crypto_options = GetCryptoOptions();
-  session_options->pooled_ice_credentials =
-      network_thread()->Invoke<std::vector<cricket::IceParameters>>(
-          RTC_FROM_HERE,
-          rtc::Bind(&cricket::PortAllocator::GetPooledIceCredentials,
-                    port_allocator_.get()));
-}
-
-void PeerConnection::GetOptionsForPlanBAnswer(
-    const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
-    cricket::MediaSessionOptions* session_options) {
-  // Figure out transceiver directional preferences.
-  bool send_audio = !GetAudioTransceiver()->internal()->senders().empty();
-  bool send_video = !GetVideoTransceiver()->internal()->senders().empty();
-
-  // 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;
-
-  // 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;
-  absl::optional<size_t> video_index;
-  absl::optional<size_t> data_index;
-
-  // Generate m= sections that match those in the offer.
-  // Note that mediasession.cc will handle intersection our preferred
-  // direction with the offered direction.
-  GenerateMediaDescriptionOptions(
-      remote_description(),
-      RtpTransceiverDirectionFromSendRecv(send_audio, recv_audio),
-      RtpTransceiverDirectionFromSendRecv(send_video, recv_video), &audio_index,
-      &video_index, &data_index, session_options);
-
-  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(GetSendersInternal(),
-                           audio_media_description_options,
-                           video_media_description_options,
-                           offer_answer_options.num_simulcast_layers);
-}
-
-void PeerConnection::GetOptionsForUnifiedPlanAnswer(
-    const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
-    cricket::MediaSessionOptions* session_options) {
-  // Rules for generating an answer are dictated by JSEP sections 5.3.1 (Initial
-  // Answers) and 5.3.2 (Subsequent Answers).
-  RTC_DCHECK(remote_description());
-  RTC_DCHECK(remote_description()->GetType() == SdpType::kOffer);
-  for (const ContentInfo& content :
-       remote_description()->description()->contents()) {
-    cricket::MediaType media_type = content.media_description()->type();
-    if (media_type == cricket::MEDIA_TYPE_AUDIO ||
-        media_type == cricket::MEDIA_TYPE_VIDEO) {
-      auto transceiver = GetAssociatedTransceiver(content.name);
-      if (transceiver) {
-        session_options->media_description_options.push_back(
-            GetMediaDescriptionOptionsForTransceiver(
-                transceiver, content.name,
-                /*is_create_offer=*/false));
-      } else {
-        // This should only happen with rejected transceivers.
-        RTC_DCHECK(content.rejected);
-        session_options->media_description_options.push_back(
-            cricket::MediaDescriptionOptions(media_type, content.name,
-                                             RtpTransceiverDirection::kInactive,
-                                             /*stopped=*/true));
-      }
-    } else {
-      RTC_CHECK_EQ(cricket::MEDIA_TYPE_DATA, media_type);
-      // Reject all data sections if data channels are disabled.
-      // Reject a data section if it has already been rejected.
-      // Reject all data sections except for the first one.
-      if (data_channel_type() == cricket::DCT_NONE || content.rejected ||
-          content.name != *GetDataMid()) {
-        session_options->media_description_options.push_back(
-            GetMediaDescriptionOptionsForRejectedData(content.name));
-      } else {
-        session_options->media_description_options.push_back(
-            GetMediaDescriptionOptionsForActiveData(content.name));
-      }
-    }
-  }
-}
 
 void PeerConnection::GenerateMediaDescriptionOptions(
     const SessionDescriptionInterface* session_desc,
@@ -3155,6 +2564,7 @@
     absl::optional<size_t>* video_index,
     absl::optional<size_t>* data_index,
     cricket::MediaSessionOptions* session_options) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
   for (const cricket::ContentInfo& content :
        session_desc->description()->contents()) {
     if (IsAudioContent(&content)) {
@@ -3209,6 +2619,7 @@
 cricket::MediaDescriptionOptions
 PeerConnection::GetMediaDescriptionOptionsForActiveData(
     const std::string& mid) const {
+  RTC_DCHECK_RUN_ON(signaling_thread());
   // Direction for data sections is meaningless, but legacy endpoints might
   // expect sendrecv.
   cricket::MediaDescriptionOptions options(cricket::MEDIA_TYPE_DATA, mid,
@@ -3222,6 +2633,7 @@
 cricket::MediaDescriptionOptions
 PeerConnection::GetMediaDescriptionOptionsForRejectedData(
     const std::string& mid) const {
+  RTC_DCHECK_RUN_ON(signaling_thread());
   cricket::MediaDescriptionOptions options(cricket::MEDIA_TYPE_DATA, mid,
                                            RtpTransceiverDirection::kInactive,
                                            /*stopped=*/true);
@@ -3511,6 +2923,7 @@
 
 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());
@@ -3525,6 +2938,7 @@
 
 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());
@@ -3839,7 +3253,7 @@
   for (const auto& transceiver : transceivers_.List()) {
     cricket::ChannelInterface* channel = transceiver->internal()->channel();
     const ContentInfo* content =
-        FindMediaSectionForTransceiver(transceiver, sdesc);
+        sdp_handler_.FindMediaSectionForTransceiver(transceiver, sdesc);
     if (!channel || !content) {
       continue;
     }
@@ -3888,7 +3302,7 @@
   // Push down the new SDP media section for each audio/video transceiver.
   for (const auto& transceiver : transceivers_.List()) {
     const ContentInfo* content_info =
-        FindMediaSectionForTransceiver(transceiver, sdesc);
+        sdp_handler_.FindMediaSectionForTransceiver(transceiver, sdesc);
     cricket::ChannelInterface* channel = transceiver->internal()->channel();
     if (!channel || !content_info || content_info->rejected) {
       continue;
@@ -4596,30 +4010,6 @@
   return content->media_description()->rtcp_mux();
 }
 
-bool PeerConnection::ExpectSetLocalDescription(SdpType type) {
-  PeerConnectionInterface::SignalingState state = signaling_state();
-  if (type == SdpType::kOffer) {
-    return (state == PeerConnectionInterface::kStable) ||
-           (state == PeerConnectionInterface::kHaveLocalOffer);
-  } else {
-    RTC_DCHECK(type == SdpType::kPrAnswer || type == SdpType::kAnswer);
-    return (state == PeerConnectionInterface::kHaveRemoteOffer) ||
-           (state == PeerConnectionInterface::kHaveLocalPrAnswer);
-  }
-}
-
-bool PeerConnection::ExpectSetRemoteDescription(SdpType type) {
-  PeerConnectionInterface::SignalingState state = signaling_state();
-  if (type == SdpType::kOffer) {
-    return (state == PeerConnectionInterface::kStable) ||
-           (state == PeerConnectionInterface::kHaveRemoteOffer);
-  } else {
-    RTC_DCHECK(type == SdpType::kPrAnswer || type == SdpType::kAnswer);
-    return (state == PeerConnectionInterface::kHaveLocalOffer) ||
-           (state == PeerConnectionInterface::kHaveRemotePrAnswer);
-  }
-}
-
 const char* PeerConnection::SessionErrorToString(SessionError error) const {
   switch (error) {
     case SessionError::kNone:
@@ -5068,6 +4458,7 @@
 }
 
 CryptoOptions PeerConnection::GetCryptoOptions() {
+  RTC_DCHECK_RUN_ON(signaling_thread());
   // TODO(bugs.webrtc.org/9891) - Remove PeerConnectionFactory::CryptoOptions
   // after it has been removed.
   return configuration_.crypto_options.has_value()
diff --git a/pc/peer_connection.h b/pc/peer_connection.h
index e57920d..e5a7ae8 100644
--- a/pc/peer_connection.h
+++ b/pc/peer_connection.h
@@ -412,15 +412,15 @@
       RTC_RUN_ON(signaling_thread());
 
   std::vector<rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>>
-  GetSendersInternal() const RTC_RUN_ON(signaling_thread());
+  GetSendersInternal() const;
   std::vector<
       rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>>
   GetReceiversInternal() const RTC_RUN_ON(signaling_thread());
 
   rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-  GetAudioTransceiver() const RTC_RUN_ON(signaling_thread());
+  GetAudioTransceiver() const;
   rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-  GetVideoTransceiver() const RTC_RUN_ON(signaling_thread());
+  GetVideoTransceiver() const;
 
   rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
   GetFirstAudioTransceiver() const RTC_RUN_ON(signaling_thread());
@@ -560,20 +560,6 @@
   rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
   GetTransceiverByMLineIndex(size_t mline_index) const;
 
-  // Returns an RtpTransciever, if available, that can be used to receive the
-  // given media type according to JSEP rules.
-  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-  FindAvailableTransceiverToReceive(cricket::MediaType media_type) const;
-
-  // Returns the media section in the given session description that is
-  // associated with the RtpTransceiver. Returns null if none found or this
-  // RtpTransceiver is not associated. Logic varies depending on the
-  // SdpSemantics specified in the configuration.
-  const cricket::ContentInfo* FindMediaSectionForTransceiver(
-      rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
-          transceiver,
-      const SessionDescriptionInterface* sdesc) const;
-
   // Runs the algorithm **process the removal of a remote track** specified in
   // the WebRTC specification.
   // This method will update the following lists:
@@ -595,21 +581,6 @@
 
   void OnNegotiationNeeded();
 
-  // Returns a MediaSessionOptions struct with options decided by |options|,
-  // the local MediaStreams and DataChannels.
-  void GetOptionsForOffer(const PeerConnectionInterface::RTCOfferAnswerOptions&
-                              offer_answer_options,
-                          cricket::MediaSessionOptions* session_options);
-  void GetOptionsForPlanBOffer(
-      const PeerConnectionInterface::RTCOfferAnswerOptions&
-          offer_answer_options,
-      cricket::MediaSessionOptions* session_options)
-      RTC_RUN_ON(signaling_thread());
-  void GetOptionsForUnifiedPlanOffer(
-      const PeerConnectionInterface::RTCOfferAnswerOptions&
-          offer_answer_options,
-      cricket::MediaSessionOptions* session_options)
-      RTC_RUN_ON(signaling_thread());
 
   RTCError HandleLegacyOfferOptions(const RTCOfferAnswerOptions& options);
   void RemoveRecvDirectionFromReceivingTransceiversOfType(
@@ -620,21 +591,6 @@
   GetReceivingTransceiversOfType(cricket::MediaType media_type)
       RTC_RUN_ON(signaling_thread());
 
-  // Returns a MediaSessionOptions struct with options decided by
-  // |constraints|, the local MediaStreams and DataChannels.
-  void GetOptionsForAnswer(const RTCOfferAnswerOptions& offer_answer_options,
-                           cricket::MediaSessionOptions* session_options);
-  void GetOptionsForPlanBAnswer(
-      const PeerConnectionInterface::RTCOfferAnswerOptions&
-          offer_answer_options,
-      cricket::MediaSessionOptions* session_options)
-      RTC_RUN_ON(signaling_thread());
-  void GetOptionsForUnifiedPlanAnswer(
-      const PeerConnectionInterface::RTCOfferAnswerOptions&
-          offer_answer_options,
-      cricket::MediaSessionOptions* session_options)
-      RTC_RUN_ON(signaling_thread());
-
   // Generates MediaDescriptionOptions for the |session_opts| based on existing
   // local description or remote description.
   void GenerateMediaDescriptionOptions(
@@ -644,18 +600,17 @@
       absl::optional<size_t>* audio_index,
       absl::optional<size_t>* video_index,
       absl::optional<size_t>* data_index,
-      cricket::MediaSessionOptions* session_options)
-      RTC_RUN_ON(signaling_thread());
+      cricket::MediaSessionOptions* session_options);
 
   // Generates the active MediaDescriptionOptions for the local data channel
   // given the specified MID.
   cricket::MediaDescriptionOptions GetMediaDescriptionOptionsForActiveData(
-      const std::string& mid) const RTC_RUN_ON(signaling_thread());
+      const std::string& mid) const;
 
   // Generates the rejected MediaDescriptionOptions for the local data channel
   // given the specified MID.
   cricket::MediaDescriptionOptions GetMediaDescriptionOptionsForRejectedData(
-      const std::string& mid) const RTC_RUN_ON(signaling_thread());
+      const std::string& mid) const;
 
   // Returns the MID for the data section associated with either the
   // RtpDataChannel or SCTP data channel, if it has been set. If no data
@@ -732,12 +687,6 @@
     return configuration_.sdp_semantics == SdpSemantics::kUnifiedPlan;
   }
 
-  // The offer/answer machinery assumes the media section MID is present and
-  // unique. To support legacy end points that do not supply a=mid lines, this
-  // method will modify the session description to add MIDs generated according
-  // to the SDP semantics.
-  void FillInMissingRemoteMids(cricket::SessionDescription* remote_description);
-
   // Return the RtpSender with the given track attached.
   rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
   FindSenderForTrack(MediaStreamTrackInterface* track) const
@@ -893,12 +842,6 @@
   bool ValidateBundleSettings(const cricket::SessionDescription* desc);
   bool HasRtcpMuxEnabled(const cricket::ContentInfo* content);
 
-  // Check if a call to SetLocalDescription is acceptable with a session
-  // description of the given type.
-  bool ExpectSetLocalDescription(SdpType type);
-  // Check if a call to SetRemoteDescription is acceptable with a session
-  // description of the given type.
-  bool ExpectSetRemoteDescription(SdpType type);
   // Verifies a=setup attribute as per RFC 5763.
   bool ValidateDtlsSetupAttribute(const cricket::SessionDescription* desc,
                                   SdpType type);
@@ -1004,7 +947,7 @@
   // 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.
-  CryptoOptions GetCryptoOptions() RTC_RUN_ON(signaling_thread());
+  CryptoOptions GetCryptoOptions();
 
   // Returns rtp transport, result can not be nullptr.
   RtpTransportInternal* GetRtpTransport(const std::string& mid)
diff --git a/pc/sdp_offer_answer.cc b/pc/sdp_offer_answer.cc
index 03d3001..b150ce9 100644
--- a/pc/sdp_offer_answer.cc
+++ b/pc/sdp_offer_answer.cc
@@ -10,6 +10,9 @@
 
 #include "pc/sdp_offer_answer.h"
 
+#include <algorithm>
+#include <queue>
+
 #include "api/media_stream_proxy.h"
 #include "api/uma_metrics.h"
 #include "pc/media_stream.h"
@@ -40,6 +43,9 @@
 
 namespace {
 
+typedef webrtc::PeerConnectionInterface::RTCOfferAnswerOptions
+    RTCOfferAnswerOptions;
+
 // Error messages
 const char kInvalidSdp[] = "Invalid session description.";
 const char kInvalidCandidates[] = "Description contains invalid candidates.";
@@ -494,6 +500,132 @@
   return sender->DisableEncodingLayers(disabled_layers);
 }
 
+// The SDP parser used to populate these values by default for the 'content
+// name' if an a=mid line was absent.
+static absl::string_view GetDefaultMidForPlanB(cricket::MediaType media_type) {
+  switch (media_type) {
+    case cricket::MEDIA_TYPE_AUDIO:
+      return cricket::CN_AUDIO;
+    case cricket::MEDIA_TYPE_VIDEO:
+      return cricket::CN_VIDEO;
+    case cricket::MEDIA_TYPE_DATA:
+      return cricket::CN_DATA;
+  }
+  RTC_NOTREACHED();
+  return "";
+}
+
+// Add options to |[audio/video]_media_description_options| from |senders|.
+void AddPlanBRtpSenderOptions(
+    const std::vector<rtc::scoped_refptr<
+        RtpSenderProxyWithInternal<RtpSenderInternal>>>& senders,
+    cricket::MediaDescriptionOptions* audio_media_description_options,
+    cricket::MediaDescriptionOptions* video_media_description_options,
+    int num_sim_layers) {
+  for (const auto& sender : senders) {
+    if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) {
+      if (audio_media_description_options) {
+        audio_media_description_options->AddAudioSender(
+            sender->id(), sender->internal()->stream_ids());
+      }
+    } else {
+      RTC_DCHECK(sender->media_type() == cricket::MEDIA_TYPE_VIDEO);
+      if (video_media_description_options) {
+        video_media_description_options->AddVideoSender(
+            sender->id(), sender->internal()->stream_ids(), {},
+            SimulcastLayerList(), num_sim_layers);
+      }
+    }
+  }
+}
+
+static cricket::MediaDescriptionOptions
+GetMediaDescriptionOptionsForTransceiver(
+    rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+        transceiver,
+    const std::string& mid,
+    bool is_create_offer) {
+  // NOTE: a stopping transceiver should be treated as a stopped one in
+  // createOffer as specified in
+  // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer.
+  bool stopped =
+      is_create_offer ? transceiver->stopping() : transceiver->stopped();
+  cricket::MediaDescriptionOptions media_description_options(
+      transceiver->media_type(), mid, transceiver->direction(), stopped);
+  media_description_options.codec_preferences =
+      transceiver->codec_preferences();
+  media_description_options.header_extensions =
+      transceiver->HeaderExtensionsToOffer();
+  // This behavior is specified in JSEP. The gist is that:
+  // 1. The MSID is included if the RtpTransceiver's direction is sendonly or
+  //    sendrecv.
+  // 2. If the MSID is included, then it must be included in any subsequent
+  //    offer/answer exactly the same until the RtpTransceiver is stopped.
+  if (stopped || (!RtpTransceiverDirectionHasSend(transceiver->direction()) &&
+                  !transceiver->internal()->has_ever_been_used_to_send())) {
+    return media_description_options;
+  }
+
+  cricket::SenderOptions sender_options;
+  sender_options.track_id = transceiver->sender()->id();
+  sender_options.stream_ids = transceiver->sender()->stream_ids();
+
+  // The following sets up RIDs and Simulcast.
+  // RIDs are included if Simulcast is requested or if any RID was specified.
+  RtpParameters send_parameters =
+      transceiver->internal()->sender_internal()->GetParametersInternal();
+  bool has_rids = std::any_of(send_parameters.encodings.begin(),
+                              send_parameters.encodings.end(),
+                              [](const RtpEncodingParameters& encoding) {
+                                return !encoding.rid.empty();
+                              });
+
+  std::vector<RidDescription> send_rids;
+  SimulcastLayerList send_layers;
+  for (const RtpEncodingParameters& encoding : send_parameters.encodings) {
+    if (encoding.rid.empty()) {
+      continue;
+    }
+    send_rids.push_back(RidDescription(encoding.rid, RidDirection::kSend));
+    send_layers.AddLayer(SimulcastLayer(encoding.rid, !encoding.active));
+  }
+
+  if (has_rids) {
+    sender_options.rids = send_rids;
+  }
+
+  sender_options.simulcast_layers = send_layers;
+  // When RIDs are configured, we must set num_sim_layers to 0 to.
+  // Otherwise, num_sim_layers must be 1 because either there is no
+  // simulcast, or simulcast is acheived by munging the SDP.
+  sender_options.num_sim_layers = has_rids ? 0 : 1;
+  media_description_options.sender_options.push_back(sender_options);
+
+  return media_description_options;
+}
+
+// Returns the ContentInfo at mline index |i|, or null if none exists.
+static const ContentInfo* GetContentByIndex(
+    const SessionDescriptionInterface* sdesc,
+    size_t i) {
+  if (!sdesc) {
+    return nullptr;
+  }
+  const ContentInfos& contents = sdesc->description()->contents();
+  return (i < contents.size() ? &contents[i] : nullptr);
+}
+
+// From |rtc_options|, fill parts of |session_options| shared by all generated
+// m= sectionss (in other words, nothing that involves a map/array).
+void ExtractSharedMediaSessionOptions(
+    const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
+    cricket::MediaSessionOptions* session_options) {
+  session_options->vad_enabled = rtc_options.voice_activity_detection;
+  session_options->bundle_enabled = rtc_options.use_rtp_mux;
+  session_options->raw_packetization_for_video =
+      rtc_options.raw_packetization_for_video;
+}
+
 }  // namespace
 
 // Used by parameterless SetLocalDescription() to create an offer or answer.
@@ -964,7 +1096,7 @@
       }
 
       const ContentInfo* content =
-          pc_->FindMediaSectionForTransceiver(transceiver, local_description());
+          FindMediaSectionForTransceiver(transceiver, local_description());
       if (!content) {
         continue;
       }
@@ -1041,7 +1173,7 @@
         continue;
       }
       const ContentInfo* content =
-          pc_->FindMediaSectionForTransceiver(transceiver, local_description());
+          FindMediaSectionForTransceiver(transceiver, local_description());
       if (!content) {
         continue;
       }
@@ -1316,8 +1448,8 @@
     std::vector<rtc::scoped_refptr<MediaStreamInterface>> added_streams;
     std::vector<rtc::scoped_refptr<MediaStreamInterface>> removed_streams;
     for (const auto& transceiver : pc_->transceivers_.List()) {
-      const ContentInfo* content = pc_->FindMediaSectionForTransceiver(
-          transceiver, remote_description());
+      const ContentInfo* content =
+          FindMediaSectionForTransceiver(transceiver, remote_description());
       if (!content) {
         continue;
       }
@@ -1668,7 +1800,7 @@
   }
 
   cricket::MediaSessionOptions session_options;
-  pc_->GetOptionsForOffer(options, &session_options);
+  GetOptionsForOffer(options, &session_options);
   webrtc_session_desc_factory_->CreateOffer(observer, options, session_options);
 }
 
@@ -1753,7 +1885,7 @@
   }
 
   cricket::MediaSessionOptions session_options;
-  pc_->GetOptionsForAnswer(options, &session_options);
+  GetOptionsForAnswer(options, &session_options);
   webrtc_session_desc_factory_->CreateAnswer(observer, session_options);
 }
 
@@ -1808,7 +1940,7 @@
 
   // Handle remote descriptions missing a=mid lines for interop with legacy end
   // points.
-  pc_->FillInMissingRemoteMids(desc->description());
+  FillInMissingRemoteMids(desc->description());
 
   RTCError error = ValidateSessionDescription(desc.get(), cricket::CS_REMOTE);
   if (!error.ok()) {
@@ -2539,9 +2671,8 @@
   }
 
   SdpType type = sdesc->GetType();
-  if ((source == cricket::CS_LOCAL && !pc_->ExpectSetLocalDescription(type)) ||
-      (source == cricket::CS_REMOTE &&
-       !pc_->ExpectSetRemoteDescription(type))) {
+  if ((source == cricket::CS_LOCAL && !ExpectSetLocalDescription(type)) ||
+      (source == cricket::CS_REMOTE && !ExpectSetRemoteDescription(type))) {
     LOG_AND_RETURN_ERROR(
         RTCErrorType::INVALID_STATE,
         "Called in wrong state: " + GetSignalingStateString(signaling_state()));
@@ -2759,7 +2890,7 @@
     if (!transceiver &&
         RtpTransceiverDirectionHasRecv(media_desc->direction()) &&
         !media_desc->HasSimulcast()) {
-      transceiver = pc_->FindAvailableTransceiverToReceive(media_desc->type());
+      transceiver = FindAvailableTransceiverToReceive(media_desc->type());
     }
     // If no RtpTransceiver was found in the previous step, create one with a
     // recvonly direction.
@@ -2918,4 +3049,501 @@
   return RTCError::OK();
 }
 
+bool SdpOfferAnswerHandler::ExpectSetLocalDescription(SdpType type) {
+  PeerConnectionInterface::SignalingState state = signaling_state();
+  if (type == SdpType::kOffer) {
+    return (state == PeerConnectionInterface::kStable) ||
+           (state == PeerConnectionInterface::kHaveLocalOffer);
+  } else {
+    RTC_DCHECK(type == SdpType::kPrAnswer || type == SdpType::kAnswer);
+    return (state == PeerConnectionInterface::kHaveRemoteOffer) ||
+           (state == PeerConnectionInterface::kHaveLocalPrAnswer);
+  }
+}
+
+bool SdpOfferAnswerHandler::ExpectSetRemoteDescription(SdpType type) {
+  PeerConnectionInterface::SignalingState state = signaling_state();
+  if (type == SdpType::kOffer) {
+    return (state == PeerConnectionInterface::kStable) ||
+           (state == PeerConnectionInterface::kHaveRemoteOffer);
+  } else {
+    RTC_DCHECK(type == SdpType::kPrAnswer || type == SdpType::kAnswer);
+    return (state == PeerConnectionInterface::kHaveLocalOffer) ||
+           (state == PeerConnectionInterface::kHaveRemotePrAnswer);
+  }
+}
+
+void SdpOfferAnswerHandler::FillInMissingRemoteMids(
+    cricket::SessionDescription* new_remote_description) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  RTC_DCHECK(new_remote_description);
+  const cricket::ContentInfos no_infos;
+  const cricket::ContentInfos& local_contents =
+      (local_description() ? local_description()->description()->contents()
+                           : no_infos);
+  const cricket::ContentInfos& remote_contents =
+      (remote_description() ? remote_description()->description()->contents()
+                            : no_infos);
+  for (size_t i = 0; i < new_remote_description->contents().size(); ++i) {
+    cricket::ContentInfo& content = new_remote_description->contents()[i];
+    if (!content.name.empty()) {
+      continue;
+    }
+    std::string new_mid;
+    absl::string_view source_explanation;
+    if (IsUnifiedPlan()) {
+      if (i < local_contents.size()) {
+        new_mid = local_contents[i].name;
+        source_explanation = "from the matching local media section";
+      } else if (i < remote_contents.size()) {
+        new_mid = remote_contents[i].name;
+        source_explanation = "from the matching previous remote media section";
+      } else {
+        new_mid = pc_->mid_generator()->GenerateString();
+        source_explanation = "generated just now";
+      }
+    } else {
+      new_mid = std::string(
+          GetDefaultMidForPlanB(content.media_description()->type()));
+      source_explanation = "to match pre-existing behavior";
+    }
+    RTC_DCHECK(!new_mid.empty());
+    content.name = new_mid;
+    new_remote_description->transport_infos()[i].content_name = new_mid;
+    RTC_LOG(LS_INFO) << "SetRemoteDescription: Remote media section at i=" << i
+                     << " is missing an a=mid line. Filling in the value '"
+                     << new_mid << "' " << source_explanation << ".";
+  }
+}
+
+rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+SdpOfferAnswerHandler::FindAvailableTransceiverToReceive(
+    cricket::MediaType media_type) const {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  RTC_DCHECK(IsUnifiedPlan());
+  // From JSEP section 5.10 (Applying a Remote Description):
+  // If the m= section is sendrecv or recvonly, and there are RtpTransceivers of
+  // 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 : pc_->transceivers_.List()) {
+    if (transceiver->media_type() == media_type &&
+        transceiver->internal()->created_by_addtrack() && !transceiver->mid() &&
+        !transceiver->stopped()) {
+      return transceiver;
+    }
+  }
+  return nullptr;
+}
+
+const cricket::ContentInfo*
+SdpOfferAnswerHandler::FindMediaSectionForTransceiver(
+    rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+        transceiver,
+    const SessionDescriptionInterface* sdesc) const {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  RTC_DCHECK(transceiver);
+  RTC_DCHECK(sdesc);
+  if (IsUnifiedPlan()) {
+    if (!transceiver->internal()->mid()) {
+      // This transceiver is not associated with a media section yet.
+      return nullptr;
+    }
+    return sdesc->description()->GetContentByName(
+        *transceiver->internal()->mid());
+  } else {
+    // Plan B only allows at most one audio and one video section, so use the
+    // first media section of that type.
+    return cricket::GetFirstMediaContent(sdesc->description()->contents(),
+                                         transceiver->media_type());
+  }
+}
+
+void SdpOfferAnswerHandler::GetOptionsForOffer(
+    const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
+    cricket::MediaSessionOptions* session_options) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  ExtractSharedMediaSessionOptions(offer_answer_options, session_options);
+
+  if (IsUnifiedPlan()) {
+    GetOptionsForUnifiedPlanOffer(offer_answer_options, session_options);
+  } else {
+    GetOptionsForPlanBOffer(offer_answer_options, session_options);
+  }
+
+  // Intentionally unset the data channel type for RTP data channel with the
+  // second condition. Otherwise the RTP data channels would be successfully
+  // negotiated by default and the unit tests in WebRtcDataBrowserTest will fail
+  // when building with chromium. We want to leave RTP data channels broken, so
+  // people won't try to use them.
+  if (pc_->data_channel_controller()->HasRtpDataChannels() ||
+      pc_->data_channel_type() != cricket::DCT_RTP) {
+    session_options->data_channel_type = pc_->data_channel_type();
+  }
+
+  // Apply ICE restart flag and renomination flag.
+  bool ice_restart = offer_answer_options.ice_restart || HasNewIceCredentials();
+  for (auto& options : session_options->media_description_options) {
+    options.transport_options.ice_restart = ice_restart;
+    options.transport_options.enable_ice_renomination =
+        pc_->configuration()->enable_ice_renomination;
+  }
+
+  session_options->rtcp_cname = pc_->rtcp_cname_;
+  session_options->crypto_options = pc_->GetCryptoOptions();
+  session_options->pooled_ice_credentials =
+      pc_->network_thread()->Invoke<std::vector<cricket::IceParameters>>(
+          RTC_FROM_HERE,
+          rtc::Bind(&cricket::PortAllocator::GetPooledIceCredentials,
+                    pc_->port_allocator_.get()));
+  session_options->offer_extmap_allow_mixed =
+      pc_->configuration()->offer_extmap_allow_mixed;
+
+  // Allow fallback for using obsolete SCTP syntax.
+  // Note that the default in |session_options| is true, while
+  // the default in |options| is false.
+  session_options->use_obsolete_sctp_sdp =
+      offer_answer_options.use_obsolete_sctp_sdp;
+}
+
+void SdpOfferAnswerHandler::GetOptionsForPlanBOffer(
+    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();
+
+  // By default, generate sendrecv/recvonly m= sections.
+  bool recv_audio = true;
+  bool 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 =
+      pc_->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 (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;
+  // If a current description exists, generate m= sections in the same order,
+  // using the first audio/video/data section that appears and rejecting
+  // extraneous ones.
+  if (local_description()) {
+    pc_->GenerateMediaDescriptionOptions(
+        local_description(),
+        RtpTransceiverDirectionFromSendRecv(send_audio, recv_audio),
+        RtpTransceiverDirectionFromSendRecv(send_video, recv_video),
+        &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 =
+        pc_->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 =
+        pc_->channel_manager()->GetSupportedVideoRtpHeaderExtensions();
+    session_options->media_description_options.push_back(options);
+    video_index = session_options->media_description_options.size() - 1;
+  }
+  if (!data_index && offer_new_data_description) {
+    session_options->media_description_options.push_back(
+        pc_->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(pc_->GetSendersInternal(),
+                           audio_media_description_options,
+                           video_media_description_options,
+                           offer_answer_options.num_simulcast_layers);
+}
+
+void SdpOfferAnswerHandler::GetOptionsForUnifiedPlanOffer(
+    const RTCOfferAnswerOptions& offer_answer_options,
+    cricket::MediaSessionOptions* session_options) {
+  // Rules for generating an offer are dictated by JSEP sections 5.2.1 (Initial
+  // Offers) and 5.2.2 (Subsequent Offers).
+  RTC_DCHECK_EQ(session_options->media_description_options.size(), 0);
+  const ContentInfos no_infos;
+  const ContentInfos& local_contents =
+      (local_description() ? local_description()->description()->contents()
+                           : no_infos);
+  const ContentInfos& remote_contents =
+      (remote_description() ? remote_description()->description()->contents()
+                            : no_infos);
+  // The mline indices that can be recycled. New transceivers should reuse these
+  // slots first.
+  std::queue<size_t> recycleable_mline_indices;
+  // First, go through each media section that exists in either the local or
+  // remote description and generate a media section in this offer for the
+  // associated transceiver. If a media section can be recycled, generate a
+  // default, rejected media section here that can be later overwritten.
+  for (size_t i = 0;
+       i < std::max(local_contents.size(), remote_contents.size()); ++i) {
+    // Either |local_content| or |remote_content| is non-null.
+    const ContentInfo* local_content =
+        (i < local_contents.size() ? &local_contents[i] : nullptr);
+    const ContentInfo* current_local_content =
+        GetContentByIndex(current_local_description(), i);
+    const ContentInfo* remote_content =
+        (i < remote_contents.size() ? &remote_contents[i] : nullptr);
+    const ContentInfo* current_remote_content =
+        GetContentByIndex(current_remote_description(), i);
+    bool had_been_rejected =
+        (current_local_content && current_local_content->rejected) ||
+        (current_remote_content && current_remote_content->rejected);
+    const std::string& mid =
+        (local_content ? local_content->name : remote_content->name);
+    cricket::MediaType media_type =
+        (local_content ? local_content->media_description()->type()
+                       : remote_content->media_description()->type());
+    if (media_type == cricket::MEDIA_TYPE_AUDIO ||
+        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 = pc_->GetAssociatedTransceiver(mid);
+      if (!transceiver) {
+        // No associated transceiver. The media section has been stopped.
+        recycleable_mline_indices.push(i);
+        session_options->media_description_options.push_back(
+            cricket::MediaDescriptionOptions(media_type, mid,
+                                             RtpTransceiverDirection::kInactive,
+                                             /*stopped=*/true));
+      } else {
+        // NOTE: a stopping transceiver should be treated as a stopped one in
+        // createOffer as specified in
+        // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer.
+        if (had_been_rejected && transceiver->stopping()) {
+          session_options->media_description_options.push_back(
+              cricket::MediaDescriptionOptions(
+                  transceiver->media_type(), mid,
+                  RtpTransceiverDirection::kInactive,
+                  /*stopped=*/true));
+          recycleable_mline_indices.push(i);
+        } else {
+          session_options->media_description_options.push_back(
+              GetMediaDescriptionOptionsForTransceiver(
+                  transceiver, mid,
+                  /*is_create_offer=*/true));
+          // CreateOffer shouldn't really cause any state changes in
+          // PeerConnection, but we need a way to match new transceivers to new
+          // media sections in SetLocalDescription and JSEP specifies this is
+          // done by recording the index of the media section generated for the
+          // transceiver in the offer.
+          transceiver->internal()->set_mline_index(i);
+        }
+      }
+    } else {
+      RTC_CHECK_EQ(cricket::MEDIA_TYPE_DATA, media_type);
+      if (had_been_rejected) {
+        session_options->media_description_options.push_back(
+            pc_->GetMediaDescriptionOptionsForRejectedData(mid));
+      } else {
+        RTC_CHECK(pc_->GetDataMid());
+        if (mid == *(pc_->GetDataMid())) {
+          session_options->media_description_options.push_back(
+              pc_->GetMediaDescriptionOptionsForActiveData(mid));
+        } else {
+          session_options->media_description_options.push_back(
+              pc_->GetMediaDescriptionOptionsForRejectedData(mid));
+        }
+      }
+    }
+  }
+
+  // Next, look for transceivers that are newly added (that is, are not stopped
+  // 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 : pc_->transceivers_.List()) {
+    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, pc_->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, pc_->mid_generator()->GenerateString(),
+              /*is_create_offer=*/true));
+    }
+    // See comment above for why CreateOffer changes the transceiver's state.
+    transceiver->internal()->set_mline_index(mline_index);
+  }
+  // Lastly, add a m-section if we have local data channels and an m section
+  // does not already exist.
+  if (!pc_->GetDataMid() && pc_->data_channel_controller()->HasDataChannels()) {
+    session_options->media_description_options.push_back(
+        pc_->GetMediaDescriptionOptionsForActiveData(
+            pc_->mid_generator()->GenerateString()));
+  }
+}
+
+void SdpOfferAnswerHandler::GetOptionsForAnswer(
+    const RTCOfferAnswerOptions& offer_answer_options,
+    cricket::MediaSessionOptions* session_options) {
+  RTC_DCHECK_RUN_ON(signaling_thread());
+  ExtractSharedMediaSessionOptions(offer_answer_options, session_options);
+
+  if (IsUnifiedPlan()) {
+    GetOptionsForUnifiedPlanAnswer(offer_answer_options, session_options);
+  } else {
+    GetOptionsForPlanBAnswer(offer_answer_options, session_options);
+  }
+
+  // Intentionally unset the data channel type for RTP data channel. Otherwise
+  // the RTP data channels would be successfully negotiated by default and the
+  // unit tests in WebRtcDataBrowserTest will fail when building with chromium.
+  // We want to leave RTP data channels broken, so people won't try to use them.
+  if (pc_->data_channel_controller()->HasRtpDataChannels() ||
+      pc_->data_channel_type() != cricket::DCT_RTP) {
+    session_options->data_channel_type = pc_->data_channel_type();
+  }
+
+  // Apply ICE renomination flag.
+  for (auto& options : session_options->media_description_options) {
+    options.transport_options.enable_ice_renomination =
+        pc_->configuration()->enable_ice_renomination;
+  }
+
+  session_options->rtcp_cname = pc_->rtcp_cname_;
+  session_options->crypto_options = pc_->GetCryptoOptions();
+  session_options->pooled_ice_credentials =
+      pc_->network_thread()->Invoke<std::vector<cricket::IceParameters>>(
+          RTC_FROM_HERE,
+          rtc::Bind(&cricket::PortAllocator::GetPooledIceCredentials,
+                    pc_->port_allocator_.get()));
+}
+
+void SdpOfferAnswerHandler::GetOptionsForPlanBAnswer(
+    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();
+
+  // 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;
+
+  // 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;
+  absl::optional<size_t> video_index;
+  absl::optional<size_t> data_index;
+
+  // Generate m= sections that match those in the offer.
+  // Note that mediasession.cc will handle intersection our preferred
+  // direction with the offered direction.
+  pc_->GenerateMediaDescriptionOptions(
+      remote_description(),
+      RtpTransceiverDirectionFromSendRecv(send_audio, recv_audio),
+      RtpTransceiverDirectionFromSendRecv(send_video, recv_video), &audio_index,
+      &video_index, &data_index, session_options);
+
+  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(pc_->GetSendersInternal(),
+                           audio_media_description_options,
+                           video_media_description_options,
+                           offer_answer_options.num_simulcast_layers);
+}
+
+void SdpOfferAnswerHandler::GetOptionsForUnifiedPlanAnswer(
+    const PeerConnectionInterface::RTCOfferAnswerOptions& offer_answer_options,
+    cricket::MediaSessionOptions* session_options) {
+  // Rules for generating an answer are dictated by JSEP sections 5.3.1 (Initial
+  // Answers) and 5.3.2 (Subsequent Answers).
+  RTC_DCHECK(remote_description());
+  RTC_DCHECK(remote_description()->GetType() == SdpType::kOffer);
+  for (const ContentInfo& content :
+       remote_description()->description()->contents()) {
+    cricket::MediaType media_type = content.media_description()->type();
+    if (media_type == cricket::MEDIA_TYPE_AUDIO ||
+        media_type == cricket::MEDIA_TYPE_VIDEO) {
+      auto transceiver = pc_->GetAssociatedTransceiver(content.name);
+      if (transceiver) {
+        session_options->media_description_options.push_back(
+            GetMediaDescriptionOptionsForTransceiver(
+                transceiver, content.name,
+                /*is_create_offer=*/false));
+      } else {
+        // This should only happen with rejected transceivers.
+        RTC_DCHECK(content.rejected);
+        session_options->media_description_options.push_back(
+            cricket::MediaDescriptionOptions(media_type, content.name,
+                                             RtpTransceiverDirection::kInactive,
+                                             /*stopped=*/true));
+      }
+    } else {
+      RTC_CHECK_EQ(cricket::MEDIA_TYPE_DATA, media_type);
+      // Reject all data sections if data channels are disabled.
+      // Reject a data section if it has already been rejected.
+      // Reject all data sections except for the first one.
+      if (pc_->data_channel_type() == cricket::DCT_NONE || content.rejected ||
+          content.name != *(pc_->GetDataMid())) {
+        session_options->media_description_options.push_back(
+            pc_->GetMediaDescriptionOptionsForRejectedData(content.name));
+      } else {
+        session_options->media_description_options.push_back(
+            pc_->GetMediaDescriptionOptionsForActiveData(content.name));
+      }
+    }
+  }
+}
+
 }  // namespace webrtc
diff --git a/pc/sdp_offer_answer.h b/pc/sdp_offer_answer.h
index 780dd93..b84c8bd 100644
--- a/pc/sdp_offer_answer.h
+++ b/pc/sdp_offer_answer.h
@@ -131,6 +131,15 @@
   bool IceRestartPending(const std::string& content_name) const;
   void UpdateNegotiationNeeded();
 
+  // Returns the media section in the given session description that is
+  // associated with the RtpTransceiver. Returns null if none found or this
+  // RtpTransceiver is not associated. Logic varies depending on the
+  // SdpSemantics specified in the configuration.
+  const cricket::ContentInfo* FindMediaSectionForTransceiver(
+      rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+          transceiver,
+      const SessionDescriptionInterface* sdesc) const;
+
  private:
   class ImplicitCreateSessionDescriptionObserver;
   friend class ImplicitCreateSessionDescriptionObserver;
@@ -253,6 +262,55 @@
                              const cricket::ContentInfo& content,
                              const cricket::ContentGroup* bundle_group)
       RTC_RUN_ON(signaling_thread());
+  // Check if a call to SetLocalDescription is acceptable with a session
+  // description of the given type.
+  bool ExpectSetLocalDescription(SdpType type);
+  // Check if a call to SetRemoteDescription is acceptable with a session
+  // description of the given type.
+  bool ExpectSetRemoteDescription(SdpType type);
+
+  // The offer/answer machinery assumes the media section MID is present and
+  // unique. To support legacy end points that do not supply a=mid lines, this
+  // method will modify the session description to add MIDs generated according
+  // to the SDP semantics.
+  void FillInMissingRemoteMids(cricket::SessionDescription* remote_description);
+
+  // Returns an RtpTransciever, if available, that can be used to receive the
+  // given media type according to JSEP rules.
+  rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
+  FindAvailableTransceiverToReceive(cricket::MediaType media_type) const;
+
+  // Returns a MediaSessionOptions struct with options decided by |options|,
+  // the local MediaStreams and DataChannels.
+  void GetOptionsForOffer(const PeerConnectionInterface::RTCOfferAnswerOptions&
+                              offer_answer_options,
+                          cricket::MediaSessionOptions* session_options);
+  void GetOptionsForPlanBOffer(
+      const PeerConnectionInterface::RTCOfferAnswerOptions&
+          offer_answer_options,
+      cricket::MediaSessionOptions* session_options)
+      RTC_RUN_ON(signaling_thread());
+  void GetOptionsForUnifiedPlanOffer(
+      const PeerConnectionInterface::RTCOfferAnswerOptions&
+          offer_answer_options,
+      cricket::MediaSessionOptions* session_options)
+      RTC_RUN_ON(signaling_thread());
+
+  // Returns a MediaSessionOptions struct with options decided by
+  // |constraints|, the local MediaStreams and DataChannels.
+  void GetOptionsForAnswer(const PeerConnectionInterface::RTCOfferAnswerOptions&
+                               offer_answer_options,
+                           cricket::MediaSessionOptions* session_options);
+  void GetOptionsForPlanBAnswer(
+      const PeerConnectionInterface::RTCOfferAnswerOptions&
+          offer_answer_options,
+      cricket::MediaSessionOptions* session_options)
+      RTC_RUN_ON(signaling_thread());
+  void GetOptionsForUnifiedPlanAnswer(
+      const PeerConnectionInterface::RTCOfferAnswerOptions&
+          offer_answer_options,
+      cricket::MediaSessionOptions* session_options)
+      RTC_RUN_ON(signaling_thread());
 
   // ===================================================================