Adding support for Unified Plan offer/answer negotiation to the mediasession layer.

This layer takes in a simplified "options" struct and the current local description,
and generates a new offer/answer. Previously the options struct assumed there would
only be one media description per media type (audio/video), but it now supports
N number of audio/video descriptions.

The |add_legacy_stream| options is removed from the mediasession.cc/.h
in this CL.

The next step is to add the ability for PeerConnection/WebRtcSession to create
"options" to represent multiple RtpTransceivers, and apply the Unified Plan
descriptions correctly. Right now, only Plan B descriptions will be
generated in unit tests.

BUG=chromium:465349

Review-Url: https://codereview.webrtc.org/2991693002
Cr-Commit-Position: refs/heads/master@{#19343}
diff --git a/webrtc/pc/mediasession.cc b/webrtc/pc/mediasession.cc
index de8f72c..4d4744a 100644
--- a/webrtc/pc/mediasession.cc
+++ b/webrtc/pc/mediasession.cc
@@ -188,11 +188,14 @@
   return true;
 }
 
-const CryptoParamsVec* GetCryptos(const MediaContentDescription* media) {
-  if (!media) {
-    return NULL;
+const CryptoParamsVec* GetCryptos(const ContentInfo* content) {
+  if (!content) {
+    return nullptr;
   }
-  return &media->cryptos();
+
+  RTC_DCHECK(IsMediaContent(content));
+  return &(static_cast<const MediaContentDescription*>(content->description)
+               ->cryptos());
 }
 
 bool FindMatchingCrypto(const CryptoParamsVec& cryptos,
@@ -428,15 +431,15 @@
  private:
 };
 
-// Adds a StreamParams for each Stream in Streams with media type
-// media_type to content_description.
+// Adds a StreamParams for each SenderOptions in |sender_options| to
+// content_description.
 // |current_params| - All currently known StreamParams of any media type.
 template <class C>
-static bool AddStreamParams(MediaType media_type,
-                            const MediaSessionOptions& options,
-                            StreamParamsVec* current_streams,
-                            MediaContentDescriptionImpl<C>* content_description,
-                            const bool add_legacy_stream) {
+static bool AddStreamParams(
+    const std::vector<SenderOptions>& sender_options,
+    const std::string& rtcp_cname,
+    StreamParamsVec* current_streams,
+    MediaContentDescriptionImpl<C>* content_description) {
   // SCTP streams are not negotiated using SDP/ContentDescriptions.
   if (IsSctp(content_description->protocol())) {
     return true;
@@ -445,44 +448,26 @@
   const bool include_rtx_streams =
       ContainsRtxCodec(content_description->codecs());
 
-  const MediaSessionOptions::Streams& streams = options.streams;
-  if (streams.empty() && add_legacy_stream) {
-    // TODO(perkj): Remove this legacy stream when all apps use StreamParams.
-    std::vector<uint32_t> ssrcs;
-    int num_ssrcs = include_rtx_streams ? 2 : 1;
-    GenerateSsrcs(*current_streams, num_ssrcs, &ssrcs);
-    if (include_rtx_streams) {
-      content_description->AddLegacyStream(ssrcs[0], ssrcs[1]);
-      content_description->set_multistream(true);
-    } else {
-      content_description->AddLegacyStream(ssrcs[0]);
-    }
-    return true;
-  }
 
   const bool include_flexfec_stream =
       ContainsFlexfecCodec(content_description->codecs());
 
-  MediaSessionOptions::Streams::const_iterator stream_it;
-  for (stream_it = streams.begin();
-       stream_it != streams.end(); ++stream_it) {
-    if (stream_it->type != media_type)
-      continue;  // Wrong media type.
-
-    StreamParams* param = GetStreamByIds(*current_streams, "", stream_it->id);
+  for (const SenderOptions& sender : sender_options) {
     // groupid is empty for StreamParams generated using
     // MediaSessionDescriptionFactory.
+    StreamParams* param =
+        GetStreamByIds(*current_streams, "" /*group_id*/, sender.track_id);
     if (!param) {
-      // This is a new stream.
+      // This is a new sender.
       std::vector<uint32_t> ssrcs;
-      GenerateSsrcs(*current_streams, stream_it->num_sim_layers, &ssrcs);
+      GenerateSsrcs(*current_streams, sender.num_sim_layers, &ssrcs);
       StreamParams stream_param;
-      stream_param.id = stream_it->id;
+      stream_param.id = sender.track_id;
       // Add the generated ssrc.
       for (size_t i = 0; i < ssrcs.size(); ++i) {
         stream_param.ssrcs.push_back(ssrcs[i]);
       }
-      if (stream_it->num_sim_layers > 1) {
+      if (sender.num_sim_layers > 1) {
         SsrcGroup group(kSimSsrcGroupSemantics, stream_param.ssrcs);
         stream_param.ssrc_groups.push_back(group);
       }
@@ -512,8 +497,8 @@
               << "media streams however, so no FlexFEC SSRC will be generated.";
         }
       }
-      stream_param.cname = options.rtcp_cname;
-      stream_param.sync_label = stream_it->sync_label;
+      stream_param.cname = rtcp_cname;
+      stream_param.sync_label = sender.stream_id;
       content_description->AddStream(stream_param);
 
       // Store the new StreamParams in current_streams.
@@ -523,7 +508,7 @@
       // Use existing generated SSRCs/groups, but update the sync_label if
       // necessary. This may be needed if a MediaStreamTrack was moved from one
       // MediaStream to another.
-      param->sync_label = stream_it->sync_label;
+      param->sync_label = sender.stream_id;
       content_description->AddStream(*param);
     }
   }
@@ -735,46 +720,34 @@
   return STR_CASE_CMP(codec.name.c_str(), kFlexfecCodecName) == 0;
 }
 
-static TransportOptions GetTransportOptions(const MediaSessionOptions& options,
-                                            const std::string& content_name) {
-  TransportOptions transport_options;
-  auto it = options.transport_options.find(content_name);
-  if (it != options.transport_options.end()) {
-    transport_options = it->second;
-  }
-  transport_options.enable_ice_renomination = options.enable_ice_renomination;
-  return transport_options;
-}
-
-// Create a media content to be offered in a session-initiate,
-// according to the given options.rtcp_mux, options.is_muc,
-// options.streams, codecs, secure_transport, crypto, and streams.  If we don't
-// currently have crypto (in current_cryptos) and it is enabled (in
-// secure_policy), crypto is created (according to crypto_suites).  If
-// add_legacy_stream is true, and current_streams is empty, a legacy
-// stream is created.  The created content is added to the offer.
+// Create a media content to be offered for the given |sender_options|,
+// according to the given options.rtcp_mux, session_options.is_muc, codecs,
+// secure_transport, crypto, and current_streams. If we don't currently have
+// crypto (in current_cryptos) and it is enabled (in secure_policy), crypto is
+// created (according to crypto_suites). The created content is added to the
+// offer.
 template <class C>
 static bool CreateMediaContentOffer(
-    const MediaSessionOptions& options,
+    const std::vector<SenderOptions>& sender_options,
+    const MediaSessionOptions& session_options,
     const std::vector<C>& codecs,
     const SecurePolicy& secure_policy,
     const CryptoParamsVec* current_cryptos,
     const std::vector<std::string>& crypto_suites,
     const RtpHeaderExtensions& rtp_extensions,
-    bool add_legacy_stream,
     StreamParamsVec* current_streams,
     MediaContentDescriptionImpl<C>* offer) {
   offer->AddCodecs(codecs);
 
-  offer->set_rtcp_mux(options.rtcp_mux_enabled);
+  offer->set_rtcp_mux(session_options.rtcp_mux_enabled);
   if (offer->type() == cricket::MEDIA_TYPE_VIDEO) {
     offer->set_rtcp_reduced_size(true);
   }
-  offer->set_multistream(options.is_muc);
+  offer->set_multistream(session_options.is_muc);
   offer->set_rtp_header_extensions(rtp_extensions);
 
-  if (!AddStreamParams(offer->type(), options, current_streams, offer,
-                       add_legacy_stream)) {
+  if (!AddStreamParams(sender_options, session_options.rtcp_cname,
+                       current_streams, offer)) {
     return false;
   }
 
@@ -882,15 +855,42 @@
   return false;
 }
 
-// Adds all codecs from |reference_codecs| to |offered_codecs| that dont'
+// Find the codec in |codec_list| that |rtx_codec| is associated with.
+template <class C>
+static const C* GetAssociatedCodec(const std::vector<C>& codec_list,
+                                   const C& rtx_codec) {
+  std::string associated_pt_str;
+  if (!rtx_codec.GetParam(kCodecParamAssociatedPayloadType,
+                          &associated_pt_str)) {
+    LOG(LS_WARNING) << "RTX codec " << rtx_codec.name
+                    << " is missing an associated payload type.";
+    return nullptr;
+  }
+
+  int associated_pt;
+  if (!rtc::FromString(associated_pt_str, &associated_pt)) {
+    LOG(LS_WARNING) << "Couldn't convert payload type " << associated_pt_str
+                    << " of RTX codec " << rtx_codec.name << " to an integer.";
+    return nullptr;
+  }
+
+  // Find the associated reference codec for the reference RTX codec.
+  const C* associated_codec = FindCodecById(codec_list, associated_pt);
+  if (!associated_codec) {
+    LOG(LS_WARNING) << "Couldn't find associated codec with payload type "
+                    << associated_pt << " for RTX codec " << rtx_codec.name
+                    << ".";
+  }
+  return associated_codec;
+}
+
+// Adds all codecs from |reference_codecs| to |offered_codecs| that don't
 // already exist in |offered_codecs| and ensure the payload types don't
 // collide.
 template <class C>
-static void FindCodecsToOffer(
-    const std::vector<C>& reference_codecs,
-    std::vector<C>* offered_codecs,
-    UsedPayloadTypes* used_pltypes) {
-
+static void MergeCodecs(const std::vector<C>& reference_codecs,
+                        std::vector<C>* offered_codecs,
+                        UsedPayloadTypes* used_pltypes) {
   // Add all new codecs that are not RTX codecs.
   for (const C& reference_codec : reference_codecs) {
     if (!IsRtxCodec(reference_codec) &&
@@ -908,33 +908,11 @@
         !FindMatchingCodec<C>(reference_codecs, *offered_codecs,
                               reference_codec, nullptr)) {
       C rtx_codec = reference_codec;
-
-      std::string associated_pt_str;
-      if (!rtx_codec.GetParam(kCodecParamAssociatedPayloadType,
-                              &associated_pt_str)) {
-        LOG(LS_WARNING) << "RTX codec " << rtx_codec.name
-                        << " is missing an associated payload type.";
-        continue;
-      }
-
-      int associated_pt;
-      if (!rtc::FromString(associated_pt_str, &associated_pt)) {
-        LOG(LS_WARNING) << "Couldn't convert payload type " << associated_pt_str
-                        << " of RTX codec " << rtx_codec.name
-                        << " to an integer.";
-        continue;
-      }
-
-      // Find the associated reference codec for the reference RTX codec.
       const C* associated_codec =
-          FindCodecById(reference_codecs, associated_pt);
+          GetAssociatedCodec(reference_codecs, rtx_codec);
       if (!associated_codec) {
-        LOG(LS_WARNING) << "Couldn't find associated codec with payload type "
-                        << associated_pt << " for RTX codec " << rtx_codec.name
-                        << ".";
         continue;
       }
-
       // Find a codec in the offered list that matches the reference codec.
       // Its payload type may be different than the reference codec.
       C matching_codec;
@@ -953,6 +931,22 @@
   }
 }
 
+static bool FindByUriAndEncryption(const RtpHeaderExtensions& extensions,
+                                   const webrtc::RtpExtension& ext_to_match,
+                                   webrtc::RtpExtension* found_extension) {
+  for (RtpHeaderExtensions::const_iterator it = extensions.begin();
+       it != extensions.end(); ++it) {
+    // We assume that all URIs are given in a canonical format.
+    if (it->uri == ext_to_match.uri && it->encrypt == ext_to_match.encrypt) {
+      if (found_extension) {
+        *found_extension = *it;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
 static bool FindByUri(const RtpHeaderExtensions& extensions,
                       const webrtc::RtpExtension& ext_to_match,
                       webrtc::RtpExtension* found_extension) {
@@ -996,50 +990,41 @@
   return false;
 }
 
-// Iterates through |offered_extensions|, adding each one to
-// |regular_extensions| (or |encrypted_extensions| if encrypted) and |used_ids|,
-// and resolving ID conflicts.
-// If an offered extension has the same URI as one in |regular_extensions| or
-// |encrypted_extensions|, it will re-use the same ID and won't be treated as
-// a conflict.
-static void FindAndSetRtpHdrExtUsed(RtpHeaderExtensions* offered_extensions,
-                                    RtpHeaderExtensions* regular_extensions,
-                                    RtpHeaderExtensions* encrypted_extensions,
-                                    UsedRtpHeaderExtensionIds* used_ids) {
-  for (auto& extension : *offered_extensions) {
-    webrtc::RtpExtension existing;
-    if ((extension.encrypt &&
-        FindByUri(*encrypted_extensions, extension, &existing)) ||
-       (!extension.encrypt &&
-        FindByUri(*regular_extensions, extension, &existing))) {
-      extension.id = existing.id;
-    } else {
-      used_ids->FindAndSetIdUsed(&extension);
-      if (extension.encrypt) {
-        encrypted_extensions->push_back(extension);
-      } else {
-        regular_extensions->push_back(extension);
-      }
-    }
-  }
-}
-
-// Adds |reference_extensions| to |offered_extensions|, while updating
-// |all_extensions| and |used_ids|.
-static void FindRtpHdrExtsToOffer(
-    const RtpHeaderExtensions& reference_extensions,
-    RtpHeaderExtensions* offered_extensions,
-    RtpHeaderExtensions* all_extensions,
-    UsedRtpHeaderExtensionIds* used_ids) {
+// Adds all extensions from |reference_extensions| to |offered_extensions| that
+// don't already exist in |offered_extensions| and ensure the IDs don't
+// collide. If an extension is added, it's also added to |regular_extensions| or
+// |encrypted_extensions|, and if the extension is in |regular_extensions| or
+// |encrypted_extensions|, its ID is marked as used in |used_ids|.
+// |offered_extensions| is for either audio or video while |regular_extensions|
+// and |encrypted_extensions| are used for both audio and video. There could be
+// overlap between audio extensions and video extensions.
+static void MergeRtpHdrExts(const RtpHeaderExtensions& reference_extensions,
+                            RtpHeaderExtensions* offered_extensions,
+                            RtpHeaderExtensions* regular_extensions,
+                            RtpHeaderExtensions* encrypted_extensions,
+                            UsedRtpHeaderExtensionIds* used_ids) {
   for (auto reference_extension : reference_extensions) {
-    if (!FindByUri(*offered_extensions, reference_extension, NULL)) {
+    if (!FindByUriAndEncryption(*offered_extensions, reference_extension,
+                                nullptr)) {
       webrtc::RtpExtension existing;
-      if (FindByUri(*all_extensions, reference_extension, &existing)) {
-        offered_extensions->push_back(existing);
+      if (reference_extension.encrypt) {
+        if (FindByUriAndEncryption(*encrypted_extensions, reference_extension,
+                                   &existing)) {
+          offered_extensions->push_back(existing);
+        } else {
+          used_ids->FindAndSetIdUsed(&reference_extension);
+          encrypted_extensions->push_back(reference_extension);
+          offered_extensions->push_back(reference_extension);
+        }
       } else {
-        used_ids->FindAndSetIdUsed(&reference_extension);
-        all_extensions->push_back(reference_extension);
-        offered_extensions->push_back(reference_extension);
+        if (FindByUriAndEncryption(*regular_extensions, reference_extension,
+                                   &existing)) {
+          offered_extensions->push_back(existing);
+        } else {
+          used_ids->FindAndSetIdUsed(&reference_extension);
+          regular_extensions->push_back(reference_extension);
+          offered_extensions->push_back(reference_extension);
+        }
       }
     }
   }
@@ -1103,26 +1088,24 @@
   }
 }
 
-// Create a media content to be answered in a session-accept,
-// according to the given options.rtcp_mux, options.streams, codecs,
-// crypto, and streams.  If we don't currently have crypto (in
-// current_cryptos) and it is enabled (in secure_policy), crypto is
-// created (according to crypto_suites).  If add_legacy_stream is
-// true, and current_streams is empty, a legacy stream is created.
-// The codecs, rtcp_mux, and crypto are all negotiated with the offer
-// from the incoming session-initiate.  If the negotiation fails, this
-// method returns false.  The created content is added to the offer.
+// Create a media content to be answered for the given |sender_options|
+// according to the given session_options.rtcp_mux, session_options.streams,
+// codecs, crypto, and current_streams.  If we don't currently have crypto (in
+// current_cryptos) and it is enabled (in secure_policy), crypto is created
+// (according to crypto_suites). The codecs, rtcp_mux, and crypto are all
+// negotiated with the offer. If the negotiation fails, this method returns
+// false.  The created content is added to the offer.
 template <class C>
 static bool CreateMediaContentAnswer(
     const MediaContentDescriptionImpl<C>* offer,
-    const MediaSessionOptions& options,
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
     const std::vector<C>& local_codecs,
     const SecurePolicy& sdes_policy,
     const CryptoParamsVec* current_cryptos,
     const RtpHeaderExtensions& local_rtp_extenstions,
     bool enable_encrypted_rtp_header_extensions,
     StreamParamsVec* current_streams,
-    bool add_legacy_stream,
     bool bundle_enabled,
     MediaContentDescriptionImpl<C>* answer) {
   std::vector<C> negotiated_codecs;
@@ -1136,14 +1119,15 @@
                                &negotiated_rtp_extensions);
   answer->set_rtp_header_extensions(negotiated_rtp_extensions);
 
-  answer->set_rtcp_mux(options.rtcp_mux_enabled && offer->rtcp_mux());
+  answer->set_rtcp_mux(session_options.rtcp_mux_enabled && offer->rtcp_mux());
   if (answer->type() == cricket::MEDIA_TYPE_VIDEO) {
     answer->set_rtcp_reduced_size(offer->rtcp_reduced_size());
   }
 
   if (sdes_policy != SEC_DISABLED) {
     CryptoParams crypto;
-    if (SelectCrypto(offer, bundle_enabled, options.crypto_options, &crypto)) {
+    if (SelectCrypto(offer, bundle_enabled, session_options.crypto_options,
+                     &crypto)) {
       if (current_cryptos) {
         FindMatchingCrypto(*current_cryptos, crypto, &crypto);
       }
@@ -1155,29 +1139,17 @@
     return false;
   }
 
-  if (!AddStreamParams(answer->type(), options, current_streams, answer,
-                       add_legacy_stream)) {
+  if (!AddStreamParams(media_description_options.sender_options,
+                       session_options.rtcp_cname, current_streams, answer)) {
     return false;  // Something went seriously wrong.
   }
 
-  // Make sure the answer media content direction is per default set as
-  // described in RFC3264 section 6.1.
-  const bool is_data = !IsRtpProtocol(answer->protocol());
-  const bool has_send_streams = !answer->streams().empty();
-  const bool wants_send = has_send_streams || is_data;
-  const bool recv_audio =
-      answer->type() == cricket::MEDIA_TYPE_AUDIO && options.recv_audio;
-  const bool recv_video =
-      answer->type() == cricket::MEDIA_TYPE_VIDEO && options.recv_video;
-  const bool recv_data =
-      answer->type() == cricket::MEDIA_TYPE_DATA;
-  const bool wants_receive = recv_audio || recv_video || recv_data;
-
   auto offer_rtd =
       RtpTransceiverDirection::FromMediaContentDirection(offer->direction());
-  auto wants_rtd = RtpTransceiverDirection(wants_send, wants_receive);
-  answer->set_direction(NegotiateRtpTransceiverDirection(offer_rtd, wants_rtd)
-                        .ToMediaContentDirection());
+
+  answer->set_direction(NegotiateRtpTransceiverDirection(
+                            offer_rtd, media_description_options.direction)
+                            .ToMediaContentDirection());
   return true;
 }
 
@@ -1239,23 +1211,20 @@
 }
 
 // Gets the current DTLS state from the transport description.
-static bool IsDtlsActive(
-    const std::string& content_name,
-    const SessionDescription* current_description) {
-  if (!current_description)
+static bool IsDtlsActive(const ContentInfo* content,
+                         const SessionDescription* current_description) {
+  if (!content) {
     return false;
+  }
 
-  const ContentInfo* content =
-      current_description->GetContentByName(content_name);
-  if (!content)
+  size_t msection_index = content - &current_description->contents()[0];
+
+  if (current_description->transport_infos().size() <= msection_index) {
     return false;
+  }
 
-  const TransportDescription* current_tdesc =
-      GetTransportDescription(content_name, current_description);
-  if (!current_tdesc)
-    return false;
-
-  return current_tdesc->secure();
+  return current_description->transport_infos()[msection_index]
+      .description.secure();
 }
 
 std::string MediaContentDirectionToString(MediaContentDirection direction) {
@@ -1281,75 +1250,54 @@
   return dir_str;
 }
 
-void MediaSessionOptions::AddSendStream(MediaType type,
-                                    const std::string& id,
-                                    const std::string& sync_label) {
-  AddSendStreamInternal(type, id, sync_label, 1);
+void MediaDescriptionOptions::AddAudioSender(const std::string& track_id,
+                                             const std::string& stream_id) {
+  RTC_DCHECK(type == MEDIA_TYPE_AUDIO);
+  AddSenderInternal(track_id, stream_id, 1);
 }
 
-void MediaSessionOptions::AddSendVideoStream(
-    const std::string& id,
-    const std::string& sync_label,
-    int num_sim_layers) {
-  AddSendStreamInternal(MEDIA_TYPE_VIDEO, id, sync_label, num_sim_layers);
+void MediaDescriptionOptions::AddVideoSender(const std::string& track_id,
+                                             const std::string& stream_id,
+                                             int num_sim_layers) {
+  RTC_DCHECK(type == MEDIA_TYPE_VIDEO);
+  AddSenderInternal(track_id, stream_id, num_sim_layers);
 }
 
-void MediaSessionOptions::AddSendStreamInternal(
-    MediaType type,
-    const std::string& id,
-    const std::string& sync_label,
-    int num_sim_layers) {
-  streams.push_back(Stream(type, id, sync_label, num_sim_layers));
-
-  // If we haven't already set the data_channel_type, and we add a
-  // stream, we assume it's an RTP data stream.
-  if (type == MEDIA_TYPE_DATA && data_channel_type == DCT_NONE)
-    data_channel_type = DCT_RTP;
+void MediaDescriptionOptions::AddRtpDataChannel(const std::string& track_id,
+                                                const std::string& stream_id) {
+  RTC_DCHECK(type == MEDIA_TYPE_DATA);
+  AddSenderInternal(track_id, stream_id, 1);
 }
 
-void MediaSessionOptions::RemoveSendStream(MediaType type,
-                                       const std::string& id) {
-  Streams::iterator stream_it = streams.begin();
-  for (; stream_it != streams.end(); ++stream_it) {
-    if (stream_it->type == type && stream_it->id == id) {
-      streams.erase(stream_it);
-      return;
-    }
-  }
-  RTC_NOTREACHED();
+void MediaDescriptionOptions::AddSenderInternal(const std::string& track_id,
+                                                const std::string& stream_id,
+                                                int num_sim_layers) {
+  sender_options.push_back(SenderOptions{track_id, stream_id, num_sim_layers});
 }
 
-bool MediaSessionOptions::HasSendMediaStream(MediaType type) const {
-  Streams::const_iterator stream_it = streams.begin();
-  for (; stream_it != streams.end(); ++stream_it) {
-    if (stream_it->type == type) {
-      return true;
-    }
-  }
-  return false;
+bool MediaSessionOptions::HasMediaDescription(MediaType type) const {
+  return std::find_if(media_description_options.begin(),
+                      media_description_options.end(),
+                      [type](const MediaDescriptionOptions& t) {
+                        return t.type == type;
+                      }) != media_description_options.end();
 }
 
 MediaSessionDescriptionFactory::MediaSessionDescriptionFactory(
     const TransportDescriptionFactory* transport_desc_factory)
-    : secure_(SEC_DISABLED),
-      add_legacy_(true),
-      transport_desc_factory_(transport_desc_factory) {
-}
+    : transport_desc_factory_(transport_desc_factory) {}
 
 MediaSessionDescriptionFactory::MediaSessionDescriptionFactory(
     ChannelManager* channel_manager,
     const TransportDescriptionFactory* transport_desc_factory)
-    : secure_(SEC_DISABLED),
-      add_legacy_(true),
-      transport_desc_factory_(transport_desc_factory) {
+    : transport_desc_factory_(transport_desc_factory) {
   channel_manager->GetSupportedAudioSendCodecs(&audio_send_codecs_);
   channel_manager->GetSupportedAudioReceiveCodecs(&audio_recv_codecs_);
   channel_manager->GetSupportedAudioRtpHeaderExtensions(&audio_rtp_extensions_);
   channel_manager->GetSupportedVideoCodecs(&video_codecs_);
   channel_manager->GetSupportedVideoRtpHeaderExtensions(&video_rtp_extensions_);
   channel_manager->GetSupportedDataCodecs(&data_codecs_);
-  NegotiateCodecs(audio_recv_codecs_, audio_send_codecs_,
-                  &audio_sendrecv_codecs_);
+  ComputeAudioCodecsIntersectionAndUnion();
 }
 
 const AudioCodecs& MediaSessionDescriptionFactory::audio_sendrecv_codecs()
@@ -1369,129 +1317,114 @@
     const AudioCodecs& send_codecs, const AudioCodecs& recv_codecs) {
   audio_send_codecs_ = send_codecs;
   audio_recv_codecs_ = recv_codecs;
-  audio_sendrecv_codecs_.clear();
-  // Use NegotiateCodecs to merge our codec lists, since the operation is
-  // essentially the same. Put send_codecs as the offered_codecs, which is the
-  // order we'd like to follow. The reasoning is that encoding is usually more
-  // expensive than decoding, and prioritizing a codec in the send list probably
-  // means it's a codec we can handle efficiently.
-  NegotiateCodecs(recv_codecs, send_codecs, &audio_sendrecv_codecs_);
+  ComputeAudioCodecsIntersectionAndUnion();
 }
 
 SessionDescription* MediaSessionDescriptionFactory::CreateOffer(
-    const MediaSessionOptions& options,
+    const MediaSessionOptions& session_options,
     const SessionDescription* current_description) const {
   std::unique_ptr<SessionDescription> offer(new SessionDescription());
 
   StreamParamsVec current_streams;
   GetCurrentStreamParams(current_description, &current_streams);
 
-  const bool wants_send =
-      options.HasSendMediaStream(MEDIA_TYPE_AUDIO) || add_legacy_;
-  const AudioCodecs& supported_audio_codecs =
-      GetAudioCodecsForOffer({wants_send, options.recv_audio});
+  AudioCodecs offer_audio_codecs;
+  VideoCodecs offer_video_codecs;
+  DataCodecs offer_data_codecs;
+  GetCodecsForOffer(current_description, &offer_audio_codecs,
+                    &offer_video_codecs, &offer_data_codecs);
 
-  AudioCodecs audio_codecs;
-  VideoCodecs video_codecs;
-  DataCodecs data_codecs;
-  GetCodecsToOffer(current_description, supported_audio_codecs,
-                   video_codecs_, data_codecs_,
-                   &audio_codecs, &video_codecs, &data_codecs);
-
-  if (!options.vad_enabled) {
+  if (!session_options.vad_enabled) {
     // If application doesn't want CN codecs in offer.
-    StripCNCodecs(&audio_codecs);
+    StripCNCodecs(&offer_audio_codecs);
   }
+  FilterDataCodecs(&offer_data_codecs,
+                   session_options.data_channel_type == DCT_SCTP);
 
   RtpHeaderExtensions audio_rtp_extensions;
   RtpHeaderExtensions video_rtp_extensions;
   GetRtpHdrExtsToOffer(current_description, &audio_rtp_extensions,
                        &video_rtp_extensions);
 
-  bool audio_added = false;
-  bool video_added = false;
-  bool data_added = false;
-
-  // Iterate through the contents of |current_description| to maintain the order
-  // of the m-lines in the new offer.
+  // Must have options for each existing section.
   if (current_description) {
-    ContentInfos::const_iterator it = current_description->contents().begin();
-    for (; it != current_description->contents().end(); ++it) {
-      if (IsMediaContentOfType(&*it, MEDIA_TYPE_AUDIO)) {
-        if (!AddAudioContentForOffer(options, current_description,
-                                     audio_rtp_extensions, audio_codecs,
-                                     &current_streams, offer.get())) {
-          return NULL;
-        }
-        audio_added = true;
-      } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_VIDEO)) {
-        if (!AddVideoContentForOffer(options, current_description,
-                                     video_rtp_extensions, video_codecs,
-                                     &current_streams, offer.get())) {
-          return NULL;
-        }
-        video_added = true;
-      } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_DATA)) {
-        MediaSessionOptions options_copy(options);
-        if (IsSctp(static_cast<const MediaContentDescription*>(it->description)
-                       ->protocol())) {
-          options_copy.data_channel_type = DCT_SCTP;
-        }
-        if (!AddDataContentForOffer(options_copy, current_description,
-                                    &data_codecs, &current_streams,
-                                    offer.get())) {
-          return NULL;
-        }
-        data_added = true;
-      } else {
-        RTC_NOTREACHED();
-      }
-    }
+    RTC_DCHECK(current_description->contents().size() <=
+               session_options.media_description_options.size());
   }
 
-  // Append contents that are not in |current_description|.
-  if (!audio_added && options.has_audio() &&
-      !AddAudioContentForOffer(options, current_description,
-                               audio_rtp_extensions, audio_codecs,
-                               &current_streams, offer.get())) {
-    return NULL;
-  }
-  if (!video_added && options.has_video() &&
-      !AddVideoContentForOffer(options, current_description,
-                               video_rtp_extensions, video_codecs,
-                               &current_streams, offer.get())) {
-    return NULL;
-  }
-  if (!data_added && options.has_data() &&
-      !AddDataContentForOffer(options, current_description, &data_codecs,
-                              &current_streams, offer.get())) {
-    return NULL;
+  // Iterate through the media description options, matching with existing media
+  // descriptions in |current_description|.
+  int msection_index = 0;
+  for (const MediaDescriptionOptions& media_description_options :
+       session_options.media_description_options) {
+    const ContentInfo* current_content = nullptr;
+    if (current_description &&
+        msection_index <
+            static_cast<int>(current_description->contents().size())) {
+      current_content = &current_description->contents()[msection_index];
+      // Media type must match.
+      RTC_DCHECK(IsMediaContentOfType(current_content,
+                                      media_description_options.type));
+    }
+    switch (media_description_options.type) {
+      case MEDIA_TYPE_AUDIO:
+        if (!AddAudioContentForOffer(media_description_options, session_options,
+                                     current_content, current_description,
+                                     audio_rtp_extensions, offer_audio_codecs,
+                                     &current_streams, offer.get())) {
+          return nullptr;
+        }
+        break;
+      case MEDIA_TYPE_VIDEO:
+        if (!AddVideoContentForOffer(media_description_options, session_options,
+                                     current_content, current_description,
+                                     video_rtp_extensions, offer_video_codecs,
+                                     &current_streams, offer.get())) {
+          return nullptr;
+        }
+        break;
+      case MEDIA_TYPE_DATA:
+        if (!AddDataContentForOffer(media_description_options, session_options,
+                                    current_content, current_description,
+                                    offer_data_codecs, &current_streams,
+                                    offer.get())) {
+          return nullptr;
+        }
+        break;
+      default:
+        RTC_NOTREACHED();
+    }
+    ++msection_index;
   }
 
   // Bundle the contents together, if we've been asked to do so, and update any
   // parameters that need to be tweaked for BUNDLE.
-  if (options.bundle_enabled) {
+  if (session_options.bundle_enabled && offer->contents().size() > 0u) {
     ContentGroup offer_bundle(GROUP_TYPE_BUNDLE);
-    for (ContentInfos::const_iterator content = offer->contents().begin();
-       content != offer->contents().end(); ++content) {
-      offer_bundle.AddContentName(content->name);
+    for (const ContentInfo& content : offer->contents()) {
+      // TODO(deadbeef): There are conditions that make bundling two media
+      // descriptions together illegal. For example, they use the same payload
+      // type to represent different codecs, or same IDs for different header
+      // extensions. We need to detect this and not try to bundle those media
+      // descriptions together.
+      offer_bundle.AddContentName(content.name);
     }
     offer->AddGroup(offer_bundle);
     if (!UpdateTransportInfoForBundle(offer_bundle, offer.get())) {
       LOG(LS_ERROR) << "CreateOffer failed to UpdateTransportInfoForBundle.";
-      return NULL;
+      return nullptr;
     }
     if (!UpdateCryptoParamsForBundle(offer_bundle, offer.get())) {
       LOG(LS_ERROR) << "CreateOffer failed to UpdateCryptoParamsForBundle.";
-      return NULL;
+      return nullptr;
     }
   }
-
   return offer.release();
 }
 
 SessionDescription* MediaSessionDescriptionFactory::CreateAnswer(
-    const SessionDescription* offer, const MediaSessionOptions& options,
+    const SessionDescription* offer,
+    const MediaSessionOptions& session_options,
     const SessionDescription* current_description) const {
   if (!offer) {
     return nullptr;
@@ -1511,32 +1444,81 @@
   // Transport info shared by the bundle group.
   std::unique_ptr<TransportInfo> bundle_transport;
 
-  ContentInfos::const_iterator it = offer->contents().begin();
-  for (; it != offer->contents().end(); ++it) {
-    if (IsMediaContentOfType(&*it, MEDIA_TYPE_AUDIO)) {
-      if (!AddAudioContentForAnswer(offer, options, current_description,
-                                    bundle_transport.get(), &current_streams,
-                                    answer.get())) {
-        return NULL;
-      }
-    } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_VIDEO)) {
-      if (!AddVideoContentForAnswer(offer, options, current_description,
-                                    bundle_transport.get(), &current_streams,
-                                    answer.get())) {
-        return NULL;
-      }
-    } else {
-      RTC_DCHECK(IsMediaContentOfType(&*it, MEDIA_TYPE_DATA));
-      if (!AddDataContentForAnswer(offer, options, current_description,
-                                   bundle_transport.get(), &current_streams,
-                                   answer.get())) {
-        return NULL;
-      }
+  // Get list of all possible codecs that respects existing payload type
+  // mappings and uses a single payload type space.
+  //
+  // Note that these lists may be further filtered for each m= section; this
+  // step is done just to establish the payload type mappings shared by all
+  // sections.
+  AudioCodecs answer_audio_codecs;
+  VideoCodecs answer_video_codecs;
+  DataCodecs answer_data_codecs;
+  GetCodecsForAnswer(current_description, offer, &answer_audio_codecs,
+                     &answer_video_codecs, &answer_data_codecs);
+
+  if (!session_options.vad_enabled) {
+    // If application doesn't want CN codecs in answer.
+    StripCNCodecs(&answer_audio_codecs);
+  }
+  FilterDataCodecs(&answer_data_codecs,
+                   session_options.data_channel_type == DCT_SCTP);
+
+  // Must have options for exactly as many sections as in the offer.
+  RTC_DCHECK(offer->contents().size() ==
+             session_options.media_description_options.size());
+  // Iterate through the media description options, matching with existing
+  // media descriptions in |current_description|.
+  int msection_index = 0;
+  for (const MediaDescriptionOptions& media_description_options :
+       session_options.media_description_options) {
+    const ContentInfo* offer_content = &offer->contents()[msection_index];
+    // Media types and MIDs must match between the remote offer and the
+    // MediaDescriptionOptions.
+    RTC_DCHECK(
+        IsMediaContentOfType(offer_content, media_description_options.type));
+    RTC_DCHECK(media_description_options.mid == offer_content->name);
+    const ContentInfo* current_content = nullptr;
+    if (current_description &&
+        msection_index <
+            static_cast<int>(current_description->contents().size())) {
+      current_content = &current_description->contents()[msection_index];
     }
+    switch (media_description_options.type) {
+      case MEDIA_TYPE_AUDIO:
+        if (!AddAudioContentForAnswer(
+                media_description_options, session_options, offer_content,
+                offer, current_content, current_description,
+                bundle_transport.get(), answer_audio_codecs, &current_streams,
+                answer.get())) {
+          return nullptr;
+        }
+        break;
+      case MEDIA_TYPE_VIDEO:
+        if (!AddVideoContentForAnswer(
+                media_description_options, session_options, offer_content,
+                offer, current_content, current_description,
+                bundle_transport.get(), answer_video_codecs, &current_streams,
+                answer.get())) {
+          return nullptr;
+        }
+        break;
+      case MEDIA_TYPE_DATA:
+        if (!AddDataContentForAnswer(media_description_options, session_options,
+                                     offer_content, offer, current_content,
+                                     current_description,
+                                     bundle_transport.get(), answer_data_codecs,
+                                     &current_streams, answer.get())) {
+          return nullptr;
+        }
+        break;
+      default:
+        RTC_NOTREACHED();
+    }
+    ++msection_index;
     // See if we can add the newly generated m= section to the BUNDLE group in
     // the answer.
     ContentInfo& added = answer->contents().back();
-    if (!added.rejected && options.bundle_enabled && offer_bundle &&
+    if (!added.rejected && session_options.bundle_enabled && offer_bundle &&
         offer_bundle->HasContentName(added.name)) {
       answer_bundle.AddContentName(added.name);
       bundle_transport.reset(
@@ -1596,11 +1578,37 @@
   }
 }
 
-void MediaSessionDescriptionFactory::GetCodecsToOffer(
+void MergeCodecsFromDescription(const SessionDescription* description,
+                                AudioCodecs* audio_codecs,
+                                VideoCodecs* video_codecs,
+                                DataCodecs* data_codecs,
+                                UsedPayloadTypes* used_pltypes) {
+  RTC_DCHECK(description);
+  for (const ContentInfo& content : description->contents()) {
+    if (IsMediaContentOfType(&content, MEDIA_TYPE_AUDIO)) {
+      const AudioContentDescription* audio =
+          static_cast<AudioContentDescription*>(content.description);
+      MergeCodecs<AudioCodec>(audio->codecs(), audio_codecs, used_pltypes);
+    } else if (IsMediaContentOfType(&content, MEDIA_TYPE_VIDEO)) {
+      const VideoContentDescription* video =
+          static_cast<VideoContentDescription*>(content.description);
+      MergeCodecs<VideoCodec>(video->codecs(), video_codecs, used_pltypes);
+    } else if (IsMediaContentOfType(&content, MEDIA_TYPE_DATA)) {
+      const DataContentDescription* data =
+          static_cast<DataContentDescription*>(content.description);
+      MergeCodecs<DataCodec>(data->codecs(), data_codecs, used_pltypes);
+    }
+  }
+}
+
+// Getting codecs for an offer involves these steps:
+//
+// 1. Construct payload type -> codec mappings for current description.
+// 2. Add any reference codecs that weren't already present
+// 3. For each individual media description (m= section), filter codecs based
+//    on the directional attribute (happens in another method).
+void MediaSessionDescriptionFactory::GetCodecsForOffer(
     const SessionDescription* current_description,
-    const AudioCodecs& supported_audio_codecs,
-    const VideoCodecs& supported_video_codecs,
-    const DataCodecs& supported_data_codecs,
     AudioCodecs* audio_codecs,
     VideoCodecs* video_codecs,
     DataCodecs* data_codecs) const {
@@ -1609,87 +1617,150 @@
   video_codecs->clear();
   data_codecs->clear();
 
-
   // First - get all codecs from the current description if the media type
-  // is used.
-  // Add them to |used_pltypes| so the payloadtype is not reused if a new media
-  // type is added.
+  // is used. Add them to |used_pltypes| so the payload type is not reused if a
+  // new media type is added.
   if (current_description) {
-    const AudioContentDescription* audio =
-        GetFirstAudioContentDescription(current_description);
-    if (audio) {
-      *audio_codecs = audio->codecs();
-      used_pltypes.FindAndSetIdUsed<AudioCodec>(audio_codecs);
-    }
-    const VideoContentDescription* video =
-        GetFirstVideoContentDescription(current_description);
-    if (video) {
-      *video_codecs = video->codecs();
-      used_pltypes.FindAndSetIdUsed<VideoCodec>(video_codecs);
-    }
-    const DataContentDescription* data =
-        GetFirstDataContentDescription(current_description);
-    if (data) {
-      *data_codecs = data->codecs();
-      used_pltypes.FindAndSetIdUsed<DataCodec>(data_codecs);
-    }
+    MergeCodecsFromDescription(current_description, audio_codecs, video_codecs,
+                               data_codecs, &used_pltypes);
   }
 
   // Add our codecs that are not in |current_description|.
-  FindCodecsToOffer<AudioCodec>(supported_audio_codecs, audio_codecs,
-                                &used_pltypes);
-  FindCodecsToOffer<VideoCodec>(supported_video_codecs, video_codecs,
-                                &used_pltypes);
-  FindCodecsToOffer<DataCodec>(supported_data_codecs, data_codecs,
-                               &used_pltypes);
+  MergeCodecs<AudioCodec>(all_audio_codecs_, audio_codecs, &used_pltypes);
+  MergeCodecs<VideoCodec>(video_codecs_, video_codecs, &used_pltypes);
+  MergeCodecs<DataCodec>(data_codecs_, data_codecs, &used_pltypes);
+}
+
+// Getting codecs for an answer involves these steps:
+//
+// 1. Construct payload type -> codec mappings for current description.
+// 2. Add any codecs from the offer that weren't already present.
+// 3. Add any remaining codecs that weren't already present.
+// 4. For each individual media description (m= section), filter codecs based
+//    on the directional attribute (happens in another method).
+void MediaSessionDescriptionFactory::GetCodecsForAnswer(
+    const SessionDescription* current_description,
+    const SessionDescription* remote_offer,
+    AudioCodecs* audio_codecs,
+    VideoCodecs* video_codecs,
+    DataCodecs* data_codecs) const {
+  UsedPayloadTypes used_pltypes;
+  audio_codecs->clear();
+  video_codecs->clear();
+  data_codecs->clear();
+
+  // First - get all codecs from the current description if the media type
+  // is used. Add them to |used_pltypes| so the payload type is not reused if a
+  // new media type is added.
+  if (current_description) {
+    MergeCodecsFromDescription(current_description, audio_codecs, video_codecs,
+                               data_codecs, &used_pltypes);
+  }
+
+  // Second - filter out codecs that we don't support at all and should ignore.
+  AudioCodecs filtered_offered_audio_codecs;
+  VideoCodecs filtered_offered_video_codecs;
+  DataCodecs filtered_offered_data_codecs;
+  for (const ContentInfo& content : remote_offer->contents()) {
+    if (IsMediaContentOfType(&content, MEDIA_TYPE_AUDIO)) {
+      const AudioContentDescription* audio =
+          static_cast<AudioContentDescription*>(content.description);
+      for (const AudioCodec& offered_audio_codec : audio->codecs()) {
+        if (!FindMatchingCodec<AudioCodec>(audio->codecs(),
+                                           filtered_offered_audio_codecs,
+                                           offered_audio_codec, nullptr) &&
+            FindMatchingCodec<AudioCodec>(audio->codecs(), all_audio_codecs_,
+                                          offered_audio_codec, nullptr)) {
+          filtered_offered_audio_codecs.push_back(offered_audio_codec);
+        }
+      }
+    } else if (IsMediaContentOfType(&content, MEDIA_TYPE_VIDEO)) {
+      const VideoContentDescription* video =
+          static_cast<VideoContentDescription*>(content.description);
+      for (const VideoCodec& offered_video_codec : video->codecs()) {
+        if (!FindMatchingCodec<VideoCodec>(video->codecs(),
+                                           filtered_offered_video_codecs,
+                                           offered_video_codec, nullptr) &&
+            FindMatchingCodec<VideoCodec>(video->codecs(), video_codecs_,
+                                          offered_video_codec, nullptr)) {
+          filtered_offered_video_codecs.push_back(offered_video_codec);
+        }
+      }
+    } else if (IsMediaContentOfType(&content, MEDIA_TYPE_DATA)) {
+      const DataContentDescription* data =
+          static_cast<DataContentDescription*>(content.description);
+      for (const DataCodec& offered_data_codec : data->codecs()) {
+        if (!FindMatchingCodec<DataCodec>(data->codecs(),
+                                          filtered_offered_data_codecs,
+                                          offered_data_codec, nullptr) &&
+            FindMatchingCodec<DataCodec>(data->codecs(), data_codecs_,
+                                         offered_data_codec, nullptr)) {
+          filtered_offered_data_codecs.push_back(offered_data_codec);
+        }
+      }
+    }
+  }
+
+  // Add codecs that are not in |current_description| but were in
+  // |remote_offer|.
+  MergeCodecs<AudioCodec>(filtered_offered_audio_codecs, audio_codecs,
+                          &used_pltypes);
+  MergeCodecs<VideoCodec>(filtered_offered_video_codecs, video_codecs,
+                          &used_pltypes);
+  MergeCodecs<DataCodec>(filtered_offered_data_codecs, data_codecs,
+                         &used_pltypes);
 }
 
 void MediaSessionDescriptionFactory::GetRtpHdrExtsToOffer(
     const SessionDescription* current_description,
-    RtpHeaderExtensions* audio_extensions,
-    RtpHeaderExtensions* video_extensions) const {
+    RtpHeaderExtensions* offer_audio_extensions,
+    RtpHeaderExtensions* offer_video_extensions) const {
   // All header extensions allocated from the same range to avoid potential
   // issues when using BUNDLE.
   UsedRtpHeaderExtensionIds used_ids;
   RtpHeaderExtensions all_regular_extensions;
   RtpHeaderExtensions all_encrypted_extensions;
-  audio_extensions->clear();
-  video_extensions->clear();
+  offer_audio_extensions->clear();
+  offer_video_extensions->clear();
 
   // First - get all extensions from the current description if the media type
   // is used.
   // Add them to |used_ids| so the local ids are not reused if a new media
   // type is added.
   if (current_description) {
-    const AudioContentDescription* audio =
-        GetFirstAudioContentDescription(current_description);
-    if (audio) {
-      *audio_extensions = audio->rtp_header_extensions();
-      FindAndSetRtpHdrExtUsed(audio_extensions, &all_regular_extensions,
-          &all_encrypted_extensions, &used_ids);
-    }
-    const VideoContentDescription* video =
-        GetFirstVideoContentDescription(current_description);
-    if (video) {
-      *video_extensions = video->rtp_header_extensions();
-      FindAndSetRtpHdrExtUsed(video_extensions, &all_regular_extensions,
-          &all_encrypted_extensions, &used_ids);
+    for (const ContentInfo& content : current_description->contents()) {
+      if (IsMediaContentOfType(&content, MEDIA_TYPE_AUDIO)) {
+        const AudioContentDescription* audio =
+            static_cast<const AudioContentDescription*>(content.description);
+        MergeRtpHdrExts(audio->rtp_header_extensions(), offer_audio_extensions,
+                        &all_regular_extensions, &all_encrypted_extensions,
+                        &used_ids);
+      } else if (IsMediaContentOfType(&content, MEDIA_TYPE_VIDEO)) {
+        const VideoContentDescription* video =
+            static_cast<const VideoContentDescription*>(content.description);
+        MergeRtpHdrExts(video->rtp_header_extensions(), offer_video_extensions,
+                        &all_regular_extensions, &all_encrypted_extensions,
+                        &used_ids);
+      }
     }
   }
 
   // Add our default RTP header extensions that are not in
   // |current_description|.
-  FindRtpHdrExtsToOffer(audio_rtp_header_extensions(), audio_extensions,
-                        &all_regular_extensions, &used_ids);
-  FindRtpHdrExtsToOffer(video_rtp_header_extensions(), video_extensions,
-                        &all_regular_extensions, &used_ids);
+  MergeRtpHdrExts(audio_rtp_header_extensions(), offer_audio_extensions,
+                  &all_regular_extensions, &all_encrypted_extensions,
+                  &used_ids);
+  MergeRtpHdrExts(video_rtp_header_extensions(), offer_video_extensions,
+                  &all_regular_extensions, &all_encrypted_extensions,
+                  &used_ids);
+
   // TODO(jbauch): Support adding encrypted header extensions to existing
   // sessions.
   if (enable_encrypted_rtp_header_extensions_ && !current_description) {
-    AddEncryptedVersionsOfHdrExts(audio_extensions, &all_encrypted_extensions,
-        &used_ids);
-    AddEncryptedVersionsOfHdrExts(video_extensions, &all_encrypted_extensions,
-        &used_ids);
+    AddEncryptedVersionsOfHdrExts(offer_audio_extensions,
+                                  &all_encrypted_extensions, &used_ids);
+    AddEncryptedVersionsOfHdrExts(offer_video_extensions,
+                                  &all_encrypted_extensions, &used_ids);
   }
 }
 
@@ -1743,35 +1814,71 @@
   return true;
 }
 
+// |audio_codecs| = set of all possible codecs that can be used, with correct
+// payload type mappings
+//
+// |supported_audio_codecs| = set of codecs that are supported for the direction
+// of this m= section
+//
+// acd->codecs() = set of previously negotiated codecs for this m= section
+//
+// The payload types should come from audio_codecs, but the order should come
+// from acd->codecs() and then supported_codecs, to ensure that re-offers don't
+// change existing codec priority, and that new codecs are added with the right
+// priority.
 bool MediaSessionDescriptionFactory::AddAudioContentForOffer(
-    const MediaSessionOptions& options,
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
+    const ContentInfo* current_content,
     const SessionDescription* current_description,
     const RtpHeaderExtensions& audio_rtp_extensions,
     const AudioCodecs& audio_codecs,
     StreamParamsVec* current_streams,
     SessionDescription* desc) const {
-  const ContentInfo* current_audio_content =
-      GetFirstAudioContent(current_description);
-  std::string content_name =
-      current_audio_content ? current_audio_content->name : CN_AUDIO;
+  // Filter audio_codecs (which includes all codecs, with correctly remapped
+  // payload types) based on transceiver direction.
+  const AudioCodecs& supported_audio_codecs =
+      GetAudioCodecsForOffer(media_description_options.direction);
+
+  AudioCodecs filtered_codecs;
+  // Add the codecs from current content if exists.
+  if (current_content) {
+    RTC_DCHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO));
+    const AudioContentDescription* acd =
+        static_cast<const AudioContentDescription*>(
+            current_content->description);
+    for (const AudioCodec& codec : acd->codecs()) {
+      if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs,
+                                        codec, nullptr)) {
+        filtered_codecs.push_back(codec);
+      }
+    }
+  }
+  // Add other supported audio codecs.
+  AudioCodec found_codec;
+  for (const AudioCodec& codec : supported_audio_codecs) {
+    if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs,
+                                      codec, &found_codec) &&
+        !FindMatchingCodec<AudioCodec>(supported_audio_codecs, filtered_codecs,
+                                       codec, nullptr)) {
+      // Use the |found_codec| from |audio_codecs| because it has the correctly
+      // mapped payload type.
+      filtered_codecs.push_back(found_codec);
+    }
+  }
 
   cricket::SecurePolicy sdes_policy =
-      IsDtlsActive(content_name, current_description) ? cricket::SEC_DISABLED
-                                                      : secure();
+      IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED
+                                                         : secure();
 
   std::unique_ptr<AudioContentDescription> audio(new AudioContentDescription());
   std::vector<std::string> crypto_suites;
-  GetSupportedAudioSdesCryptoSuiteNames(options.crypto_options, &crypto_suites);
+  GetSupportedAudioSdesCryptoSuiteNames(session_options.crypto_options,
+                                        &crypto_suites);
   if (!CreateMediaContentOffer(
-          options,
-          audio_codecs,
-          sdes_policy,
-          GetCryptos(GetFirstAudioContentDescription(current_description)),
-          crypto_suites,
-          audio_rtp_extensions,
-          add_legacy_,
-          current_streams,
-          audio.get())) {
+          media_description_options.sender_options, session_options,
+          filtered_codecs, sdes_policy, GetCryptos(current_content),
+          crypto_suites, audio_rtp_extensions, current_streams, audio.get())) {
     return false;
   }
   audio->set_lang(lang_);
@@ -1779,13 +1886,13 @@
   bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
   SetMediaProtocol(secure_transport, audio.get());
 
-  auto offer_rtd =
-      RtpTransceiverDirection(!audio->streams().empty(), options.recv_audio);
-  audio->set_direction(offer_rtd.ToMediaContentDirection());
+  audio->set_direction(
+      media_description_options.direction.ToMediaContentDirection());
 
-  desc->AddContent(content_name, NS_JINGLE_RTP, audio.release());
-  if (!AddTransportOffer(content_name,
-                         GetTransportOptions(options, content_name),
+  desc->AddContent(media_description_options.mid, NS_JINGLE_RTP,
+                   media_description_options.stopped, audio.release());
+  if (!AddTransportOffer(media_description_options.mid,
+                         media_description_options.transport_options,
                          current_description, desc)) {
     return false;
   }
@@ -1794,87 +1901,98 @@
 }
 
 bool MediaSessionDescriptionFactory::AddVideoContentForOffer(
-    const MediaSessionOptions& options,
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
+    const ContentInfo* current_content,
     const SessionDescription* current_description,
     const RtpHeaderExtensions& video_rtp_extensions,
     const VideoCodecs& video_codecs,
     StreamParamsVec* current_streams,
     SessionDescription* desc) const {
-  const ContentInfo* current_video_content =
-      GetFirstVideoContent(current_description);
-  std::string content_name =
-      current_video_content ? current_video_content->name : CN_VIDEO;
-
   cricket::SecurePolicy sdes_policy =
-      IsDtlsActive(content_name, current_description) ? cricket::SEC_DISABLED
-                                                      : secure();
+      IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED
+                                                         : secure();
 
   std::unique_ptr<VideoContentDescription> video(new VideoContentDescription());
   std::vector<std::string> crypto_suites;
-  GetSupportedVideoSdesCryptoSuiteNames(options.crypto_options, &crypto_suites);
+  GetSupportedVideoSdesCryptoSuiteNames(session_options.crypto_options,
+                                        &crypto_suites);
+
+  VideoCodecs filtered_codecs;
+  // Add the codecs from current content if exists.
+  if (current_content) {
+    RTC_DCHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO));
+    const VideoContentDescription* vcd =
+        static_cast<const VideoContentDescription*>(
+            current_content->description);
+    for (const VideoCodec& codec : vcd->codecs()) {
+      if (FindMatchingCodec<VideoCodec>(video_codecs_, video_codecs, codec,
+                                        nullptr)) {
+        filtered_codecs.push_back(codec);
+      }
+    }
+  }
+  // Add other supported video codecs.
+  VideoCodec found_codec;
+  for (const VideoCodec& codec : video_codecs_) {
+    if (FindMatchingCodec<VideoCodec>(video_codecs_, video_codecs, codec,
+                                      &found_codec) &&
+        !FindMatchingCodec<VideoCodec>(video_codecs_, filtered_codecs, codec,
+                                       nullptr)) {
+      // Use the |found_codec| from |video_codecs| because it has the correctly
+      // mapped payload type.
+      filtered_codecs.push_back(found_codec);
+    }
+  }
+
   if (!CreateMediaContentOffer(
-          options,
-          video_codecs,
-          sdes_policy,
-          GetCryptos(GetFirstVideoContentDescription(current_description)),
-          crypto_suites,
-          video_rtp_extensions,
-          add_legacy_,
-          current_streams,
-          video.get())) {
+          media_description_options.sender_options, session_options,
+          filtered_codecs, sdes_policy, GetCryptos(current_content),
+          crypto_suites, video_rtp_extensions, current_streams, video.get())) {
     return false;
   }
 
-  video->set_bandwidth(options.video_bandwidth);
+  video->set_bandwidth(kAutoBandwidth);
 
   bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
   SetMediaProtocol(secure_transport, video.get());
 
-  if (!video->streams().empty()) {
-    if (options.recv_video) {
-      video->set_direction(MD_SENDRECV);
-    } else {
-      video->set_direction(MD_SENDONLY);
-    }
-  } else {
-    if (options.recv_video) {
-      video->set_direction(MD_RECVONLY);
-    } else {
-      video->set_direction(MD_INACTIVE);
-    }
-  }
+  video->set_direction(
+      media_description_options.direction.ToMediaContentDirection());
 
-  desc->AddContent(content_name, NS_JINGLE_RTP, video.release());
-  if (!AddTransportOffer(content_name,
-                         GetTransportOptions(options, content_name),
+  desc->AddContent(media_description_options.mid, NS_JINGLE_RTP,
+                   media_description_options.stopped, video.release());
+  if (!AddTransportOffer(media_description_options.mid,
+                         media_description_options.transport_options,
                          current_description, desc)) {
     return false;
   }
-
   return true;
 }
 
 bool MediaSessionDescriptionFactory::AddDataContentForOffer(
-    const MediaSessionOptions& options,
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
+    const ContentInfo* current_content,
     const SessionDescription* current_description,
-    DataCodecs* data_codecs,
+    const DataCodecs& data_codecs,
     StreamParamsVec* current_streams,
     SessionDescription* desc) const {
   bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
 
   std::unique_ptr<DataContentDescription> data(new DataContentDescription());
-  bool is_sctp = (options.data_channel_type == DCT_SCTP);
-
-  FilterDataCodecs(data_codecs, is_sctp);
-
-  const ContentInfo* current_data_content =
-      GetFirstDataContent(current_description);
-  std::string content_name =
-      current_data_content ? current_data_content->name : CN_DATA;
+  bool is_sctp = (session_options.data_channel_type == DCT_SCTP);
+  // If the DataChannel type is not specified, use the DataChannel type in
+  // the current description.
+  if (session_options.data_channel_type == DCT_NONE && current_content) {
+    is_sctp = (static_cast<const DataContentDescription*>(
+                   current_content->description)
+                   ->protocol() == kMediaProtocolSctp);
+  }
 
   cricket::SecurePolicy sdes_policy =
-      IsDtlsActive(content_name, current_description) ? cricket::SEC_DISABLED
-                                                      : secure();
+      IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED
+                                                         : secure();
   std::vector<std::string> crypto_suites;
   if (is_sctp) {
     // SDES doesn't make sense for SCTP, so we disable it, and we only
@@ -1890,227 +2008,279 @@
     data->set_protocol(
         secure_transport ? kMediaProtocolDtlsSctp : kMediaProtocolSctp);
   } else {
-    GetSupportedDataSdesCryptoSuiteNames(options.crypto_options,
+    GetSupportedDataSdesCryptoSuiteNames(session_options.crypto_options,
                                          &crypto_suites);
   }
 
+  // Even SCTP uses a "codec".
   if (!CreateMediaContentOffer(
-          options,
-          *data_codecs,
-          sdes_policy,
-          GetCryptos(GetFirstDataContentDescription(current_description)),
-          crypto_suites,
-          RtpHeaderExtensions(),
-          add_legacy_,
-          current_streams,
-          data.get())) {
+          media_description_options.sender_options, session_options,
+          data_codecs, sdes_policy, GetCryptos(current_content), crypto_suites,
+          RtpHeaderExtensions(), current_streams, data.get())) {
     return false;
   }
 
   if (is_sctp) {
-    desc->AddContent(content_name, NS_JINGLE_DRAFT_SCTP, data.release());
+    desc->AddContent(media_description_options.mid, NS_JINGLE_DRAFT_SCTP,
+                     data.release());
   } else {
-    data->set_bandwidth(options.data_bandwidth);
+    data->set_bandwidth(kDataMaxBandwidth);
     SetMediaProtocol(secure_transport, data.get());
-    desc->AddContent(content_name, NS_JINGLE_RTP, data.release());
+    desc->AddContent(media_description_options.mid, NS_JINGLE_RTP,
+                     media_description_options.stopped, data.release());
   }
-  if (!AddTransportOffer(content_name,
-                         GetTransportOptions(options, content_name),
+  if (!AddTransportOffer(media_description_options.mid,
+                         media_description_options.transport_options,
                          current_description, desc)) {
     return false;
   }
   return true;
 }
 
+// |audio_codecs| = set of all possible codecs that can be used, with correct
+// payload type mappings
+//
+// |supported_audio_codecs| = set of codecs that are supported for the direction
+// of this m= section
+//
+// acd->codecs() = set of previously negotiated codecs for this m= section
+//
+// The payload types should come from audio_codecs, but the order should come
+// from acd->codecs() and then supported_codecs, to ensure that re-offers don't
+// change existing codec priority, and that new codecs are added with the right
+// priority.
 bool MediaSessionDescriptionFactory::AddAudioContentForAnswer(
-    const SessionDescription* offer,
-    const MediaSessionOptions& options,
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
+    const ContentInfo* offer_content,
+    const SessionDescription* offer_description,
+    const ContentInfo* current_content,
     const SessionDescription* current_description,
     const TransportInfo* bundle_transport,
+    const AudioCodecs& audio_codecs,
     StreamParamsVec* current_streams,
     SessionDescription* answer) const {
-  const ContentInfo* audio_content = GetFirstAudioContent(offer);
-  const AudioContentDescription* offer_audio =
-      static_cast<const AudioContentDescription*>(audio_content->description);
+  const AudioContentDescription* offer_audio_description =
+      static_cast<const AudioContentDescription*>(offer_content->description);
 
   std::unique_ptr<TransportDescription> audio_transport(
-      CreateTransportAnswer(audio_content->name, offer,
-                            GetTransportOptions(options, audio_content->name),
+      CreateTransportAnswer(media_description_options.mid, offer_description,
+                            media_description_options.transport_options,
                             current_description, bundle_transport != nullptr));
   if (!audio_transport) {
     return false;
   }
 
-  // Pick codecs based on the requested communications direction in the offer.
-  const bool wants_send =
-      options.HasSendMediaStream(MEDIA_TYPE_AUDIO) || add_legacy_;
-  auto wants_rtd = RtpTransceiverDirection(wants_send, options.recv_audio);
-  auto offer_rtd =
-      RtpTransceiverDirection::FromMediaContentDirection(
-          offer_audio->direction());
+  // Pick codecs based on the requested communications direction in the offer
+  // and the selected direction in the answer.
+  // Note these will be filtered one final time in CreateMediaContentAnswer.
+  auto wants_rtd = media_description_options.direction;
+  auto offer_rtd = RtpTransceiverDirection::FromMediaContentDirection(
+      offer_audio_description->direction());
   auto answer_rtd = NegotiateRtpTransceiverDirection(offer_rtd, wants_rtd);
-  AudioCodecs audio_codecs = GetAudioCodecsForAnswer(offer_rtd, answer_rtd);
-  if (!options.vad_enabled) {
-    StripCNCodecs(&audio_codecs);
+  AudioCodecs supported_audio_codecs =
+      GetAudioCodecsForAnswer(offer_rtd, answer_rtd);
+
+  AudioCodecs filtered_codecs;
+  // Add the codecs from current content if exists.
+  if (current_content) {
+    RTC_DCHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO));
+    const AudioContentDescription* acd =
+        static_cast<const AudioContentDescription*>(
+            current_content->description);
+    for (const AudioCodec& codec : acd->codecs()) {
+      if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs,
+                                        codec, nullptr)) {
+        filtered_codecs.push_back(codec);
+      }
+    }
+  }
+  // Add other supported audio codecs.
+  AudioCodec found_codec;
+  for (const AudioCodec& codec : supported_audio_codecs) {
+    if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs,
+                                      codec, &found_codec) &&
+        !FindMatchingCodec<AudioCodec>(supported_audio_codecs, filtered_codecs,
+                                       codec, nullptr)) {
+      // Use the |found_codec| from |audio_codecs| because it has the correctly
+      // mapped payload type.
+      filtered_codecs.push_back(found_codec);
+    }
   }
 
-  bool bundle_enabled =
-      offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled;
+  bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) &&
+                        session_options.bundle_enabled;
   std::unique_ptr<AudioContentDescription> audio_answer(
       new AudioContentDescription());
   // Do not require or create SDES cryptos if DTLS is used.
   cricket::SecurePolicy sdes_policy =
       audio_transport->secure() ? cricket::SEC_DISABLED : secure();
   if (!CreateMediaContentAnswer(
-          offer_audio,
-          options,
-          audio_codecs,
-          sdes_policy,
-          GetCryptos(GetFirstAudioContentDescription(current_description)),
-          audio_rtp_extensions_,
-          enable_encrypted_rtp_header_extensions_,
-          current_streams,
-          add_legacy_,
-          bundle_enabled,
-          audio_answer.get())) {
+          offer_audio_description, media_description_options, session_options,
+          filtered_codecs, sdes_policy, GetCryptos(current_content),
+          audio_rtp_extensions_, enable_encrypted_rtp_header_extensions_,
+          current_streams, bundle_enabled, audio_answer.get())) {
     return false;  // Fails the session setup.
   }
 
   bool secure = bundle_transport ? bundle_transport->description.secure()
                                  : audio_transport->secure();
-  bool rejected = !options.has_audio() || audio_content->rejected ||
+  bool rejected = media_description_options.stopped ||
+                  offer_content->rejected ||
                   !IsMediaProtocolSupported(MEDIA_TYPE_AUDIO,
                                             audio_answer->protocol(), secure);
   if (!rejected) {
-    AddTransportAnswer(audio_content->name, *(audio_transport.get()), answer);
+    AddTransportAnswer(media_description_options.mid, *(audio_transport.get()),
+                       answer);
   } else {
-    // RFC 3264
-    // The answer MUST contain the same number of m-lines as the offer.
-    LOG(LS_INFO) << "Audio is not supported in the answer.";
+    LOG(LS_INFO) << "Audio m= section '" << media_description_options.mid
+                 << "' being rejected in answer.";
   }
 
-  answer->AddContent(audio_content->name, audio_content->type, rejected,
-                     audio_answer.release());
+  answer->AddContent(media_description_options.mid, offer_content->type,
+                     rejected, audio_answer.release());
   return true;
 }
 
 bool MediaSessionDescriptionFactory::AddVideoContentForAnswer(
-    const SessionDescription* offer,
-    const MediaSessionOptions& options,
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
+    const ContentInfo* offer_content,
+    const SessionDescription* offer_description,
+    const ContentInfo* current_content,
     const SessionDescription* current_description,
     const TransportInfo* bundle_transport,
+    const VideoCodecs& video_codecs,
     StreamParamsVec* current_streams,
     SessionDescription* answer) const {
-  const ContentInfo* video_content = GetFirstVideoContent(offer);
+  const VideoContentDescription* offer_video_description =
+      static_cast<const VideoContentDescription*>(offer_content->description);
+
   std::unique_ptr<TransportDescription> video_transport(
-      CreateTransportAnswer(video_content->name, offer,
-                            GetTransportOptions(options, video_content->name),
+      CreateTransportAnswer(media_description_options.mid, offer_description,
+                            media_description_options.transport_options,
                             current_description, bundle_transport != nullptr));
   if (!video_transport) {
     return false;
   }
 
+  VideoCodecs filtered_codecs;
+  // Add the codecs from current content if exists.
+  if (current_content) {
+    RTC_DCHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO));
+    const VideoContentDescription* vcd =
+        static_cast<const VideoContentDescription*>(
+            current_content->description);
+    for (const VideoCodec& codec : vcd->codecs()) {
+      if (FindMatchingCodec<VideoCodec>(video_codecs_, video_codecs, codec,
+                                        nullptr)) {
+        filtered_codecs.push_back(codec);
+      }
+    }
+  }
+  // Add other supported video codecs.
+  VideoCodec found_codec;
+  for (const VideoCodec& codec : video_codecs_) {
+    if (FindMatchingCodec<VideoCodec>(video_codecs_, video_codecs, codec,
+                                      &found_codec) &&
+        !FindMatchingCodec<VideoCodec>(video_codecs_, filtered_codecs, codec,
+                                       nullptr)) {
+      // Use the |found_codec| from |video_codecs| because it has the correctly
+      // mapped payload type.
+      filtered_codecs.push_back(found_codec);
+    }
+  }
+
+  bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) &&
+                        session_options.bundle_enabled;
+
   std::unique_ptr<VideoContentDescription> video_answer(
       new VideoContentDescription());
   // Do not require or create SDES cryptos if DTLS is used.
   cricket::SecurePolicy sdes_policy =
       video_transport->secure() ? cricket::SEC_DISABLED : secure();
-  bool bundle_enabled =
-      offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled;
   if (!CreateMediaContentAnswer(
-          static_cast<const VideoContentDescription*>(
-              video_content->description),
-          options,
-          video_codecs_,
-          sdes_policy,
-          GetCryptos(GetFirstVideoContentDescription(current_description)),
-          video_rtp_extensions_,
-          enable_encrypted_rtp_header_extensions_,
-          current_streams,
-          add_legacy_,
-          bundle_enabled,
-          video_answer.get())) {
-    return false;
+          offer_video_description, media_description_options, session_options,
+          filtered_codecs, sdes_policy, GetCryptos(current_content),
+          video_rtp_extensions_, enable_encrypted_rtp_header_extensions_,
+          current_streams, bundle_enabled, video_answer.get())) {
+    return false;  // Failed the sessin setup.
   }
   bool secure = bundle_transport ? bundle_transport->description.secure()
                                  : video_transport->secure();
-  bool rejected = !options.has_video() || video_content->rejected ||
+  bool rejected = media_description_options.stopped ||
+                  offer_content->rejected ||
                   !IsMediaProtocolSupported(MEDIA_TYPE_VIDEO,
                                             video_answer->protocol(), secure);
   if (!rejected) {
-    if (!AddTransportAnswer(video_content->name, *(video_transport.get()),
-                            answer)) {
+    if (!AddTransportAnswer(media_description_options.mid,
+                            *(video_transport.get()), answer)) {
       return false;
     }
-    video_answer->set_bandwidth(options.video_bandwidth);
+    video_answer->set_bandwidth(kAutoBandwidth);
   } else {
-    // RFC 3264
-    // The answer MUST contain the same number of m-lines as the offer.
-    LOG(LS_INFO) << "Video is not supported in the answer.";
+    LOG(LS_INFO) << "Video m= section '" << media_description_options.mid
+                 << "' being rejected in answer.";
   }
-  answer->AddContent(video_content->name, video_content->type, rejected,
-                     video_answer.release());
+  answer->AddContent(media_description_options.mid, offer_content->type,
+                     rejected, video_answer.release());
   return true;
 }
 
 bool MediaSessionDescriptionFactory::AddDataContentForAnswer(
-    const SessionDescription* offer,
-    const MediaSessionOptions& options,
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
+    const ContentInfo* offer_content,
+    const SessionDescription* offer_description,
+    const ContentInfo* current_content,
     const SessionDescription* current_description,
     const TransportInfo* bundle_transport,
+    const DataCodecs& data_codecs,
     StreamParamsVec* current_streams,
     SessionDescription* answer) const {
-  const ContentInfo* data_content = GetFirstDataContent(offer);
   std::unique_ptr<TransportDescription> data_transport(
-      CreateTransportAnswer(data_content->name, offer,
-                            GetTransportOptions(options, data_content->name),
+      CreateTransportAnswer(media_description_options.mid, offer_description,
+                            media_description_options.transport_options,
                             current_description, bundle_transport != nullptr));
   if (!data_transport) {
     return false;
   }
-  bool is_sctp = (options.data_channel_type == DCT_SCTP);
-  std::vector<DataCodec> data_codecs(data_codecs_);
-  FilterDataCodecs(&data_codecs, is_sctp);
 
   std::unique_ptr<DataContentDescription> data_answer(
       new DataContentDescription());
   // Do not require or create SDES cryptos if DTLS is used.
   cricket::SecurePolicy sdes_policy =
       data_transport->secure() ? cricket::SEC_DISABLED : secure();
-  bool bundle_enabled =
-      offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled;
+  bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) &&
+                        session_options.bundle_enabled;
   if (!CreateMediaContentAnswer(
           static_cast<const DataContentDescription*>(
-              data_content->description),
-          options,
-          data_codecs_,
-          sdes_policy,
-          GetCryptos(GetFirstDataContentDescription(current_description)),
-          RtpHeaderExtensions(),
-          enable_encrypted_rtp_header_extensions_,
-          current_streams,
-          add_legacy_,
-          bundle_enabled,
-          data_answer.get())) {
+              offer_content->description),
+          media_description_options, session_options, data_codecs, sdes_policy,
+          GetCryptos(current_content), RtpHeaderExtensions(),
+          enable_encrypted_rtp_header_extensions_, current_streams,
+          bundle_enabled, data_answer.get())) {
     return false;  // Fails the session setup.
   }
 
   // Respond with sctpmap if the offer uses sctpmap.
   const DataContentDescription* offer_data_description =
-      static_cast<const DataContentDescription*>(data_content->description);
+      static_cast<const DataContentDescription*>(offer_content->description);
   bool offer_uses_sctpmap = offer_data_description->use_sctpmap();
   data_answer->set_use_sctpmap(offer_uses_sctpmap);
 
   bool secure = bundle_transport ? bundle_transport->description.secure()
                                  : data_transport->secure();
 
-  bool rejected = !options.has_data() || data_content->rejected ||
+  bool rejected = session_options.data_channel_type == DCT_NONE ||
+                  media_description_options.stopped ||
+                  offer_content->rejected ||
                   !IsMediaProtocolSupported(MEDIA_TYPE_DATA,
                                             data_answer->protocol(), secure);
   if (!rejected) {
-    data_answer->set_bandwidth(options.data_bandwidth);
-    if (!AddTransportAnswer(data_content->name, *(data_transport.get()),
-                            answer)) {
+    data_answer->set_bandwidth(kDataMaxBandwidth);
+    if (!AddTransportAnswer(media_description_options.mid,
+                            *(data_transport.get()), answer)) {
       return false;
     }
   } else {
@@ -2118,11 +2288,39 @@
     // The answer MUST contain the same number of m-lines as the offer.
     LOG(LS_INFO) << "Data is not supported in the answer.";
   }
-  answer->AddContent(data_content->name, data_content->type, rejected,
-                     data_answer.release());
+  answer->AddContent(media_description_options.mid, offer_content->type,
+                     rejected, data_answer.release());
   return true;
 }
 
+void MediaSessionDescriptionFactory::ComputeAudioCodecsIntersectionAndUnion() {
+  audio_sendrecv_codecs_.clear();
+  all_audio_codecs_.clear();
+  // Compute the audio codecs union.
+  for (const AudioCodec& send : audio_send_codecs_) {
+    all_audio_codecs_.push_back(send);
+    if (!FindMatchingCodec<AudioCodec>(audio_send_codecs_, audio_recv_codecs_,
+                                       send, nullptr)) {
+      // It doesn't make sense to have an RTX codec we support sending but not
+      // receiving.
+      RTC_DCHECK(!IsRtxCodec(send));
+    }
+  }
+  for (const AudioCodec& recv : audio_recv_codecs_) {
+    if (!FindMatchingCodec<AudioCodec>(audio_recv_codecs_, audio_send_codecs_,
+                                       recv, nullptr)) {
+      all_audio_codecs_.push_back(recv);
+    }
+  }
+  // Use NegotiateCodecs to merge our codec lists, since the operation is
+  // essentially the same. Put send_codecs as the offered_codecs, which is the
+  // order we'd like to follow. The reasoning is that encoding is usually more
+  // expensive than decoding, and prioritizing a codec in the send list probably
+  // means it's a codec we can handle efficiently.
+  NegotiateCodecs(audio_recv_codecs_, audio_send_codecs_,
+                  &audio_sendrecv_codecs_);
+}
+
 bool IsMediaContent(const ContentInfo* content) {
   return (content &&
           (content->type == NS_JINGLE_RTP ||
diff --git a/webrtc/pc/mediasession.h b/webrtc/pc/mediasession.h
index 596bd18..35bd444 100644
--- a/webrtc/pc/mediasession.h
+++ b/webrtc/pc/mediasession.h
@@ -102,83 +102,72 @@
 NegotiateRtpTransceiverDirection(RtpTransceiverDirection offer,
                                  RtpTransceiverDirection wants);
 
-struct MediaSessionOptions {
-  MediaSessionOptions()
-      : recv_audio(true),
-        recv_video(false),
-        data_channel_type(DCT_NONE),
-        is_muc(false),
-        vad_enabled(true),  // When disabled, removes all CN codecs from SDP.
-        rtcp_mux_enabled(true),
-        bundle_enabled(false),
-        video_bandwidth(kAutoBandwidth),
-        data_bandwidth(kDataMaxBandwidth),
-        rtcp_cname(kDefaultRtcpCname) {}
+// Options for an RtpSender contained with an media description/"m=" section.
+struct SenderOptions {
+  std::string track_id;
+  std::string stream_id;
+  int num_sim_layers;
+};
 
-  bool has_audio() const {
-    return recv_audio || HasSendMediaStream(MEDIA_TYPE_AUDIO);
-  }
-  bool has_video() const {
-    return recv_video || HasSendMediaStream(MEDIA_TYPE_VIDEO);
-  }
-  bool has_data() const { return data_channel_type != DCT_NONE; }
+// Options for an individual media description/"m=" section.
+struct MediaDescriptionOptions {
+  MediaDescriptionOptions(MediaType type,
+                          const std::string& mid,
+                          RtpTransceiverDirection direction,
+                          bool stopped)
+      : type(type), mid(mid), direction(direction), stopped(stopped) {}
 
-  // Add a stream with MediaType type and id.
-  // All streams with the same sync_label will get the same CNAME.
-  // All ids must be unique.
-  void AddSendStream(MediaType type,
-                 const std::string& id,
-                 const std::string& sync_label);
-  void AddSendVideoStream(const std::string& id,
-                      const std::string& sync_label,
+  // TODO(deadbeef): When we don't support Plan B, there will only be one
+  // sender per media description and this can be simplified.
+  void AddAudioSender(const std::string& track_id,
+                      const std::string& stream_id);
+  void AddVideoSender(const std::string& track_id,
+                      const std::string& stream_id,
                       int num_sim_layers);
-  void RemoveSendStream(MediaType type, const std::string& id);
 
+  // Internally just uses sender_options.
+  void AddRtpDataChannel(const std::string& track_id,
+                         const std::string& stream_id);
 
-  // Helper function.
-  void AddSendStreamInternal(MediaType type,
-                         const std::string& id,
-                         const std::string& sync_label,
+  MediaType type;
+  std::string mid;
+  RtpTransceiverDirection direction;
+  bool stopped;
+  TransportOptions transport_options;
+  // Note: There's no equivalent "RtpReceiverOptions" because only send
+  // stream information goes in the local descriptions.
+  std::vector<SenderOptions> sender_options;
+
+ private:
+  // Doesn't DCHECK on |type|.
+  void AddSenderInternal(const std::string& track_id,
+                         const std::string& stream_id,
                          int num_sim_layers);
+};
 
-  bool HasSendMediaStream(MediaType type) const;
+// Provides a mechanism for describing how m= sections should be generated.
+// The m= section with index X will use media_description_options[X]. There
+// must be an option for each existing section if creating an answer, or a
+// subsequent offer.
+struct MediaSessionOptions {
+  MediaSessionOptions() {}
 
-  // TODO(deadbeef): Put all the audio/video/data-specific options into a map
-  // structure (content name -> options).
-  // MediaSessionDescriptionFactory assumes there will never be more than one
-  // audio/video/data content, but this will change with unified plan.
-  bool recv_audio;
-  bool recv_video;
-  DataChannelType data_channel_type;
-  bool is_muc;
-  bool vad_enabled;
-  bool rtcp_mux_enabled;
-  bool bundle_enabled;
-  // bps. -1 == auto.
-  int video_bandwidth;
-  int data_bandwidth;
-  bool enable_ice_renomination = false;
-  // content name ("mid") => options.
-  std::map<std::string, TransportOptions> transport_options;
-  std::string rtcp_cname;
+  bool has_audio() const { return HasMediaDescription(MEDIA_TYPE_AUDIO); }
+  bool has_video() const { return HasMediaDescription(MEDIA_TYPE_VIDEO); }
+  bool has_data() const { return HasMediaDescription(MEDIA_TYPE_DATA); }
+
+  bool HasMediaDescription(MediaType type) const;
+
+  DataChannelType data_channel_type = DCT_NONE;
+  bool is_muc = false;
+  bool vad_enabled = true;  // When disabled, removes all CN codecs from SDP.
+  bool rtcp_mux_enabled = true;
+  bool bundle_enabled = false;
+  std::string rtcp_cname = kDefaultRtcpCname;
   rtc::CryptoOptions crypto_options;
-
-  struct Stream {
-    Stream(MediaType type,
-           const std::string& id,
-           const std::string& sync_label,
-           int num_sim_layers)
-        : type(type), id(id), sync_label(sync_label),
-          num_sim_layers(num_sim_layers) {
-    }
-    MediaType type;
-    std::string id;
-    std::string sync_label;
-    int num_sim_layers;
-  };
-
-  typedef std::vector<Stream> Streams;
-  Streams streams;
+  // List of media description options in the same order that the media
+  // descriptions will be generated.
+  std::vector<MediaDescriptionOptions> media_description_options;
 };
 
 // "content" (as used in XEP-0166) descriptions for voice and video.
@@ -277,7 +266,6 @@
     streams_.push_back(sp);
   }
   // Sets the CNAME of all StreamParams if it have not been set.
-  // This can be used to set the CNAME of legacy streams.
   void SetCnameIfEmpty(const std::string& cname) {
     for (cricket::StreamParamsVec::iterator it = streams_.begin();
          it != streams_.end(); ++it) {
@@ -469,11 +457,6 @@
   void set_data_codecs(const DataCodecs& codecs) { data_codecs_ = codecs; }
   SecurePolicy secure() const { return secure_; }
   void set_secure(SecurePolicy s) { secure_ = s; }
-  // Decides if a StreamParams shall be added to the audio and video media
-  // content in SessionDescription when CreateOffer and CreateAnswer is called
-  // even if |options| don't include a Stream. This is needed to support legacy
-  // applications. |add_legacy_| is true per default.
-  void set_add_legacy_streams(bool add_legacy) { add_legacy_ = add_legacy; }
 
   void set_enable_encrypted_rtp_header_extensions(bool enable) {
     enable_encrypted_rtp_header_extensions_ = enable;
@@ -493,13 +476,15 @@
   const AudioCodecs& GetAudioCodecsForAnswer(
       const RtpTransceiverDirection& offer,
       const RtpTransceiverDirection& answer) const;
-  void GetCodecsToOffer(const SessionDescription* current_description,
-                        const AudioCodecs& supported_audio_codecs,
-                        const VideoCodecs& supported_video_codecs,
-                        const DataCodecs& supported_data_codecs,
-                        AudioCodecs* audio_codecs,
-                        VideoCodecs* video_codecs,
-                        DataCodecs* data_codecs) const;
+  void GetCodecsForOffer(const SessionDescription* current_description,
+                         AudioCodecs* audio_codecs,
+                         VideoCodecs* video_codecs,
+                         DataCodecs* data_codecs) const;
+  void GetCodecsForAnswer(const SessionDescription* current_description,
+                          const SessionDescription* remote_offer,
+                          AudioCodecs* audio_codecs,
+                          VideoCodecs* video_codecs,
+                          DataCodecs* data_codecs) const;
   void GetRtpHdrExtsToOffer(const SessionDescription* current_description,
                             RtpHeaderExtensions* audio_extensions,
                             RtpHeaderExtensions* video_extensions) const;
@@ -526,7 +511,9 @@
   // error.
 
   bool AddAudioContentForOffer(
-      const MediaSessionOptions& options,
+      const MediaDescriptionOptions& media_description_options,
+      const MediaSessionOptions& session_options,
+      const ContentInfo* current_content,
       const SessionDescription* current_description,
       const RtpHeaderExtensions& audio_rtp_extensions,
       const AudioCodecs& audio_codecs,
@@ -534,7 +521,9 @@
       SessionDescription* desc) const;
 
   bool AddVideoContentForOffer(
-      const MediaSessionOptions& options,
+      const MediaDescriptionOptions& media_description_options,
+      const MediaSessionOptions& session_options,
+      const ContentInfo* current_content,
       const SessionDescription* current_description,
       const RtpHeaderExtensions& video_rtp_extensions,
       const VideoCodecs& video_codecs,
@@ -542,43 +531,66 @@
       SessionDescription* desc) const;
 
   bool AddDataContentForOffer(
-      const MediaSessionOptions& options,
+      const MediaDescriptionOptions& media_description_options,
+      const MediaSessionOptions& session_options,
+      const ContentInfo* current_content,
       const SessionDescription* current_description,
-      DataCodecs* data_codecs,
+      const DataCodecs& data_codecs,
       StreamParamsVec* current_streams,
       SessionDescription* desc) const;
 
-  bool AddAudioContentForAnswer(const SessionDescription* offer,
-                                const MediaSessionOptions& options,
-                                const SessionDescription* current_description,
-                                const TransportInfo* bundle_transport,
-                                StreamParamsVec* current_streams,
-                                SessionDescription* answer) const;
+  bool AddAudioContentForAnswer(
+      const MediaDescriptionOptions& media_description_options,
+      const MediaSessionOptions& session_options,
+      const ContentInfo* offer_content,
+      const SessionDescription* offer_description,
+      const ContentInfo* current_content,
+      const SessionDescription* current_description,
+      const TransportInfo* bundle_transport,
+      const AudioCodecs& audio_codecs,
+      StreamParamsVec* current_streams,
+      SessionDescription* answer) const;
 
-  bool AddVideoContentForAnswer(const SessionDescription* offer,
-                                const MediaSessionOptions& options,
-                                const SessionDescription* current_description,
-                                const TransportInfo* bundle_transport,
-                                StreamParamsVec* current_streams,
-                                SessionDescription* answer) const;
+  bool AddVideoContentForAnswer(
+      const MediaDescriptionOptions& media_description_options,
+      const MediaSessionOptions& session_options,
+      const ContentInfo* offer_content,
+      const SessionDescription* offer_description,
+      const ContentInfo* current_content,
+      const SessionDescription* current_description,
+      const TransportInfo* bundle_transport,
+      const VideoCodecs& video_codecs,
+      StreamParamsVec* current_streams,
+      SessionDescription* answer) const;
 
-  bool AddDataContentForAnswer(const SessionDescription* offer,
-                               const MediaSessionOptions& options,
-                               const SessionDescription* current_description,
-                               const TransportInfo* bundle_transport,
-                               StreamParamsVec* current_streams,
-                               SessionDescription* answer) const;
+  bool AddDataContentForAnswer(
+      const MediaDescriptionOptions& media_description_options,
+      const MediaSessionOptions& session_options,
+      const ContentInfo* offer_content,
+      const SessionDescription* offer_description,
+      const ContentInfo* current_content,
+      const SessionDescription* current_description,
+      const TransportInfo* bundle_transport,
+      const DataCodecs& data_codecs,
+      StreamParamsVec* current_streams,
+      SessionDescription* answer) const;
+
+  void ComputeAudioCodecsIntersectionAndUnion();
 
   AudioCodecs audio_send_codecs_;
   AudioCodecs audio_recv_codecs_;
+  // Intersection of send and recv.
   AudioCodecs audio_sendrecv_codecs_;
+  // Union of send and recv.
+  AudioCodecs all_audio_codecs_;
   RtpHeaderExtensions audio_rtp_extensions_;
   VideoCodecs video_codecs_;
   RtpHeaderExtensions video_rtp_extensions_;
   DataCodecs data_codecs_;
-  SecurePolicy secure_;
-  bool add_legacy_;
   bool enable_encrypted_rtp_header_extensions_ = false;
+  // TODO(zhihuang): Rename secure_ to sdec_policy_; rename the related getter
+  // and setter.
+  SecurePolicy secure_ = SEC_DISABLED;
   std::string lang_;
   const TransportDescriptionFactory* transport_desc_factory_;
 };
diff --git a/webrtc/pc/mediasession_unittest.cc b/webrtc/pc/mediasession_unittest.cc
index ae8d139..f87dda1 100644
--- a/webrtc/pc/mediasession_unittest.cc
+++ b/webrtc/pc/mediasession_unittest.cc
@@ -34,6 +34,7 @@
 using cricket::MediaContentDescription;
 using cricket::MediaSessionDescriptionFactory;
 using cricket::MediaContentDirection;
+using cricket::MediaDescriptionOptions;
 using cricket::MediaSessionOptions;
 using cricket::MediaType;
 using cricket::SessionDescription;
@@ -65,6 +66,7 @@
 using cricket::SEC_DISABLED;
 using cricket::SEC_ENABLED;
 using cricket::SEC_REQUIRED;
+using cricket::RtpTransceiverDirection;
 using rtc::CS_AES_CM_128_HMAC_SHA1_32;
 using rtc::CS_AES_CM_128_HMAC_SHA1_80;
 using rtc::CS_AEAD_AES_128_GCM;
@@ -93,6 +95,9 @@
 static const VideoCodec kVideoCodecs1[] = {VideoCodec(96, "H264-SVC"),
                                            VideoCodec(97, "H264")};
 
+static const VideoCodec kVideoCodecs1Reverse[] = {VideoCodec(97, "H264"),
+                                                  VideoCodec(96, "H264-SVC")};
+
 static const VideoCodec kVideoCodecs2[] = {VideoCodec(126, "H264"),
                                            VideoCodec(127, "H263")};
 
@@ -208,6 +213,11 @@
     "TCP/TLS/RTP/SAVPF", "TCP/TLS/RTP/SAVP", "UDP/TLS/RTP/SAVPF",
     "UDP/TLS/RTP/SAVP"};
 
+// These constants are used to make the code using "AddMediaSection" more
+// readable.
+static constexpr bool kStopped = true;
+static constexpr bool kActive = false;
+
 static bool IsMediaContentOfType(const ContentInfo* content,
                                  MediaType media_type) {
   const MediaContentDescription* mdesc =
@@ -237,11 +247,95 @@
   return codec_names;
 }
 
+// This is used for test only. MIDs are not the identification of the
+// MediaDescriptionOptions since some end points may not support MID and the SDP
+// may not contain 'mid'.
+std::vector<MediaDescriptionOptions>::iterator FindFirstMediaDescriptionByMid(
+    const std::string& mid,
+    MediaSessionOptions* opts) {
+  return std::find_if(
+      opts->media_description_options.begin(),
+      opts->media_description_options.end(),
+      [mid](const MediaDescriptionOptions& t) { return t.mid == mid; });
+}
+
+// Add a media section to the |session_options|.
+static void AddMediaSection(MediaType type,
+                            const std::string& mid,
+                            MediaContentDirection direction,
+                            bool stopped,
+                            MediaSessionOptions* opts) {
+  opts->media_description_options.push_back(MediaDescriptionOptions(
+      type, mid,
+      cricket::RtpTransceiverDirection::FromMediaContentDirection(direction),
+      stopped));
+}
+
+static void AddAudioVideoSections(MediaContentDirection direction,
+                                  MediaSessionOptions* opts) {
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", direction, kActive, opts);
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", direction, kActive, opts);
+}
+
+static void AddDataSection(cricket::DataChannelType dct,
+                           MediaContentDirection direction,
+                           MediaSessionOptions* opts) {
+  opts->data_channel_type = dct;
+  AddMediaSection(MEDIA_TYPE_DATA, "data", direction, kActive, opts);
+}
+
+static void AttachSenderToMediaSection(const std::string& mid,
+                                       MediaType type,
+                                       const std::string& track_id,
+                                       const std::string& stream_id,
+                                       int num_sim_layer,
+                                       MediaSessionOptions* session_options) {
+  auto it = FindFirstMediaDescriptionByMid(mid, session_options);
+  switch (type) {
+    case MEDIA_TYPE_AUDIO:
+      it->AddAudioSender(track_id, stream_id);
+      break;
+    case MEDIA_TYPE_VIDEO:
+      it->AddVideoSender(track_id, stream_id, num_sim_layer);
+      break;
+    case MEDIA_TYPE_DATA:
+      it->AddRtpDataChannel(track_id, stream_id);
+      break;
+    default:
+      RTC_NOTREACHED();
+  }
+}
+
+static void DetachSenderFromMediaSection(const std::string& mid,
+                                         const std::string& track_id,
+                                         MediaSessionOptions* session_options) {
+  auto it = FindFirstMediaDescriptionByMid(mid, session_options);
+  auto sender_it = it->sender_options.begin();
+  for (; sender_it != it->sender_options.end(); ++sender_it) {
+    if (sender_it->track_id == track_id) {
+      it->sender_options.erase(sender_it);
+      return;
+    }
+  }
+  RTC_NOTREACHED();
+}
+
+// Helper function used to create a default MediaSessionOptions for Plan B SDP.
+// (https://tools.ietf.org/html/draft-uberti-rtcweb-plan-00).
+static MediaSessionOptions CreatePlanBMediaSessionOptions() {
+  MediaSessionOptions session_options;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
+                  &session_options);
+  return session_options;
+}
+
+// TODO(zhihuang): Most of these tests were written while MediaSessionOptions
+// was designed for Plan B SDP, where only one audio "m=" section and one video
+// "m=" section could be generated, and ordering couldn't be controlled. Many of
+// these tests may be obsolete as a result, and should be refactored or removed.
 class MediaSessionDescriptionFactoryTest : public testing::Test {
  public:
-  MediaSessionDescriptionFactoryTest()
-      : f1_(&tdf1_),
-        f2_(&tdf2_) {
+  MediaSessionDescriptionFactoryTest() : f1_(&tdf1_), f2_(&tdf2_) {
     f1_.set_audio_codecs(MAKE_VECTOR(kAudioCodecs1),
                          MAKE_VECTOR(kAudioCodecs1));
     f1_.set_video_codecs(MAKE_VECTOR(kVideoCodecs1));
@@ -305,7 +399,8 @@
     return iter != ice_options.end();
   }
 
-  void TestTransportInfo(bool offer, const MediaSessionOptions& options,
+  void TestTransportInfo(bool offer,
+                         MediaSessionOptions& options,
                          bool has_current_desc) {
     const std::string current_audio_ufrag = "current_audio_ufrag";
     const std::string current_audio_pwd = "current_audio_pwd";
@@ -350,7 +445,11 @@
         EXPECT_EQ(static_cast<size_t>(cricket::ICE_PWD_LENGTH),
                   ti_audio->description.ice_pwd.size());
       }
-      EXPECT_EQ(options.enable_ice_renomination, GetIceRenomination(ti_audio));
+      auto media_desc_options_it =
+          FindFirstMediaDescriptionByMid("audio", &options);
+      EXPECT_EQ(
+          media_desc_options_it->transport_options.enable_ice_renomination,
+          GetIceRenomination(ti_audio));
 
     } else {
       EXPECT_TRUE(ti_audio == NULL);
@@ -374,7 +473,11 @@
                     ti_video->description.ice_pwd.size());
         }
       }
-      EXPECT_EQ(options.enable_ice_renomination, GetIceRenomination(ti_video));
+      auto media_desc_options_it =
+          FindFirstMediaDescriptionByMid("video", &options);
+      EXPECT_EQ(
+          media_desc_options_it->transport_options.enable_ice_renomination,
+          GetIceRenomination(ti_video));
     } else {
       EXPECT_TRUE(ti_video == NULL);
     }
@@ -397,7 +500,11 @@
                     ti_data->description.ice_pwd.size());
         }
       }
-      EXPECT_EQ(options.enable_ice_renomination, GetIceRenomination(ti_data));
+      auto media_desc_options_it =
+          FindFirstMediaDescriptionByMid("data", &options);
+      EXPECT_EQ(
+          media_desc_options_it->transport_options.enable_ice_renomination,
+          GetIceRenomination(ti_data));
 
     } else {
       EXPECT_TRUE(ti_video == NULL);
@@ -407,9 +514,8 @@
   void TestCryptoWithBundle(bool offer) {
     f1_.set_secure(SEC_ENABLED);
     MediaSessionOptions options;
-    options.recv_audio = true;
-    options.recv_video = true;
-    options.data_channel_type = cricket::DCT_RTP;
+    AddAudioVideoSections(cricket::MD_RECVONLY, &options);
+    AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
     std::unique_ptr<SessionDescription> ref_desc;
     std::unique_ptr<SessionDescription> desc;
     if (offer) {
@@ -455,27 +561,25 @@
 
   // This test that the audio and video media direction is set to
   // |expected_direction_in_answer| in an answer if the offer direction is set
-  // to |direction_in_offer|.
+  // to |direction_in_offer| and the answer is willing to both send and receive.
   void TestMediaDirectionInAnswer(
       cricket::MediaContentDirection direction_in_offer,
       cricket::MediaContentDirection expected_direction_in_answer) {
-    MediaSessionOptions opts;
-    opts.recv_video = true;
-    std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+    MediaSessionOptions offer_opts;
+    AddAudioVideoSections(direction_in_offer, &offer_opts);
+
+    std::unique_ptr<SessionDescription> offer(
+        f1_.CreateOffer(offer_opts, NULL));
     ASSERT_TRUE(offer.get() != NULL);
     ContentInfo* ac_offer = offer->GetContentByName("audio");
     ASSERT_TRUE(ac_offer != NULL);
-    AudioContentDescription* acd_offer =
-        static_cast<AudioContentDescription*>(ac_offer->description);
-    acd_offer->set_direction(direction_in_offer);
     ContentInfo* vc_offer = offer->GetContentByName("video");
     ASSERT_TRUE(vc_offer != NULL);
-    VideoContentDescription* vcd_offer =
-        static_cast<VideoContentDescription*>(vc_offer->description);
-    vcd_offer->set_direction(direction_in_offer);
 
+    MediaSessionOptions answer_opts;
+    AddAudioVideoSections(cricket::MD_SENDRECV, &answer_opts);
     std::unique_ptr<SessionDescription> answer(
-        f2_.CreateAnswer(offer.get(), opts, NULL));
+        f2_.CreateAnswer(offer.get(), answer_opts, NULL));
     const AudioContentDescription* acd_answer =
         GetFirstAudioContentDescription(answer.get());
     EXPECT_EQ(expected_direction_in_answer, acd_answer->direction());
@@ -499,11 +603,13 @@
 
   void TestVideoGcmCipher(bool gcm_offer, bool gcm_answer) {
     MediaSessionOptions offer_opts;
-    offer_opts.recv_video = true;
+    AddAudioVideoSections(cricket::MD_RECVONLY, &offer_opts);
     offer_opts.crypto_options.enable_gcm_crypto_suites = gcm_offer;
+
     MediaSessionOptions answer_opts;
-    answer_opts.recv_video = true;
+    AddAudioVideoSections(cricket::MD_RECVONLY, &answer_opts);
     answer_opts.crypto_options.enable_gcm_crypto_suites = gcm_answer;
+
     f1_.set_secure(SEC_ENABLED);
     f2_.set_secure(SEC_ENABLED);
     std::unique_ptr<SessionDescription> offer(
@@ -524,7 +630,7 @@
     EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
     EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
     EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
-    EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
+    EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
     EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
     if (gcm_offer && gcm_answer) {
       ASSERT_CRYPTO(acd, 1U, CS_AEAD_AES_256_GCM);
@@ -533,7 +639,7 @@
     }
     EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
     EXPECT_EQ(MAKE_VECTOR(kVideoCodecsAnswer), vcd->codecs());
-    EXPECT_NE(0U, vcd->first_ssrc());             // a random nonzero ssrc
+    EXPECT_EQ(0U, vcd->first_ssrc());             // no sender is attached
     EXPECT_TRUE(vcd->rtcp_mux());                 // negotiated rtcp-mux
     if (gcm_offer && gcm_answer) {
       ASSERT_CRYPTO(vcd, 1U, CS_AEAD_AES_256_GCM);
@@ -554,7 +660,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioOffer) {
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(
-      f1_.CreateOffer(MediaSessionOptions(), NULL));
+      f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL));
   ASSERT_TRUE(offer.get() != NULL);
   const ContentInfo* ac = offer->GetContentByName("audio");
   const ContentInfo* vc = offer->GetContentByName("video");
@@ -565,7 +671,7 @@
       static_cast<const AudioContentDescription*>(ac->description);
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(f1_.audio_sendrecv_codecs(), acd->codecs());
-  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached.
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // default bandwidth (auto)
   EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on
   ASSERT_CRYPTO(acd, 2U, CS_AES_CM_128_HMAC_SHA1_32);
@@ -575,7 +681,7 @@
 // Create a typical video offer, and ensure it matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoOffer) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
@@ -591,14 +697,14 @@
       static_cast<const VideoContentDescription*>(vc->description);
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(f1_.audio_sendrecv_codecs(), acd->codecs());
-  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // default bandwidth (auto)
   EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on
   ASSERT_CRYPTO(acd, 2U, CS_AES_CM_128_HMAC_SHA1_32);
   EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), acd->protocol());
   EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
   EXPECT_EQ(f1_.video_codecs(), vcd->codecs());
-  EXPECT_NE(0U, vcd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(0U, vcd->first_ssrc());             // no sender is attached
   EXPECT_EQ(kAutoBandwidth, vcd->bandwidth());  // default bandwidth (auto)
   EXPECT_TRUE(vcd->rtcp_mux());                 // rtcp-mux defaults on
   ASSERT_CRYPTO(vcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
@@ -616,9 +722,8 @@
   ASSERT_EQ(offered_video_codec.id, offered_data_codec.id);
 
   MediaSessionOptions opts;
-  opts.recv_audio = true;
-  opts.recv_video = true;
-  opts.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
   opts.bundle_enabled = true;
   std::unique_ptr<SessionDescription> offer(f2_.CreateOffer(opts, NULL));
   const VideoContentDescription* vcd =
@@ -638,15 +743,17 @@
   EXPECT_EQ(dcd->codecs()[0].name, offered_data_codec.name);
 }
 
-// Test creating an updated offer with with bundle, audio, video and data
+// Test creating an updated offer with bundle, audio, video and data
 // after an audio only session has been negotiated.
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestCreateUpdatedVideoOfferWithBundle) {
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
   MediaSessionOptions opts;
-  opts.recv_audio = true;
-  opts.recv_video = false;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
+                  &opts);
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_INACTIVE, kStopped,
+                  &opts);
   opts.data_channel_type = cricket::DCT_NONE;
   opts.bundle_enabled = true;
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
@@ -654,9 +761,8 @@
       f2_.CreateAnswer(offer.get(), opts, NULL));
 
   MediaSessionOptions updated_opts;
-  updated_opts.recv_audio = true;
-  updated_opts.recv_video = true;
-  updated_opts.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &updated_opts);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &updated_opts);
   updated_opts.bundle_enabled = true;
   std::unique_ptr<SessionDescription> updated_offer(
       f1_.CreateOffer(updated_opts, answer.get()));
@@ -682,7 +788,8 @@
 // Create a RTP data offer, and ensure it matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateRtpDataOffer) {
   MediaSessionOptions opts;
-  opts.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
@@ -698,14 +805,14 @@
       static_cast<const DataContentDescription*>(dc->description);
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(f1_.audio_sendrecv_codecs(), acd->codecs());
-  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attched.
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // default bandwidth (auto)
   EXPECT_TRUE(acd->rtcp_mux());                 // rtcp-mux defaults on
   ASSERT_CRYPTO(acd, 2U, CS_AES_CM_128_HMAC_SHA1_32);
   EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), acd->protocol());
   EXPECT_EQ(MEDIA_TYPE_DATA, dcd->type());
   EXPECT_EQ(f1_.data_codecs(), dcd->codecs());
-  EXPECT_NE(0U, dcd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(0U, dcd->first_ssrc());  // no sender is attached.
   EXPECT_EQ(cricket::kDataMaxBandwidth,
             dcd->bandwidth());                  // default bandwidth (auto)
   EXPECT_TRUE(dcd->rtcp_mux());                 // rtcp-mux defaults on
@@ -716,9 +823,8 @@
 // Create an SCTP data offer with bundle without error.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSctpDataOffer) {
   MediaSessionOptions opts;
-  opts.recv_audio = false;
   opts.bundle_enabled = true;
-  opts.data_channel_type = cricket::DCT_SCTP;
+  AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts);
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   EXPECT_TRUE(offer.get() != NULL);
@@ -728,9 +834,8 @@
 // Test creating an sctp data channel from an already generated offer.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateImplicitSctpDataOffer) {
   MediaSessionOptions opts;
-  opts.recv_audio = false;
   opts.bundle_enabled = true;
-  opts.data_channel_type = cricket::DCT_SCTP;
+  AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts);
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer1(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer1.get() != NULL);
@@ -756,8 +861,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestCreateOfferWithoutLegacyStreams) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
-  f1_.set_add_legacy_streams(false);
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   const ContentInfo* ac = offer->GetContentByName("audio");
@@ -775,13 +879,14 @@
 
 // Creates an audio+video sendonly offer.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSendOnlyOffer) {
-  MediaSessionOptions options;
-  options.recv_audio = false;
-  options.recv_video = false;
-  options.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1);
-  options.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
+  MediaSessionOptions opts;
+  AddAudioVideoSections(cricket::MD_SENDONLY, &opts);
+  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                             kMediaStream1, 1, &opts);
+  AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
+                             kMediaStream1, 1, &opts);
 
-  std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(options, NULL));
+  std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   EXPECT_EQ(2u, offer->contents().size());
   EXPECT_TRUE(IsMediaContentOfType(&offer->contents()[0], MEDIA_TYPE_AUDIO));
@@ -795,16 +900,15 @@
 // SessionDescription is preserved in the new SessionDescription.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateOfferContentOrder) {
   MediaSessionOptions opts;
-  opts.recv_audio = false;
-  opts.recv_video = false;
-  opts.data_channel_type = cricket::DCT_SCTP;
+  AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts);
 
   std::unique_ptr<SessionDescription> offer1(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer1.get() != NULL);
   EXPECT_EQ(1u, offer1->contents().size());
   EXPECT_TRUE(IsMediaContentOfType(&offer1->contents()[0], MEDIA_TYPE_DATA));
 
-  opts.recv_video = true;
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
+                  &opts);
   std::unique_ptr<SessionDescription> offer2(
       f1_.CreateOffer(opts, offer1.get()));
   ASSERT_TRUE(offer2.get() != NULL);
@@ -812,7 +916,8 @@
   EXPECT_TRUE(IsMediaContentOfType(&offer2->contents()[0], MEDIA_TYPE_DATA));
   EXPECT_TRUE(IsMediaContentOfType(&offer2->contents()[1], MEDIA_TYPE_VIDEO));
 
-  opts.recv_audio = true;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
+                  &opts);
   std::unique_ptr<SessionDescription> offer3(
       f1_.CreateOffer(opts, offer2.get()));
   ASSERT_TRUE(offer3.get() != NULL);
@@ -820,15 +925,6 @@
   EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[0], MEDIA_TYPE_DATA));
   EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[1], MEDIA_TYPE_VIDEO));
   EXPECT_TRUE(IsMediaContentOfType(&offer3->contents()[2], MEDIA_TYPE_AUDIO));
-
-  // Verifies the default order is audio-video-data, so that the previous checks
-  // didn't pass by accident.
-  std::unique_ptr<SessionDescription> offer4(f1_.CreateOffer(opts, NULL));
-  ASSERT_TRUE(offer4.get() != NULL);
-  EXPECT_EQ(3u, offer4->contents().size());
-  EXPECT_TRUE(IsMediaContentOfType(&offer4->contents()[0], MEDIA_TYPE_AUDIO));
-  EXPECT_TRUE(IsMediaContentOfType(&offer4->contents()[1], MEDIA_TYPE_VIDEO));
-  EXPECT_TRUE(IsMediaContentOfType(&offer4->contents()[2], MEDIA_TYPE_DATA));
 }
 
 // Create a typical audio answer, and ensure it matches what we expect.
@@ -836,10 +932,10 @@
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(
-      f1_.CreateOffer(MediaSessionOptions(), NULL));
+      f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL));
   ASSERT_TRUE(offer.get() != NULL);
   std::unique_ptr<SessionDescription> answer(
-      f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL));
+      f2_.CreateAnswer(offer.get(), CreatePlanBMediaSessionOptions(), NULL));
   const ContentInfo* ac = answer->GetContentByName("audio");
   const ContentInfo* vc = answer->GetContentByName("video");
   ASSERT_TRUE(ac != NULL);
@@ -849,7 +945,7 @@
       static_cast<const AudioContentDescription*>(ac->description);
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
-  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
   EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
   ASSERT_CRYPTO(acd, 1U, CS_AES_CM_128_HMAC_SHA1_32);
@@ -861,13 +957,12 @@
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerGcm) {
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
-  MediaSessionOptions options;
-  options.crypto_options.enable_gcm_crypto_suites = true;
-  std::unique_ptr<SessionDescription> offer(
-      f1_.CreateOffer(options, NULL));
+  MediaSessionOptions opts = CreatePlanBMediaSessionOptions();
+  opts.crypto_options.enable_gcm_crypto_suites = true;
+  std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   std::unique_ptr<SessionDescription> answer(
-      f2_.CreateAnswer(offer.get(), options, NULL));
+      f2_.CreateAnswer(offer.get(), opts, NULL));
   const ContentInfo* ac = answer->GetContentByName("audio");
   const ContentInfo* vc = answer->GetContentByName("video");
   ASSERT_TRUE(ac != NULL);
@@ -877,7 +972,7 @@
       static_cast<const AudioContentDescription*>(ac->description);
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
-  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
   EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
   ASSERT_CRYPTO(acd, 1U, CS_AEAD_AES_256_GCM);
@@ -887,7 +982,7 @@
 // Create a typical video answer, and ensure it matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswer) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
@@ -907,12 +1002,12 @@
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
-  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
   EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
   ASSERT_CRYPTO(acd, 1U, CS_AES_CM_128_HMAC_SHA1_32);
   EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
   EXPECT_EQ(MAKE_VECTOR(kVideoCodecsAnswer), vcd->codecs());
-  EXPECT_NE(0U, vcd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(0U, vcd->first_ssrc());             // no sender is attached
   EXPECT_TRUE(vcd->rtcp_mux());                 // negotiated rtcp-mux
   ASSERT_CRYPTO(vcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
   EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), vcd->protocol());
@@ -937,8 +1032,8 @@
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswer) {
-  MediaSessionOptions opts;
-  opts.data_channel_type = cricket::DCT_RTP;
+  MediaSessionOptions opts = CreatePlanBMediaSessionOptions();
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
@@ -958,20 +1053,20 @@
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
-  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
   EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
   ASSERT_CRYPTO(acd, 1U, CS_AES_CM_128_HMAC_SHA1_32);
   EXPECT_EQ(MEDIA_TYPE_DATA, dcd->type());
   EXPECT_EQ(MAKE_VECTOR(kDataCodecsAnswer), dcd->codecs());
-  EXPECT_NE(0U, dcd->first_ssrc());  // a random nonzero ssrc
+  EXPECT_EQ(0U, dcd->first_ssrc());  // no sender is attached
   EXPECT_TRUE(dcd->rtcp_mux());      // negotiated rtcp-mux
   ASSERT_CRYPTO(dcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
   EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), dcd->protocol());
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerGcm) {
-  MediaSessionOptions opts;
-  opts.data_channel_type = cricket::DCT_RTP;
+  MediaSessionOptions opts = CreatePlanBMediaSessionOptions();
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
   opts.crypto_options.enable_gcm_crypto_suites = true;
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
@@ -992,12 +1087,12 @@
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
-  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
+  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
   EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
   ASSERT_CRYPTO(acd, 1U, CS_AEAD_AES_256_GCM);
   EXPECT_EQ(MEDIA_TYPE_DATA, dcd->type());
   EXPECT_EQ(MAKE_VECTOR(kDataCodecsAnswer), dcd->codecs());
-  EXPECT_NE(0U, dcd->first_ssrc());  // a random nonzero ssrc
+  EXPECT_EQ(0U, dcd->first_ssrc());  // no sender is attached
   EXPECT_TRUE(dcd->rtcp_mux());      // negotiated rtcp-mux
   ASSERT_CRYPTO(dcd, 1U, CS_AEAD_AES_256_GCM);
   EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), dcd->protocol());
@@ -1007,7 +1102,7 @@
 // The answer's use_sctpmap flag should match the offer's.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerUsesSctpmap) {
   MediaSessionOptions opts;
-  opts.data_channel_type = cricket::DCT_SCTP;
+  AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   ContentInfo* dc_offer = offer->GetContentByName("data");
@@ -1028,7 +1123,7 @@
 // The answer's use_sctpmap flag should match the offer's.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerWithoutSctpmap) {
   MediaSessionOptions opts;
-  opts.data_channel_type = cricket::DCT_SCTP;
+  AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   ContentInfo* dc_offer = offer->GetContentByName("data");
@@ -1058,7 +1153,7 @@
   tdf2_.set_secure(SEC_ENABLED);
 
   MediaSessionOptions opts;
-  opts.data_channel_type = cricket::DCT_SCTP;
+  AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, nullptr));
   ASSERT_TRUE(offer.get() != nullptr);
   ContentInfo* dc_offer = offer->GetContentByName("data");
@@ -1087,19 +1182,20 @@
   MediaSessionOptions opts;
 
   // Creates a data only offer.
-  opts.recv_audio = false;
-  opts.data_channel_type = cricket::DCT_SCTP;
+  AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts);
   std::unique_ptr<SessionDescription> offer1(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer1.get() != NULL);
 
   // Appends audio to the offer.
-  opts.recv_audio = true;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
+                  &opts);
   std::unique_ptr<SessionDescription> offer2(
       f1_.CreateOffer(opts, offer1.get()));
   ASSERT_TRUE(offer2.get() != NULL);
 
   // Appends video to the offer.
-  opts.recv_video = true;
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
+                  &opts);
   std::unique_ptr<SessionDescription> offer3(
       f1_.CreateOffer(opts, offer2.get()));
   ASSERT_TRUE(offer3.get() != NULL);
@@ -1144,8 +1240,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        CreateDataAnswerToOfferWithUnknownProtocol) {
   MediaSessionOptions opts;
-  opts.data_channel_type = cricket::DCT_RTP;
-  opts.recv_audio = false;
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
@@ -1171,7 +1266,7 @@
 
 // Test that the media protocol is RTP/AVPF if DTLS and SDES are disabled.
 TEST_F(MediaSessionDescriptionFactoryTest, AudioOfferAnswerWithCryptoDisabled) {
-  MediaSessionOptions opts;
+  MediaSessionOptions opts = CreatePlanBMediaSessionOptions();
   f1_.set_secure(SEC_DISABLED);
   f2_.set_secure(SEC_DISABLED);
   tdf1_.set_secure(SEC_DISABLED);
@@ -1200,8 +1295,7 @@
 // matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestOfferAnswerWithRtpExtensions) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
-
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
   f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension1));
   f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension1));
   f2_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension2));
@@ -1229,7 +1323,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestOfferAnswerWithEncryptedRtpExtensionsBoth) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
 
   f1_.set_enable_encrypted_rtp_header_extensions(true);
   f2_.set_enable_encrypted_rtp_header_extensions(true);
@@ -1265,7 +1359,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestOfferAnswerWithEncryptedRtpExtensionsOffer) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
 
   f1_.set_enable_encrypted_rtp_header_extensions(true);
 
@@ -1300,7 +1394,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestOfferAnswerWithEncryptedRtpExtensionsAnswer) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
 
   f2_.set_enable_encrypted_rtp_header_extensions(true);
 
@@ -1336,10 +1430,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestCreateAnswerWithoutLegacyStreams) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
-  opts.data_channel_type = cricket::DCT_RTP;
-  f1_.set_add_legacy_streams(false);
-  f2_.set_add_legacy_streams(false);
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   std::unique_ptr<SessionDescription> answer(
@@ -1363,8 +1455,8 @@
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestPartial) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
-  opts.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
@@ -1400,18 +1492,18 @@
 // Create a typical video answer, and ensure it matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswerRtcpMux) {
   MediaSessionOptions offer_opts;
+  AddAudioVideoSections(cricket::MD_SENDRECV, &offer_opts);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_SENDRECV, &offer_opts);
+
   MediaSessionOptions answer_opts;
-  answer_opts.recv_video = true;
-  offer_opts.recv_video = true;
-  answer_opts.data_channel_type = cricket::DCT_RTP;
-  offer_opts.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_SENDRECV, &answer_opts);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_SENDRECV, &answer_opts);
 
   std::unique_ptr<SessionDescription> offer;
   std::unique_ptr<SessionDescription> answer;
 
   offer_opts.rtcp_mux_enabled = true;
   answer_opts.rtcp_mux_enabled = true;
-
   offer.reset(f1_.CreateOffer(offer_opts, NULL));
   answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
   ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
@@ -1429,7 +1521,6 @@
 
   offer_opts.rtcp_mux_enabled = true;
   answer_opts.rtcp_mux_enabled = false;
-
   offer.reset(f1_.CreateOffer(offer_opts, NULL));
   answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
   ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
@@ -1447,7 +1538,6 @@
 
   offer_opts.rtcp_mux_enabled = false;
   answer_opts.rtcp_mux_enabled = true;
-
   offer.reset(f1_.CreateOffer(offer_opts, NULL));
   answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
   ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
@@ -1465,7 +1555,6 @@
 
   offer_opts.rtcp_mux_enabled = false;
   answer_opts.rtcp_mux_enabled = false;
-
   offer.reset(f1_.CreateOffer(offer_opts, NULL));
   answer.reset(f2_.CreateAnswer(offer.get(), answer_opts, NULL));
   ASSERT_TRUE(NULL != GetFirstAudioContentDescription(offer.get()));
@@ -1485,11 +1574,16 @@
 // Create an audio-only answer to a video offer.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerToVideo) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
+                  &opts);
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
+                  &opts);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
+
+  opts.media_description_options[1].stopped = true;
   std::unique_ptr<SessionDescription> answer(
-      f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL));
+      f2_.CreateAnswer(offer.get(), opts, NULL));
   const ContentInfo* ac = answer->GetContentByName("audio");
   const ContentInfo* vc = answer->GetContentByName("video");
   ASSERT_TRUE(ac != NULL);
@@ -1500,12 +1594,16 @@
 
 // Create an audio-only answer to an offer with data.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateNoDataAnswerToDataOffer) {
-  MediaSessionOptions opts;
+  MediaSessionOptions opts = CreatePlanBMediaSessionOptions();
   opts.data_channel_type = cricket::DCT_RTP;
+  AddMediaSection(MEDIA_TYPE_DATA, "data", cricket::MD_RECVONLY, kActive,
+                  &opts);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
+
+  opts.media_description_options[1].stopped = true;
   std::unique_ptr<SessionDescription> answer(
-      f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL));
+      f2_.CreateAnswer(offer.get(), opts, NULL));
   const ContentInfo* ac = answer->GetContentByName("audio");
   const ContentInfo* dc = answer->GetContentByName("data");
   ASSERT_TRUE(ac != NULL);
@@ -1518,8 +1616,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        CreateAnswerToOfferWithRejectedMedia) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
-  opts.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   ContentInfo* ac = offer->GetContentByName("audio");
@@ -1552,12 +1650,19 @@
 // adding a new video track and replaces one of the audio tracks.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoOffer) {
   MediaSessionOptions opts;
-  opts.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1);
-  opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
-  opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack2, kMediaStream1);
-  opts.data_channel_type = cricket::DCT_RTP;
-  opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack1, kMediaStream1);
-  opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack2, kMediaStream1);
+  AddAudioVideoSections(cricket::MD_SENDRECV, &opts);
+  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                             kMediaStream1, 1, &opts);
+  AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
+                             kMediaStream1, 1, &opts);
+  AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack2,
+                             kMediaStream1, 1, &opts);
+
+  AddDataSection(cricket::DCT_RTP, cricket::MD_SENDRECV, &opts);
+  AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack1,
+                             kMediaStream1, 1, &opts);
+  AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack2,
+                             kMediaStream1, 1, &opts);
 
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
@@ -1622,14 +1727,16 @@
   EXPECT_TRUE(dcd->rtcp_mux());                 // rtcp-mux defaults on
   ASSERT_CRYPTO(dcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
 
-
   // Update the offer. Add a new video track that is not synched to the
   // other tracks and replace audio track 2 with audio track 3.
-  opts.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack2, kMediaStream2);
-  opts.RemoveSendStream(MEDIA_TYPE_AUDIO, kAudioTrack2);
-  opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack3, kMediaStream1);
-  opts.RemoveSendStream(MEDIA_TYPE_DATA, kDataTrack2);
-  opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack3, kMediaStream1);
+  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack2,
+                             kMediaStream2, 1, &opts);
+  DetachSenderFromMediaSection("audio", kAudioTrack2, &opts);
+  AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack3,
+                             kMediaStream1, 1, &opts);
+  DetachSenderFromMediaSection("data", kDataTrack2, &opts);
+  AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack3,
+                             kMediaStream1, 1, &opts);
   std::unique_ptr<SessionDescription> updated_offer(
       f1_.CreateOffer(opts, offer.get()));
 
@@ -1691,8 +1798,13 @@
 // Create an offer with simulcast video stream.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSimulcastVideoOffer) {
   MediaSessionOptions opts;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
+                  &opts);
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive,
+                  &opts);
   const int num_sim_layers = 3;
-  opts.AddSendVideoStream(kVideoTrack1, kMediaStream1, num_sim_layers);
+  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                             kMediaStream1, num_sim_layers, &opts);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
 
   ASSERT_TRUE(offer.get() != NULL);
@@ -1718,22 +1830,39 @@
 // adding a new video track and removes one of the audio tracks.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoAnswer) {
   MediaSessionOptions offer_opts;
-  offer_opts.recv_video = true;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
+                  &offer_opts);
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
+                  &offer_opts);
   offer_opts.data_channel_type = cricket::DCT_RTP;
+  AddMediaSection(MEDIA_TYPE_DATA, "data", cricket::MD_RECVONLY, kActive,
+                  &offer_opts);
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(offer_opts, NULL));
 
-  MediaSessionOptions opts;
-  opts.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1);
-  opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
-  opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack2, kMediaStream1);
-  opts.data_channel_type = cricket::DCT_RTP;
-  opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack1, kMediaStream1);
-  opts.AddSendStream(MEDIA_TYPE_DATA, kDataTrack2, kMediaStream1);
+  MediaSessionOptions answer_opts;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_SENDRECV, kActive,
+                  &answer_opts);
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive,
+                  &answer_opts);
+  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                             kMediaStream1, 1, &answer_opts);
+  AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
+                             kMediaStream1, 1, &answer_opts);
+  AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack2,
+                             kMediaStream1, 1, &answer_opts);
+
+  AddMediaSection(MEDIA_TYPE_DATA, "data", cricket::MD_SENDRECV, kActive,
+                  &answer_opts);
+  AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack1,
+                             kMediaStream1, 1, &answer_opts);
+  AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack2,
+                             kMediaStream1, 1, &answer_opts);
+  answer_opts.data_channel_type = cricket::DCT_RTP;
 
   std::unique_ptr<SessionDescription> answer(
-      f2_.CreateAnswer(offer.get(), opts, NULL));
+      f2_.CreateAnswer(offer.get(), answer_opts, NULL));
 
   ASSERT_TRUE(answer.get() != NULL);
   const ContentInfo* ac = answer->GetContentByName("audio");
@@ -1797,11 +1926,12 @@
 
   // Update the answer. Add a new video track that is not synched to the
   // other tracks and remove 1 audio track.
-  opts.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack2, kMediaStream2);
-  opts.RemoveSendStream(MEDIA_TYPE_AUDIO, kAudioTrack2);
-  opts.RemoveSendStream(MEDIA_TYPE_DATA, kDataTrack2);
+  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack2,
+                             kMediaStream2, 1, &answer_opts);
+  DetachSenderFromMediaSection("audio", kAudioTrack2, &answer_opts);
+  DetachSenderFromMediaSection("data", kDataTrack2, &answer_opts);
   std::unique_ptr<SessionDescription> updated_answer(
-      f2_.CreateAnswer(offer.get(), opts, answer.get()));
+      f2_.CreateAnswer(offer.get(), answer_opts, answer.get()));
 
   ASSERT_TRUE(updated_answer.get() != NULL);
   ac = updated_answer->GetContentByName("audio");
@@ -1853,8 +1983,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        RespondentCreatesOfferAfterCreatingAnswer) {
   MediaSessionOptions opts;
-  opts.recv_audio = true;
-  opts.recv_video = true;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
 
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   std::unique_ptr<SessionDescription> answer(
@@ -1905,8 +2034,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        RespondentCreatesOfferAfterCreatingAnswerWithRtx) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
-  opts.recv_audio = false;
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
+                  &opts);
   std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
   // This creates rtx for H264 with the payload type |f1_| uses.
   AddRtxCodec(VideoCodec::CreateRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs);
@@ -1958,8 +2087,8 @@
   f1_.set_video_codecs(f1_codecs);
 
   MediaSessionOptions opts;
-  opts.recv_audio = true;
-  opts.recv_video = false;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
+                  &opts);
 
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   std::unique_ptr<SessionDescription> answer(
@@ -1972,8 +2101,8 @@
   // Now - let |f2_| add video with RTX and let the payload type the RTX codec
   // reference  be the same as an audio codec that was negotiated in the
   // first offer/answer exchange.
-  opts.recv_audio = true;
-  opts.recv_video = true;
+  opts.media_description_options.clear();
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
 
   std::vector<VideoCodec> f2_codecs = MAKE_VECTOR(kVideoCodecs2);
   int used_pl_type = acd->codecs()[0].id;
@@ -2010,8 +2139,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        RespondentCreatesOfferWithRtxAfterCreatingAnswerWithoutRtx) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
-  opts.recv_audio = true;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
 
   std::vector<VideoCodec> f2_codecs = MAKE_VECTOR(kVideoCodecs2);
   // This creates rtx for H264 with the payload type |f2_| uses.
@@ -2049,8 +2177,8 @@
 // Test that RTX is ignored when there is no associated payload type parameter.
 TEST_F(MediaSessionDescriptionFactoryTest, RtxWithoutApt) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
-  opts.recv_audio = false;
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
+                  &opts);
   std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
   // This creates RTX without associated payload type parameter.
   AddRtxCodec(VideoCodec(126, cricket::kRtxCodecName), &f1_codecs);
@@ -2093,8 +2221,8 @@
 // type doesn't match the local value.
 TEST_F(MediaSessionDescriptionFactoryTest, FilterOutRtxIfAptDoesntMatch) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
-  opts.recv_audio = false;
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
+                  &opts);
   std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
   // This creates RTX for H264 in sender.
   AddRtxCodec(VideoCodec::CreateRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs);
@@ -2123,8 +2251,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        FilterOutUnsupportedRtxWhenCreatingAnswer) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
-  opts.recv_audio = false;
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
+                  &opts);
   std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
   // This creates RTX for H264-SVC in sender.
   AddRtxCodec(VideoCodec::CreateRtxCodec(125, kVideoCodecs1[0].id), &f1_codecs);
@@ -2158,8 +2286,8 @@
 // to add another.
 TEST_F(MediaSessionDescriptionFactoryTest, AddSecondRtxInNewOffer) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
-  opts.recv_audio = false;
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
+                  &opts);
   std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
   // This creates RTX for H264 for the offerer.
   AddRtxCodec(VideoCodec::CreateRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs);
@@ -2193,11 +2321,11 @@
 // generated for each simulcast ssrc and correctly grouped.
 TEST_F(MediaSessionDescriptionFactoryTest, SimSsrcsGenerateMultipleRtxSsrcs) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
-  opts.recv_audio = false;
-
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive,
+                  &opts);
   // Add simulcast streams.
-  opts.AddSendVideoStream("stream1", "stream1label", 3);
+  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, "stream1",
+                             "stream1label", 3, &opts);
 
   // Use a single real codec, and then add RTX for it.
   std::vector<VideoCodec> f1_codecs;
@@ -2234,11 +2362,11 @@
 // together with a FEC-FR grouping.
 TEST_F(MediaSessionDescriptionFactoryTest, GenerateFlexfecSsrc) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
-  opts.recv_audio = false;
-
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive,
+                  &opts);
   // Add single stream.
-  opts.AddSendVideoStream("stream1", "stream1label", 1);
+  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, "stream1",
+                             "stream1label", 1, &opts);
 
   // Use a single real codec, and then add FlexFEC for it.
   std::vector<VideoCodec> f1_codecs;
@@ -2274,11 +2402,11 @@
 // multiple FlexfecSenders, or through multistream protection.
 TEST_F(MediaSessionDescriptionFactoryTest, SimSsrcsGenerateNoFlexfecSsrcs) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
-  opts.recv_audio = false;
-
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive,
+                  &opts);
   // Add simulcast streams.
-  opts.AddSendVideoStream("stream1", "stream1label", 3);
+  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, "stream1",
+                             "stream1label", 3, &opts);
 
   // Use a single real codec, and then add FlexFEC for it.
   std::vector<VideoCodec> f1_codecs;
@@ -2318,8 +2446,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        RespondentCreatesOfferAfterCreatingAnswerWithRtpExtensions) {
   MediaSessionOptions opts;
-  opts.recv_audio = true;
-  opts.recv_video = true;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
 
   f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension1));
   f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension1));
@@ -2373,8 +2500,7 @@
 // updated offer (this was previously a bug).
 TEST_F(MediaSessionDescriptionFactoryTest, RtpExtensionIdReused) {
   MediaSessionOptions opts;
-  opts.recv_audio = true;
-  opts.recv_video = true;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
 
   f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension3));
   f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension3));
@@ -2409,8 +2535,7 @@
 // Same as "RtpExtensionIdReused" above for encrypted RTP extensions.
 TEST_F(MediaSessionDescriptionFactoryTest, RtpExtensionIdReusedEncrypted) {
   MediaSessionOptions opts;
-  opts.recv_audio = true;
-  opts.recv_video = true;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
 
   f1_.set_enable_encrypted_rtp_header_extensions(true);
   f2_.set_enable_encrypted_rtp_header_extensions(true);
@@ -2486,45 +2611,47 @@
 // ensure the TransportInfo in the SessionDescription matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferAudio) {
   MediaSessionOptions options;
-  options.recv_audio = true;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
+                  &options);
   TestTransportInfo(true, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestTransportInfoOfferIceRenomination) {
   MediaSessionOptions options;
-  options.enable_ice_renomination = true;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
+                  &options);
+  options.media_description_options[0]
+      .transport_options.enable_ice_renomination = true;
   TestTransportInfo(true, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferAudioCurrent) {
   MediaSessionOptions options;
-  options.recv_audio = true;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
+                  &options);
   TestTransportInfo(true, options, true);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferMultimedia) {
   MediaSessionOptions options;
-  options.recv_audio = true;
-  options.recv_video = true;
-  options.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
   TestTransportInfo(true, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestTransportInfoOfferMultimediaCurrent) {
   MediaSessionOptions options;
-  options.recv_audio = true;
-  options.recv_video = true;
-  options.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
   TestTransportInfo(true, options, true);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferBundle) {
   MediaSessionOptions options;
-  options.recv_audio = true;
-  options.recv_video = true;
-  options.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
   options.bundle_enabled = true;
   TestTransportInfo(true, options, false);
 }
@@ -2532,55 +2659,56 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestTransportInfoOfferBundleCurrent) {
   MediaSessionOptions options;
-  options.recv_audio = true;
-  options.recv_video = true;
-  options.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
   options.bundle_enabled = true;
   TestTransportInfo(true, options, true);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerAudio) {
   MediaSessionOptions options;
-  options.recv_audio = true;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
+                  &options);
   TestTransportInfo(false, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestTransportInfoAnswerIceRenomination) {
   MediaSessionOptions options;
-  options.enable_ice_renomination = true;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
+                  &options);
+  options.media_description_options[0]
+      .transport_options.enable_ice_renomination = true;
   TestTransportInfo(false, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestTransportInfoAnswerAudioCurrent) {
   MediaSessionOptions options;
-  options.recv_audio = true;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
+                  &options);
   TestTransportInfo(false, options, true);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerMultimedia) {
   MediaSessionOptions options;
-  options.recv_audio = true;
-  options.recv_video = true;
-  options.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
   TestTransportInfo(false, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestTransportInfoAnswerMultimediaCurrent) {
   MediaSessionOptions options;
-  options.recv_audio = true;
-  options.recv_video = true;
-  options.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
   TestTransportInfo(false, options, true);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerBundle) {
   MediaSessionOptions options;
-  options.recv_audio = true;
-  options.recv_video = true;
-  options.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
   options.bundle_enabled = true;
   TestTransportInfo(false, options, false);
 }
@@ -2588,9 +2716,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestTransportInfoAnswerBundleCurrent) {
   MediaSessionOptions options;
-  options.recv_audio = true;
-  options.recv_video = true;
-  options.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
   options.bundle_enabled = true;
   TestTransportInfo(false, options, true);
 }
@@ -2617,7 +2744,7 @@
   tdf2_.set_secure(SEC_DISABLED);
 
   std::unique_ptr<SessionDescription> offer(
-      f1_.CreateOffer(MediaSessionOptions(), NULL));
+      f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL));
   ASSERT_TRUE(offer.get() != NULL);
   ContentInfo* offer_content = offer->GetContentByName("audio");
   ASSERT_TRUE(offer_content != NULL);
@@ -2626,7 +2753,7 @@
   offer_audio_desc->set_protocol(cricket::kMediaProtocolDtlsSavpf);
 
   std::unique_ptr<SessionDescription> answer(
-      f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL));
+      f2_.CreateAnswer(offer.get(), CreatePlanBMediaSessionOptions(), NULL));
   ASSERT_TRUE(answer != NULL);
   ContentInfo* answer_content = answer->GetContentByName("audio");
   ASSERT_TRUE(answer_content != NULL);
@@ -2643,7 +2770,7 @@
   tdf2_.set_secure(SEC_ENABLED);
 
   std::unique_ptr<SessionDescription> offer(
-      f1_.CreateOffer(MediaSessionOptions(), NULL));
+      f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL));
   ASSERT_TRUE(offer.get() != NULL);
   ContentInfo* offer_content = offer->GetContentByName("audio");
   ASSERT_TRUE(offer_content != NULL);
@@ -2652,7 +2779,7 @@
   offer_audio_desc->set_protocol(cricket::kMediaProtocolDtlsSavpf);
 
   std::unique_ptr<SessionDescription> answer(
-      f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL));
+      f2_.CreateAnswer(offer.get(), CreatePlanBMediaSessionOptions(), NULL));
   ASSERT_TRUE(answer != NULL);
 
   const ContentInfo* answer_content = answer->GetContentByName("audio");
@@ -2673,8 +2800,7 @@
   tdf1_.set_secure(SEC_ENABLED);
   tdf2_.set_secure(SEC_DISABLED);
   MediaSessionOptions options;
-  options.recv_audio = true;
-  options.recv_video = true;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
   std::unique_ptr<SessionDescription> offer, answer;
   const cricket::MediaContentDescription* audio_media_desc;
   const cricket::MediaContentDescription* video_media_desc;
@@ -2770,7 +2896,7 @@
 // Test that an answer can't be created if cryptos are required but the offer is
 // unsecure.
 TEST_F(MediaSessionDescriptionFactoryTest, TestSecureAnswerToUnsecureOffer) {
-  MediaSessionOptions options;
+  MediaSessionOptions options = CreatePlanBMediaSessionOptions();
   f1_.set_secure(SEC_DISABLED);
   tdf1_.set_secure(SEC_DISABLED);
   f2_.set_secure(SEC_REQUIRED);
@@ -2791,9 +2917,8 @@
   tdf1_.set_secure(SEC_ENABLED);
   tdf2_.set_secure(SEC_ENABLED);
   MediaSessionOptions options;
-  options.recv_audio = true;
-  options.recv_video = true;
-  options.data_channel_type = cricket::DCT_RTP;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
+  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
 
   std::unique_ptr<SessionDescription> offer, answer;
 
@@ -2840,8 +2965,7 @@
 // offer or answer.
 TEST_F(MediaSessionDescriptionFactoryTest, TestVADEnableOption) {
   MediaSessionOptions options;
-  options.recv_audio = true;
-  options.recv_video = true;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(options, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   const ContentInfo* audio_content = offer->GetContentByName("audio");
@@ -2859,22 +2983,21 @@
   EXPECT_TRUE(VerifyNoCNCodecs(audio_content));
 }
 
-// Test that the content name ("mid" in SDP) is unchanged when creating a
-// new offer.
-TEST_F(MediaSessionDescriptionFactoryTest,
-       TestContentNameNotChangedInSubsequentOffers) {
+// Test that the generated MIDs match the existing offer.
+TEST_F(MediaSessionDescriptionFactoryTest, TestMIDsMatchesExistingOffer) {
   MediaSessionOptions opts;
-  opts.recv_audio = true;
-  opts.recv_video = true;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio_modified", cricket::MD_RECVONLY,
+                  kActive, &opts);
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video_modified", cricket::MD_RECVONLY,
+                  kActive, &opts);
   opts.data_channel_type = cricket::DCT_SCTP;
-  // Create offer and modify the default content names.
+  AddMediaSection(MEDIA_TYPE_DATA, "data_modified", cricket::MD_SENDRECV,
+                  kActive, &opts);
+  // Create offer.
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, nullptr));
-  for (ContentInfo& content : offer->contents()) {
-    content.name.append("_modified");
-  }
-
   std::unique_ptr<SessionDescription> updated_offer(
       f1_.CreateOffer(opts, offer.get()));
+
   const ContentInfo* audio_content = GetFirstAudioContent(updated_offer.get());
   const ContentInfo* video_content = GetFirstVideoContent(updated_offer.get());
   const ContentInfo* data_content = GetFirstDataContent(updated_offer.get());
@@ -2886,6 +3009,340 @@
   EXPECT_EQ("data_modified", data_content->name);
 }
 
+// The following tests verify that the unified plan SDP is supported.
+// Test that we can create an offer with multiple media sections of same media
+// type.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       CreateOfferWithMultipleAVMediaSections) {
+  MediaSessionOptions opts;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio_1", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  AttachSenderToMediaSection("audio_1", MEDIA_TYPE_AUDIO, kAudioTrack1,
+                             kMediaStream1, 1, &opts);
+
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video_1", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  AttachSenderToMediaSection("video_1", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                             kMediaStream1, 1, &opts);
+
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio_2", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  AttachSenderToMediaSection("audio_2", MEDIA_TYPE_AUDIO, kAudioTrack2,
+                             kMediaStream2, 1, &opts);
+
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video_2", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  AttachSenderToMediaSection("video_2", MEDIA_TYPE_VIDEO, kVideoTrack2,
+                             kMediaStream2, 1, &opts);
+  std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, nullptr));
+  ASSERT_TRUE(offer);
+
+  ASSERT_EQ(4u, offer->contents().size());
+  EXPECT_FALSE(offer->contents()[0].rejected);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(
+          offer->contents()[0].description);
+  ASSERT_EQ(1u, acd->streams().size());
+  EXPECT_EQ(kAudioTrack1, acd->streams()[0].id);
+  EXPECT_EQ(cricket::MD_SENDRECV, acd->direction());
+
+  EXPECT_FALSE(offer->contents()[1].rejected);
+  const VideoContentDescription* vcd =
+      static_cast<const VideoContentDescription*>(
+          offer->contents()[1].description);
+  ASSERT_EQ(1u, vcd->streams().size());
+  EXPECT_EQ(kVideoTrack1, vcd->streams()[0].id);
+  EXPECT_EQ(cricket::MD_SENDRECV, vcd->direction());
+
+  EXPECT_FALSE(offer->contents()[2].rejected);
+  acd = static_cast<const AudioContentDescription*>(
+      offer->contents()[2].description);
+  ASSERT_EQ(1u, acd->streams().size());
+  EXPECT_EQ(kAudioTrack2, acd->streams()[0].id);
+  EXPECT_EQ(cricket::MD_SENDRECV, acd->direction());
+
+  EXPECT_FALSE(offer->contents()[3].rejected);
+  vcd = static_cast<const VideoContentDescription*>(
+      offer->contents()[3].description);
+  ASSERT_EQ(1u, vcd->streams().size());
+  EXPECT_EQ(kVideoTrack2, vcd->streams()[0].id);
+  EXPECT_EQ(cricket::MD_SENDRECV, vcd->direction());
+}
+
+// Test that we can create an answer with multiple media sections of same media
+// type.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       CreateAnswerWithMultipleAVMediaSections) {
+  MediaSessionOptions opts;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio_1", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  AttachSenderToMediaSection("audio_1", MEDIA_TYPE_AUDIO, kAudioTrack1,
+                             kMediaStream1, 1, &opts);
+
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video_1", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  AttachSenderToMediaSection("video_1", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                             kMediaStream1, 1, &opts);
+
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio_2", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  AttachSenderToMediaSection("audio_2", MEDIA_TYPE_AUDIO, kAudioTrack2,
+                             kMediaStream2, 1, &opts);
+
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video_2", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  AttachSenderToMediaSection("video_2", MEDIA_TYPE_VIDEO, kVideoTrack2,
+                             kMediaStream2, 1, &opts);
+
+  std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, nullptr));
+  ASSERT_TRUE(offer);
+  std::unique_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), opts, nullptr));
+
+  ASSERT_EQ(4u, answer->contents().size());
+  EXPECT_FALSE(answer->contents()[0].rejected);
+  const AudioContentDescription* acd =
+      static_cast<const AudioContentDescription*>(
+          answer->contents()[0].description);
+  ASSERT_EQ(1u, acd->streams().size());
+  EXPECT_EQ(kAudioTrack1, acd->streams()[0].id);
+  EXPECT_EQ(cricket::MD_SENDRECV, acd->direction());
+
+  EXPECT_FALSE(answer->contents()[1].rejected);
+  const VideoContentDescription* vcd =
+      static_cast<const VideoContentDescription*>(
+          answer->contents()[1].description);
+  ASSERT_EQ(1u, vcd->streams().size());
+  EXPECT_EQ(kVideoTrack1, vcd->streams()[0].id);
+  EXPECT_EQ(cricket::MD_SENDRECV, vcd->direction());
+
+  EXPECT_FALSE(answer->contents()[2].rejected);
+  acd = static_cast<const AudioContentDescription*>(
+      answer->contents()[2].description);
+  ASSERT_EQ(1u, acd->streams().size());
+  EXPECT_EQ(kAudioTrack2, acd->streams()[0].id);
+  EXPECT_EQ(cricket::MD_SENDRECV, acd->direction());
+
+  EXPECT_FALSE(answer->contents()[3].rejected);
+  vcd = static_cast<const VideoContentDescription*>(
+      answer->contents()[3].description);
+  ASSERT_EQ(1u, vcd->streams().size());
+  EXPECT_EQ(kVideoTrack2, vcd->streams()[0].id);
+  EXPECT_EQ(cricket::MD_SENDRECV, vcd->direction());
+}
+
+// Test that the media section will be rejected in offer if the corresponding
+// MediaDescriptionOptions is stopped by the offerer.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       CreateOfferWithMediaSectionStoppedByOfferer) {
+  // Create an offer with two audio sections and one of them is stopped.
+  MediaSessionOptions offer_opts;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio1", cricket::MD_SENDRECV, kActive,
+                  &offer_opts);
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio2", cricket::MD_INACTIVE, kStopped,
+                  &offer_opts);
+  std::unique_ptr<SessionDescription> offer(
+      f1_.CreateOffer(offer_opts, nullptr));
+  ASSERT_TRUE(offer);
+  ASSERT_EQ(2u, offer->contents().size());
+  EXPECT_FALSE(offer->contents()[0].rejected);
+  EXPECT_TRUE(offer->contents()[1].rejected);
+}
+
+// Test that the media section will be rejected in answer if the corresponding
+// MediaDescriptionOptions is stopped by the offerer.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       CreateAnswerWithMediaSectionStoppedByOfferer) {
+  // Create an offer with two audio sections and one of them is stopped.
+  MediaSessionOptions offer_opts;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio1", cricket::MD_SENDRECV, kActive,
+                  &offer_opts);
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio2", cricket::MD_INACTIVE, kStopped,
+                  &offer_opts);
+  std::unique_ptr<SessionDescription> offer(
+      f1_.CreateOffer(offer_opts, nullptr));
+  ASSERT_TRUE(offer);
+  ASSERT_EQ(2u, offer->contents().size());
+  EXPECT_FALSE(offer->contents()[0].rejected);
+  EXPECT_TRUE(offer->contents()[1].rejected);
+
+  // Create an answer based on the offer.
+  MediaSessionOptions answer_opts;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio1", cricket::MD_SENDRECV, kActive,
+                  &answer_opts);
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio2", cricket::MD_SENDRECV, kActive,
+                  &answer_opts);
+  std::unique_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), answer_opts, nullptr));
+  ASSERT_EQ(2u, answer->contents().size());
+  EXPECT_FALSE(answer->contents()[0].rejected);
+  EXPECT_TRUE(answer->contents()[1].rejected);
+}
+
+// Test that the media section will be rejected in answer if the corresponding
+// MediaDescriptionOptions is stopped by the answerer.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       CreateAnswerWithMediaSectionRejectedByAnswerer) {
+  // Create an offer with two audio sections.
+  MediaSessionOptions offer_opts;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio1", cricket::MD_SENDRECV, kActive,
+                  &offer_opts);
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio2", cricket::MD_SENDRECV, kActive,
+                  &offer_opts);
+  std::unique_ptr<SessionDescription> offer(
+      f1_.CreateOffer(offer_opts, nullptr));
+  ASSERT_TRUE(offer);
+  ASSERT_EQ(2u, offer->contents().size());
+  ASSERT_FALSE(offer->contents()[0].rejected);
+  ASSERT_FALSE(offer->contents()[1].rejected);
+
+  // The answerer rejects one of the audio sections.
+  MediaSessionOptions answer_opts;
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio1", cricket::MD_SENDRECV, kActive,
+                  &answer_opts);
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio2", cricket::MD_INACTIVE, kStopped,
+                  &answer_opts);
+  std::unique_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), answer_opts, nullptr));
+  ASSERT_EQ(2u, answer->contents().size());
+  EXPECT_FALSE(answer->contents()[0].rejected);
+  EXPECT_TRUE(answer->contents()[1].rejected);
+}
+
+// Test the generated media sections has the same order of the
+// corresponding MediaDescriptionOptions.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       CreateOfferRespectsMediaDescriptionOptionsOrder) {
+  MediaSessionOptions opts;
+  // This tests put video section first because normally audio comes first by
+  // default.
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, nullptr));
+
+  ASSERT_TRUE(offer);
+  ASSERT_EQ(2u, offer->contents().size());
+  EXPECT_EQ("video", offer->contents()[0].name);
+  EXPECT_EQ("audio", offer->contents()[1].name);
+}
+
+// Test that different media sections using the same codec have same payload
+// type.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       PayloadTypesSharedByMediaSectionsOfSameType) {
+  MediaSessionOptions opts;
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video1", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video2", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  // Create an offer with two video sections using same codecs.
+  std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, nullptr));
+  ASSERT_TRUE(offer);
+  ASSERT_EQ(2u, offer->contents().size());
+  const VideoContentDescription* vcd1 =
+      static_cast<const VideoContentDescription*>(
+          offer->contents()[0].description);
+  const VideoContentDescription* vcd2 =
+      static_cast<const VideoContentDescription*>(
+          offer->contents()[1].description);
+  EXPECT_EQ(vcd1->codecs().size(), vcd2->codecs().size());
+  ASSERT_EQ(2u, vcd1->codecs().size());
+  EXPECT_EQ(vcd1->codecs()[0].name, vcd2->codecs()[0].name);
+  EXPECT_EQ(vcd1->codecs()[0].id, vcd2->codecs()[0].id);
+  EXPECT_EQ(vcd1->codecs()[1].name, vcd2->codecs()[1].name);
+  EXPECT_EQ(vcd1->codecs()[1].id, vcd2->codecs()[1].id);
+
+  // Create answer and negotiate the codecs.
+  std::unique_ptr<SessionDescription> answer(
+      f2_.CreateAnswer(offer.get(), opts, nullptr));
+  ASSERT_TRUE(answer);
+  ASSERT_EQ(2u, answer->contents().size());
+  vcd1 = static_cast<const VideoContentDescription*>(
+      answer->contents()[0].description);
+  vcd2 = static_cast<const VideoContentDescription*>(
+      answer->contents()[1].description);
+  EXPECT_EQ(vcd1->codecs().size(), vcd2->codecs().size());
+  ASSERT_EQ(1u, vcd1->codecs().size());
+  EXPECT_EQ(vcd1->codecs()[0].name, vcd2->codecs()[0].name);
+  EXPECT_EQ(vcd1->codecs()[0].id, vcd2->codecs()[0].id);
+}
+
+// Test that the codec preference order per media section is respected in
+// subsequent offer.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       CreateOfferRespectsCodecPreferenceOrder) {
+  MediaSessionOptions opts;
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video1", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video2", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  // Create an offer with two video sections using same codecs.
+  std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, nullptr));
+  ASSERT_TRUE(offer);
+  ASSERT_EQ(2u, offer->contents().size());
+  VideoContentDescription* vcd1 =
+      static_cast<VideoContentDescription*>(offer->contents()[0].description);
+  const VideoContentDescription* vcd2 =
+      static_cast<const VideoContentDescription*>(
+          offer->contents()[1].description);
+  auto video_codecs = MAKE_VECTOR(kVideoCodecs1);
+  EXPECT_EQ(video_codecs, vcd1->codecs());
+  EXPECT_EQ(video_codecs, vcd2->codecs());
+
+  // Change the codec preference of the first video section and create a
+  // follow-up offer.
+  auto video_codecs_reverse = MAKE_VECTOR(kVideoCodecs1Reverse);
+  vcd1->set_codecs(video_codecs_reverse);
+  std::unique_ptr<SessionDescription> updated_offer(
+      f1_.CreateOffer(opts, offer.get()));
+  vcd1 = static_cast<VideoContentDescription*>(
+      updated_offer->contents()[0].description);
+  vcd2 = static_cast<const VideoContentDescription*>(
+      updated_offer->contents()[1].description);
+  // The video codec preference order should be respected.
+  EXPECT_EQ(video_codecs_reverse, vcd1->codecs());
+  EXPECT_EQ(video_codecs, vcd2->codecs());
+}
+
+// Test that the codec preference order per media section is respected in
+// the answer.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       CreateAnswerRespectsCodecPreferenceOrder) {
+  MediaSessionOptions opts;
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video1", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  AddMediaSection(MEDIA_TYPE_VIDEO, "video2", cricket::MD_SENDRECV, kActive,
+                  &opts);
+  // Create an offer with two video sections using same codecs.
+  std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, nullptr));
+  ASSERT_TRUE(offer);
+  ASSERT_EQ(2u, offer->contents().size());
+  VideoContentDescription* vcd1 =
+      static_cast<VideoContentDescription*>(offer->contents()[0].description);
+  const VideoContentDescription* vcd2 =
+      static_cast<const VideoContentDescription*>(
+          offer->contents()[1].description);
+  auto video_codecs = MAKE_VECTOR(kVideoCodecs1);
+  EXPECT_EQ(video_codecs, vcd1->codecs());
+  EXPECT_EQ(video_codecs, vcd2->codecs());
+
+  // Change the codec preference of the first video section and create an
+  // answer.
+  auto video_codecs_reverse = MAKE_VECTOR(kVideoCodecs1Reverse);
+  vcd1->set_codecs(video_codecs_reverse);
+  std::unique_ptr<SessionDescription> answer(
+      f1_.CreateAnswer(offer.get(), opts, nullptr));
+  vcd1 =
+      static_cast<VideoContentDescription*>(answer->contents()[0].description);
+  vcd2 = static_cast<const VideoContentDescription*>(
+      answer->contents()[1].description);
+  // The video codec preference order should be respected.
+  EXPECT_EQ(video_codecs_reverse, vcd1->codecs());
+  EXPECT_EQ(video_codecs, vcd2->codecs());
+}
+
 class MediaProtocolTest : public ::testing::TestWithParam<const char*> {
  public:
   MediaProtocolTest() : f1_(&tdf1_), f2_(&tdf2_) {
@@ -2916,7 +3373,7 @@
 
 TEST_P(MediaProtocolTest, TestAudioVideoAcceptance) {
   MediaSessionOptions opts;
-  opts.recv_video = true;
+  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, nullptr));
   ASSERT_TRUE(offer.get() != nullptr);
   // Set the protocol for all the contents.
@@ -2976,32 +3433,47 @@
 
   // Test proper merge
   sf.set_audio_codecs(send_codecs, recv_codecs);
-  EXPECT_TRUE(sf.audio_send_codecs() == send_codecs);
-  EXPECT_TRUE(sf.audio_recv_codecs() == recv_codecs);
-  EXPECT_TRUE(sf.audio_sendrecv_codecs() == sendrecv_codecs);
+  EXPECT_EQ(send_codecs, sf.audio_send_codecs());
+  EXPECT_EQ(recv_codecs, sf.audio_recv_codecs());
+  EXPECT_EQ(sendrecv_codecs, sf.audio_sendrecv_codecs());
 
   // Test empty send codecs list
   sf.set_audio_codecs(no_codecs, recv_codecs);
-  EXPECT_TRUE(sf.audio_send_codecs() == no_codecs);
-  EXPECT_TRUE(sf.audio_recv_codecs() == recv_codecs);
-  EXPECT_TRUE(sf.audio_sendrecv_codecs() == no_codecs);
+  EXPECT_EQ(no_codecs, sf.audio_send_codecs());
+  EXPECT_EQ(recv_codecs, sf.audio_recv_codecs());
+  EXPECT_EQ(no_codecs, sf.audio_sendrecv_codecs());
 
   // Test empty recv codecs list
   sf.set_audio_codecs(send_codecs, no_codecs);
-  EXPECT_TRUE(sf.audio_send_codecs() == send_codecs);
-  EXPECT_TRUE(sf.audio_recv_codecs() == no_codecs);
-  EXPECT_TRUE(sf.audio_sendrecv_codecs() == no_codecs);
+  EXPECT_EQ(send_codecs, sf.audio_send_codecs());
+  EXPECT_EQ(no_codecs, sf.audio_recv_codecs());
+  EXPECT_EQ(no_codecs, sf.audio_sendrecv_codecs());
 
   // Test all empty codec lists
   sf.set_audio_codecs(no_codecs, no_codecs);
-  EXPECT_TRUE(sf.audio_send_codecs() == no_codecs);
-  EXPECT_TRUE(sf.audio_recv_codecs() == no_codecs);
-  EXPECT_TRUE(sf.audio_sendrecv_codecs() == no_codecs);
+  EXPECT_EQ(no_codecs, sf.audio_send_codecs());
+  EXPECT_EQ(no_codecs, sf.audio_recv_codecs());
+  EXPECT_EQ(no_codecs, sf.audio_sendrecv_codecs());
 }
 
 namespace {
-void TestAudioCodecsOffer(MediaContentDirection direction,
-                          bool add_legacy_stream) {
+// Compare the two vectors of codecs ignoring the payload type.
+template <class Codec>
+bool CodecsMatch(const std::vector<Codec>& codecs1,
+                 const std::vector<Codec>& codecs2) {
+  if (codecs1.size() != codecs2.size()) {
+    return false;
+  }
+
+  for (size_t i = 0; i < codecs1.size(); ++i) {
+    if (!codecs1[i].Matches(codecs2[i])) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void TestAudioCodecsOffer(MediaContentDirection direction) {
   TransportDescriptionFactory tdf;
   MediaSessionDescriptionFactory sf(&tdf);
   const std::vector<AudioCodec> send_codecs = MAKE_VECTOR(kAudioCodecs1);
@@ -3009,57 +3481,56 @@
   const std::vector<AudioCodec> sendrecv_codecs =
       MAKE_VECTOR(kAudioCodecsAnswer);
   sf.set_audio_codecs(send_codecs, recv_codecs);
-  sf.set_add_legacy_streams(add_legacy_stream);
 
   MediaSessionOptions opts;
-  opts.recv_audio = (direction == cricket::MD_RECVONLY ||
-                     direction == cricket::MD_SENDRECV);
-  opts.recv_video = false;
-  if (direction == cricket::MD_SENDONLY || direction == cricket::MD_SENDRECV)
-    opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", direction, kActive, &opts);
+
+  if (RtpTransceiverDirection::FromMediaContentDirection(direction).send) {
+    AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
+                               kMediaStream1, 1, &opts);
+  }
 
   std::unique_ptr<SessionDescription> offer(sf.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   const ContentInfo* ac = offer->GetContentByName("audio");
 
   // If the factory didn't add any audio content to the offer, we cannot check
-  // that the codecs put in are right. This happens when we neither want to send
-  // nor receive audio. The checks are still in place if at some point we'd
-  // instead create an inactive stream.
+  // that the codecs put in are right. This happens when we neither want to
+  // send nor receive audio. The checks are still in place if at some point
+  // we'd instead create an inactive stream.
   if (ac) {
     AudioContentDescription* acd =
         static_cast<AudioContentDescription*>(ac->description);
-    // sendrecv and inactive should both present lists as if the channel was to
-    // be used for sending and receiving. Inactive essentially means it might
-    // eventually be used anything, but we don't know more at this moment.
+    // sendrecv and inactive should both present lists as if the channel was
+    // to be used for sending and receiving. Inactive essentially means it
+    // might eventually be used anything, but we don't know more at this
+    // moment.
     if (acd->direction() == cricket::MD_SENDONLY) {
-      EXPECT_TRUE(acd->codecs() == send_codecs);
+      EXPECT_TRUE(CodecsMatch<AudioCodec>(send_codecs, acd->codecs()));
     } else if (acd->direction() == cricket::MD_RECVONLY) {
-      EXPECT_TRUE(acd->codecs() == recv_codecs);
+      EXPECT_TRUE(CodecsMatch<AudioCodec>(recv_codecs, acd->codecs()));
     } else {
-      EXPECT_TRUE(acd->codecs() == sendrecv_codecs);
+      EXPECT_TRUE(CodecsMatch<AudioCodec>(sendrecv_codecs, acd->codecs()));
     }
   }
 }
 
 static const AudioCodec kOfferAnswerCodecs[] = {
-  AudioCodec(0, "codec0", 16000,    -1, 1),
-  AudioCodec(1, "codec1",  8000, 13300, 1),
-  AudioCodec(2, "codec2",  8000, 64000, 1),
-  AudioCodec(3, "codec3",  8000, 64000, 1),
-  AudioCodec(4, "codec4",  8000,     0, 2),
-  AudioCodec(5, "codec5", 32000,     0, 1),
-  AudioCodec(6, "codec6", 48000,     0, 1)
-};
+    AudioCodec(0, "codec0", 16000, -1, 1),
+    AudioCodec(1, "codec1", 8000, 13300, 1),
+    AudioCodec(2, "codec2", 8000, 64000, 1),
+    AudioCodec(3, "codec3", 8000, 64000, 1),
+    AudioCodec(4, "codec4", 8000, 0, 2),
+    AudioCodec(5, "codec5", 32000, 0, 1),
+    AudioCodec(6, "codec6", 48000, 0, 1)};
 
-
-/* The codecs groups below are chosen as per the matrix below. The objective is
- * to have different sets of codecs in the inputs, to get unique sets of codecs
- * after negotiation, depending on offer and answer communication directions.
- * One-way directions in the offer should either result in the opposite
- * direction in the answer, or an inactive answer. Regardless, the choice of
- * codecs should be as if the answer contained the opposite direction.
- * Inactive offers should be treated as sendrecv/sendrecv.
+/* The codecs groups below are chosen as per the matrix below. The objective
+ * is to have different sets of codecs in the inputs, to get unique sets of
+ * codecs after negotiation, depending on offer and answer communication
+ * directions. One-way directions in the offer should either result in the
+ * opposite direction in the answer, or an inactive answer. Regardless, the
+ * choice of codecs should be as if the answer contained the opposite
+ * direction. Inactive offers should be treated as sendrecv/sendrecv.
  *
  *         |     Offer   |      Answer  |         Result
  *    codec|send recv sr | send recv sr | s/r  r/s sr/s sr/r sr/sr
@@ -3072,18 +3543,18 @@
  *     6   | x    x    x |  x    x    x |  x    x    x    x    x
  */
 // Codecs used by offerer in the AudioCodecsAnswerTest
-static const int kOfferSendCodecs[]               = { 0, 1, 3, 5, 6 };
-static const int kOfferRecvCodecs[]               = { 1, 2, 3, 4, 6 };
+static const int kOfferSendCodecs[] = {0, 1, 3, 5, 6};
+static const int kOfferRecvCodecs[] = {1, 2, 3, 4, 6};
 // Codecs used in the answerer in the AudioCodecsAnswerTest.  The order is
 // jumbled to catch the answer not following the order in the offer.
-static const int kAnswerSendCodecs[]              = { 6, 5, 2, 3, 4 };
-static const int kAnswerRecvCodecs[]              = { 6, 5, 4, 1, 0 };
+static const int kAnswerSendCodecs[] = {6, 5, 2, 3, 4};
+static const int kAnswerRecvCodecs[] = {6, 5, 4, 1, 0};
 // The resulting sets of codecs in the answer in the AudioCodecsAnswerTest
-static const int kResultSend_RecvCodecs[]         = { 0, 1, 5, 6 };
-static const int kResultRecv_SendCodecs[]         = { 2, 3, 4, 6 };
-static const int kResultSendrecv_SendCodecs[]     = { 3, 6 };
-static const int kResultSendrecv_RecvCodecs[]     = { 1, 6 };
-static const int kResultSendrecv_SendrecvCodecs[] = { 6 };
+static const int kResultSend_RecvCodecs[] = {0, 1, 5, 6};
+static const int kResultRecv_SendCodecs[] = {2, 3, 4, 6};
+static const int kResultSendrecv_SendCodecs[] = {3, 6};
+static const int kResultSendrecv_RecvCodecs[] = {1, 6};
+static const int kResultSendrecv_SendrecvCodecs[] = {6};
 
 template <typename T, int IDXS>
 std::vector<T> VectorFromIndices(const T* array, const int (&indices)[IDXS]) {
@@ -3109,17 +3580,14 @@
       VectorFromIndices(kOfferAnswerCodecs, kAnswerSendCodecs),
       VectorFromIndices(kOfferAnswerCodecs, kAnswerRecvCodecs));
 
-  // Never add a legacy stream to offer - we want to control the offer
-  // parameters exactly.
-  offer_factory.set_add_legacy_streams(false);
-  answer_factory.set_add_legacy_streams(add_legacy_stream);
   MediaSessionOptions offer_opts;
-  offer_opts.recv_audio = (offer_direction == cricket::MD_RECVONLY ||
-                           offer_direction == cricket::MD_SENDRECV);
-  offer_opts.recv_video = false;
-  if (offer_direction == cricket::MD_SENDONLY ||
-      offer_direction == cricket::MD_SENDRECV) {
-    offer_opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", offer_direction, kActive,
+                  &offer_opts);
+
+  if (RtpTransceiverDirection::FromMediaContentDirection(offer_direction)
+          .send) {
+    AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
+                               kMediaStream1, 1, &offer_opts);
   }
 
   std::unique_ptr<SessionDescription> offer(
@@ -3127,27 +3595,27 @@
   ASSERT_TRUE(offer.get() != NULL);
 
   MediaSessionOptions answer_opts;
-  answer_opts.recv_audio = (answer_direction == cricket::MD_RECVONLY ||
-                            answer_direction == cricket::MD_SENDRECV);
-  answer_opts.recv_video = false;
-  if (answer_direction == cricket::MD_SENDONLY ||
-      answer_direction == cricket::MD_SENDRECV) {
-    answer_opts.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
+  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", answer_direction, kActive,
+                  &answer_opts);
+
+  if (RtpTransceiverDirection::FromMediaContentDirection(answer_direction)
+          .send) {
+    AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
+                               kMediaStream1, 1, &answer_opts);
   }
   std::unique_ptr<SessionDescription> answer(
       answer_factory.CreateAnswer(offer.get(), answer_opts, NULL));
   const ContentInfo* ac = answer->GetContentByName("audio");
 
-  // If the factory didn't add any audio content to the answer, we cannot check
-  // that the codecs put in are right. This happens when we neither want to send
-  // nor receive audio. The checks are still in place if at some point we'd
-  // instead create an inactive stream.
+  // If the factory didn't add any audio content to the answer, we cannot
+  // check that the codecs put in are right. This happens when we neither want
+  // to send nor receive audio. The checks are still in place if at some point
+  // we'd instead create an inactive stream.
   if (ac) {
     const AudioContentDescription* acd =
         static_cast<const AudioContentDescription*>(ac->description);
     EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
 
-
     std::vector<AudioCodec> target_codecs;
     // For offers with sendrecv or inactive, we should never reply with more
     // codecs than offered, with these codec sets.
@@ -3157,20 +3625,20 @@
                                           kResultSendrecv_SendrecvCodecs);
         break;
       case cricket::MD_SENDONLY:
-        target_codecs = VectorFromIndices(kOfferAnswerCodecs,
-                                          kResultSend_RecvCodecs);
+        target_codecs =
+            VectorFromIndices(kOfferAnswerCodecs, kResultSend_RecvCodecs);
         break;
       case cricket::MD_RECVONLY:
-        target_codecs = VectorFromIndices(kOfferAnswerCodecs,
-                                          kResultRecv_SendCodecs);
+        target_codecs =
+            VectorFromIndices(kOfferAnswerCodecs, kResultRecv_SendCodecs);
         break;
       case cricket::MD_SENDRECV:
         if (acd->direction() == cricket::MD_SENDONLY) {
-          target_codecs = VectorFromIndices(kOfferAnswerCodecs,
-                                            kResultSendrecv_SendCodecs);
+          target_codecs =
+              VectorFromIndices(kOfferAnswerCodecs, kResultSendrecv_SendCodecs);
         } else if (acd->direction() == cricket::MD_RECVONLY) {
-          target_codecs = VectorFromIndices(kOfferAnswerCodecs,
-                                            kResultSendrecv_RecvCodecs);
+          target_codecs =
+              VectorFromIndices(kOfferAnswerCodecs, kResultSendrecv_RecvCodecs);
         } else {
           target_codecs = VectorFromIndices(kOfferAnswerCodecs,
                                             kResultSendrecv_SendrecvCodecs);
@@ -3178,7 +3646,7 @@
         break;
     }
 
-    auto format_codecs = [] (const std::vector<AudioCodec>& codecs) {
+    auto format_codecs = [](const std::vector<AudioCodec>& codecs) {
       std::stringstream os;
       bool first = true;
       os << "{";
@@ -3199,36 +3667,31 @@
         << "; got: " << MediaContentDirectionToString(acd->direction());
   } else {
     EXPECT_EQ(offer_direction, cricket::MD_INACTIVE)
-        << "Only inactive offers are allowed to not generate any audio content";
+        << "Only inactive offers are allowed to not generate any audio "
+           "content";
   }
 }
 
 }  // namespace
 
 class AudioCodecsOfferTest
-    : public ::testing::TestWithParam<::testing::tuple<MediaContentDirection,
-                                                       bool>> {
-};
+    : public ::testing::TestWithParam<MediaContentDirection> {};
 
 TEST_P(AudioCodecsOfferTest, TestCodecsInOffer) {
-  TestAudioCodecsOffer(::testing::get<0>(GetParam()),
-                       ::testing::get<1>(GetParam()));
+  TestAudioCodecsOffer(GetParam());
 }
 
 INSTANTIATE_TEST_CASE_P(MediaSessionDescriptionFactoryTest,
                         AudioCodecsOfferTest,
-                        ::testing::Combine(
-                             ::testing::Values(cricket::MD_SENDONLY,
-                                               cricket::MD_RECVONLY,
-                                               cricket::MD_SENDRECV,
-                                               cricket::MD_INACTIVE),
-                             ::testing::Bool()));
+                        ::testing::Values(cricket::MD_SENDONLY,
+                                          cricket::MD_RECVONLY,
+                                          cricket::MD_SENDRECV,
+                                          cricket::MD_INACTIVE));
 
 class AudioCodecsAnswerTest
     : public ::testing::TestWithParam<::testing::tuple<MediaContentDirection,
                                                        MediaContentDirection,
-                                                       bool>> {
-};
+                                                       bool>> {};
 
 TEST_P(AudioCodecsAnswerTest, TestCodecsInAnswer) {
   TestAudioCodecsAnswer(::testing::get<0>(GetParam()),
@@ -3236,15 +3699,15 @@
                         ::testing::get<2>(GetParam()));
 }
 
-INSTANTIATE_TEST_CASE_P(MediaSessionDescriptionFactoryTest,
-                        AudioCodecsAnswerTest,
-                        ::testing::Combine(
-                             ::testing::Values(cricket::MD_SENDONLY,
-                                               cricket::MD_RECVONLY,
-                                               cricket::MD_SENDRECV,
-                                               cricket::MD_INACTIVE),
-                             ::testing::Values(cricket::MD_SENDONLY,
-                                               cricket::MD_RECVONLY,
-                                               cricket::MD_SENDRECV,
-                                               cricket::MD_INACTIVE),
-                             ::testing::Bool()));
+INSTANTIATE_TEST_CASE_P(
+    MediaSessionDescriptionFactoryTest,
+    AudioCodecsAnswerTest,
+    ::testing::Combine(::testing::Values(cricket::MD_SENDONLY,
+                                         cricket::MD_RECVONLY,
+                                         cricket::MD_SENDRECV,
+                                         cricket::MD_INACTIVE),
+                       ::testing::Values(cricket::MD_SENDONLY,
+                                         cricket::MD_RECVONLY,
+                                         cricket::MD_SENDRECV,
+                                         cricket::MD_INACTIVE),
+                       ::testing::Bool()));
diff --git a/webrtc/pc/peerconnection.cc b/webrtc/pc/peerconnection.cc
index 3af5667..d975ed9 100644
--- a/webrtc/pc/peerconnection.cc
+++ b/webrtc/pc/peerconnection.cc
@@ -133,32 +133,45 @@
          (value <= Options::kMaxOfferToReceiveMedia);
 }
 
-// Add the stream and RTP data channel info to |session_options|.
-void AddSendStreams(
-    cricket::MediaSessionOptions* session_options,
+// Add options to |[audio/video]_media_description_options| from |senders|.
+void AddRtpSenderOptions(
     const std::vector<rtc::scoped_refptr<
         RtpSenderProxyWithInternal<RtpSenderInternal>>>& senders,
-    const std::map<std::string, rtc::scoped_refptr<DataChannel>>&
-        rtp_data_channels) {
-  session_options->streams.clear();
+    cricket::MediaDescriptionOptions* audio_media_description_options,
+    cricket::MediaDescriptionOptions* video_media_description_options) {
   for (const auto& sender : senders) {
-    session_options->AddSendStream(sender->media_type(), sender->id(),
-                                   sender->internal()->stream_id());
+    if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) {
+      if (audio_media_description_options) {
+        audio_media_description_options->AddAudioSender(
+            sender->id(), sender->internal()->stream_id());
+      }
+    } 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_id(), 1);
+      }
+    }
   }
+}
 
+// Add options to |session_options| from |rtp_data_channels|.
+void AddRtpDataChannelOptions(
+    const std::map<std::string, rtc::scoped_refptr<DataChannel>>&
+        rtp_data_channels,
+    cricket::MediaDescriptionOptions* data_media_description_options) {
+  if (!data_media_description_options) {
+    return;
+  }
   // Check for data channels.
   for (const auto& kv : rtp_data_channels) {
     const DataChannel* channel = kv.second;
     if (channel->state() == DataChannel::kConnecting ||
         channel->state() == DataChannel::kOpen) {
-      // |streamid| and |sync_label| are both set to the DataChannel label
-      // here so they can be signaled the same way as MediaStreams and Tracks.
-      // For MediaStreams, the sync_label is the MediaStream label and the
-      // track label is the same as |streamid|.
-      const std::string& streamid = channel->label();
-      const std::string& sync_label = channel->label();
-      session_options->AddSendStream(cricket::MEDIA_TYPE_DATA, streamid,
-                                     sync_label);
+      // Legacy RTP data channels are signaled with the track/stream ID set to
+      // the data channel's label.
+      data_media_description_options->AddRtpDataChannel(channel->label(),
+                                                        channel->label());
     }
   }
 }
@@ -314,92 +327,62 @@
   return cname;
 }
 
-bool ExtractMediaSessionOptions(
-    const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
-    bool is_offer,
-    cricket::MediaSessionOptions* session_options) {
-  typedef PeerConnectionInterface::RTCOfferAnswerOptions RTCOfferAnswerOptions;
-  if (!IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_audio) ||
-      !IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_video)) {
-    return false;
-  }
-
-  // If constraints don't prevent us, we always accept video.
-  if (rtc_options.offer_to_receive_audio != RTCOfferAnswerOptions::kUndefined) {
-    session_options->recv_audio = (rtc_options.offer_to_receive_audio > 0);
-  } else {
-    session_options->recv_audio = true;
-  }
-  // For offers, we only offer video if we have it or it's forced by options.
-  // For answers, we will always accept video (if offered).
-  if (rtc_options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) {
-    session_options->recv_video = (rtc_options.offer_to_receive_video > 0);
-  } else if (is_offer) {
-    session_options->recv_video = false;
-  } else {
-    session_options->recv_video = true;
-  }
-
-  session_options->vad_enabled = rtc_options.voice_activity_detection;
-  session_options->bundle_enabled = rtc_options.use_rtp_mux;
-  for (auto& kv : session_options->transport_options) {
-    kv.second.ice_restart = rtc_options.ice_restart;
-  }
-
-  return true;
+bool ValidateOfferAnswerOptions(
+    const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options) {
+  return IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_audio) &&
+         IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_video);
 }
 
-bool ParseConstraintsForAnswer(const MediaConstraintsInterface* constraints,
-                               cricket::MediaSessionOptions* session_options) {
-  bool value = false;
-  size_t mandatory_constraints_satisfied = 0;
+// From |rtc_options|, fill parts of |session_options| shared by all generated
+// m= sections (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;
+}
 
-  // kOfferToReceiveAudio defaults to true according to spec.
-  if (!FindConstraint(constraints,
-                      MediaConstraintsInterface::kOfferToReceiveAudio, &value,
-                      &mandatory_constraints_satisfied) ||
-      value) {
-    session_options->recv_audio = true;
-  }
-
-  // kOfferToReceiveVideo defaults to false according to spec. But
-  // if it is an answer and video is offered, we should still accept video
-  // per default.
-  value = false;
-  if (!FindConstraint(constraints,
-                      MediaConstraintsInterface::kOfferToReceiveVideo, &value,
-                      &mandatory_constraints_satisfied) ||
-      value) {
-    session_options->recv_video = true;
-  }
-
-  if (FindConstraint(constraints,
-                     MediaConstraintsInterface::kVoiceActivityDetection, &value,
-                     &mandatory_constraints_satisfied)) {
-    session_options->vad_enabled = value;
-  }
-
-  if (FindConstraint(constraints, MediaConstraintsInterface::kUseRtpMux, &value,
-                     &mandatory_constraints_satisfied)) {
-    session_options->bundle_enabled = value;
-  } else {
-    // kUseRtpMux defaults to true according to spec.
-    session_options->bundle_enabled = true;
-  }
-
-  bool ice_restart = false;
-  if (FindConstraint(constraints, MediaConstraintsInterface::kIceRestart,
-                     &value, &mandatory_constraints_satisfied)) {
-    // kIceRestart defaults to false according to spec.
-    ice_restart = true;
-  }
-  for (auto& kv : session_options->transport_options) {
-    kv.second.ice_restart = ice_restart;
-  }
-
+bool ConvertConstraintsToOfferAnswerOptions(
+    const MediaConstraintsInterface* constraints,
+    PeerConnectionInterface::RTCOfferAnswerOptions* offer_answer_options) {
   if (!constraints) {
     return true;
   }
+
+  bool value = false;
+  size_t mandatory_constraints_satisfied = 0;
+
+  if (FindConstraint(constraints,
+                     MediaConstraintsInterface::kOfferToReceiveAudio, &value,
+                     &mandatory_constraints_satisfied)) {
+    offer_answer_options->offer_to_receive_audio =
+        value ? PeerConnectionInterface::RTCOfferAnswerOptions::
+                    kOfferToReceiveMediaTrue
+              : 0;
+  }
+
+  if (FindConstraint(constraints,
+                     MediaConstraintsInterface::kOfferToReceiveVideo, &value,
+                     &mandatory_constraints_satisfied)) {
+    offer_answer_options->offer_to_receive_video =
+        value ? PeerConnectionInterface::RTCOfferAnswerOptions::
+                    kOfferToReceiveMediaTrue
+              : 0;
+  }
+  if (FindConstraint(constraints,
+                     MediaConstraintsInterface::kVoiceActivityDetection, &value,
+                     &mandatory_constraints_satisfied)) {
+    offer_answer_options->voice_activity_detection = value;
+  }
+  if (FindConstraint(constraints, MediaConstraintsInterface::kUseRtpMux, &value,
+                     &mandatory_constraints_satisfied)) {
+    offer_answer_options->use_rtp_mux = value;
+  }
+  if (FindConstraint(constraints, MediaConstraintsInterface::kIceRestart,
+                     &value, &mandatory_constraints_satisfied)) {
+    offer_answer_options->ice_restart = value;
+  }
+
   return mandatory_constraints_satisfied == constraints->GetMandatory().size();
 }
 
@@ -842,49 +825,17 @@
     LOG(LS_ERROR) << "CreateOffer - observer is NULL.";
     return;
   }
-  RTCOfferAnswerOptions options;
+  PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options;
+  // Always create an offer even if |ConvertConstraintsToOfferAnswerOptions|
+  // returns false for now. Because |ConvertConstraintsToOfferAnswerOptions|
+  // compares the mandatory fields parsed with the mandatory fields added in the
+  // |constraints| and some downstream applications might create offers with
+  // mandatory fields which would not be parsed in the helper method. For
+  // example, in Chromium/remoting, |kEnableDtlsSrtp| is added to the
+  // |constraints| as a mandatory field but it is not parsed.
+  ConvertConstraintsToOfferAnswerOptions(constraints, &offer_answer_options);
 
-  bool value;
-  size_t mandatory_constraints = 0;
-
-  if (FindConstraint(constraints,
-                     MediaConstraintsInterface::kOfferToReceiveAudio,
-                     &value,
-                     &mandatory_constraints)) {
-    options.offer_to_receive_audio =
-        value ? RTCOfferAnswerOptions::kOfferToReceiveMediaTrue : 0;
-  }
-
-  if (FindConstraint(constraints,
-                     MediaConstraintsInterface::kOfferToReceiveVideo,
-                     &value,
-                     &mandatory_constraints)) {
-    options.offer_to_receive_video =
-        value ? RTCOfferAnswerOptions::kOfferToReceiveMediaTrue : 0;
-  }
-
-  if (FindConstraint(constraints,
-                     MediaConstraintsInterface::kVoiceActivityDetection,
-                     &value,
-                     &mandatory_constraints)) {
-    options.voice_activity_detection = value;
-  }
-
-  if (FindConstraint(constraints,
-                     MediaConstraintsInterface::kIceRestart,
-                     &value,
-                     &mandatory_constraints)) {
-    options.ice_restart = value;
-  }
-
-  if (FindConstraint(constraints,
-                     MediaConstraintsInterface::kUseRtpMux,
-                     &value,
-                     &mandatory_constraints)) {
-    options.use_rtp_mux = value;
-  }
-
-  CreateOffer(observer, options);
+  CreateOffer(observer, offer_answer_options);
 }
 
 void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer,
@@ -895,14 +846,15 @@
     return;
   }
 
-  cricket::MediaSessionOptions session_options;
-  if (!GetOptionsForOffer(options, &session_options)) {
+  if (!ValidateOfferAnswerOptions(options)) {
     std::string error = "CreateOffer called with invalid options.";
     LOG(LS_ERROR) << error;
     PostCreateSessionDescriptionFailure(observer, error);
     return;
   }
 
+  cricket::MediaSessionOptions session_options;
+  GetOptionsForOffer(options, &session_options);
   session_->CreateOffer(observer, options, session_options);
 }
 
@@ -915,14 +867,26 @@
     return;
   }
 
-  cricket::MediaSessionOptions session_options;
-  if (!GetOptionsForAnswer(constraints, &session_options)) {
+  if (!session_->remote_description() ||
+      session_->remote_description()->type() !=
+          SessionDescriptionInterface::kOffer) {
+    std::string error = "CreateAnswer called without remote offer.";
+    LOG(LS_ERROR) << error;
+    PostCreateSessionDescriptionFailure(observer, error);
+    return;
+  }
+
+  PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options;
+  if (!ConvertConstraintsToOfferAnswerOptions(constraints,
+                                              &offer_answer_options)) {
     std::string error = "CreateAnswer called with invalid constraints.";
     LOG(LS_ERROR) << error;
     PostCreateSessionDescriptionFailure(observer, error);
     return;
   }
 
+  cricket::MediaSessionOptions session_options;
+  GetOptionsForAnswer(offer_answer_options, &session_options);
   session_->CreateAnswer(observer, session_options);
 }
 
@@ -935,12 +899,7 @@
   }
 
   cricket::MediaSessionOptions session_options;
-  if (!GetOptionsForAnswer(options, &session_options)) {
-    std::string error = "CreateAnswer called with invalid options.";
-    LOG(LS_ERROR) << error;
-    PostCreateSessionDescriptionFailure(observer, error);
-    return;
-  }
+  GetOptionsForAnswer(options, &session_options);
 
   session_->CreateAnswer(observer, session_options);
 }
@@ -1698,121 +1657,242 @@
                            MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg);
 }
 
-bool PeerConnection::GetOptionsForOffer(
+void PeerConnection::GetOptionsForOffer(
     const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
     cricket::MediaSessionOptions* session_options) {
-  // TODO(deadbeef): Once we have transceivers, enumerate them here instead of
-  // ContentInfos.
+  ExtractSharedMediaSessionOptions(rtc_options, session_options);
+
+  // Figure out transceiver directional preferences.
+  bool send_audio = HasRtpSender(cricket::MEDIA_TYPE_AUDIO);
+  bool send_video = HasRtpSender(cricket::MEDIA_TYPE_VIDEO);
+
+  // 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 = HasDataChannels();
+
+  // The "offer_to_receive_X" options allow those defaults to be overridden.
+  if (rtc_options.offer_to_receive_audio != RTCOfferAnswerOptions::kUndefined) {
+    recv_audio = (rtc_options.offer_to_receive_audio > 0);
+    offer_new_audio_description =
+        offer_new_audio_description || (rtc_options.offer_to_receive_audio > 0);
+  }
+  if (rtc_options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) {
+    recv_video = (rtc_options.offer_to_receive_video > 0);
+    offer_new_video_description =
+        offer_new_video_description || (rtc_options.offer_to_receive_video > 0);
+  }
+
+  int audio_index = -1;
+  int video_index = -1;
+  int data_index = -1;
+  // 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 (session_->local_description()) {
-    for (const cricket::ContentInfo& content :
-         session_->local_description()->description()->contents()) {
-      session_options->transport_options[content.name] =
-          cricket::TransportOptions();
-    }
-  }
-  session_options->enable_ice_renomination =
-      configuration_.enable_ice_renomination;
-
-  if (!ExtractMediaSessionOptions(rtc_options, true, session_options)) {
-    return false;
+    GenerateMediaDescriptionOptions(
+        session_->local_description(),
+        cricket::RtpTransceiverDirection(send_audio, recv_audio),
+        cricket::RtpTransceiverDirection(send_video, recv_video), &audio_index,
+        &video_index, &data_index, session_options);
   }
 
-  AddSendStreams(session_options, senders_, rtp_data_channels_);
-  // Offer to receive audio/video if the constraint is not set and there are
-  // send streams, or we're currently receiving.
-  if (rtc_options.offer_to_receive_audio == RTCOfferAnswerOptions::kUndefined) {
-    session_options->recv_audio =
-        session_options->HasSendMediaStream(cricket::MEDIA_TYPE_AUDIO) ||
-        !remote_audio_tracks_.empty();
+  // Add audio/video/data m= sections to the end if needed.
+  if (audio_index == -1 && offer_new_audio_description) {
+    session_options->media_description_options.push_back(
+        cricket::MediaDescriptionOptions(
+            cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO,
+            cricket::RtpTransceiverDirection(send_audio, recv_audio), false));
+    audio_index = session_options->media_description_options.size() - 1;
   }
-  if (rtc_options.offer_to_receive_video == RTCOfferAnswerOptions::kUndefined) {
-    session_options->recv_video =
-        session_options->HasSendMediaStream(cricket::MEDIA_TYPE_VIDEO) ||
-        !remote_video_tracks_.empty();
+  if (video_index == -1 && offer_new_video_description) {
+    session_options->media_description_options.push_back(
+        cricket::MediaDescriptionOptions(
+            cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO,
+            cricket::RtpTransceiverDirection(send_video, recv_video), false));
+    video_index = session_options->media_description_options.size() - 1;
   }
+  if (data_index == -1 && offer_new_data_description) {
+    session_options->media_description_options.push_back(
+        cricket::MediaDescriptionOptions(
+            cricket::MEDIA_TYPE_DATA, cricket::CN_DATA,
+            cricket::RtpTransceiverDirection(true, true), false));
+    data_index = session_options->media_description_options.size() - 1;
+  }
+
+  cricket::MediaDescriptionOptions* audio_media_description_options =
+      audio_index == -1
+          ? nullptr
+          : &session_options->media_description_options[audio_index];
+  cricket::MediaDescriptionOptions* video_media_description_options =
+      video_index == -1
+          ? nullptr
+          : &session_options->media_description_options[video_index];
+  cricket::MediaDescriptionOptions* data_media_description_options =
+      data_index == -1
+          ? nullptr
+          : &session_options->media_description_options[data_index];
+
+  // Apply ICE restart flag and renomination flag.
+  for (auto& options : session_options->media_description_options) {
+    options.transport_options.ice_restart = rtc_options.ice_restart;
+    options.transport_options.enable_ice_renomination =
+        configuration_.enable_ice_renomination;
+  }
+
+  AddRtpSenderOptions(senders_, audio_media_description_options,
+                      video_media_description_options);
+  AddRtpDataChannelOptions(rtp_data_channels_, data_media_description_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 (HasDataChannels() && session_->data_channel_type() != cricket::DCT_RTP) {
+  if (!rtp_data_channels_.empty() ||
+      session_->data_channel_type() != cricket::DCT_RTP) {
     session_options->data_channel_type = session_->data_channel_type();
   }
 
-  session_options->bundle_enabled =
-      session_options->bundle_enabled &&
-      (session_options->has_audio() || session_options->has_video() ||
-       session_options->has_data());
-
   session_options->rtcp_cname = rtcp_cname_;
   session_options->crypto_options = factory_->options().crypto_options;
-  return true;
 }
 
-void PeerConnection::InitializeOptionsForAnswer(
+void PeerConnection::GetOptionsForAnswer(
+    const RTCOfferAnswerOptions& rtc_options,
     cricket::MediaSessionOptions* session_options) {
-  session_options->recv_audio = false;
-  session_options->recv_video = false;
-  session_options->enable_ice_renomination =
-      configuration_.enable_ice_renomination;
-}
+  ExtractSharedMediaSessionOptions(rtc_options, session_options);
 
-void PeerConnection::FinishOptionsForAnswer(
-    cricket::MediaSessionOptions* session_options) {
-  // TODO(deadbeef): Once we have transceivers, enumerate them here instead of
-  // ContentInfos.
-  if (session_->remote_description()) {
-    // Initialize the transport_options map.
-    for (const cricket::ContentInfo& content :
-         session_->remote_description()->description()->contents()) {
-      session_options->transport_options[content.name] =
-          cricket::TransportOptions();
-    }
+  // Figure out transceiver directional preferences.
+  bool send_audio = HasRtpSender(cricket::MEDIA_TYPE_AUDIO);
+  bool send_video = HasRtpSender(cricket::MEDIA_TYPE_VIDEO);
+
+  // 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 (rtc_options.offer_to_receive_audio != RTCOfferAnswerOptions::kUndefined) {
+    recv_audio = (rtc_options.offer_to_receive_audio > 0);
   }
-  AddSendStreams(session_options, senders_, rtp_data_channels_);
-  // RTP data channel is handled in MediaSessionOptions::AddStream. SCTP streams
-  // are not signaled in the SDP so does not go through that path and must be
-  // handled here.
+  if (rtc_options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) {
+    recv_video = (rtc_options.offer_to_receive_video > 0);
+  }
+
+  int audio_index = -1;
+  int video_index = -1;
+  int data_index = -1;
+  // There should be a pending remote description that's an offer...
+  RTC_DCHECK(session_->remote_description());
+  RTC_DCHECK(session_->remote_description()->type() ==
+             SessionDescriptionInterface::kOffer);
+  // Generate m= sections that match those in the offer.
+  // Note that mediasession.cc will handle intersection our preferred direction
+  // with the offered direction.
+  GenerateMediaDescriptionOptions(
+      session_->remote_description(),
+      cricket::RtpTransceiverDirection(send_audio, recv_audio),
+      cricket::RtpTransceiverDirection(send_video, recv_video), &audio_index,
+      &video_index, &data_index, session_options);
+
+  cricket::MediaDescriptionOptions* audio_media_description_options =
+      audio_index == -1
+          ? nullptr
+          : &session_options->media_description_options[audio_index];
+  cricket::MediaDescriptionOptions* video_media_description_options =
+      video_index == -1
+          ? nullptr
+          : &session_options->media_description_options[video_index];
+  cricket::MediaDescriptionOptions* data_media_description_options =
+      data_index == -1
+          ? nullptr
+          : &session_options->media_description_options[data_index];
+
+  // Apply ICE renomination flag.
+  for (auto& options : session_options->media_description_options) {
+    options.transport_options.enable_ice_renomination =
+        configuration_.enable_ice_renomination;
+  }
+
+  AddRtpSenderOptions(senders_, audio_media_description_options,
+                      video_media_description_options);
+  AddRtpDataChannelOptions(rtp_data_channels_, data_media_description_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 (session_->data_channel_type() != cricket::DCT_RTP) {
+  if (!rtp_data_channels_.empty() ||
+      session_->data_channel_type() != cricket::DCT_RTP) {
     session_options->data_channel_type = session_->data_channel_type();
   }
-  session_options->bundle_enabled =
-      session_options->bundle_enabled &&
-      (session_options->has_audio() || session_options->has_video() ||
-       session_options->has_data());
 
+  session_options->rtcp_cname = rtcp_cname_;
   session_options->crypto_options = factory_->options().crypto_options;
 }
 
-bool PeerConnection::GetOptionsForAnswer(
-    const MediaConstraintsInterface* constraints,
+void PeerConnection::GenerateMediaDescriptionOptions(
+    const SessionDescriptionInterface* session_desc,
+    cricket::RtpTransceiverDirection audio_direction,
+    cricket::RtpTransceiverDirection video_direction,
+    int* audio_index,
+    int* video_index,
+    int* data_index,
     cricket::MediaSessionOptions* session_options) {
-  InitializeOptionsForAnswer(session_options);
-  if (!ParseConstraintsForAnswer(constraints, session_options)) {
-    return false;
+  for (const cricket::ContentInfo& content :
+       session_desc->description()->contents()) {
+    if (IsAudioContent(&content)) {
+      // If we already have an audio m= section, reject this extra one.
+      if (*audio_index != -1) {
+        session_options->media_description_options.push_back(
+            cricket::MediaDescriptionOptions(
+                cricket::MEDIA_TYPE_AUDIO, content.name,
+                cricket::RtpTransceiverDirection(false, false), true));
+      } else {
+        session_options->media_description_options.push_back(
+            cricket::MediaDescriptionOptions(
+                cricket::MEDIA_TYPE_AUDIO, content.name, audio_direction,
+                !audio_direction.send && !audio_direction.recv));
+        *audio_index = session_options->media_description_options.size() - 1;
+      }
+    } else if (IsVideoContent(&content)) {
+      // If we already have an video m= section, reject this extra one.
+      if (*video_index != -1) {
+        session_options->media_description_options.push_back(
+            cricket::MediaDescriptionOptions(
+                cricket::MEDIA_TYPE_VIDEO, content.name,
+                cricket::RtpTransceiverDirection(false, false), true));
+      } else {
+        session_options->media_description_options.push_back(
+            cricket::MediaDescriptionOptions(
+                cricket::MEDIA_TYPE_VIDEO, content.name, video_direction,
+                !video_direction.send && !video_direction.recv));
+        *video_index = session_options->media_description_options.size() - 1;
+      }
+    } else {
+      RTC_DCHECK(IsDataContent(&content));
+      // If we already have an data m= section, reject this extra one.
+      if (*data_index != -1) {
+        session_options->media_description_options.push_back(
+            cricket::MediaDescriptionOptions(
+                cricket::MEDIA_TYPE_DATA, content.name,
+                cricket::RtpTransceiverDirection(false, false), true));
+      } else {
+        session_options->media_description_options.push_back(
+            cricket::MediaDescriptionOptions(
+                cricket::MEDIA_TYPE_DATA, content.name,
+                // Direction for data sections is meaningless, but legacy
+                // endpoints might expect sendrecv.
+                cricket::RtpTransceiverDirection(true, true), false));
+        *data_index = session_options->media_description_options.size() - 1;
+      }
+    }
   }
-  session_options->rtcp_cname = rtcp_cname_;
-
-  FinishOptionsForAnswer(session_options);
-  return true;
-}
-
-bool PeerConnection::GetOptionsForAnswer(
-    const RTCOfferAnswerOptions& options,
-    cricket::MediaSessionOptions* session_options) {
-  InitializeOptionsForAnswer(session_options);
-  if (!ExtractMediaSessionOptions(options, false, session_options)) {
-    return false;
-  }
-  session_options->rtcp_cname = rtcp_cname_;
-
-  FinishOptionsForAnswer(session_options);
-  return true;
 }
 
 void PeerConnection::RemoveTracks(cricket::MediaType media_type) {
@@ -2285,6 +2365,15 @@
   observer_->OnDataChannel(std::move(proxy_channel));
 }
 
+bool PeerConnection::HasRtpSender(cricket::MediaType type) const {
+  return std::find_if(
+             senders_.begin(), senders_.end(),
+             [type](const rtc::scoped_refptr<
+                    RtpSenderProxyWithInternal<RtpSenderInternal>>& sender) {
+               return sender->media_type() == type;
+             }) != senders_.end();
+}
+
 RtpSenderInternal* PeerConnection::FindSenderById(const std::string& id) {
   auto it = std::find_if(
       senders_.begin(), senders_.end(),
diff --git a/webrtc/pc/peerconnection.h b/webrtc/pc/peerconnection.h
index f8b6a54..48e9628 100644
--- a/webrtc/pc/peerconnection.h
+++ b/webrtc/pc/peerconnection.h
@@ -32,28 +32,12 @@
 class VideoRtpReceiver;
 class RtcEventLog;
 
-// Populates |session_options| from |rtc_options|, and returns true if options
-// are valid.
-// |session_options|->transport_options map entries must exist in order for
-// them to be populated from |rtc_options|.
-bool ExtractMediaSessionOptions(
+// TODO(zhihuang): Remove this declaration when the WebRtcSession tests don't
+// need it.
+void ExtractSharedMediaSessionOptions(
     const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
-    bool is_offer,
     cricket::MediaSessionOptions* session_options);
 
-// Populates |session_options| from |constraints|, and returns true if all
-// mandatory constraints are satisfied.
-// Assumes that |session_options|->transport_options map entries exist.
-// Will also set defaults if corresponding constraints are not present:
-// recv_audio=true, recv_video=true, bundle_enabled=true.
-// Other fields will be left with existing values.
-//
-// Deprecated. Will be removed once callers that use constraints are gone.
-// TODO(hta): Remove when callers are gone.
-// https://bugs.chromium.org/p/webrtc/issues/detail?id=5617
-bool ParseConstraintsForAnswer(const MediaConstraintsInterface* constraints,
-                               cricket::MediaSessionOptions* session_options);
-
 // PeerConnection implements the PeerConnectionInterface interface.
 // It uses WebRtcSession to implement the PeerConnection functionality.
 class PeerConnection : public PeerConnectionInterface,
@@ -244,26 +228,24 @@
 
   // Returns a MediaSessionOptions struct with options decided by |options|,
   // the local MediaStreams and DataChannels.
-  virtual bool GetOptionsForOffer(
+  void GetOptionsForOffer(
       const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
       cricket::MediaSessionOptions* session_options);
 
   // Returns a MediaSessionOptions struct with options decided by
   // |constraints|, the local MediaStreams and DataChannels.
-  // Deprecated, use version without constraints.
-  virtual bool GetOptionsForAnswer(
-      const MediaConstraintsInterface* constraints,
-      cricket::MediaSessionOptions* session_options);
-  virtual bool GetOptionsForAnswer(
-      const RTCOfferAnswerOptions& options,
-      cricket::MediaSessionOptions* session_options);
+  void GetOptionsForAnswer(const RTCOfferAnswerOptions& options,
+                           cricket::MediaSessionOptions* session_options);
 
-  void InitializeOptionsForAnswer(
-      cricket::MediaSessionOptions* session_options);
-
-  // Helper function for options processing.
-  // Deprecated.
-  virtual void FinishOptionsForAnswer(
+  // Generates MediaDescriptionOptions for the |session_opts| based on existing
+  // local description or remote description.
+  void GenerateMediaDescriptionOptions(
+      const SessionDescriptionInterface* session_desc,
+      cricket::RtpTransceiverDirection audio_direction,
+      cricket::RtpTransceiverDirection video_direction,
+      int* audio_index,
+      int* video_index,
+      int* data_index,
       cricket::MediaSessionOptions* session_options);
 
   // Remove all local and remote tracks of type |media_type|.
@@ -361,6 +343,7 @@
   void OnDataChannelOpenMessage(const std::string& label,
                                 const InternalDataChannelInit& config);
 
+  bool HasRtpSender(cricket::MediaType type) const;
   RtpSenderInternal* FindSenderById(const std::string& id);
 
   std::vector<rtc::scoped_refptr<
diff --git a/webrtc/pc/peerconnectioninterface_unittest.cc b/webrtc/pc/peerconnectioninterface_unittest.cc
index 0e52c20..892615a 100644
--- a/webrtc/pc/peerconnectioninterface_unittest.cc
+++ b/webrtc/pc/peerconnectioninterface_unittest.cc
@@ -1184,6 +1184,57 @@
     return audio_desc->streams()[0].cname;
   }
 
+  std::unique_ptr<SessionDescriptionInterface> CreateOfferWithOptions(
+      const RTCOfferAnswerOptions& offer_answer_options) {
+    RTC_DCHECK(pc_);
+    rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observer(
+        new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>());
+    pc_->CreateOffer(observer, offer_answer_options);
+    EXPECT_EQ_WAIT(true, observer->called(), kTimeout);
+    return observer->MoveDescription();
+  }
+
+  void CreateOfferWithOptionsAsRemoteDescription(
+      std::unique_ptr<SessionDescriptionInterface>* desc,
+      const RTCOfferAnswerOptions& offer_answer_options) {
+    *desc = CreateOfferWithOptions(offer_answer_options);
+    ASSERT_TRUE(desc != nullptr);
+    std::string sdp;
+    EXPECT_TRUE((*desc)->ToString(&sdp));
+    SessionDescriptionInterface* remote_offer =
+        webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                         sdp, NULL);
+    EXPECT_TRUE(DoSetRemoteDescription(remote_offer));
+    EXPECT_EQ(PeerConnectionInterface::kHaveRemoteOffer, observer_.state_);
+  }
+
+  void CreateOfferWithOptionsAsLocalDescription(
+      std::unique_ptr<SessionDescriptionInterface>* desc,
+      const RTCOfferAnswerOptions& offer_answer_options) {
+    *desc = CreateOfferWithOptions(offer_answer_options);
+    ASSERT_TRUE(desc != nullptr);
+    std::string sdp;
+    EXPECT_TRUE((*desc)->ToString(&sdp));
+    SessionDescriptionInterface* new_offer = webrtc::CreateSessionDescription(
+        SessionDescriptionInterface::kOffer, sdp, NULL);
+
+    EXPECT_TRUE(DoSetLocalDescription(new_offer));
+    EXPECT_EQ(PeerConnectionInterface::kHaveLocalOffer, observer_.state_);
+  }
+
+  bool HasCNCodecs(const cricket::ContentInfo* content) {
+    const cricket::ContentDescription* description = content->description;
+    RTC_DCHECK(description);
+    const cricket::AudioContentDescription* audio_content_desc =
+        static_cast<const cricket::AudioContentDescription*>(description);
+    RTC_DCHECK(audio_content_desc);
+    for (size_t i = 0; i < audio_content_desc->codecs().size(); ++i) {
+      if (audio_content_desc->codecs()[i].name == "CN")
+        return true;
+    }
+    return false;
+  }
+
   std::unique_ptr<rtc::VirtualSocketServer> vss_;
   rtc::AutoSocketServerThread main_;
   cricket::FakePortAllocator* port_allocator_ = nullptr;
@@ -3494,6 +3545,224 @@
   EXPECT_TRUE(pc_->SetBitrate(bitrate).ok());
 }
 
+// The following tests verify that the offer can be created correctly.
+TEST_F(PeerConnectionInterfaceTest,
+       CreateOfferFailsWithInvalidOfferToReceiveAudio) {
+  RTCOfferAnswerOptions rtc_options;
+
+  // Setting offer_to_receive_audio to a value lower than kUndefined or greater
+  // than kMaxOfferToReceiveMedia should be treated as invalid.
+  rtc_options.offer_to_receive_audio = RTCOfferAnswerOptions::kUndefined - 1;
+  CreatePeerConnection();
+  EXPECT_FALSE(CreateOfferWithOptions(rtc_options));
+
+  rtc_options.offer_to_receive_audio =
+      RTCOfferAnswerOptions::kMaxOfferToReceiveMedia + 1;
+  EXPECT_FALSE(CreateOfferWithOptions(rtc_options));
+}
+
+TEST_F(PeerConnectionInterfaceTest,
+       CreateOfferFailsWithInvalidOfferToReceiveVideo) {
+  RTCOfferAnswerOptions rtc_options;
+
+  // Setting offer_to_receive_video to a value lower than kUndefined or greater
+  // than kMaxOfferToReceiveMedia should be treated as invalid.
+  rtc_options.offer_to_receive_video = RTCOfferAnswerOptions::kUndefined - 1;
+  CreatePeerConnection();
+  EXPECT_FALSE(CreateOfferWithOptions(rtc_options));
+
+  rtc_options.offer_to_receive_video =
+      RTCOfferAnswerOptions::kMaxOfferToReceiveMedia + 1;
+  EXPECT_FALSE(CreateOfferWithOptions(rtc_options));
+}
+
+// Test that the audio and video content will be added to an offer if both
+// |offer_to_receive_audio| and |offer_to_receive_video| options are 1.
+TEST_F(PeerConnectionInterfaceTest, CreateOfferWithAudioVideoOptions) {
+  RTCOfferAnswerOptions rtc_options;
+  rtc_options.offer_to_receive_audio = 1;
+  rtc_options.offer_to_receive_video = 1;
+
+  std::unique_ptr<SessionDescriptionInterface> offer;
+  CreatePeerConnection();
+  offer = CreateOfferWithOptions(rtc_options);
+  ASSERT_TRUE(offer);
+  EXPECT_NE(nullptr, GetFirstAudioContent(offer->description()));
+  EXPECT_NE(nullptr, GetFirstVideoContent(offer->description()));
+}
+
+// Test that only audio content will be added to the offer if only
+// |offer_to_receive_audio| options is 1.
+TEST_F(PeerConnectionInterfaceTest, CreateOfferWithAudioOnlyOptions) {
+  RTCOfferAnswerOptions rtc_options;
+  rtc_options.offer_to_receive_audio = 1;
+  rtc_options.offer_to_receive_video = 0;
+
+  std::unique_ptr<SessionDescriptionInterface> offer;
+  CreatePeerConnection();
+  offer = CreateOfferWithOptions(rtc_options);
+  ASSERT_TRUE(offer);
+  EXPECT_NE(nullptr, GetFirstAudioContent(offer->description()));
+  EXPECT_EQ(nullptr, GetFirstVideoContent(offer->description()));
+}
+
+// Test that only video content will be added if only |offer_to_receive_video|
+// options is 1.
+TEST_F(PeerConnectionInterfaceTest, CreateOfferWithVideoOnlyOptions) {
+  RTCOfferAnswerOptions rtc_options;
+  rtc_options.offer_to_receive_audio = 0;
+  rtc_options.offer_to_receive_video = 1;
+
+  std::unique_ptr<SessionDescriptionInterface> offer;
+  CreatePeerConnection();
+  offer = CreateOfferWithOptions(rtc_options);
+  ASSERT_TRUE(offer);
+  EXPECT_EQ(nullptr, GetFirstAudioContent(offer->description()));
+  EXPECT_NE(nullptr, GetFirstVideoContent(offer->description()));
+}
+
+// Test that if |voice_activity_detection| is false, no CN codec is added to the
+// offer.
+TEST_F(PeerConnectionInterfaceTest, CreateOfferWithVADOptions) {
+  RTCOfferAnswerOptions rtc_options;
+  rtc_options.offer_to_receive_audio = 1;
+  rtc_options.offer_to_receive_video = 0;
+
+  std::unique_ptr<SessionDescriptionInterface> offer;
+  CreatePeerConnection();
+  offer = CreateOfferWithOptions(rtc_options);
+  ASSERT_TRUE(offer);
+  const cricket::ContentInfo* audio_content =
+      offer->description()->GetContentByName(cricket::CN_AUDIO);
+  ASSERT_TRUE(audio_content);
+  // |voice_activity_detection| is true by default.
+  EXPECT_TRUE(HasCNCodecs(audio_content));
+
+  rtc_options.voice_activity_detection = false;
+  CreatePeerConnection();
+  offer = CreateOfferWithOptions(rtc_options);
+  ASSERT_TRUE(offer);
+  audio_content = offer->description()->GetContentByName(cricket::CN_AUDIO);
+  ASSERT_TRUE(audio_content);
+  EXPECT_FALSE(HasCNCodecs(audio_content));
+}
+
+// Test that no media content will be added to the offer if using default
+// RTCOfferAnswerOptions.
+TEST_F(PeerConnectionInterfaceTest, CreateOfferWithDefaultOfferAnswerOptions) {
+  RTCOfferAnswerOptions rtc_options;
+
+  std::unique_ptr<SessionDescriptionInterface> offer;
+  CreatePeerConnection();
+  offer = CreateOfferWithOptions(rtc_options);
+  ASSERT_TRUE(offer);
+  EXPECT_EQ(nullptr, GetFirstAudioContent(offer->description()));
+  EXPECT_EQ(nullptr, GetFirstVideoContent(offer->description()));
+}
+
+// Test that if |ice_restart| is true, the ufrag/pwd will change, otherwise
+// ufrag/pwd will be the same in the new offer.
+TEST_F(PeerConnectionInterfaceTest, CreateOfferWithIceRestart) {
+  RTCOfferAnswerOptions rtc_options;
+  rtc_options.ice_restart = false;
+  rtc_options.offer_to_receive_audio = 1;
+
+  std::unique_ptr<SessionDescriptionInterface> offer;
+  CreatePeerConnection();
+  CreateOfferWithOptionsAsLocalDescription(&offer, rtc_options);
+  auto ufrag1 = offer->description()
+                    ->GetTransportInfoByName(cricket::CN_AUDIO)
+                    ->description.ice_ufrag;
+  auto pwd1 = offer->description()
+                  ->GetTransportInfoByName(cricket::CN_AUDIO)
+                  ->description.ice_pwd;
+
+  // |ice_restart| is false, the ufrag/pwd shouldn't change.
+  CreateOfferWithOptionsAsLocalDescription(&offer, rtc_options);
+  auto ufrag2 = offer->description()
+                    ->GetTransportInfoByName(cricket::CN_AUDIO)
+                    ->description.ice_ufrag;
+  auto pwd2 = offer->description()
+                  ->GetTransportInfoByName(cricket::CN_AUDIO)
+                  ->description.ice_pwd;
+
+  // |ice_restart| is true, the ufrag/pwd should change.
+  rtc_options.ice_restart = true;
+  CreateOfferWithOptionsAsLocalDescription(&offer, rtc_options);
+  auto ufrag3 = offer->description()
+                    ->GetTransportInfoByName(cricket::CN_AUDIO)
+                    ->description.ice_ufrag;
+  auto pwd3 = offer->description()
+                  ->GetTransportInfoByName(cricket::CN_AUDIO)
+                  ->description.ice_pwd;
+
+  EXPECT_EQ(ufrag1, ufrag2);
+  EXPECT_EQ(pwd1, pwd2);
+  EXPECT_NE(ufrag2, ufrag3);
+  EXPECT_NE(pwd2, pwd3);
+}
+
+// Test that if |use_rtp_mux| is true, the bundling will be enabled in the
+// offer; if it is false, there won't be any bundle group in the offer.
+TEST_F(PeerConnectionInterfaceTest, CreateOfferWithRtpMux) {
+  RTCOfferAnswerOptions rtc_options;
+  rtc_options.offer_to_receive_audio = 1;
+  rtc_options.offer_to_receive_video = 1;
+
+  std::unique_ptr<SessionDescriptionInterface> offer;
+  CreatePeerConnection();
+
+  rtc_options.use_rtp_mux = true;
+  offer = CreateOfferWithOptions(rtc_options);
+  ASSERT_TRUE(offer);
+  EXPECT_NE(nullptr, GetFirstAudioContent(offer->description()));
+  EXPECT_NE(nullptr, GetFirstVideoContent(offer->description()));
+  EXPECT_TRUE(offer->description()->HasGroup(cricket::GROUP_TYPE_BUNDLE));
+
+  rtc_options.use_rtp_mux = false;
+  offer = CreateOfferWithOptions(rtc_options);
+  ASSERT_TRUE(offer);
+  EXPECT_NE(nullptr, GetFirstAudioContent(offer->description()));
+  EXPECT_NE(nullptr, GetFirstVideoContent(offer->description()));
+  EXPECT_FALSE(offer->description()->HasGroup(cricket::GROUP_TYPE_BUNDLE));
+}
+
+// If SetMandatoryReceiveAudio(false) and SetMandatoryReceiveVideo(false) are
+// called for the answer constraints, but an audio and a video section were
+// offered, there will still be an audio and a video section in the answer.
+TEST_F(PeerConnectionInterfaceTest,
+       RejectAudioAndVideoInAnswerWithConstraints) {
+  // Offer both audio and video.
+  RTCOfferAnswerOptions rtc_offer_options;
+  rtc_offer_options.offer_to_receive_audio = 1;
+  rtc_offer_options.offer_to_receive_video = 1;
+
+  CreatePeerConnection();
+  std::unique_ptr<SessionDescriptionInterface> offer;
+  CreateOfferWithOptionsAsRemoteDescription(&offer, rtc_offer_options);
+  EXPECT_NE(nullptr, GetFirstAudioContent(offer->description()));
+  EXPECT_NE(nullptr, GetFirstVideoContent(offer->description()));
+
+  // Since an offer has been created with both audio and video,
+  // Answers will contain the media types that exist in the offer regardless of
+  // the value of |answer_options.has_audio| and |answer_options.has_video|.
+  FakeConstraints answer_c;
+  // Reject both audio and video.
+  answer_c.SetMandatoryReceiveAudio(false);
+  answer_c.SetMandatoryReceiveVideo(false);
+
+  std::unique_ptr<SessionDescriptionInterface> answer;
+  ASSERT_TRUE(DoCreateAnswer(&answer, &answer_c));
+  const cricket::ContentInfo* audio_content =
+      GetFirstAudioContent(answer->description());
+  const cricket::ContentInfo* video_content =
+      GetFirstVideoContent(answer->description());
+  ASSERT_NE(nullptr, audio_content);
+  ASSERT_NE(nullptr, video_content);
+  EXPECT_TRUE(audio_content->rejected);
+  EXPECT_TRUE(video_content->rejected);
+}
+
 class PeerConnectionMediaConfigTest : public testing::Test {
  protected:
   void SetUp() override {
@@ -3502,8 +3771,7 @@
   }
   const cricket::MediaConfig TestCreatePeerConnection(
       const PeerConnectionInterface::RTCConfiguration& config,
-      const MediaConstraintsInterface *constraints) {
-
+      const MediaConstraintsInterface* constraints) {
     rtc::scoped_refptr<PeerConnectionInterface> pc(pcf_->CreatePeerConnection(
         config, constraints, nullptr, nullptr, &observer_));
     EXPECT_TRUE(pc.get());
@@ -3585,177 +3853,6 @@
   EXPECT_TRUE(media_config.video.suspend_below_min_bitrate);
 }
 
-// The following tests verify that session options are created correctly.
-// TODO(deadbeef): Convert these tests to be more end-to-end. Instead of
-// "verify options are converted correctly", should be "pass options into
-// CreateOffer and verify the correct offer is produced."
-
-TEST(CreateSessionOptionsTest, GetOptionsForOfferWithInvalidAudioOption) {
-  RTCOfferAnswerOptions rtc_options;
-  rtc_options.offer_to_receive_audio = RTCOfferAnswerOptions::kUndefined - 1;
-
-  cricket::MediaSessionOptions options;
-  EXPECT_FALSE(ExtractMediaSessionOptions(rtc_options, true, &options));
-
-  rtc_options.offer_to_receive_audio =
-      RTCOfferAnswerOptions::kMaxOfferToReceiveMedia + 1;
-  EXPECT_FALSE(ExtractMediaSessionOptions(rtc_options, true, &options));
-}
-
-TEST(CreateSessionOptionsTest, GetOptionsForOfferWithInvalidVideoOption) {
-  RTCOfferAnswerOptions rtc_options;
-  rtc_options.offer_to_receive_video = RTCOfferAnswerOptions::kUndefined - 1;
-
-  cricket::MediaSessionOptions options;
-  EXPECT_FALSE(ExtractMediaSessionOptions(rtc_options, true, &options));
-
-  rtc_options.offer_to_receive_video =
-      RTCOfferAnswerOptions::kMaxOfferToReceiveMedia + 1;
-  EXPECT_FALSE(ExtractMediaSessionOptions(rtc_options, true, &options));
-}
-
-// Test that a MediaSessionOptions is created for an offer if
-// OfferToReceiveAudio and OfferToReceiveVideo options are set.
-TEST(CreateSessionOptionsTest, GetMediaSessionOptionsForOfferWithAudioVideo) {
-  RTCOfferAnswerOptions rtc_options;
-  rtc_options.offer_to_receive_audio = 1;
-  rtc_options.offer_to_receive_video = 1;
-
-  cricket::MediaSessionOptions options;
-  EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options));
-  EXPECT_TRUE(options.has_audio());
-  EXPECT_TRUE(options.has_video());
-  EXPECT_TRUE(options.bundle_enabled);
-}
-
-// Test that a correct MediaSessionOptions is created for an offer if
-// OfferToReceiveAudio is set.
-TEST(CreateSessionOptionsTest, GetMediaSessionOptionsForOfferWithAudio) {
-  RTCOfferAnswerOptions rtc_options;
-  rtc_options.offer_to_receive_audio = 1;
-
-  cricket::MediaSessionOptions options;
-  EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options));
-  EXPECT_TRUE(options.has_audio());
-  EXPECT_FALSE(options.has_video());
-  EXPECT_TRUE(options.bundle_enabled);
-}
-
-// Test that a correct MediaSessionOptions is created for an offer if
-// the default OfferOptions are used.
-TEST(CreateSessionOptionsTest, GetDefaultMediaSessionOptionsForOffer) {
-  RTCOfferAnswerOptions rtc_options;
-
-  cricket::MediaSessionOptions options;
-  options.transport_options["audio"] = cricket::TransportOptions();
-  options.transport_options["video"] = cricket::TransportOptions();
-  EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options));
-  EXPECT_TRUE(options.has_audio());
-  EXPECT_FALSE(options.has_video());
-  EXPECT_TRUE(options.bundle_enabled);
-  EXPECT_TRUE(options.vad_enabled);
-  EXPECT_FALSE(options.transport_options["audio"].ice_restart);
-  EXPECT_FALSE(options.transport_options["video"].ice_restart);
-}
-
-// Test that a correct MediaSessionOptions is created for an offer if
-// OfferToReceiveVideo is set.
-TEST(CreateSessionOptionsTest, GetMediaSessionOptionsForOfferWithVideo) {
-  RTCOfferAnswerOptions rtc_options;
-  rtc_options.offer_to_receive_audio = 0;
-  rtc_options.offer_to_receive_video = 1;
-
-  cricket::MediaSessionOptions options;
-  EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options));
-  EXPECT_FALSE(options.has_audio());
-  EXPECT_TRUE(options.has_video());
-  EXPECT_TRUE(options.bundle_enabled);
-}
-
-// Test that a correct MediaSessionOptions is created for an offer if
-// UseRtpMux is set to false.
-TEST(CreateSessionOptionsTest,
-     GetMediaSessionOptionsForOfferWithBundleDisabled) {
-  RTCOfferAnswerOptions rtc_options;
-  rtc_options.offer_to_receive_audio = 1;
-  rtc_options.offer_to_receive_video = 1;
-  rtc_options.use_rtp_mux = false;
-
-  cricket::MediaSessionOptions options;
-  EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options));
-  EXPECT_TRUE(options.has_audio());
-  EXPECT_TRUE(options.has_video());
-  EXPECT_FALSE(options.bundle_enabled);
-}
-
-// Test that a correct MediaSessionOptions is created to restart ice if
-// IceRestart is set. It also tests that subsequent MediaSessionOptions don't
-// have |audio_transport_options.ice_restart| etc. set.
-TEST(CreateSessionOptionsTest, GetMediaSessionOptionsForOfferWithIceRestart) {
-  RTCOfferAnswerOptions rtc_options;
-  rtc_options.ice_restart = true;
-
-  cricket::MediaSessionOptions options;
-  options.transport_options["audio"] = cricket::TransportOptions();
-  options.transport_options["video"] = cricket::TransportOptions();
-  EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options));
-  EXPECT_TRUE(options.transport_options["audio"].ice_restart);
-  EXPECT_TRUE(options.transport_options["video"].ice_restart);
-
-  rtc_options = RTCOfferAnswerOptions();
-  EXPECT_TRUE(ExtractMediaSessionOptions(rtc_options, true, &options));
-  EXPECT_FALSE(options.transport_options["audio"].ice_restart);
-  EXPECT_FALSE(options.transport_options["video"].ice_restart);
-}
-
-// Test that the MediaConstraints in an answer don't affect if audio and video
-// is offered in an offer but that if kOfferToReceiveAudio or
-// kOfferToReceiveVideo constraints are true in an offer, the media type will be
-// included in subsequent answers.
-TEST(CreateSessionOptionsTest, MediaConstraintsInAnswer) {
-  FakeConstraints answer_c;
-  answer_c.SetMandatoryReceiveAudio(true);
-  answer_c.SetMandatoryReceiveVideo(true);
-
-  cricket::MediaSessionOptions answer_options;
-  EXPECT_TRUE(ParseConstraintsForAnswer(&answer_c, &answer_options));
-  EXPECT_TRUE(answer_options.has_audio());
-  EXPECT_TRUE(answer_options.has_video());
-
-  RTCOfferAnswerOptions rtc_offer_options;
-
-  cricket::MediaSessionOptions offer_options;
-  EXPECT_TRUE(
-      ExtractMediaSessionOptions(rtc_offer_options, false, &offer_options));
-  EXPECT_TRUE(offer_options.has_audio());
-  EXPECT_TRUE(offer_options.has_video());
-
-  RTCOfferAnswerOptions updated_rtc_offer_options;
-  updated_rtc_offer_options.offer_to_receive_audio = 1;
-  updated_rtc_offer_options.offer_to_receive_video = 1;
-
-  cricket::MediaSessionOptions updated_offer_options;
-  EXPECT_TRUE(ExtractMediaSessionOptions(updated_rtc_offer_options, false,
-                                         &updated_offer_options));
-  EXPECT_TRUE(updated_offer_options.has_audio());
-  EXPECT_TRUE(updated_offer_options.has_video());
-
-  // Since an offer has been created with both audio and video, subsequent
-  // offers and answers should contain both audio and video.
-  // Answers will only contain the media types that exist in the offer
-  // regardless of the value of |updated_answer_options.has_audio| and
-  // |updated_answer_options.has_video|.
-  FakeConstraints updated_answer_c;
-  answer_c.SetMandatoryReceiveAudio(false);
-  answer_c.SetMandatoryReceiveVideo(false);
-
-  cricket::MediaSessionOptions updated_answer_options;
-  EXPECT_TRUE(
-      ParseConstraintsForAnswer(&updated_answer_c, &updated_answer_options));
-  EXPECT_TRUE(updated_answer_options.has_audio());
-  EXPECT_TRUE(updated_answer_options.has_video());
-}
-
 // Tests a few random fields being different.
 TEST(RTCConfigurationTest, ComparisonOperators) {
   PeerConnectionInterface::RTCConfiguration a;
diff --git a/webrtc/pc/webrtcsession_unittest.cc b/webrtc/pc/webrtcsession_unittest.cc
index f6d334e..bb1c877 100644
--- a/webrtc/pc/webrtcsession_unittest.cc
+++ b/webrtc/pc/webrtcsession_unittest.cc
@@ -143,6 +143,9 @@
 static const char kVideoTrack2[] = "video2";
 static const char kAudioTrack2[] = "audio2";
 
+static constexpr bool kStopped = true;
+static constexpr bool kActive = false;
+
 enum RTCCertificateGenerationMethod { ALREADY_GENERATED, DTLS_IDENTITY_STORE };
 
 class MockIceObserver : public webrtc::IceObserver {
@@ -398,7 +401,6 @@
     allocator_->set_flags(cricket::PORTALLOCATOR_DISABLE_TCP |
                           cricket::PORTALLOCATOR_DISABLE_RELAY);
     EXPECT_TRUE(channel_manager_->Init());
-    desc_factory_->set_add_legacy_streams(false);
     allocator_->set_step_delay(cricket::kMinimumStepDelay);
   }
 
@@ -507,107 +509,237 @@
     InitWithCryptoOptions(crypto_options);
   }
 
+  // The following convenience functions can be applied for both local side and
+  // remote side. The flags can be overwritten for different use cases.
   void SendAudioVideoStream1() {
     send_stream_1_ = true;
     send_stream_2_ = false;
-    send_audio_ = true;
-    send_video_ = true;
+    local_send_audio_ = true;
+    local_send_video_ = true;
+    remote_send_audio_ = true;
+    remote_send_video_ = true;
   }
 
   void SendAudioVideoStream2() {
     send_stream_1_ = false;
     send_stream_2_ = true;
-    send_audio_ = true;
-    send_video_ = true;
+    local_send_audio_ = true;
+    local_send_video_ = true;
+    remote_send_audio_ = true;
+    remote_send_video_ = true;
   }
 
   void SendAudioVideoStream1And2() {
     send_stream_1_ = true;
     send_stream_2_ = true;
-    send_audio_ = true;
-    send_video_ = true;
+    local_send_audio_ = true;
+    local_send_video_ = true;
+    remote_send_audio_ = true;
+    remote_send_video_ = true;
   }
 
   void SendNothing() {
     send_stream_1_ = false;
     send_stream_2_ = false;
-    send_audio_ = false;
-    send_video_ = false;
+    local_send_audio_ = false;
+    local_send_video_ = false;
+    remote_send_audio_ = false;
+    remote_send_video_ = false;
   }
 
   void SendAudioOnlyStream2() {
     send_stream_1_ = false;
     send_stream_2_ = true;
-    send_audio_ = true;
-    send_video_ = false;
+    local_send_audio_ = true;
+    local_send_video_ = false;
+    remote_send_audio_ = true;
+    remote_send_video_ = false;
   }
 
   void SendVideoOnlyStream2() {
     send_stream_1_ = false;
     send_stream_2_ = true;
-    send_audio_ = false;
-    send_video_ = true;
+    local_send_audio_ = false;
+    local_send_video_ = true;
+    remote_send_audio_ = false;
+    remote_send_video_ = true;
   }
 
-  void AddStreamsToOptions(cricket::MediaSessionOptions* session_options) {
-    if (send_stream_1_ && send_audio_) {
-      session_options->AddSendStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack1,
-                                     kStream1);
+  // Helper function used to add a specific media section to the
+  // |session_options|.
+  void AddMediaSection(cricket::MediaType type,
+                       const std::string& mid,
+                       cricket::MediaContentDirection direction,
+                       bool stopped,
+                       cricket::MediaSessionOptions* opts) {
+    opts->media_description_options.push_back(cricket::MediaDescriptionOptions(
+        type, mid,
+        cricket::RtpTransceiverDirection::FromMediaContentDirection(direction),
+        stopped));
+  }
+
+  // Add the media sections to the options from |offered_media_sections_| when
+  // creating an answer or a new offer.
+  // This duplicates a lot of logic from PeerConnection but this can be fixed
+  // when PeerConnection and WebRtcSession are merged.
+  void AddExistingMediaSectionsAndSendersToOptions(
+      cricket::MediaSessionOptions* session_options,
+      bool send_audio,
+      bool recv_audio,
+      bool send_video,
+      bool recv_video) {
+    int num_sim_layer = 1;
+    for (auto media_description_options : offered_media_sections_) {
+      if (media_description_options.type == cricket::MEDIA_TYPE_AUDIO) {
+        bool stopped = !send_audio && !recv_audio;
+        auto media_desc_options = cricket::MediaDescriptionOptions(
+            cricket::MEDIA_TYPE_AUDIO, media_description_options.mid,
+            cricket::RtpTransceiverDirection(send_audio, recv_audio), stopped);
+        if (send_stream_1_ && send_audio) {
+          media_desc_options.AddAudioSender(kAudioTrack1, kStream1);
+        }
+        if (send_stream_2_ && send_audio) {
+          media_desc_options.AddAudioSender(kAudioTrack2, kStream2);
+        }
+        session_options->media_description_options.push_back(
+            media_desc_options);
+      } else if (media_description_options.type == cricket::MEDIA_TYPE_VIDEO) {
+        bool stopped = !send_video && !recv_video;
+        auto media_desc_options = cricket::MediaDescriptionOptions(
+            cricket::MEDIA_TYPE_VIDEO, media_description_options.mid,
+            cricket::RtpTransceiverDirection(send_video, recv_video), stopped);
+        if (send_stream_1_ && send_video) {
+          media_desc_options.AddVideoSender(kVideoTrack1, kStream1,
+                                            num_sim_layer);
+        }
+        if (send_stream_2_ && send_video) {
+          media_desc_options.AddVideoSender(kVideoTrack2, kStream2,
+                                            num_sim_layer);
+        }
+        session_options->media_description_options.push_back(
+            media_desc_options);
+      } else if (media_description_options.type == cricket::MEDIA_TYPE_DATA) {
+        session_options->media_description_options.push_back(
+            cricket::MediaDescriptionOptions(
+                cricket::MEDIA_TYPE_DATA, media_description_options.mid,
+                // Direction for data sections is meaningless, but legacy
+                // endpoints might expect sendrecv.
+                cricket::RtpTransceiverDirection(true, true), false));
+      } else {
+        RTC_NOTREACHED();
+      }
     }
-    if (send_stream_1_ && send_video_) {
-      session_options->AddSendStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack1,
-                                     kStream1);
+  }
+
+  // Add the existing media sections first and then add new media sections if
+  // needed.
+  void AddMediaSectionsAndSendersToOptions(
+      cricket::MediaSessionOptions* session_options,
+      bool send_audio,
+      bool recv_audio,
+      bool send_video,
+      bool recv_video) {
+    AddExistingMediaSectionsAndSendersToOptions(
+        session_options, send_audio, recv_audio, send_video, recv_video);
+
+    if (!session_options->has_audio() && (send_audio || recv_audio)) {
+      cricket::MediaDescriptionOptions media_desc_options =
+          cricket::MediaDescriptionOptions(
+              cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO,
+              cricket::RtpTransceiverDirection(send_audio, recv_audio),
+              kActive);
+      if (send_stream_1_ && send_audio) {
+        media_desc_options.AddAudioSender(kAudioTrack1, kStream1);
+      }
+      if (send_stream_2_ && send_audio) {
+        media_desc_options.AddAudioSender(kAudioTrack2, kStream2);
+      }
+      session_options->media_description_options.push_back(media_desc_options);
+      offered_media_sections_.push_back(media_desc_options);
     }
-    if (send_stream_2_ && send_audio_) {
-      session_options->AddSendStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack2,
-                                     kStream2);
+
+    if (!session_options->has_video() && (send_video || recv_video)) {
+      cricket::MediaDescriptionOptions media_desc_options =
+          cricket::MediaDescriptionOptions(
+              cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO,
+              cricket::RtpTransceiverDirection(send_video, recv_video),
+              kActive);
+      int num_sim_layer = 1;
+      if (send_stream_1_ && send_video) {
+        media_desc_options.AddVideoSender(kVideoTrack1, kStream1,
+                                          num_sim_layer);
+      }
+      if (send_stream_2_ && send_video) {
+        media_desc_options.AddVideoSender(kVideoTrack2, kStream2,
+                                          num_sim_layer);
+      }
+      session_options->media_description_options.push_back(media_desc_options);
+      offered_media_sections_.push_back(media_desc_options);
     }
-    if (send_stream_2_ && send_video_) {
-      session_options->AddSendStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack2,
-                                     kStream2);
-    }
-    if (data_channel_ && session_->data_channel_type() == cricket::DCT_RTP) {
-      session_options->AddSendStream(cricket::MEDIA_TYPE_DATA,
-                                     data_channel_->label(),
-                                     data_channel_->label());
+
+    if (!session_options->has_data() &&
+        (data_channel_ ||
+         session_options->data_channel_type != cricket::DCT_NONE)) {
+      cricket::MediaDescriptionOptions media_desc_options =
+          cricket::MediaDescriptionOptions(
+              cricket::MEDIA_TYPE_DATA, cricket::CN_DATA,
+              cricket::RtpTransceiverDirection(true, true), kActive);
+      if (session_options->data_channel_type == cricket::DCT_RTP) {
+        media_desc_options.AddRtpDataChannel(data_channel_->label(),
+                                             data_channel_->label());
+      }
+      session_options->media_description_options.push_back(media_desc_options);
+      offered_media_sections_.push_back(media_desc_options);
     }
   }
 
   void GetOptionsForOffer(
       const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
       cricket::MediaSessionOptions* session_options) {
-    ASSERT_TRUE(ExtractMediaSessionOptions(rtc_options, true, session_options));
+    ExtractSharedMediaSessionOptions(rtc_options, session_options);
 
-    AddStreamsToOptions(session_options);
-    if (rtc_options.offer_to_receive_audio ==
-        RTCOfferAnswerOptions::kUndefined) {
-      session_options->recv_audio =
-          session_options->HasSendMediaStream(cricket::MEDIA_TYPE_AUDIO);
-    }
-    if (rtc_options.offer_to_receive_video ==
-        RTCOfferAnswerOptions::kUndefined) {
-      session_options->recv_video =
-          session_options->HasSendMediaStream(cricket::MEDIA_TYPE_VIDEO);
-    }
+    // |recv_X| is true by default if |offer_to_receive_X| is undefined.
+    bool recv_audio = rtc_options.offer_to_receive_audio != 0;
+    bool recv_video = rtc_options.offer_to_receive_video != 0;
+
+    AddMediaSectionsAndSendersToOptions(session_options, local_send_audio_,
+                                        recv_audio, local_send_video_,
+                                        recv_video);
     session_options->bundle_enabled =
         session_options->bundle_enabled &&
         (session_options->has_audio() || session_options->has_video() ||
          session_options->has_data());
 
-    if (session_->data_channel_type() == cricket::DCT_SCTP && data_channel_) {
-      session_options->data_channel_type = cricket::DCT_SCTP;
-    } else if (session_->data_channel_type() == cricket::DCT_QUIC) {
-      session_options->data_channel_type = cricket::DCT_QUIC;
+    session_options->crypto_options = crypto_options_;
+  }
+
+  void GetOptionsForAnswer(cricket::MediaSessionOptions* session_options) {
+    AddExistingMediaSectionsAndSendersToOptions(
+        session_options, local_send_audio_, local_recv_audio_,
+        local_send_video_, local_recv_video_);
+
+    session_options->bundle_enabled =
+        session_options->bundle_enabled &&
+        (session_options->has_audio() || session_options->has_video() ||
+         session_options->has_data());
+
+    if (session_->data_channel_type() != cricket::DCT_RTP) {
+      session_options->data_channel_type = session_->data_channel_type();
     }
 
     session_options->crypto_options = crypto_options_;
   }
 
-  void GetOptionsForAnswer(cricket::MediaSessionOptions* session_options) {
-    // ParseConstraintsForAnswer is used to set some defaults.
-    ASSERT_TRUE(webrtc::ParseConstraintsForAnswer(nullptr, session_options));
+  void GetOptionsForRemoteAnswer(
+      cricket::MediaSessionOptions* session_options) {
+    bool recv_audio = local_send_audio_ || remote_recv_audio_;
+    bool recv_video = local_send_video_ || remote_recv_video_;
+    bool send_audio = false;
+    bool send_video = false;
 
-    AddStreamsToOptions(session_options);
+    AddExistingMediaSectionsAndSendersToOptions(
+        session_options, send_audio, recv_audio, send_video, recv_video);
+
     session_options->bundle_enabled =
         session_options->bundle_enabled &&
         (session_options->has_audio() || session_options->has_video() ||
@@ -620,6 +752,28 @@
     session_options->crypto_options = crypto_options_;
   }
 
+  void GetOptionsForAudioOnlyRemoteOffer(
+      cricket::MediaSessionOptions* session_options) {
+    remote_recv_audio_ = true;
+    remote_recv_video_ = false;
+    GetOptionsForRemoteOffer(session_options);
+  }
+
+  void GetOptionsForRemoteOffer(cricket::MediaSessionOptions* session_options) {
+    AddMediaSectionsAndSendersToOptions(session_options, remote_send_audio_,
+                                        remote_recv_audio_, remote_send_video_,
+                                        remote_recv_video_);
+    session_options->bundle_enabled =
+        (session_options->has_audio() || session_options->has_video() ||
+         session_options->has_data());
+
+    if (session_->data_channel_type() != cricket::DCT_RTP) {
+      session_options->data_channel_type = session_->data_channel_type();
+    }
+
+    session_options->crypto_options = crypto_options_;
+  }
+
   // Creates a local offer and applies it. Starts ICE.
   // Call SendAudioVideoStreamX() before this function
   // to decide which streams to create.
@@ -635,7 +789,6 @@
     PeerConnectionInterface::RTCOfferAnswerOptions options;
     options.offer_to_receive_audio =
         RTCOfferAnswerOptions::kOfferToReceiveMediaTrue;
-
     return CreateOffer(options);
   }
 
@@ -658,9 +811,6 @@
         = new WebRtcSessionCreateSDPObserverForTest();
     cricket::MediaSessionOptions session_options = options;
     GetOptionsForAnswer(&session_options);
-    // Overwrite recv_audio and recv_video with passed-in values.
-    session_options.recv_video = options.recv_video;
-    session_options.recv_audio = options.recv_audio;
     session_->CreateAnswer(observer, session_options);
     EXPECT_TRUE_WAIT(
         observer->state() != WebRtcSessionCreateSDPObserverForTest::kInit,
@@ -670,8 +820,7 @@
 
   SessionDescriptionInterface* CreateAnswer() {
     cricket::MediaSessionOptions options;
-    options.recv_video = true;
-    options.recv_audio = true;
+    options.bundle_enabled = true;
     return CreateAnswer(options);
   }
 
@@ -789,7 +938,7 @@
   void VerifyAnswerFromNonCryptoOffer() {
     // Create an SDP without Crypto.
     cricket::MediaSessionOptions options;
-    options.recv_video = true;
+    GetOptionsForRemoteOffer(&options);
     JsepSessionDescription* offer(
         CreateRemoteOffer(options, cricket::SEC_DISABLED));
     ASSERT_TRUE(offer != NULL);
@@ -803,7 +952,7 @@
 
   void VerifyAnswerFromCryptoOffer() {
     cricket::MediaSessionOptions options;
-    options.recv_video = true;
+    GetOptionsForRemoteOffer(&options);
     options.bundle_enabled = true;
     std::unique_ptr<JsepSessionDescription> offer(
         CreateRemoteOffer(options, cricket::SEC_REQUIRED));
@@ -926,7 +1075,7 @@
     SetLocalDescriptionWithoutError(answer);
   }
   void SetLocalDescriptionWithoutError(SessionDescriptionInterface* desc) {
-    EXPECT_TRUE(session_->SetLocalDescription(desc, NULL));
+    ASSERT_TRUE(session_->SetLocalDescription(desc, nullptr));
     session_->MaybeStartGathering();
   }
   void SetLocalDescriptionExpectState(SessionDescriptionInterface* desc,
@@ -955,7 +1104,7 @@
                                    expected_error, desc);
   }
   void SetRemoteDescriptionWithoutError(SessionDescriptionInterface* desc) {
-    EXPECT_TRUE(session_->SetRemoteDescription(desc, NULL));
+    ASSERT_TRUE(session_->SetRemoteDescription(desc, nullptr));
   }
   void SetRemoteDescriptionExpectState(SessionDescriptionInterface* desc,
                                        WebRtcSession::State expected_state) {
@@ -992,21 +1141,26 @@
       SessionDescriptionInterface** nocrypto_answer) {
     // Create a SDP without Crypto.
     cricket::MediaSessionOptions options;
-    options.recv_video = true;
+    GetOptionsForRemoteOffer(&options);
     options.bundle_enabled = true;
     *offer = CreateRemoteOffer(options, cricket::SEC_ENABLED);
     ASSERT_TRUE(*offer != NULL);
     VerifyCryptoParams((*offer)->description());
 
-    *nocrypto_answer = CreateRemoteAnswer(*offer, options,
-                                          cricket::SEC_DISABLED);
+    cricket::MediaSessionOptions answer_options;
+    GetOptionsForRemoteAnswer(&answer_options);
+    *nocrypto_answer =
+        CreateRemoteAnswer(*offer, answer_options, cricket::SEC_DISABLED);
     EXPECT_TRUE(*nocrypto_answer != NULL);
   }
 
   void CreateDtlsOfferAndNonDtlsAnswer(SessionDescriptionInterface** offer,
       SessionDescriptionInterface** nodtls_answer) {
     cricket::MediaSessionOptions options;
-    options.recv_video = true;
+    AddMediaSection(cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO,
+                    cricket::MD_RECVONLY, kActive, &options);
+    AddMediaSection(cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO,
+                    cricket::MD_RECVONLY, kActive, &options);
     options.bundle_enabled = true;
 
     std::unique_ptr<SessionDescriptionInterface> temp_offer(
@@ -1068,8 +1222,7 @@
       const char* sctp_stream_name, int new_port,
       cricket::MediaSessionOptions options) {
     options.data_channel_type = cricket::DCT_SCTP;
-    options.AddSendStream(cricket::MEDIA_TYPE_DATA, "datachannel",
-                          sctp_stream_name);
+    GetOptionsForRemoteOffer(&options);
     return ChangeSDPSctpPort(new_port, CreateRemoteOffer(options));
   }
 
@@ -1097,7 +1250,7 @@
   // before this function to decide which streams to create.
   JsepSessionDescription* CreateRemoteOffer() {
     cricket::MediaSessionOptions options;
-    GetOptionsForAnswer(&options);
+    GetOptionsForRemoteOffer(&options);
     return CreateRemoteOffer(options, session_->remote_description());
   }
 
@@ -1132,6 +1285,7 @@
       const SessionDescriptionInterface* offer) {
     cricket::MediaSessionOptions options;
     GetOptionsForAnswer(&options);
+    options.bundle_enabled = true;
     return CreateRemoteAnswer(offer, options, cricket::SEC_REQUIRED);
   }
 
@@ -1455,6 +1609,7 @@
     SetFactoryDtlsSrtp();
     if (type == CreateSessionDescriptionRequest::kAnswer) {
       cricket::MediaSessionOptions options;
+      GetOptionsForRemoteOffer(&options);
       std::unique_ptr<JsepSessionDescription> offer(
           CreateRemoteOffer(options, cricket::SEC_DISABLED));
       ASSERT_TRUE(offer.get() != NULL);
@@ -1462,16 +1617,19 @@
     }
 
     PeerConnectionInterface::RTCOfferAnswerOptions options;
-    cricket::MediaSessionOptions session_options;
+    cricket::MediaSessionOptions offer_session_options;
+    cricket::MediaSessionOptions answer_session_options;
+    GetOptionsForOffer(options, &offer_session_options);
+    GetOptionsForAnswer(&answer_session_options);
     const int kNumber = 3;
     rtc::scoped_refptr<WebRtcSessionCreateSDPObserverForTest>
         observers[kNumber];
     for (int i = 0; i < kNumber; ++i) {
       observers[i] = new WebRtcSessionCreateSDPObserverForTest();
       if (type == CreateSessionDescriptionRequest::kOffer) {
-        session_->CreateOffer(observers[i], options, session_options);
+        session_->CreateOffer(observers[i], options, offer_session_options);
       } else {
-        session_->CreateAnswer(observers[i], session_options);
+        session_->CreateAnswer(observers[i], answer_session_options);
       }
     }
 
@@ -1529,8 +1687,15 @@
   // The following flags affect options created for CreateOffer/CreateAnswer.
   bool send_stream_1_ = false;
   bool send_stream_2_ = false;
-  bool send_audio_ = false;
-  bool send_video_ = false;
+  bool local_send_audio_ = false;
+  bool local_send_video_ = false;
+  bool local_recv_audio_ = true;
+  bool local_recv_video_ = true;
+  bool remote_send_audio_ = false;
+  bool remote_send_video_ = false;
+  bool remote_recv_audio_ = true;
+  bool remote_recv_video_ = true;
+  std::vector<cricket::MediaDescriptionOptions> offered_media_sections_;
   rtc::scoped_refptr<DataChannel> data_channel_;
   // Last values received from data channel creation signal.
   std::string last_data_channel_label_;
@@ -1770,7 +1935,7 @@
 TEST_F(WebRtcSessionTest, TestSetNonSdesOfferWhenSdesOn) {
   Init();
   cricket::MediaSessionOptions options;
-  options.recv_video = true;
+  GetOptionsForRemoteOffer(&options);
   JsepSessionDescription* offer = CreateRemoteOffer(
       options, cricket::SEC_DISABLED);
   ASSERT_TRUE(offer != NULL);
@@ -1816,7 +1981,7 @@
   InitWithDtls(GetParam());
   SetFactoryDtlsSrtp();
   cricket::MediaSessionOptions options;
-  options.recv_video = true;
+  GetOptionsForRemoteOffer(&options);
   JsepSessionDescription* offer =
       CreateRemoteOffer(options, cricket::SEC_DISABLED);
   ASSERT_TRUE(offer != NULL);
@@ -1855,7 +2020,7 @@
   SetLocalDescriptionWithoutError(offer);
 
   cricket::MediaSessionOptions options;
-  options.recv_video = true;
+  GetOptionsForAnswer(&options);
   JsepSessionDescription* answer =
       CreateRemoteAnswer(offer, options, cricket::SEC_DISABLED);
   ASSERT_TRUE(answer != NULL);
@@ -1871,7 +2036,7 @@
 TEST_P(WebRtcSessionTest, TestReceiveNonDtlsOfferWhenDtlsOn) {
   InitWithDtls(GetParam());
   cricket::MediaSessionOptions options;
-  options.recv_video = true;
+  GetOptionsForRemoteOffer(&options);
   options.bundle_enabled = true;
   JsepSessionDescription* offer = CreateRemoteOffer(
       options, cricket::SEC_REQUIRED);
@@ -1909,12 +2074,16 @@
 TEST_P(WebRtcSessionTest, TestSetRemoteNonDtlsAnswerWhenDtlsOn) {
   InitWithDtls(GetParam());
   SessionDescriptionInterface* offer = CreateOffer();
-  cricket::MediaSessionOptions options;
-  options.recv_video = true;
+  cricket::MediaSessionOptions offer_options;
+  GetOptionsForRemoteOffer(&offer_options);
+
   std::unique_ptr<SessionDescriptionInterface> temp_offer(
-      CreateRemoteOffer(options, cricket::SEC_ENABLED));
-  JsepSessionDescription* answer =
-      CreateRemoteAnswer(temp_offer.get(), options, cricket::SEC_ENABLED);
+      CreateRemoteOffer(offer_options, cricket::SEC_ENABLED));
+
+  cricket::MediaSessionOptions answer_options;
+  GetOptionsForAnswer(&answer_options);
+  JsepSessionDescription* answer = CreateRemoteAnswer(
+      temp_offer.get(), answer_options, cricket::SEC_ENABLED);
 
   // SetRemoteDescription and SetLocalDescription will take the ownership of
   // the offer and answer.
@@ -1941,7 +2110,7 @@
   SetLocalDescriptionWithoutError(offer);
 
   cricket::MediaSessionOptions options;
-  options.recv_video = true;
+  GetOptionsForAnswer(&options);
   JsepSessionDescription* answer =
       CreateRemoteAnswer(offer, options, cricket::SEC_DISABLED);
   ASSERT_TRUE(answer != NULL);
@@ -1959,7 +2128,7 @@
   InitWithDtls(GetParam());
 
   cricket::MediaSessionOptions options;
-  options.recv_video = true;
+  GetOptionsForRemoteOffer(&options);
   JsepSessionDescription* offer =
       CreateRemoteOffer(options, cricket::SEC_DISABLED);
   ASSERT_TRUE(offer != NULL);
@@ -1992,7 +2161,7 @@
   SetLocalDescriptionWithoutError(offer);
 
   cricket::MediaSessionOptions options;
-  options.recv_video = true;
+  GetOptionsForAnswer(&options);
 
   // First, negotiate different SSL roles.
   SessionDescriptionInterface* answer =
@@ -2014,7 +2183,9 @@
                                        session_->remote_description());
   SetRemoteDescriptionWithoutError(offer);
 
-  answer = CreateAnswer();
+  cricket::MediaSessionOptions answer_options;
+  answer_options.bundle_enabled = true;
+  answer = CreateAnswer(answer_options);
   audio_transport_info = answer->description()->GetTransportInfoByName("audio");
   EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE,
             audio_transport_info->description.connection_role);
@@ -2031,7 +2202,7 @@
                                        kSessionVersion,
                                        session_->remote_description());
   SetRemoteDescriptionWithoutError(offer);
-  answer = CreateAnswer();
+  answer = CreateAnswer(answer_options);
   audio_transport_info = answer->description()->GetTransportInfoByName("audio");
   EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE,
             audio_transport_info->description.connection_role);
@@ -2392,6 +2563,7 @@
 
   std::unique_ptr<SessionDescriptionInterface> local_offer(CreateOffer());
 
+  ASSERT_TRUE(local_offer);
   ASSERT_TRUE(local_offer->candidates(kMediaContentIndex0) != NULL);
   EXPECT_LT(0u, local_offer->candidates(kMediaContentIndex0)->count());
 
@@ -2417,42 +2589,27 @@
   // present in SDP.
   std::string sdp;
   EXPECT_TRUE(offer->ToString(&sdp));
-  const std::string kAudioMid = "a=mid:audio";
-  const std::string kAudioMidReplaceStr = "a=mid:audio_content_name";
-  const std::string kVideoMid = "a=mid:video";
-  const std::string kVideoMidReplaceStr = "a=mid:video_content_name";
-
-  // Replacing |audio| with |audio_content_name|.
-  rtc::replace_substrs(kAudioMid.c_str(), kAudioMid.length(),
-                             kAudioMidReplaceStr.c_str(),
-                             kAudioMidReplaceStr.length(),
-                             &sdp);
-  // Replacing |video| with |video_content_name|.
-  rtc::replace_substrs(kVideoMid.c_str(), kVideoMid.length(),
-                             kVideoMidReplaceStr.c_str(),
-                             kVideoMidReplaceStr.length(),
-                             &sdp);
 
   SessionDescriptionInterface* modified_offer =
       CreateSessionDescription(JsepSessionDescription::kOffer, sdp, NULL);
 
   SetRemoteDescriptionWithoutError(modified_offer);
 
-  SessionDescriptionInterface* answer = CreateAnswer();
+  cricket::MediaSessionOptions answer_options;
+  answer_options.bundle_enabled = false;
+  SessionDescriptionInterface* answer = CreateAnswer(answer_options);
   SetLocalDescriptionWithoutError(answer);
 
   rtc::PacketTransportInternal* voice_transport_channel =
       session_->voice_rtp_transport_channel();
   EXPECT_TRUE(voice_transport_channel != NULL);
   EXPECT_EQ(voice_transport_channel->debug_name(),
-            "audio_content_name " +
-                std::to_string(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+            "audio " + std::to_string(cricket::ICE_CANDIDATE_COMPONENT_RTP));
   rtc::PacketTransportInternal* video_transport_channel =
       session_->video_rtp_transport_channel();
   ASSERT_TRUE(video_transport_channel != NULL);
   EXPECT_EQ(video_transport_channel->debug_name(),
-            "video_content_name " +
-                std::to_string(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+            "video " + std::to_string(cricket::ICE_CANDIDATE_COMPONENT_RTP));
   EXPECT_TRUE((video_channel_ = media_engine_->GetVideoChannel(0)) != NULL);
   EXPECT_TRUE((voice_channel_ = media_engine_->GetVoiceChannel(0)) != NULL);
 }
@@ -2466,9 +2623,17 @@
   ASSERT_TRUE(offer != NULL);
   const cricket::ContentInfo* content =
       cricket::GetFirstAudioContent(offer->description());
-  EXPECT_TRUE(content != NULL);
+  ASSERT_TRUE(content != NULL);
+  EXPECT_EQ(
+      cricket::MD_RECVONLY,
+      static_cast<const cricket::AudioContentDescription*>(content->description)
+          ->direction());
   content = cricket::GetFirstVideoContent(offer->description());
-  EXPECT_TRUE(content == NULL);
+  ASSERT_TRUE(content != NULL);
+  EXPECT_EQ(
+      cricket::MD_RECVONLY,
+      static_cast<const cricket::VideoContentDescription*>(content->description)
+          ->direction());
 }
 
 // Test that an offer contains the correct media content descriptions based on
@@ -2481,17 +2646,34 @@
 
   const cricket::ContentInfo* content =
       cricket::GetFirstAudioContent(offer->description());
-  EXPECT_TRUE(content != NULL);
+  ASSERT_TRUE(content != NULL);
+  EXPECT_EQ(
+      cricket::MD_SENDRECV,
+      static_cast<const cricket::AudioContentDescription*>(content->description)
+          ->direction());
   content = cricket::GetFirstVideoContent(offer->description());
-  EXPECT_TRUE(content == NULL);
+  ASSERT_TRUE(content != NULL);
+  EXPECT_EQ(
+      cricket::MD_RECVONLY,
+      static_cast<const cricket::VideoContentDescription*>(content->description)
+          ->direction());
 
   // Test Audio / Video offer.
   SendAudioVideoStream1();
   offer.reset(CreateOffer());
   content = cricket::GetFirstAudioContent(offer->description());
-  EXPECT_TRUE(content != NULL);
+  ASSERT_TRUE(content != NULL);
+  EXPECT_EQ(
+      cricket::MD_SENDRECV,
+      static_cast<const cricket::AudioContentDescription*>(content->description)
+          ->direction());
+
   content = cricket::GetFirstVideoContent(offer->description());
-  EXPECT_TRUE(content != NULL);
+  ASSERT_TRUE(content != NULL);
+  EXPECT_EQ(
+      cricket::MD_SENDRECV,
+      static_cast<const cricket::VideoContentDescription*>(content->description)
+          ->direction());
 }
 
 // Test that an offer contains no media content descriptions if
@@ -2519,6 +2701,7 @@
   PeerConnectionInterface::RTCOfferAnswerOptions options;
   options.offer_to_receive_audio =
       RTCOfferAnswerOptions::kOfferToReceiveMediaTrue;
+  options.offer_to_receive_video = 0;
 
   std::unique_ptr<SessionDescriptionInterface> offer(CreateOffer(options));
 
@@ -2553,6 +2736,8 @@
   // removed.
   options.offer_to_receive_audio = 0;
   options.offer_to_receive_video = 0;
+  // Remove the media sections added in previous offer.
+  offered_media_sections_.clear();
   offer.reset(CreateOffer(options));
 
   content = cricket::GetFirstAudioContent(offer->description());
@@ -2596,6 +2781,7 @@
   Init();
   // Create a remote offer with audio only.
   cricket::MediaSessionOptions options;
+  GetOptionsForAudioOnlyRemoteOffer(&options);
 
   std::unique_ptr<JsepSessionDescription> offer(CreateRemoteOffer(options));
   ASSERT_TRUE(cricket::GetFirstVideoContent(offer->description()) == NULL);
@@ -2640,8 +2826,10 @@
   SetRemoteDescriptionWithoutError(offer.release());
 
   cricket::MediaSessionOptions session_options;
-  session_options.recv_audio = false;
-  session_options.recv_video = false;
+  remote_send_audio_ = false;
+  remote_send_video_ = false;
+  local_recv_audio_ = false;
+  local_recv_video_ = false;
   std::unique_ptr<SessionDescriptionInterface> answer(
       CreateAnswer(session_options));
 
@@ -2664,9 +2852,6 @@
   SetRemoteDescriptionWithoutError(offer.release());
 
   cricket::MediaSessionOptions options;
-  options.recv_audio = false;
-  options.recv_video = false;
-
   // Test with a stream with tracks.
   SendAudioVideoStream1();
   std::unique_ptr<SessionDescriptionInterface> answer(CreateAnswer(options));
@@ -2726,6 +2911,11 @@
   SessionDescriptionInterface* offer = CreateOffer();
 
   cricket::MediaSessionOptions options;
+  AddMediaSection(cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO,
+                  cricket::MD_RECVONLY, kActive, &options);
+  AddMediaSection(cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO,
+                  cricket::MD_INACTIVE, kStopped, &options);
+  local_recv_video_ = false;
   SessionDescriptionInterface* answer = CreateRemoteAnswer(offer, options);
 
   // SetLocalDescription and SetRemoteDescriptions takes ownership of offer
@@ -2736,7 +2926,7 @@
   video_channel_ = media_engine_->GetVideoChannel(0);
   voice_channel_ = media_engine_->GetVoiceChannel(0);
 
-  ASSERT_TRUE(video_channel_ == NULL);
+  ASSERT_TRUE(video_channel_ == nullptr);
 
   ASSERT_EQ(0u, voice_channel_->recv_streams().size());
   ASSERT_EQ(1u, voice_channel_->send_streams().size());
@@ -2744,13 +2934,14 @@
 
   // Let the remote end update the session descriptions, with Audio and Video.
   SendAudioVideoStream2();
+  local_recv_video_ = true;
   CreateAndSetRemoteOfferAndLocalAnswer();
 
   video_channel_ = media_engine_->GetVideoChannel(0);
   voice_channel_ = media_engine_->GetVoiceChannel(0);
 
-  ASSERT_TRUE(video_channel_ != NULL);
-  ASSERT_TRUE(voice_channel_ != NULL);
+  ASSERT_TRUE(video_channel_ != nullptr);
+  ASSERT_TRUE(voice_channel_ != nullptr);
 
   ASSERT_EQ(1u, video_channel_->recv_streams().size());
   ASSERT_EQ(1u, video_channel_->send_streams().size());
@@ -2762,10 +2953,17 @@
   EXPECT_EQ(kAudioTrack2, voice_channel_->send_streams()[0].id);
 
   // Change session back to audio only.
+  // The remote side doesn't send and recv video.
   SendAudioOnlyStream2();
+  remote_recv_video_ = false;
   CreateAndSetRemoteOfferAndLocalAnswer();
 
-  EXPECT_EQ(0u, video_channel_->recv_streams().size());
+  video_channel_ = media_engine_->GetVideoChannel(0);
+  voice_channel_ = media_engine_->GetVoiceChannel(0);
+
+  // The audio is expected to be rejected.
+  EXPECT_TRUE(video_channel_ == nullptr);
+
   ASSERT_EQ(1u, voice_channel_->recv_streams().size());
   EXPECT_EQ(kAudioTrack2, voice_channel_->recv_streams()[0].id);
   ASSERT_EQ(1u, voice_channel_->send_streams().size());
@@ -2782,10 +2980,13 @@
   SessionDescriptionInterface* offer = CreateOffer();
 
   cricket::MediaSessionOptions options;
-  options.recv_audio = false;
-  options.recv_video = true;
-  SessionDescriptionInterface* answer = CreateRemoteAnswer(
-      offer, options, cricket::SEC_ENABLED);
+  AddMediaSection(cricket::MEDIA_TYPE_AUDIO, cricket::CN_AUDIO,
+                  cricket::MD_INACTIVE, kStopped, &options);
+  AddMediaSection(cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO,
+                  cricket::MD_RECVONLY, kActive, &options);
+  local_recv_audio_ = false;
+  SessionDescriptionInterface* answer =
+      CreateRemoteAnswer(offer, options, cricket::SEC_ENABLED);
 
   // SetLocalDescription and SetRemoteDescriptions takes ownership of offer
   // and answer.
@@ -2804,23 +3005,41 @@
 
   // Update the session descriptions, with Audio and Video.
   SendAudioVideoStream2();
-  CreateAndSetRemoteOfferAndLocalAnswer();
+  local_recv_audio_ = true;
+  SessionDescriptionInterface* offer2 = CreateRemoteOffer();
+  SetRemoteDescriptionWithoutError(offer2);
+  cricket::MediaSessionOptions answer_options;
+  // Disable the bundling here. If the media is bundled on audio
+  // transport, then we can't reject the audio because switching the bundled
+  // transport is not currently supported.
+  // (https://bugs.chromium.org/p/webrtc/issues/detail?id=6704)
+  answer_options.bundle_enabled = false;
+  SessionDescriptionInterface* answer2 = CreateAnswer(answer_options);
+  SetLocalDescriptionWithoutError(answer2);
 
   voice_channel_ = media_engine_->GetVoiceChannel(0);
-  ASSERT_TRUE(voice_channel_ != NULL);
 
+  ASSERT_TRUE(voice_channel_ != NULL);
   ASSERT_EQ(1u, voice_channel_->recv_streams().size());
   ASSERT_EQ(1u, voice_channel_->send_streams().size());
   EXPECT_EQ(kAudioTrack2, voice_channel_->recv_streams()[0].id);
   EXPECT_EQ(kAudioTrack2, voice_channel_->send_streams()[0].id);
 
   // Change session back to video only.
+  // The remote side doesn't send and recv audio.
   SendVideoOnlyStream2();
-  CreateAndSetRemoteOfferAndLocalAnswer();
+  remote_recv_audio_ = false;
+  SessionDescriptionInterface* offer3 = CreateRemoteOffer();
+  SetRemoteDescriptionWithoutError(offer3);
+  SessionDescriptionInterface* answer3 = CreateAnswer(answer_options);
+  SetLocalDescriptionWithoutError(answer3);
 
   video_channel_ = media_engine_->GetVideoChannel(0);
   voice_channel_ = media_engine_->GetVoiceChannel(0);
 
+  // The video is expected to be rejected.
+  EXPECT_TRUE(voice_channel_ == nullptr);
+
   ASSERT_EQ(1u, video_channel_->recv_streams().size());
   EXPECT_EQ(kVideoTrack2, video_channel_->recv_streams()[0].id);
   ASSERT_EQ(1u, video_channel_->send_streams().size());
@@ -3025,13 +3244,16 @@
   InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyBalanced);
   SendAudioVideoStream1();
 
-  PeerConnectionInterface::RTCOfferAnswerOptions options;
-  options.use_rtp_mux = true;
+  cricket::MediaSessionOptions offer_options;
+  GetOptionsForRemoteOffer(&offer_options);
+  offer_options.bundle_enabled = true;
 
-  SessionDescriptionInterface* offer = CreateRemoteOffer();
+  SessionDescriptionInterface* offer = CreateRemoteOffer(offer_options);
   SetRemoteDescriptionWithoutError(offer);
 
-  SessionDescriptionInterface* answer = CreateAnswer();
+  cricket::MediaSessionOptions answer_options;
+  answer_options.bundle_enabled = true;
+  SessionDescriptionInterface* answer = CreateAnswer(answer_options);
   SetLocalDescriptionWithoutError(answer);
 
   EXPECT_EQ(session_->voice_rtp_transport_channel(),
@@ -3196,10 +3418,11 @@
   EXPECT_EQ(session_->voice_rtp_transport_channel(),
             session_->video_rtp_transport_channel());
 
-  SendAudioVideoStream2();
+  SendVideoOnlyStream2();
+  local_send_audio_ = false;
+  remote_recv_audio_ = false;
   cricket::MediaSessionOptions recv_options;
-  recv_options.recv_audio = false;
-  recv_options.recv_video = true;
+  GetOptionsForRemoteAnswer(&recv_options);
   SessionDescriptionInterface* answer =
       CreateRemoteAnswer(session_->local_description(), recv_options);
   SetRemoteDescriptionWithoutError(answer);
@@ -3286,10 +3509,10 @@
   InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxCompat);
   SendAudioVideoStream1();
 
-  PeerConnectionInterface::RTCOfferAnswerOptions options;
-  options.use_rtp_mux = true;
+  PeerConnectionInterface::RTCOfferAnswerOptions rtc_options;
+  rtc_options.use_rtp_mux = true;
 
-  SessionDescriptionInterface* offer = CreateOffer(options);
+  SessionDescriptionInterface* offer = CreateOffer(rtc_options);
   SetLocalDescriptionWithoutError(offer);
 
   EXPECT_NE(session_->voice_rtp_transport_channel(),
@@ -3645,7 +3868,7 @@
 TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewUfragAndPassword) {
   Init();
   cricket::MediaSessionOptions options;
-  options.recv_video = true;
+  GetOptionsForRemoteOffer(&options);
   std::unique_ptr<JsepSessionDescription> offer(CreateRemoteOffer(options));
   SetRemoteDescriptionWithoutError(offer.release());
 
@@ -3654,10 +3877,10 @@
   SetLocalDescriptionWithoutError(answer.release());
 
   // Receive an offer with new ufrag and password.
-  for (const cricket::ContentInfo& content :
-       session_->local_description()->description()->contents()) {
-    options.transport_options[content.name].ice_restart = true;
+  for (size_t i = 0; i < options.media_description_options.size(); ++i) {
+    options.media_description_options[i].transport_options.ice_restart = true;
   }
+
   std::unique_ptr<JsepSessionDescription> updated_offer1(
       CreateRemoteOffer(options, session_->remote_description()));
   SetRemoteDescriptionWithoutError(updated_offer1.release());
@@ -3685,8 +3908,7 @@
 TEST_F(WebRtcSessionTest, TestOfferChangingOnlyUfragOrPassword) {
   Init();
   cricket::MediaSessionOptions options;
-  options.recv_audio = true;
-  options.recv_video = true;
+  GetOptionsForRemoteOffer(&options);
   // Create an offer with audio and video.
   std::unique_ptr<JsepSessionDescription> offer(CreateRemoteOffer(options));
   SetIceUfragPwd(offer.get(), "original_ufrag", "original_password12345");
@@ -3726,7 +3948,7 @@
 TEST_F(WebRtcSessionTest, TestCreateAnswerWithOldUfragAndPassword) {
   Init();
   cricket::MediaSessionOptions options;
-  options.recv_video = true;
+  GetOptionsForRemoteOffer(&options);
   std::unique_ptr<JsepSessionDescription> offer(CreateRemoteOffer(options));
   SetRemoteDescriptionWithoutError(offer.release());
 
@@ -3753,8 +3975,7 @@
 TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewAndOldUfragAndPassword) {
   Init();
   cricket::MediaSessionOptions options;
-  options.recv_video = true;
-  options.recv_audio = true;
+  GetOptionsForRemoteOffer(&options);
   options.bundle_enabled = false;
   std::unique_ptr<JsepSessionDescription> offer(CreateRemoteOffer(options));
 
@@ -3886,9 +4107,9 @@
   // Create answer that finishes BUNDLE negotiation, which means everything
   // should be bundled on the first transport (audio).
   cricket::MediaSessionOptions answer_options;
-  answer_options.recv_video = true;
   answer_options.bundle_enabled = true;
   answer_options.data_channel_type = cricket::DCT_SCTP;
+  GetOptionsForAnswer(&answer_options);
   SetRemoteDescriptionWithoutError(CreateRemoteAnswer(
       session_->local_description(), answer_options, cricket::SEC_DISABLED));
   ASSERT_TRUE(session_->sctp_content_name());
@@ -3912,6 +4133,7 @@
   // Create remote offer with SCTP.
   cricket::MediaSessionOptions options;
   options.data_channel_type = cricket::DCT_SCTP;
+  GetOptionsForRemoteOffer(&options);
   JsepSessionDescription* offer =
       CreateRemoteOffer(options, cricket::SEC_DISABLED);
   SetRemoteDescriptionWithoutError(offer);
@@ -4055,7 +4277,7 @@
   SetFactoryDtlsSrtp();
 
   cricket::MediaSessionOptions options;
-  options.recv_video = true;
+  GetOptionsForRemoteOffer(&options);
   std::unique_ptr<JsepSessionDescription> offer(
       CreateRemoteOffer(options, cricket::SEC_DISABLED));
   ASSERT_TRUE(offer.get() != NULL);
@@ -4129,6 +4351,7 @@
   Init();
   // Create a remote offer with secured transport disabled.
   cricket::MediaSessionOptions options;
+  GetOptionsForRemoteOffer(&options);
   JsepSessionDescription* offer(CreateRemoteOffer(
       options, cricket::SEC_DISABLED));
   // Adds a DTLS fingerprint to the remote offer.
@@ -4172,7 +4395,7 @@
   SetRemoteDescriptionWithoutError(answer);
 
   cricket::MediaSessionOptions options;
-  options.recv_video = true;
+  GetOptionsForRemoteOffer(&options);
   offer = CreateRemoteOffer(options, cricket::SEC_DISABLED);
 
   cricket::Candidate candidate1;
@@ -4201,7 +4424,7 @@
   SetRemoteDescriptionWithoutError(answer);
 
   cricket::MediaSessionOptions options;
-  options.recv_video = true;
+  GetOptionsForRemoteOffer(&options);
   offer = CreateRemoteOffer(options, cricket::SEC_DISABLED);
   SetRemoteDescriptionWithoutError(offer);
 
@@ -4226,8 +4449,7 @@
   ASSERT_TRUE(offer->description());
   SetLocalDescriptionWithoutError(offer);
   cricket::MediaSessionOptions options;
-  options.recv_audio = true;
-  options.recv_video = true;
+  GetOptionsForAnswer(&options);
   SessionDescriptionInterface* answer =
       CreateRemoteAnswer(offer, options, cricket::SEC_DISABLED);
   ASSERT_TRUE(answer);
@@ -4240,7 +4462,8 @@
 // by local side.
 TEST_F(WebRtcSessionTest, TestRtxRemovedByCreateAnswer) {
   Init();
-  SendAudioVideoStream1();
+  // Send video only to match the |kSdpWithRtx|.
+  SendVideoOnlyStream2();
   std::string offer_sdp(kSdpWithRtx);
 
   SessionDescriptionInterface* offer =
@@ -4251,6 +4474,11 @@
   EXPECT_TRUE(ContainsVideoCodecWithName(offer, "rtx"));
   SetRemoteDescriptionWithoutError(offer);
 
+  // |offered_media_sections_| is used when creating answer.
+  offered_media_sections_.push_back(cricket::MediaDescriptionOptions(
+      cricket::MEDIA_TYPE_VIDEO, cricket::CN_VIDEO,
+      cricket::RtpTransceiverDirection(true, true), false));
+  // Don't create media section for audio in the answer.
   SessionDescriptionInterface* answer = CreateAnswer();
   // Answer SDP does not contain the RTX codec.
   EXPECT_FALSE(ContainsVideoCodecWithName(answer, "rtx"));
@@ -4316,8 +4544,7 @@
   options.offer_to_receive_audio =
       RTCOfferAnswerOptions::kOfferToReceiveMediaTrue;
   cricket::MediaSessionOptions session_options;
-  session_options.recv_audio = true;
-
+  GetOptionsForOffer(options, &session_options);
   for (auto& o : observers) {
     o = new WebRtcSessionCreateSDPObserverForTest();
     session_->CreateOffer(o, options, session_options);
diff --git a/webrtc/pc/webrtcsessiondescriptionfactory.cc b/webrtc/pc/webrtcsessiondescriptionfactory.cc
index 8eccd65..beb8d1e 100644
--- a/webrtc/pc/webrtcsessiondescriptionfactory.cc
+++ b/webrtc/pc/webrtcsessiondescriptionfactory.cc
@@ -30,24 +30,30 @@
 
 static const uint64_t kInitSessionVersion = 2;
 
-static bool CompareStream(const MediaSessionOptions::Stream& stream1,
-                          const MediaSessionOptions::Stream& stream2) {
-  return stream1.id < stream2.id;
+static bool CompareSenderOptions(const cricket::SenderOptions& sender1,
+                                 const cricket::SenderOptions& sender2) {
+  return sender1.track_id < sender2.track_id;
 }
 
-static bool SameId(const MediaSessionOptions::Stream& stream1,
-                   const MediaSessionOptions::Stream& stream2) {
-  return stream1.id == stream2.id;
+static bool SameId(const cricket::SenderOptions& sender1,
+                   const cricket::SenderOptions& sender2) {
+  return sender1.track_id == sender2.track_id;
 }
 
-// Checks if each Stream within the |streams| has unique id.
-static bool ValidStreams(const MediaSessionOptions::Streams& streams) {
-  MediaSessionOptions::Streams sorted_streams = streams;
-  std::sort(sorted_streams.begin(), sorted_streams.end(), CompareStream);
-  MediaSessionOptions::Streams::iterator it =
-      std::adjacent_find(sorted_streams.begin(), sorted_streams.end(),
-                         SameId);
-  return it == sorted_streams.end();
+// Check that each sender has a unique ID.
+static bool ValidMediaSessionOptions(
+    const cricket::MediaSessionOptions& session_options) {
+  std::vector<cricket::SenderOptions> sorted_senders;
+  for (const cricket::MediaDescriptionOptions& media_description_options :
+       session_options.media_description_options) {
+    sorted_senders.insert(sorted_senders.end(),
+                          media_description_options.sender_options.begin(),
+                          media_description_options.sender_options.end());
+  }
+  std::sort(sorted_senders.begin(), sorted_senders.end(), CompareSenderOptions);
+  std::vector<cricket::SenderOptions>::iterator it =
+      std::adjacent_find(sorted_senders.begin(), sorted_senders.end(), SameId);
+  return it == sorted_senders.end();
 }
 
 enum {
@@ -128,7 +134,6 @@
       session_id_(session_id),
       certificate_request_state_(CERTIFICATE_NOT_NEEDED) {
   RTC_DCHECK(signaling_thread_);
-  session_desc_factory_.set_add_legacy_streams(false);
   bool dtls_enabled = cert_generator_ || certificate;
   // SRTP-SDES is disabled if DTLS is on.
   SetSdesPolicy(dtls_enabled ? cricket::SEC_DISABLED : cricket::SEC_REQUIRED);
@@ -237,8 +242,8 @@
     return;
   }
 
-  if (!ValidStreams(session_options.streams)) {
-    error += " called with invalid media streams.";
+  if (!ValidMediaSessionOptions(session_options)) {
+    error += " called with invalid session options";
     LOG(LS_ERROR) << error;
     PostCreateSessionDescriptionFailed(observer, error);
     return;
@@ -279,8 +284,8 @@
     return;
   }
 
-  if (!ValidStreams(session_options.streams)) {
-    error += " called with invalid media streams.";
+  if (!ValidMediaSessionOptions(session_options)) {
+    error += " called with invalid session options.";
     LOG(LS_ERROR) << error;
     PostCreateSessionDescriptionFailed(observer, error);
     return;
@@ -340,13 +345,12 @@
 void WebRtcSessionDescriptionFactory::InternalCreateOffer(
     CreateSessionDescriptionRequest request) {
   if (session_->local_description()) {
-    for (const cricket::TransportInfo& transport :
-         session_->local_description()->description()->transport_infos()) {
-      // If the needs-ice-restart flag is set as described by JSEP, we should
-      // generate an offer with a new ufrag/password to trigger an ICE restart.
-      if (session_->NeedsIceRestart(transport.content_name)) {
-        request.options.transport_options[transport.content_name].ice_restart =
-            true;
+    // If the needs-ice-restart flag is set as described by JSEP, we should
+    // generate an offer with a new ufrag/password to trigger an ICE restart.
+    for (cricket::MediaDescriptionOptions& options :
+         request.options.media_description_options) {
+      if (session_->NeedsIceRestart(options.mid)) {
+        options.transport_options.ice_restart = true;
       }
     }
   }
@@ -375,13 +379,11 @@
     return;
   }
   if (session_->local_description()) {
-    for (const cricket::ContentInfo& content :
-         session_->local_description()->description()->contents()) {
-      // Include all local ICE candidates in the SessionDescription unless
-      // an ICE restart was requested.
-      if (!request.options.transport_options[content.name].ice_restart) {
+    for (const cricket::MediaDescriptionOptions& options :
+         request.options.media_description_options) {
+      if (!options.transport_options.ice_restart) {
         CopyCandidatesFromSessionDescription(session_->local_description(),
-                                             content.name, offer);
+                                             options.mid, offer);
       }
     }
   }
@@ -391,18 +393,18 @@
 void WebRtcSessionDescriptionFactory::InternalCreateAnswer(
     CreateSessionDescriptionRequest request) {
   if (session_->remote_description()) {
-    for (const cricket::ContentInfo& content :
-         session_->remote_description()->description()->contents()) {
+    for (cricket::MediaDescriptionOptions& options :
+         request.options.media_description_options) {
       // According to http://tools.ietf.org/html/rfc5245#section-9.2.1.1
       // an answer should also contain new ICE ufrag and password if an offer
       // has been received with new ufrag and password.
-      request.options.transport_options[content.name].ice_restart =
-          session_->IceRestartPending(content.name);
+      options.transport_options.ice_restart =
+          session_->IceRestartPending(options.mid);
       // We should pass the current SSL role to the transport description
       // factory, if there is already an existing ongoing session.
       rtc::SSLRole ssl_role;
-      if (session_->GetSslRole(content.name, &ssl_role)) {
-        request.options.transport_options[content.name].prefer_passive_role =
+      if (session_->GetSslRole(options.mid, &ssl_role)) {
+        options.transport_options.prefer_passive_role =
             (rtc::SSL_SERVER == ssl_role);
       }
     }
@@ -433,13 +435,13 @@
     return;
   }
   if (session_->local_description()) {
-    for (const cricket::ContentInfo& content :
-         session_->local_description()->description()->contents()) {
-      // Include all local ICE candidates in the SessionDescription unless
-      // the remote peer has requested an ICE restart.
-      if (!request.options.transport_options[content.name].ice_restart) {
+    // Include all local ICE candidates in the SessionDescription unless
+    // the remote peer has requested an ICE restart.
+    for (const cricket::MediaDescriptionOptions& options :
+         request.options.media_description_options) {
+      if (!options.transport_options.ice_restart) {
         CopyCandidatesFromSessionDescription(session_->local_description(),
-                                             content.name, answer);
+                                             options.mid, answer);
       }
     }
   }