Revert of Adding support for Unified Plan offer/answer negotiation. (patchset #9 id:500001 of https://codereview.webrtc.org/2991693002/ )

Reason for revert:
BUG=webrtc:8108: breaks Clang build.

Original issue's description:
> 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}
> Committed: https://chromium.googlesource.com/external/webrtc/+/a77e6bbd30276bdc5b30f2cbc1e92ca181ae76f0

TBR=deadbeef@webrtc.org,zhihuang@webrtc.org
# Not skipping CQ checks because original CL landed more than 1 days ago.
BUG=chromium:465349

Review-Url: https://codereview.webrtc.org/3001083002
Cr-Commit-Position: refs/heads/master@{#19384}
diff --git a/webrtc/pc/mediasession.cc b/webrtc/pc/mediasession.cc
index 4d4744a..de8f72c 100644
--- a/webrtc/pc/mediasession.cc
+++ b/webrtc/pc/mediasession.cc
@@ -188,14 +188,11 @@
   return true;
 }
 
-const CryptoParamsVec* GetCryptos(const ContentInfo* content) {
-  if (!content) {
-    return nullptr;
+const CryptoParamsVec* GetCryptos(const MediaContentDescription* media) {
+  if (!media) {
+    return NULL;
   }
-
-  RTC_DCHECK(IsMediaContent(content));
-  return &(static_cast<const MediaContentDescription*>(content->description)
-               ->cryptos());
+  return &media->cryptos();
 }
 
 bool FindMatchingCrypto(const CryptoParamsVec& cryptos,
@@ -431,15 +428,15 @@
  private:
 };
 
-// Adds a StreamParams for each SenderOptions in |sender_options| to
-// content_description.
+// Adds a StreamParams for each Stream in Streams with media type
+// media_type to content_description.
 // |current_params| - All currently known StreamParams of any media type.
 template <class C>
-static bool AddStreamParams(
-    const std::vector<SenderOptions>& sender_options,
-    const std::string& rtcp_cname,
-    StreamParamsVec* current_streams,
-    MediaContentDescriptionImpl<C>* content_description) {
+static bool AddStreamParams(MediaType media_type,
+                            const MediaSessionOptions& options,
+                            StreamParamsVec* current_streams,
+                            MediaContentDescriptionImpl<C>* content_description,
+                            const bool add_legacy_stream) {
   // SCTP streams are not negotiated using SDP/ContentDescriptions.
   if (IsSctp(content_description->protocol())) {
     return true;
@@ -448,26 +445,44 @@
   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());
 
-  for (const SenderOptions& sender : sender_options) {
+  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);
     // groupid is empty for StreamParams generated using
     // MediaSessionDescriptionFactory.
-    StreamParams* param =
-        GetStreamByIds(*current_streams, "" /*group_id*/, sender.track_id);
     if (!param) {
-      // This is a new sender.
+      // This is a new stream.
       std::vector<uint32_t> ssrcs;
-      GenerateSsrcs(*current_streams, sender.num_sim_layers, &ssrcs);
+      GenerateSsrcs(*current_streams, stream_it->num_sim_layers, &ssrcs);
       StreamParams stream_param;
-      stream_param.id = sender.track_id;
+      stream_param.id = stream_it->id;
       // Add the generated ssrc.
       for (size_t i = 0; i < ssrcs.size(); ++i) {
         stream_param.ssrcs.push_back(ssrcs[i]);
       }
-      if (sender.num_sim_layers > 1) {
+      if (stream_it->num_sim_layers > 1) {
         SsrcGroup group(kSimSsrcGroupSemantics, stream_param.ssrcs);
         stream_param.ssrc_groups.push_back(group);
       }
@@ -497,8 +512,8 @@
               << "media streams however, so no FlexFEC SSRC will be generated.";
         }
       }
-      stream_param.cname = rtcp_cname;
-      stream_param.sync_label = sender.stream_id;
+      stream_param.cname = options.rtcp_cname;
+      stream_param.sync_label = stream_it->sync_label;
       content_description->AddStream(stream_param);
 
       // Store the new StreamParams in current_streams.
@@ -508,7 +523,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 = sender.stream_id;
+      param->sync_label = stream_it->sync_label;
       content_description->AddStream(*param);
     }
   }
@@ -720,34 +735,46 @@
   return STR_CASE_CMP(codec.name.c_str(), kFlexfecCodecName) == 0;
 }
 
-// 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.
+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.
 template <class C>
 static bool CreateMediaContentOffer(
-    const std::vector<SenderOptions>& sender_options,
-    const MediaSessionOptions& session_options,
+    const MediaSessionOptions& 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(session_options.rtcp_mux_enabled);
+  offer->set_rtcp_mux(options.rtcp_mux_enabled);
   if (offer->type() == cricket::MEDIA_TYPE_VIDEO) {
     offer->set_rtcp_reduced_size(true);
   }
-  offer->set_multistream(session_options.is_muc);
+  offer->set_multistream(options.is_muc);
   offer->set_rtp_header_extensions(rtp_extensions);
 
-  if (!AddStreamParams(sender_options, session_options.rtcp_cname,
-                       current_streams, offer)) {
+  if (!AddStreamParams(offer->type(), options, current_streams, offer,
+                       add_legacy_stream)) {
     return false;
   }
 
@@ -855,42 +882,15 @@
   return false;
 }
 
-// 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
+// Adds all codecs from |reference_codecs| to |offered_codecs| that dont'
 // already exist in |offered_codecs| and ensure the payload types don't
 // collide.
 template <class C>
-static void MergeCodecs(const std::vector<C>& reference_codecs,
-                        std::vector<C>* offered_codecs,
-                        UsedPayloadTypes* used_pltypes) {
+static void FindCodecsToOffer(
+    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,11 +908,33 @@
         !FindMatchingCodec<C>(reference_codecs, *offered_codecs,
                               reference_codec, nullptr)) {
       C rtx_codec = reference_codec;
-      const C* associated_codec =
-          GetAssociatedCodec(reference_codecs, rtx_codec);
-      if (!associated_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);
+      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;
@@ -931,22 +953,6 @@
   }
 }
 
-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) {
@@ -990,41 +996,50 @@
   return false;
 }
 
-// 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 (!FindByUriAndEncryption(*offered_extensions, reference_extension,
-                                nullptr)) {
-      webrtc::RtpExtension 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);
-        }
+// 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 {
-        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);
-        }
+        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) {
+  for (auto reference_extension : reference_extensions) {
+    if (!FindByUri(*offered_extensions, reference_extension, NULL)) {
+      webrtc::RtpExtension existing;
+      if (FindByUri(*all_extensions, reference_extension, &existing)) {
+        offered_extensions->push_back(existing);
+      } else {
+        used_ids->FindAndSetIdUsed(&reference_extension);
+        all_extensions->push_back(reference_extension);
+        offered_extensions->push_back(reference_extension);
       }
     }
   }
@@ -1088,24 +1103,26 @@
   }
 }
 
-// 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.
+// 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.
 template <class C>
 static bool CreateMediaContentAnswer(
     const MediaContentDescriptionImpl<C>* offer,
-    const MediaDescriptionOptions& media_description_options,
-    const MediaSessionOptions& session_options,
+    const MediaSessionOptions& 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;
@@ -1119,15 +1136,14 @@
                                &negotiated_rtp_extensions);
   answer->set_rtp_header_extensions(negotiated_rtp_extensions);
 
-  answer->set_rtcp_mux(session_options.rtcp_mux_enabled && offer->rtcp_mux());
+  answer->set_rtcp_mux(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, session_options.crypto_options,
-                     &crypto)) {
+    if (SelectCrypto(offer, bundle_enabled, options.crypto_options, &crypto)) {
       if (current_cryptos) {
         FindMatchingCrypto(*current_cryptos, crypto, &crypto);
       }
@@ -1139,17 +1155,29 @@
     return false;
   }
 
-  if (!AddStreamParams(media_description_options.sender_options,
-                       session_options.rtcp_cname, current_streams, answer)) {
+  if (!AddStreamParams(answer->type(), options, current_streams, answer,
+                       add_legacy_stream)) {
     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());
-
-  answer->set_direction(NegotiateRtpTransceiverDirection(
-                            offer_rtd, media_description_options.direction)
-                            .ToMediaContentDirection());
+  auto wants_rtd = RtpTransceiverDirection(wants_send, wants_receive);
+  answer->set_direction(NegotiateRtpTransceiverDirection(offer_rtd, wants_rtd)
+                        .ToMediaContentDirection());
   return true;
 }
 
@@ -1211,20 +1239,23 @@
 }
 
 // Gets the current DTLS state from the transport description.
-static bool IsDtlsActive(const ContentInfo* content,
-                         const SessionDescription* current_description) {
-  if (!content) {
+static bool IsDtlsActive(
+    const std::string& content_name,
+    const SessionDescription* current_description) {
+  if (!current_description)
     return false;
-  }
 
-  size_t msection_index = content - &current_description->contents()[0];
-
-  if (current_description->transport_infos().size() <= msection_index) {
+  const ContentInfo* content =
+      current_description->GetContentByName(content_name);
+  if (!content)
     return false;
-  }
 
-  return current_description->transport_infos()[msection_index]
-      .description.secure();
+  const TransportDescription* current_tdesc =
+      GetTransportDescription(content_name, current_description);
+  if (!current_tdesc)
+    return false;
+
+  return current_tdesc->secure();
 }
 
 std::string MediaContentDirectionToString(MediaContentDirection direction) {
@@ -1250,54 +1281,75 @@
   return dir_str;
 }
 
-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::AddSendStream(MediaType type,
+                                    const std::string& id,
+                                    const std::string& sync_label) {
+  AddSendStreamInternal(type, id, sync_label, 1);
 }
 
-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::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::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::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::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});
+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();
 }
 
-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();
+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;
 }
 
 MediaSessionDescriptionFactory::MediaSessionDescriptionFactory(
     const TransportDescriptionFactory* transport_desc_factory)
-    : transport_desc_factory_(transport_desc_factory) {}
+    : secure_(SEC_DISABLED),
+      add_legacy_(true),
+      transport_desc_factory_(transport_desc_factory) {
+}
 
 MediaSessionDescriptionFactory::MediaSessionDescriptionFactory(
     ChannelManager* channel_manager,
     const TransportDescriptionFactory* transport_desc_factory)
-    : transport_desc_factory_(transport_desc_factory) {
+    : secure_(SEC_DISABLED),
+      add_legacy_(true),
+      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_);
-  ComputeAudioCodecsIntersectionAndUnion();
+  NegotiateCodecs(audio_recv_codecs_, audio_send_codecs_,
+                  &audio_sendrecv_codecs_);
 }
 
 const AudioCodecs& MediaSessionDescriptionFactory::audio_sendrecv_codecs()
@@ -1317,114 +1369,129 @@
     const AudioCodecs& send_codecs, const AudioCodecs& recv_codecs) {
   audio_send_codecs_ = send_codecs;
   audio_recv_codecs_ = recv_codecs;
-  ComputeAudioCodecsIntersectionAndUnion();
+  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_);
 }
 
 SessionDescription* MediaSessionDescriptionFactory::CreateOffer(
-    const MediaSessionOptions& session_options,
+    const MediaSessionOptions& options,
     const SessionDescription* current_description) const {
   std::unique_ptr<SessionDescription> offer(new SessionDescription());
 
   StreamParamsVec current_streams;
   GetCurrentStreamParams(current_description, &current_streams);
 
-  AudioCodecs offer_audio_codecs;
-  VideoCodecs offer_video_codecs;
-  DataCodecs offer_data_codecs;
-  GetCodecsForOffer(current_description, &offer_audio_codecs,
-                    &offer_video_codecs, &offer_data_codecs);
+  const bool wants_send =
+      options.HasSendMediaStream(MEDIA_TYPE_AUDIO) || add_legacy_;
+  const AudioCodecs& supported_audio_codecs =
+      GetAudioCodecsForOffer({wants_send, options.recv_audio});
 
-  if (!session_options.vad_enabled) {
+  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 application doesn't want CN codecs in offer.
-    StripCNCodecs(&offer_audio_codecs);
+    StripCNCodecs(&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);
 
-  // Must have options for each existing section.
+  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.
   if (current_description) {
-    RTC_DCHECK(current_description->contents().size() <=
-               session_options.media_description_options.size());
+    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();
+      }
+    }
   }
 
-  // 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;
+  // 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;
   }
 
   // Bundle the contents together, if we've been asked to do so, and update any
   // parameters that need to be tweaked for BUNDLE.
-  if (session_options.bundle_enabled && offer->contents().size() > 0u) {
+  if (options.bundle_enabled) {
     ContentGroup offer_bundle(GROUP_TYPE_BUNDLE);
-    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);
+    for (ContentInfos::const_iterator content = offer->contents().begin();
+       content != offer->contents().end(); ++content) {
+      offer_bundle.AddContentName(content->name);
     }
     offer->AddGroup(offer_bundle);
     if (!UpdateTransportInfoForBundle(offer_bundle, offer.get())) {
       LOG(LS_ERROR) << "CreateOffer failed to UpdateTransportInfoForBundle.";
-      return nullptr;
+      return NULL;
     }
     if (!UpdateCryptoParamsForBundle(offer_bundle, offer.get())) {
       LOG(LS_ERROR) << "CreateOffer failed to UpdateCryptoParamsForBundle.";
-      return nullptr;
+      return NULL;
     }
   }
+
   return offer.release();
 }
 
 SessionDescription* MediaSessionDescriptionFactory::CreateAnswer(
-    const SessionDescription* offer,
-    const MediaSessionOptions& session_options,
+    const SessionDescription* offer, const MediaSessionOptions& options,
     const SessionDescription* current_description) const {
   if (!offer) {
     return nullptr;
@@ -1444,81 +1511,32 @@
   // Transport info shared by the bundle group.
   std::unique_ptr<TransportInfo> bundle_transport;
 
-  // 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];
+  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;
+      }
     }
-    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 && session_options.bundle_enabled && offer_bundle &&
+    if (!added.rejected && options.bundle_enabled && offer_bundle &&
         offer_bundle->HasContentName(added.name)) {
       answer_bundle.AddContentName(added.name);
       bundle_transport.reset(
@@ -1578,37 +1596,11 @@
   }
 }
 
-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(
+void MediaSessionDescriptionFactory::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 {
@@ -1617,150 +1609,87 @@
   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.
+  // is used.
+  // Add them to |used_pltypes| so the payloadtype is not reused if a new media
+  // type is added.
   if (current_description) {
-    MergeCodecsFromDescription(current_description, audio_codecs, video_codecs,
-                               data_codecs, &used_pltypes);
+    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);
+    }
   }
 
   // Add our codecs that are not in |current_description|.
-  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);
+  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);
 }
 
 void MediaSessionDescriptionFactory::GetRtpHdrExtsToOffer(
     const SessionDescription* current_description,
-    RtpHeaderExtensions* offer_audio_extensions,
-    RtpHeaderExtensions* offer_video_extensions) const {
+    RtpHeaderExtensions* audio_extensions,
+    RtpHeaderExtensions* 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;
-  offer_audio_extensions->clear();
-  offer_video_extensions->clear();
+  audio_extensions->clear();
+  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) {
-    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);
-      }
+    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);
     }
   }
 
   // Add our default RTP header extensions that are not in
   // |current_description|.
-  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);
-
+  FindRtpHdrExtsToOffer(audio_rtp_header_extensions(), audio_extensions,
+                        &all_regular_extensions, &used_ids);
+  FindRtpHdrExtsToOffer(video_rtp_header_extensions(), video_extensions,
+                        &all_regular_extensions, &used_ids);
   // TODO(jbauch): Support adding encrypted header extensions to existing
   // sessions.
   if (enable_encrypted_rtp_header_extensions_ && !current_description) {
-    AddEncryptedVersionsOfHdrExts(offer_audio_extensions,
-                                  &all_encrypted_extensions, &used_ids);
-    AddEncryptedVersionsOfHdrExts(offer_video_extensions,
-                                  &all_encrypted_extensions, &used_ids);
+    AddEncryptedVersionsOfHdrExts(audio_extensions, &all_encrypted_extensions,
+        &used_ids);
+    AddEncryptedVersionsOfHdrExts(video_extensions, &all_encrypted_extensions,
+        &used_ids);
   }
 }
 
@@ -1814,71 +1743,35 @@
   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 MediaDescriptionOptions& media_description_options,
-    const MediaSessionOptions& session_options,
-    const ContentInfo* current_content,
+    const MediaSessionOptions& options,
     const SessionDescription* current_description,
     const RtpHeaderExtensions& audio_rtp_extensions,
     const AudioCodecs& audio_codecs,
     StreamParamsVec* current_streams,
     SessionDescription* desc) const {
-  // 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);
-    }
-  }
+  const ContentInfo* current_audio_content =
+      GetFirstAudioContent(current_description);
+  std::string content_name =
+      current_audio_content ? current_audio_content->name : CN_AUDIO;
 
   cricket::SecurePolicy sdes_policy =
-      IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED
-                                                         : secure();
+      IsDtlsActive(content_name, current_description) ? cricket::SEC_DISABLED
+                                                      : secure();
 
   std::unique_ptr<AudioContentDescription> audio(new AudioContentDescription());
   std::vector<std::string> crypto_suites;
-  GetSupportedAudioSdesCryptoSuiteNames(session_options.crypto_options,
-                                        &crypto_suites);
+  GetSupportedAudioSdesCryptoSuiteNames(options.crypto_options, &crypto_suites);
   if (!CreateMediaContentOffer(
-          media_description_options.sender_options, session_options,
-          filtered_codecs, sdes_policy, GetCryptos(current_content),
-          crypto_suites, audio_rtp_extensions, current_streams, audio.get())) {
+          options,
+          audio_codecs,
+          sdes_policy,
+          GetCryptos(GetFirstAudioContentDescription(current_description)),
+          crypto_suites,
+          audio_rtp_extensions,
+          add_legacy_,
+          current_streams,
+          audio.get())) {
     return false;
   }
   audio->set_lang(lang_);
@@ -1886,13 +1779,13 @@
   bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
   SetMediaProtocol(secure_transport, audio.get());
 
-  audio->set_direction(
-      media_description_options.direction.ToMediaContentDirection());
+  auto offer_rtd =
+      RtpTransceiverDirection(!audio->streams().empty(), options.recv_audio);
+  audio->set_direction(offer_rtd.ToMediaContentDirection());
 
-  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,
+  desc->AddContent(content_name, NS_JINGLE_RTP, audio.release());
+  if (!AddTransportOffer(content_name,
+                         GetTransportOptions(options, content_name),
                          current_description, desc)) {
     return false;
   }
@@ -1901,98 +1794,87 @@
 }
 
 bool MediaSessionDescriptionFactory::AddVideoContentForOffer(
-    const MediaDescriptionOptions& media_description_options,
-    const MediaSessionOptions& session_options,
-    const ContentInfo* current_content,
+    const MediaSessionOptions& options,
     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(current_content, current_description) ? cricket::SEC_DISABLED
-                                                         : secure();
+      IsDtlsActive(content_name, current_description) ? cricket::SEC_DISABLED
+                                                      : secure();
 
   std::unique_ptr<VideoContentDescription> video(new VideoContentDescription());
   std::vector<std::string> 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);
-    }
-  }
-
+  GetSupportedVideoSdesCryptoSuiteNames(options.crypto_options, &crypto_suites);
   if (!CreateMediaContentOffer(
-          media_description_options.sender_options, session_options,
-          filtered_codecs, sdes_policy, GetCryptos(current_content),
-          crypto_suites, video_rtp_extensions, current_streams, video.get())) {
+          options,
+          video_codecs,
+          sdes_policy,
+          GetCryptos(GetFirstVideoContentDescription(current_description)),
+          crypto_suites,
+          video_rtp_extensions,
+          add_legacy_,
+          current_streams,
+          video.get())) {
     return false;
   }
 
-  video->set_bandwidth(kAutoBandwidth);
+  video->set_bandwidth(options.video_bandwidth);
 
   bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
   SetMediaProtocol(secure_transport, video.get());
 
-  video->set_direction(
-      media_description_options.direction.ToMediaContentDirection());
+  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);
+    }
+  }
 
-  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,
+  desc->AddContent(content_name, NS_JINGLE_RTP, video.release());
+  if (!AddTransportOffer(content_name,
+                         GetTransportOptions(options, content_name),
                          current_description, desc)) {
     return false;
   }
+
   return true;
 }
 
 bool MediaSessionDescriptionFactory::AddDataContentForOffer(
-    const MediaDescriptionOptions& media_description_options,
-    const MediaSessionOptions& session_options,
-    const ContentInfo* current_content,
+    const MediaSessionOptions& options,
     const SessionDescription* current_description,
-    const DataCodecs& data_codecs,
+    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 = (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);
-  }
+  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;
 
   cricket::SecurePolicy sdes_policy =
-      IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED
-                                                         : secure();
+      IsDtlsActive(content_name, 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
@@ -2008,279 +1890,227 @@
     data->set_protocol(
         secure_transport ? kMediaProtocolDtlsSctp : kMediaProtocolSctp);
   } else {
-    GetSupportedDataSdesCryptoSuiteNames(session_options.crypto_options,
+    GetSupportedDataSdesCryptoSuiteNames(options.crypto_options,
                                          &crypto_suites);
   }
 
-  // Even SCTP uses a "codec".
   if (!CreateMediaContentOffer(
-          media_description_options.sender_options, session_options,
-          data_codecs, sdes_policy, GetCryptos(current_content), crypto_suites,
-          RtpHeaderExtensions(), current_streams, data.get())) {
+          options,
+          *data_codecs,
+          sdes_policy,
+          GetCryptos(GetFirstDataContentDescription(current_description)),
+          crypto_suites,
+          RtpHeaderExtensions(),
+          add_legacy_,
+          current_streams,
+          data.get())) {
     return false;
   }
 
   if (is_sctp) {
-    desc->AddContent(media_description_options.mid, NS_JINGLE_DRAFT_SCTP,
-                     data.release());
+    desc->AddContent(content_name, NS_JINGLE_DRAFT_SCTP, data.release());
   } else {
-    data->set_bandwidth(kDataMaxBandwidth);
+    data->set_bandwidth(options.data_bandwidth);
     SetMediaProtocol(secure_transport, data.get());
-    desc->AddContent(media_description_options.mid, NS_JINGLE_RTP,
-                     media_description_options.stopped, data.release());
+    desc->AddContent(content_name, NS_JINGLE_RTP, data.release());
   }
-  if (!AddTransportOffer(media_description_options.mid,
-                         media_description_options.transport_options,
+  if (!AddTransportOffer(content_name,
+                         GetTransportOptions(options, content_name),
                          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 MediaDescriptionOptions& media_description_options,
-    const MediaSessionOptions& session_options,
-    const ContentInfo* offer_content,
-    const SessionDescription* offer_description,
-    const ContentInfo* current_content,
+    const SessionDescription* offer,
+    const MediaSessionOptions& options,
     const SessionDescription* current_description,
     const TransportInfo* bundle_transport,
-    const AudioCodecs& audio_codecs,
     StreamParamsVec* current_streams,
     SessionDescription* answer) const {
-  const AudioContentDescription* offer_audio_description =
-      static_cast<const AudioContentDescription*>(offer_content->description);
+  const ContentInfo* audio_content = GetFirstAudioContent(offer);
+  const AudioContentDescription* offer_audio =
+      static_cast<const AudioContentDescription*>(audio_content->description);
 
   std::unique_ptr<TransportDescription> audio_transport(
-      CreateTransportAnswer(media_description_options.mid, offer_description,
-                            media_description_options.transport_options,
+      CreateTransportAnswer(audio_content->name, offer,
+                            GetTransportOptions(options, audio_content->name),
                             current_description, bundle_transport != nullptr));
   if (!audio_transport) {
     return false;
   }
 
-  // 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());
+  // 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());
   auto answer_rtd = NegotiateRtpTransceiverDirection(offer_rtd, wants_rtd);
-  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);
-    }
+  AudioCodecs audio_codecs = GetAudioCodecsForAnswer(offer_rtd, answer_rtd);
+  if (!options.vad_enabled) {
+    StripCNCodecs(&audio_codecs);
   }
 
-  bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) &&
-                        session_options.bundle_enabled;
+  bool bundle_enabled =
+      offer->HasGroup(GROUP_TYPE_BUNDLE) && 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_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())) {
+          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())) {
     return false;  // Fails the session setup.
   }
 
   bool secure = bundle_transport ? bundle_transport->description.secure()
                                  : audio_transport->secure();
-  bool rejected = media_description_options.stopped ||
-                  offer_content->rejected ||
+  bool rejected = !options.has_audio() || audio_content->rejected ||
                   !IsMediaProtocolSupported(MEDIA_TYPE_AUDIO,
                                             audio_answer->protocol(), secure);
   if (!rejected) {
-    AddTransportAnswer(media_description_options.mid, *(audio_transport.get()),
-                       answer);
+    AddTransportAnswer(audio_content->name, *(audio_transport.get()), answer);
   } else {
-    LOG(LS_INFO) << "Audio m= section '" << media_description_options.mid
-                 << "' being rejected in answer.";
+    // 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.";
   }
 
-  answer->AddContent(media_description_options.mid, offer_content->type,
-                     rejected, audio_answer.release());
+  answer->AddContent(audio_content->name, audio_content->type, rejected,
+                     audio_answer.release());
   return true;
 }
 
 bool MediaSessionDescriptionFactory::AddVideoContentForAnswer(
-    const MediaDescriptionOptions& media_description_options,
-    const MediaSessionOptions& session_options,
-    const ContentInfo* offer_content,
-    const SessionDescription* offer_description,
-    const ContentInfo* current_content,
+    const SessionDescription* offer,
+    const MediaSessionOptions& options,
     const SessionDescription* current_description,
     const TransportInfo* bundle_transport,
-    const VideoCodecs& video_codecs,
     StreamParamsVec* current_streams,
     SessionDescription* answer) const {
-  const VideoContentDescription* offer_video_description =
-      static_cast<const VideoContentDescription*>(offer_content->description);
-
+  const ContentInfo* video_content = GetFirstVideoContent(offer);
   std::unique_ptr<TransportDescription> video_transport(
-      CreateTransportAnswer(media_description_options.mid, offer_description,
-                            media_description_options.transport_options,
+      CreateTransportAnswer(video_content->name, offer,
+                            GetTransportOptions(options, video_content->name),
                             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(
-          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.
+          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;
   }
   bool secure = bundle_transport ? bundle_transport->description.secure()
                                  : video_transport->secure();
-  bool rejected = media_description_options.stopped ||
-                  offer_content->rejected ||
+  bool rejected = !options.has_video() || video_content->rejected ||
                   !IsMediaProtocolSupported(MEDIA_TYPE_VIDEO,
                                             video_answer->protocol(), secure);
   if (!rejected) {
-    if (!AddTransportAnswer(media_description_options.mid,
-                            *(video_transport.get()), answer)) {
+    if (!AddTransportAnswer(video_content->name, *(video_transport.get()),
+                            answer)) {
       return false;
     }
-    video_answer->set_bandwidth(kAutoBandwidth);
+    video_answer->set_bandwidth(options.video_bandwidth);
   } else {
-    LOG(LS_INFO) << "Video m= section '" << media_description_options.mid
-                 << "' being rejected in answer.";
+    // 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.";
   }
-  answer->AddContent(media_description_options.mid, offer_content->type,
-                     rejected, video_answer.release());
+  answer->AddContent(video_content->name, video_content->type, rejected,
+                     video_answer.release());
   return true;
 }
 
 bool MediaSessionDescriptionFactory::AddDataContentForAnswer(
-    const MediaDescriptionOptions& media_description_options,
-    const MediaSessionOptions& session_options,
-    const ContentInfo* offer_content,
-    const SessionDescription* offer_description,
-    const ContentInfo* current_content,
+    const SessionDescription* offer,
+    const MediaSessionOptions& options,
     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(media_description_options.mid, offer_description,
-                            media_description_options.transport_options,
+      CreateTransportAnswer(data_content->name, offer,
+                            GetTransportOptions(options, data_content->name),
                             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_description->HasGroup(GROUP_TYPE_BUNDLE) &&
-                        session_options.bundle_enabled;
+  bool bundle_enabled =
+      offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled;
   if (!CreateMediaContentAnswer(
           static_cast<const DataContentDescription*>(
-              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())) {
+              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())) {
     return false;  // Fails the session setup.
   }
 
   // Respond with sctpmap if the offer uses sctpmap.
   const DataContentDescription* offer_data_description =
-      static_cast<const DataContentDescription*>(offer_content->description);
+      static_cast<const DataContentDescription*>(data_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 = session_options.data_channel_type == DCT_NONE ||
-                  media_description_options.stopped ||
-                  offer_content->rejected ||
+  bool rejected = !options.has_data() || data_content->rejected ||
                   !IsMediaProtocolSupported(MEDIA_TYPE_DATA,
                                             data_answer->protocol(), secure);
   if (!rejected) {
-    data_answer->set_bandwidth(kDataMaxBandwidth);
-    if (!AddTransportAnswer(media_description_options.mid,
-                            *(data_transport.get()), answer)) {
+    data_answer->set_bandwidth(options.data_bandwidth);
+    if (!AddTransportAnswer(data_content->name, *(data_transport.get()),
+                            answer)) {
       return false;
     }
   } else {
@@ -2288,39 +2118,11 @@
     // 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(media_description_options.mid, offer_content->type,
-                     rejected, data_answer.release());
+  answer->AddContent(data_content->name, data_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 35bd444..596bd18 100644
--- a/webrtc/pc/mediasession.h
+++ b/webrtc/pc/mediasession.h
@@ -102,72 +102,83 @@
 NegotiateRtpTransceiverDirection(RtpTransceiverDirection offer,
                                  RtpTransceiverDirection wants);
 
-// 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;
-};
-
-// 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) {}
-
-  // 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);
-
-  // Internally just uses sender_options.
-  void AddRtpDataChannel(const std::string& track_id,
-                         const std::string& stream_id);
-
-  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);
-};
-
-// 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() {}
+  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) {}
 
-  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 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; }
 
-  bool HasMediaDescription(MediaType type) const;
+  // 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,
+                      int num_sim_layers);
+  void RemoveSendStream(MediaType type, const std::string& id);
 
-  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;
+
+  // Helper function.
+  void AddSendStreamInternal(MediaType type,
+                         const std::string& id,
+                         const std::string& sync_label,
+                         int num_sim_layers);
+
+  bool HasSendMediaStream(MediaType type) const;
+
+  // 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;
   rtc::CryptoOptions crypto_options;
-  // List of media description options in the same order that the media
-  // descriptions will be generated.
-  std::vector<MediaDescriptionOptions> media_description_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;
 };
 
 // "content" (as used in XEP-0166) descriptions for voice and video.
@@ -266,6 +277,7 @@
     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) {
@@ -457,6 +469,11 @@
   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;
@@ -476,15 +493,13 @@
   const AudioCodecs& GetAudioCodecsForAnswer(
       const RtpTransceiverDirection& offer,
       const RtpTransceiverDirection& answer) 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 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 GetRtpHdrExtsToOffer(const SessionDescription* current_description,
                             RtpHeaderExtensions* audio_extensions,
                             RtpHeaderExtensions* video_extensions) const;
@@ -511,9 +526,7 @@
   // error.
 
   bool AddAudioContentForOffer(
-      const MediaDescriptionOptions& media_description_options,
-      const MediaSessionOptions& session_options,
-      const ContentInfo* current_content,
+      const MediaSessionOptions& options,
       const SessionDescription* current_description,
       const RtpHeaderExtensions& audio_rtp_extensions,
       const AudioCodecs& audio_codecs,
@@ -521,9 +534,7 @@
       SessionDescription* desc) const;
 
   bool AddVideoContentForOffer(
-      const MediaDescriptionOptions& media_description_options,
-      const MediaSessionOptions& session_options,
-      const ContentInfo* current_content,
+      const MediaSessionOptions& options,
       const SessionDescription* current_description,
       const RtpHeaderExtensions& video_rtp_extensions,
       const VideoCodecs& video_codecs,
@@ -531,66 +542,43 @@
       SessionDescription* desc) const;
 
   bool AddDataContentForOffer(
-      const MediaDescriptionOptions& media_description_options,
-      const MediaSessionOptions& session_options,
-      const ContentInfo* current_content,
+      const MediaSessionOptions& options,
       const SessionDescription* current_description,
-      const DataCodecs& data_codecs,
+      DataCodecs* data_codecs,
       StreamParamsVec* current_streams,
       SessionDescription* desc) 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 AddAudioContentForAnswer(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 AddVideoContentForAnswer(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();
+  bool AddDataContentForAnswer(const SessionDescription* offer,
+                               const MediaSessionOptions& options,
+                               const SessionDescription* current_description,
+                               const TransportInfo* bundle_transport,
+                               StreamParamsVec* current_streams,
+                               SessionDescription* answer) const;
 
   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 f87dda1..ae8d139 100644
--- a/webrtc/pc/mediasession_unittest.cc
+++ b/webrtc/pc/mediasession_unittest.cc
@@ -34,7 +34,6 @@
 using cricket::MediaContentDescription;
 using cricket::MediaSessionDescriptionFactory;
 using cricket::MediaContentDirection;
-using cricket::MediaDescriptionOptions;
 using cricket::MediaSessionOptions;
 using cricket::MediaType;
 using cricket::SessionDescription;
@@ -66,7 +65,6 @@
 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;
@@ -95,9 +93,6 @@
 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")};
 
@@ -213,11 +208,6 @@
     "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 =
@@ -247,95 +237,11 @@
   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));
@@ -399,8 +305,7 @@
     return iter != ice_options.end();
   }
 
-  void TestTransportInfo(bool offer,
-                         MediaSessionOptions& options,
+  void TestTransportInfo(bool offer, const MediaSessionOptions& options,
                          bool has_current_desc) {
     const std::string current_audio_ufrag = "current_audio_ufrag";
     const std::string current_audio_pwd = "current_audio_pwd";
@@ -445,11 +350,7 @@
         EXPECT_EQ(static_cast<size_t>(cricket::ICE_PWD_LENGTH),
                   ti_audio->description.ice_pwd.size());
       }
-      auto media_desc_options_it =
-          FindFirstMediaDescriptionByMid("audio", &options);
-      EXPECT_EQ(
-          media_desc_options_it->transport_options.enable_ice_renomination,
-          GetIceRenomination(ti_audio));
+      EXPECT_EQ(options.enable_ice_renomination, GetIceRenomination(ti_audio));
 
     } else {
       EXPECT_TRUE(ti_audio == NULL);
@@ -473,11 +374,7 @@
                     ti_video->description.ice_pwd.size());
         }
       }
-      auto media_desc_options_it =
-          FindFirstMediaDescriptionByMid("video", &options);
-      EXPECT_EQ(
-          media_desc_options_it->transport_options.enable_ice_renomination,
-          GetIceRenomination(ti_video));
+      EXPECT_EQ(options.enable_ice_renomination, GetIceRenomination(ti_video));
     } else {
       EXPECT_TRUE(ti_video == NULL);
     }
@@ -500,11 +397,7 @@
                     ti_data->description.ice_pwd.size());
         }
       }
-      auto media_desc_options_it =
-          FindFirstMediaDescriptionByMid("data", &options);
-      EXPECT_EQ(
-          media_desc_options_it->transport_options.enable_ice_renomination,
-          GetIceRenomination(ti_data));
+      EXPECT_EQ(options.enable_ice_renomination, GetIceRenomination(ti_data));
 
     } else {
       EXPECT_TRUE(ti_video == NULL);
@@ -514,8 +407,9 @@
   void TestCryptoWithBundle(bool offer) {
     f1_.set_secure(SEC_ENABLED);
     MediaSessionOptions options;
-    AddAudioVideoSections(cricket::MD_RECVONLY, &options);
-    AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
+    options.recv_audio = true;
+    options.recv_video = true;
+    options.data_channel_type = cricket::DCT_RTP;
     std::unique_ptr<SessionDescription> ref_desc;
     std::unique_ptr<SessionDescription> desc;
     if (offer) {
@@ -561,25 +455,27 @@
 
   // 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| and the answer is willing to both send and receive.
+  // to |direction_in_offer|.
   void TestMediaDirectionInAnswer(
       cricket::MediaContentDirection direction_in_offer,
       cricket::MediaContentDirection expected_direction_in_answer) {
-    MediaSessionOptions offer_opts;
-    AddAudioVideoSections(direction_in_offer, &offer_opts);
-
-    std::unique_ptr<SessionDescription> offer(
-        f1_.CreateOffer(offer_opts, NULL));
+    MediaSessionOptions opts;
+    opts.recv_video = true;
+    std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(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(), answer_opts, NULL));
+        f2_.CreateAnswer(offer.get(), opts, NULL));
     const AudioContentDescription* acd_answer =
         GetFirstAudioContentDescription(answer.get());
     EXPECT_EQ(expected_direction_in_answer, acd_answer->direction());
@@ -603,13 +499,11 @@
 
   void TestVideoGcmCipher(bool gcm_offer, bool gcm_answer) {
     MediaSessionOptions offer_opts;
-    AddAudioVideoSections(cricket::MD_RECVONLY, &offer_opts);
+    offer_opts.recv_video = true;
     offer_opts.crypto_options.enable_gcm_crypto_suites = gcm_offer;
-
     MediaSessionOptions answer_opts;
-    AddAudioVideoSections(cricket::MD_RECVONLY, &answer_opts);
+    answer_opts.recv_video = true;
     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(
@@ -630,7 +524,7 @@
     EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
     EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
     EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
-    EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
+    EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
     EXPECT_TRUE(acd->rtcp_mux());                 // negotiated rtcp-mux
     if (gcm_offer && gcm_answer) {
       ASSERT_CRYPTO(acd, 1U, CS_AEAD_AES_256_GCM);
@@ -639,7 +533,7 @@
     }
     EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
     EXPECT_EQ(MAKE_VECTOR(kVideoCodecsAnswer), vcd->codecs());
-    EXPECT_EQ(0U, vcd->first_ssrc());             // no sender is attached
+    EXPECT_NE(0U, vcd->first_ssrc());             // a random nonzero ssrc
     EXPECT_TRUE(vcd->rtcp_mux());                 // negotiated rtcp-mux
     if (gcm_offer && gcm_answer) {
       ASSERT_CRYPTO(vcd, 1U, CS_AEAD_AES_256_GCM);
@@ -660,7 +554,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioOffer) {
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(
-      f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL));
+      f1_.CreateOffer(MediaSessionOptions(), NULL));
   ASSERT_TRUE(offer.get() != NULL);
   const ContentInfo* ac = offer->GetContentByName("audio");
   const ContentInfo* vc = offer->GetContentByName("video");
@@ -671,7 +565,7 @@
       static_cast<const AudioContentDescription*>(ac->description);
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(f1_.audio_sendrecv_codecs(), acd->codecs());
-  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached.
+  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
   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);
@@ -681,7 +575,7 @@
 // Create a typical video offer, and ensure it matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoOffer) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  opts.recv_video = true;
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
@@ -697,14 +591,14 @@
       static_cast<const VideoContentDescription*>(vc->description);
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(f1_.audio_sendrecv_codecs(), acd->codecs());
-  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
+  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
   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_EQ(0U, vcd->first_ssrc());             // no sender is attached
+  EXPECT_NE(0U, vcd->first_ssrc());             // a random nonzero ssrc
   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);
@@ -722,8 +616,9 @@
   ASSERT_EQ(offered_video_codec.id, offered_data_codec.id);
 
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
+  opts.recv_audio = true;
+  opts.recv_video = true;
+  opts.data_channel_type = cricket::DCT_RTP;
   opts.bundle_enabled = true;
   std::unique_ptr<SessionDescription> offer(f2_.CreateOffer(opts, NULL));
   const VideoContentDescription* vcd =
@@ -743,17 +638,15 @@
   EXPECT_EQ(dcd->codecs()[0].name, offered_data_codec.name);
 }
 
-// Test creating an updated offer with bundle, audio, video and data
+// Test creating an updated offer with 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;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
-                  &opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_INACTIVE, kStopped,
-                  &opts);
+  opts.recv_audio = true;
+  opts.recv_video = false;
   opts.data_channel_type = cricket::DCT_NONE;
   opts.bundle_enabled = true;
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
@@ -761,8 +654,9 @@
       f2_.CreateAnswer(offer.get(), opts, NULL));
 
   MediaSessionOptions updated_opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &updated_opts);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &updated_opts);
+  updated_opts.recv_audio = true;
+  updated_opts.recv_video = true;
+  updated_opts.data_channel_type = cricket::DCT_RTP;
   updated_opts.bundle_enabled = true;
   std::unique_ptr<SessionDescription> updated_offer(
       f1_.CreateOffer(updated_opts, answer.get()));
@@ -788,8 +682,7 @@
 // Create a RTP data offer, and ensure it matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateRtpDataOffer) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
+  opts.data_channel_type = cricket::DCT_RTP;
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
@@ -805,14 +698,14 @@
       static_cast<const DataContentDescription*>(dc->description);
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(f1_.audio_sendrecv_codecs(), acd->codecs());
-  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attched.
+  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
   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_EQ(0U, dcd->first_ssrc());  // no sender is attached.
+  EXPECT_NE(0U, dcd->first_ssrc());             // a random nonzero ssrc
   EXPECT_EQ(cricket::kDataMaxBandwidth,
             dcd->bandwidth());                  // default bandwidth (auto)
   EXPECT_TRUE(dcd->rtcp_mux());                 // rtcp-mux defaults on
@@ -823,8 +716,9 @@
 // Create an SCTP data offer with bundle without error.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSctpDataOffer) {
   MediaSessionOptions opts;
+  opts.recv_audio = false;
   opts.bundle_enabled = true;
-  AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts);
+  opts.data_channel_type = cricket::DCT_SCTP;
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   EXPECT_TRUE(offer.get() != NULL);
@@ -834,8 +728,9 @@
 // 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;
-  AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts);
+  opts.data_channel_type = cricket::DCT_SCTP;
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer1(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer1.get() != NULL);
@@ -861,7 +756,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestCreateOfferWithoutLegacyStreams) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  opts.recv_video = true;
+  f1_.set_add_legacy_streams(false);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   const ContentInfo* ac = offer->GetContentByName("audio");
@@ -879,14 +775,13 @@
 
 // Creates an audio+video sendonly offer.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSendOnlyOffer) {
-  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);
+  MediaSessionOptions options;
+  options.recv_audio = false;
+  options.recv_video = false;
+  options.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack1, kMediaStream1);
+  options.AddSendStream(MEDIA_TYPE_AUDIO, kAudioTrack1, kMediaStream1);
 
-  std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+  std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(options, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   EXPECT_EQ(2u, offer->contents().size());
   EXPECT_TRUE(IsMediaContentOfType(&offer->contents()[0], MEDIA_TYPE_AUDIO));
@@ -900,15 +795,16 @@
 // SessionDescription is preserved in the new SessionDescription.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateOfferContentOrder) {
   MediaSessionOptions opts;
-  AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts);
+  opts.recv_audio = false;
+  opts.recv_video = false;
+  opts.data_channel_type = cricket::DCT_SCTP;
 
   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));
 
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
-                  &opts);
+  opts.recv_video = true;
   std::unique_ptr<SessionDescription> offer2(
       f1_.CreateOffer(opts, offer1.get()));
   ASSERT_TRUE(offer2.get() != NULL);
@@ -916,8 +812,7 @@
   EXPECT_TRUE(IsMediaContentOfType(&offer2->contents()[0], MEDIA_TYPE_DATA));
   EXPECT_TRUE(IsMediaContentOfType(&offer2->contents()[1], MEDIA_TYPE_VIDEO));
 
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
-                  &opts);
+  opts.recv_audio = true;
   std::unique_ptr<SessionDescription> offer3(
       f1_.CreateOffer(opts, offer2.get()));
   ASSERT_TRUE(offer3.get() != NULL);
@@ -925,6 +820,15 @@
   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.
@@ -932,10 +836,10 @@
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(
-      f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL));
+      f1_.CreateOffer(MediaSessionOptions(), NULL));
   ASSERT_TRUE(offer.get() != NULL);
   std::unique_ptr<SessionDescription> answer(
-      f2_.CreateAnswer(offer.get(), CreatePlanBMediaSessionOptions(), NULL));
+      f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL));
   const ContentInfo* ac = answer->GetContentByName("audio");
   const ContentInfo* vc = answer->GetContentByName("video");
   ASSERT_TRUE(ac != NULL);
@@ -945,7 +849,7 @@
       static_cast<const AudioContentDescription*>(ac->description);
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
-  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
+  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
   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);
@@ -957,12 +861,13 @@
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerGcm) {
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
-  MediaSessionOptions opts = CreatePlanBMediaSessionOptions();
-  opts.crypto_options.enable_gcm_crypto_suites = true;
-  std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+  MediaSessionOptions options;
+  options.crypto_options.enable_gcm_crypto_suites = true;
+  std::unique_ptr<SessionDescription> offer(
+      f1_.CreateOffer(options, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   std::unique_ptr<SessionDescription> answer(
-      f2_.CreateAnswer(offer.get(), opts, NULL));
+      f2_.CreateAnswer(offer.get(), options, NULL));
   const ContentInfo* ac = answer->GetContentByName("audio");
   const ContentInfo* vc = answer->GetContentByName("video");
   ASSERT_TRUE(ac != NULL);
@@ -972,7 +877,7 @@
       static_cast<const AudioContentDescription*>(ac->description);
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
-  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
+  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
   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);
@@ -982,7 +887,7 @@
 // Create a typical video answer, and ensure it matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoAnswer) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  opts.recv_video = true;
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
@@ -1002,12 +907,12 @@
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
-  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
+  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
   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_EQ(0U, vcd->first_ssrc());             // no sender is attached
+  EXPECT_NE(0U, vcd->first_ssrc());             // a random nonzero ssrc
   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());
@@ -1032,8 +937,8 @@
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswer) {
-  MediaSessionOptions opts = CreatePlanBMediaSessionOptions();
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
+  MediaSessionOptions opts;
+  opts.data_channel_type = cricket::DCT_RTP;
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
@@ -1053,20 +958,20 @@
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
-  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
+  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
   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_EQ(0U, dcd->first_ssrc());  // no sender is attached
+  EXPECT_NE(0U, dcd->first_ssrc());  // a random nonzero ssrc
   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 = CreatePlanBMediaSessionOptions();
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
+  MediaSessionOptions opts;
+  opts.data_channel_type = cricket::DCT_RTP;
   opts.crypto_options.enable_gcm_crypto_suites = true;
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
@@ -1087,12 +992,12 @@
   EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
   EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
   EXPECT_EQ(kAutoBandwidth, acd->bandwidth());  // negotiated auto bw
-  EXPECT_EQ(0U, acd->first_ssrc());             // no sender is attached
+  EXPECT_NE(0U, acd->first_ssrc());             // a random nonzero ssrc
   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_EQ(0U, dcd->first_ssrc());  // no sender is attached
+  EXPECT_NE(0U, dcd->first_ssrc());  // a random nonzero ssrc
   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());
@@ -1102,7 +1007,7 @@
 // The answer's use_sctpmap flag should match the offer's.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerUsesSctpmap) {
   MediaSessionOptions opts;
-  AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts);
+  opts.data_channel_type = cricket::DCT_SCTP;
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   ContentInfo* dc_offer = offer->GetContentByName("data");
@@ -1123,7 +1028,7 @@
 // The answer's use_sctpmap flag should match the offer's.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerWithoutSctpmap) {
   MediaSessionOptions opts;
-  AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts);
+  opts.data_channel_type = cricket::DCT_SCTP;
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   ContentInfo* dc_offer = offer->GetContentByName("data");
@@ -1153,7 +1058,7 @@
   tdf2_.set_secure(SEC_ENABLED);
 
   MediaSessionOptions opts;
-  AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts);
+  opts.data_channel_type = cricket::DCT_SCTP;
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, nullptr));
   ASSERT_TRUE(offer.get() != nullptr);
   ContentInfo* dc_offer = offer->GetContentByName("data");
@@ -1182,20 +1087,19 @@
   MediaSessionOptions opts;
 
   // Creates a data only offer.
-  AddDataSection(cricket::DCT_SCTP, cricket::MD_SENDRECV, &opts);
+  opts.recv_audio = false;
+  opts.data_channel_type = cricket::DCT_SCTP;
   std::unique_ptr<SessionDescription> offer1(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer1.get() != NULL);
 
   // Appends audio to the offer.
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
-                  &opts);
+  opts.recv_audio = true;
   std::unique_ptr<SessionDescription> offer2(
       f1_.CreateOffer(opts, offer1.get()));
   ASSERT_TRUE(offer2.get() != NULL);
 
   // Appends video to the offer.
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
-                  &opts);
+  opts.recv_video = true;
   std::unique_ptr<SessionDescription> offer3(
       f1_.CreateOffer(opts, offer2.get()));
   ASSERT_TRUE(offer3.get() != NULL);
@@ -1240,7 +1144,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        CreateDataAnswerToOfferWithUnknownProtocol) {
   MediaSessionOptions opts;
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
+  opts.data_channel_type = cricket::DCT_RTP;
+  opts.recv_audio = false;
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
@@ -1266,7 +1171,7 @@
 
 // Test that the media protocol is RTP/AVPF if DTLS and SDES are disabled.
 TEST_F(MediaSessionDescriptionFactoryTest, AudioOfferAnswerWithCryptoDisabled) {
-  MediaSessionOptions opts = CreatePlanBMediaSessionOptions();
+  MediaSessionOptions opts;
   f1_.set_secure(SEC_DISABLED);
   f2_.set_secure(SEC_DISABLED);
   tdf1_.set_secure(SEC_DISABLED);
@@ -1295,7 +1200,8 @@
 // matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestOfferAnswerWithRtpExtensions) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  opts.recv_video = true;
+
   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));
@@ -1323,7 +1229,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestOfferAnswerWithEncryptedRtpExtensionsBoth) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  opts.recv_video = true;
 
   f1_.set_enable_encrypted_rtp_header_extensions(true);
   f2_.set_enable_encrypted_rtp_header_extensions(true);
@@ -1359,7 +1265,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestOfferAnswerWithEncryptedRtpExtensionsOffer) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  opts.recv_video = true;
 
   f1_.set_enable_encrypted_rtp_header_extensions(true);
 
@@ -1394,7 +1300,7 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestOfferAnswerWithEncryptedRtpExtensionsAnswer) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  opts.recv_video = true;
 
   f2_.set_enable_encrypted_rtp_header_extensions(true);
 
@@ -1430,8 +1336,10 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestCreateAnswerWithoutLegacyStreams) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
+  opts.recv_video = true;
+  opts.data_channel_type = cricket::DCT_RTP;
+  f1_.set_add_legacy_streams(false);
+  f2_.set_add_legacy_streams(false);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   std::unique_ptr<SessionDescription> answer(
@@ -1455,8 +1363,8 @@
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestPartial) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
+  opts.recv_video = true;
+  opts.data_channel_type = cricket::DCT_RTP;
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
@@ -1492,18 +1400,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;
-  AddAudioVideoSections(cricket::MD_SENDRECV, &answer_opts);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_SENDRECV, &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;
 
   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()));
@@ -1521,6 +1429,7 @@
 
   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()));
@@ -1538,6 +1447,7 @@
 
   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()));
@@ -1555,6 +1465,7 @@
 
   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()));
@@ -1574,16 +1485,11 @@
 // Create an audio-only answer to a video offer.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerToVideo) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
-                  &opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
-                  &opts);
+  opts.recv_video = true;
   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(), opts, NULL));
+      f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL));
   const ContentInfo* ac = answer->GetContentByName("audio");
   const ContentInfo* vc = answer->GetContentByName("video");
   ASSERT_TRUE(ac != NULL);
@@ -1594,16 +1500,12 @@
 
 // Create an audio-only answer to an offer with data.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateNoDataAnswerToDataOffer) {
-  MediaSessionOptions opts = CreatePlanBMediaSessionOptions();
+  MediaSessionOptions opts;
   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(), opts, NULL));
+      f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL));
   const ContentInfo* ac = answer->GetContentByName("audio");
   const ContentInfo* dc = answer->GetContentByName("data");
   ASSERT_TRUE(ac != NULL);
@@ -1616,8 +1518,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        CreateAnswerToOfferWithRejectedMedia) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &opts);
+  opts.recv_video = true;
+  opts.data_channel_type = cricket::DCT_RTP;
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   ContentInfo* ac = offer->GetContentByName("audio");
@@ -1650,19 +1552,12 @@
 // adding a new video track and replaces one of the audio tracks.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoOffer) {
   MediaSessionOptions opts;
-  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);
+  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);
 
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
@@ -1727,16 +1622,14 @@
   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.
-  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);
+  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);
   std::unique_ptr<SessionDescription> updated_offer(
       f1_.CreateOffer(opts, offer.get()));
 
@@ -1798,13 +1691,8 @@
 // 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;
-  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
-                             kMediaStream1, num_sim_layers, &opts);
+  opts.AddSendVideoStream(kVideoTrack1, kMediaStream1, num_sim_layers);
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
 
   ASSERT_TRUE(offer.get() != NULL);
@@ -1830,39 +1718,22 @@
 // adding a new video track and removes one of the audio tracks.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoAnswer) {
   MediaSessionOptions offer_opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
-                  &offer_opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
-                  &offer_opts);
+  offer_opts.recv_video = true;
   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 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;
+  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);
 
   std::unique_ptr<SessionDescription> answer(
-      f2_.CreateAnswer(offer.get(), answer_opts, NULL));
+      f2_.CreateAnswer(offer.get(), opts, NULL));
 
   ASSERT_TRUE(answer.get() != NULL);
   const ContentInfo* ac = answer->GetContentByName("audio");
@@ -1926,12 +1797,11 @@
 
   // Update the answer. Add a new video track that is not synched to the
   // other tracks and remove 1 audio track.
-  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack2,
-                             kMediaStream2, 1, &answer_opts);
-  DetachSenderFromMediaSection("audio", kAudioTrack2, &answer_opts);
-  DetachSenderFromMediaSection("data", kDataTrack2, &answer_opts);
+  opts.AddSendStream(MEDIA_TYPE_VIDEO, kVideoTrack2, kMediaStream2);
+  opts.RemoveSendStream(MEDIA_TYPE_AUDIO, kAudioTrack2);
+  opts.RemoveSendStream(MEDIA_TYPE_DATA, kDataTrack2);
   std::unique_ptr<SessionDescription> updated_answer(
-      f2_.CreateAnswer(offer.get(), answer_opts, answer.get()));
+      f2_.CreateAnswer(offer.get(), opts, answer.get()));
 
   ASSERT_TRUE(updated_answer.get() != NULL);
   ac = updated_answer->GetContentByName("audio");
@@ -1983,7 +1853,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        RespondentCreatesOfferAfterCreatingAnswer) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  opts.recv_audio = true;
+  opts.recv_video = true;
 
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   std::unique_ptr<SessionDescription> answer(
@@ -2034,8 +1905,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        RespondentCreatesOfferAfterCreatingAnswerWithRtx) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
-                  &opts);
+  opts.recv_video = true;
+  opts.recv_audio = false;
   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);
@@ -2087,8 +1958,8 @@
   f1_.set_video_codecs(f1_codecs);
 
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
-                  &opts);
+  opts.recv_audio = true;
+  opts.recv_video = false;
 
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
   std::unique_ptr<SessionDescription> answer(
@@ -2101,8 +1972,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.media_description_options.clear();
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  opts.recv_audio = true;
+  opts.recv_video = true;
 
   std::vector<VideoCodec> f2_codecs = MAKE_VECTOR(kVideoCodecs2);
   int used_pl_type = acd->codecs()[0].id;
@@ -2139,7 +2010,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        RespondentCreatesOfferWithRtxAfterCreatingAnswerWithoutRtx) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  opts.recv_video = true;
+  opts.recv_audio = true;
 
   std::vector<VideoCodec> f2_codecs = MAKE_VECTOR(kVideoCodecs2);
   // This creates rtx for H264 with the payload type |f2_| uses.
@@ -2177,8 +2049,8 @@
 // Test that RTX is ignored when there is no associated payload type parameter.
 TEST_F(MediaSessionDescriptionFactoryTest, RtxWithoutApt) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
-                  &opts);
+  opts.recv_video = true;
+  opts.recv_audio = false;
   std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
   // This creates RTX without associated payload type parameter.
   AddRtxCodec(VideoCodec(126, cricket::kRtxCodecName), &f1_codecs);
@@ -2221,8 +2093,8 @@
 // type doesn't match the local value.
 TEST_F(MediaSessionDescriptionFactoryTest, FilterOutRtxIfAptDoesntMatch) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
-                  &opts);
+  opts.recv_video = true;
+  opts.recv_audio = false;
   std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
   // This creates RTX for H264 in sender.
   AddRtxCodec(VideoCodec::CreateRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs);
@@ -2251,8 +2123,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        FilterOutUnsupportedRtxWhenCreatingAnswer) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
-                  &opts);
+  opts.recv_video = true;
+  opts.recv_audio = false;
   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);
@@ -2286,8 +2158,8 @@
 // to add another.
 TEST_F(MediaSessionDescriptionFactoryTest, AddSecondRtxInNewOffer) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_RECVONLY, kActive,
-                  &opts);
+  opts.recv_video = true;
+  opts.recv_audio = false;
   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);
@@ -2321,11 +2193,11 @@
 // generated for each simulcast ssrc and correctly grouped.
 TEST_F(MediaSessionDescriptionFactoryTest, SimSsrcsGenerateMultipleRtxSsrcs) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive,
-                  &opts);
+  opts.recv_video = true;
+  opts.recv_audio = false;
+
   // Add simulcast streams.
-  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, "stream1",
-                             "stream1label", 3, &opts);
+  opts.AddSendVideoStream("stream1", "stream1label", 3);
 
   // Use a single real codec, and then add RTX for it.
   std::vector<VideoCodec> f1_codecs;
@@ -2362,11 +2234,11 @@
 // together with a FEC-FR grouping.
 TEST_F(MediaSessionDescriptionFactoryTest, GenerateFlexfecSsrc) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive,
-                  &opts);
+  opts.recv_video = true;
+  opts.recv_audio = false;
+
   // Add single stream.
-  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, "stream1",
-                             "stream1label", 1, &opts);
+  opts.AddSendVideoStream("stream1", "stream1label", 1);
 
   // Use a single real codec, and then add FlexFEC for it.
   std::vector<VideoCodec> f1_codecs;
@@ -2402,11 +2274,11 @@
 // multiple FlexfecSenders, or through multistream protection.
 TEST_F(MediaSessionDescriptionFactoryTest, SimSsrcsGenerateNoFlexfecSsrcs) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", cricket::MD_SENDRECV, kActive,
-                  &opts);
+  opts.recv_video = true;
+  opts.recv_audio = false;
+
   // Add simulcast streams.
-  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, "stream1",
-                             "stream1label", 3, &opts);
+  opts.AddSendVideoStream("stream1", "stream1label", 3);
 
   // Use a single real codec, and then add FlexFEC for it.
   std::vector<VideoCodec> f1_codecs;
@@ -2446,7 +2318,8 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        RespondentCreatesOfferAfterCreatingAnswerWithRtpExtensions) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  opts.recv_audio = true;
+  opts.recv_video = true;
 
   f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension1));
   f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension1));
@@ -2500,7 +2373,8 @@
 // updated offer (this was previously a bug).
 TEST_F(MediaSessionDescriptionFactoryTest, RtpExtensionIdReused) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  opts.recv_audio = true;
+  opts.recv_video = true;
 
   f1_.set_audio_rtp_header_extensions(MAKE_VECTOR(kAudioRtpExtension3));
   f1_.set_video_rtp_header_extensions(MAKE_VECTOR(kVideoRtpExtension3));
@@ -2535,7 +2409,8 @@
 // Same as "RtpExtensionIdReused" above for encrypted RTP extensions.
 TEST_F(MediaSessionDescriptionFactoryTest, RtpExtensionIdReusedEncrypted) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  opts.recv_audio = true;
+  opts.recv_video = true;
 
   f1_.set_enable_encrypted_rtp_header_extensions(true);
   f2_.set_enable_encrypted_rtp_header_extensions(true);
@@ -2611,47 +2486,45 @@
 // ensure the TransportInfo in the SessionDescription matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferAudio) {
   MediaSessionOptions options;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
-                  &options);
+  options.recv_audio = true;
   TestTransportInfo(true, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestTransportInfoOfferIceRenomination) {
   MediaSessionOptions options;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
-                  &options);
-  options.media_description_options[0]
-      .transport_options.enable_ice_renomination = true;
+  options.enable_ice_renomination = true;
   TestTransportInfo(true, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferAudioCurrent) {
   MediaSessionOptions options;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
-                  &options);
+  options.recv_audio = true;
   TestTransportInfo(true, options, true);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferMultimedia) {
   MediaSessionOptions options;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
+  options.recv_audio = true;
+  options.recv_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
   TestTransportInfo(true, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestTransportInfoOfferMultimediaCurrent) {
   MediaSessionOptions options;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
+  options.recv_audio = true;
+  options.recv_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
   TestTransportInfo(true, options, true);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferBundle) {
   MediaSessionOptions options;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
+  options.recv_audio = true;
+  options.recv_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
   options.bundle_enabled = true;
   TestTransportInfo(true, options, false);
 }
@@ -2659,56 +2532,55 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestTransportInfoOfferBundleCurrent) {
   MediaSessionOptions options;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
+  options.recv_audio = true;
+  options.recv_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
   options.bundle_enabled = true;
   TestTransportInfo(true, options, true);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerAudio) {
   MediaSessionOptions options;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
-                  &options);
+  options.recv_audio = true;
   TestTransportInfo(false, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestTransportInfoAnswerIceRenomination) {
   MediaSessionOptions options;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
-                  &options);
-  options.media_description_options[0]
-      .transport_options.enable_ice_renomination = true;
+  options.enable_ice_renomination = true;
   TestTransportInfo(false, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestTransportInfoAnswerAudioCurrent) {
   MediaSessionOptions options;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", cricket::MD_RECVONLY, kActive,
-                  &options);
+  options.recv_audio = true;
   TestTransportInfo(false, options, true);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerMultimedia) {
   MediaSessionOptions options;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
+  options.recv_audio = true;
+  options.recv_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
   TestTransportInfo(false, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestTransportInfoAnswerMultimediaCurrent) {
   MediaSessionOptions options;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
+  options.recv_audio = true;
+  options.recv_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
   TestTransportInfo(false, options, true);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerBundle) {
   MediaSessionOptions options;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
+  options.recv_audio = true;
+  options.recv_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
   options.bundle_enabled = true;
   TestTransportInfo(false, options, false);
 }
@@ -2716,8 +2588,9 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
     TestTransportInfoAnswerBundleCurrent) {
   MediaSessionOptions options;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
+  options.recv_audio = true;
+  options.recv_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
   options.bundle_enabled = true;
   TestTransportInfo(false, options, true);
 }
@@ -2744,7 +2617,7 @@
   tdf2_.set_secure(SEC_DISABLED);
 
   std::unique_ptr<SessionDescription> offer(
-      f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL));
+      f1_.CreateOffer(MediaSessionOptions(), NULL));
   ASSERT_TRUE(offer.get() != NULL);
   ContentInfo* offer_content = offer->GetContentByName("audio");
   ASSERT_TRUE(offer_content != NULL);
@@ -2753,7 +2626,7 @@
   offer_audio_desc->set_protocol(cricket::kMediaProtocolDtlsSavpf);
 
   std::unique_ptr<SessionDescription> answer(
-      f2_.CreateAnswer(offer.get(), CreatePlanBMediaSessionOptions(), NULL));
+      f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL));
   ASSERT_TRUE(answer != NULL);
   ContentInfo* answer_content = answer->GetContentByName("audio");
   ASSERT_TRUE(answer_content != NULL);
@@ -2770,7 +2643,7 @@
   tdf2_.set_secure(SEC_ENABLED);
 
   std::unique_ptr<SessionDescription> offer(
-      f1_.CreateOffer(CreatePlanBMediaSessionOptions(), NULL));
+      f1_.CreateOffer(MediaSessionOptions(), NULL));
   ASSERT_TRUE(offer.get() != NULL);
   ContentInfo* offer_content = offer->GetContentByName("audio");
   ASSERT_TRUE(offer_content != NULL);
@@ -2779,7 +2652,7 @@
   offer_audio_desc->set_protocol(cricket::kMediaProtocolDtlsSavpf);
 
   std::unique_ptr<SessionDescription> answer(
-      f2_.CreateAnswer(offer.get(), CreatePlanBMediaSessionOptions(), NULL));
+      f2_.CreateAnswer(offer.get(), MediaSessionOptions(), NULL));
   ASSERT_TRUE(answer != NULL);
 
   const ContentInfo* answer_content = answer->GetContentByName("audio");
@@ -2800,7 +2673,8 @@
   tdf1_.set_secure(SEC_ENABLED);
   tdf2_.set_secure(SEC_DISABLED);
   MediaSessionOptions options;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
+  options.recv_audio = true;
+  options.recv_video = true;
   std::unique_ptr<SessionDescription> offer, answer;
   const cricket::MediaContentDescription* audio_media_desc;
   const cricket::MediaContentDescription* video_media_desc;
@@ -2896,7 +2770,7 @@
 // Test that an answer can't be created if cryptos are required but the offer is
 // unsecure.
 TEST_F(MediaSessionDescriptionFactoryTest, TestSecureAnswerToUnsecureOffer) {
-  MediaSessionOptions options = CreatePlanBMediaSessionOptions();
+  MediaSessionOptions options;
   f1_.set_secure(SEC_DISABLED);
   tdf1_.set_secure(SEC_DISABLED);
   f2_.set_secure(SEC_REQUIRED);
@@ -2917,8 +2791,9 @@
   tdf1_.set_secure(SEC_ENABLED);
   tdf2_.set_secure(SEC_ENABLED);
   MediaSessionOptions options;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
-  AddDataSection(cricket::DCT_RTP, cricket::MD_RECVONLY, &options);
+  options.recv_audio = true;
+  options.recv_video = true;
+  options.data_channel_type = cricket::DCT_RTP;
 
   std::unique_ptr<SessionDescription> offer, answer;
 
@@ -2965,7 +2840,8 @@
 // offer or answer.
 TEST_F(MediaSessionDescriptionFactoryTest, TestVADEnableOption) {
   MediaSessionOptions options;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &options);
+  options.recv_audio = true;
+  options.recv_video = true;
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(options, NULL));
   ASSERT_TRUE(offer.get() != NULL);
   const ContentInfo* audio_content = offer->GetContentByName("audio");
@@ -2983,21 +2859,22 @@
   EXPECT_TRUE(VerifyNoCNCodecs(audio_content));
 }
 
-// Test that the generated MIDs match the existing offer.
-TEST_F(MediaSessionDescriptionFactoryTest, TestMIDsMatchesExistingOffer) {
+// Test that the content name ("mid" in SDP) is unchanged when creating a
+// new offer.
+TEST_F(MediaSessionDescriptionFactoryTest,
+       TestContentNameNotChangedInSubsequentOffers) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio_modified", cricket::MD_RECVONLY,
-                  kActive, &opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video_modified", cricket::MD_RECVONLY,
-                  kActive, &opts);
+  opts.recv_audio = true;
+  opts.recv_video = true;
   opts.data_channel_type = cricket::DCT_SCTP;
-  AddMediaSection(MEDIA_TYPE_DATA, "data_modified", cricket::MD_SENDRECV,
-                  kActive, &opts);
-  // Create offer.
+  // Create offer and modify the default content names.
   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());
@@ -3009,340 +2886,6 @@
   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_) {
@@ -3373,7 +2916,7 @@
 
 TEST_P(MediaProtocolTest, TestAudioVideoAcceptance) {
   MediaSessionOptions opts;
-  AddAudioVideoSections(cricket::MD_RECVONLY, &opts);
+  opts.recv_video = true;
   std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, nullptr));
   ASSERT_TRUE(offer.get() != nullptr);
   // Set the protocol for all the contents.
@@ -3433,47 +2976,32 @@
 
   // Test proper merge
   sf.set_audio_codecs(send_codecs, recv_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());
+  EXPECT_TRUE(sf.audio_send_codecs() == send_codecs);
+  EXPECT_TRUE(sf.audio_recv_codecs() == recv_codecs);
+  EXPECT_TRUE(sf.audio_sendrecv_codecs() == sendrecv_codecs);
 
   // Test empty send codecs list
   sf.set_audio_codecs(no_codecs, recv_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());
+  EXPECT_TRUE(sf.audio_send_codecs() == no_codecs);
+  EXPECT_TRUE(sf.audio_recv_codecs() == recv_codecs);
+  EXPECT_TRUE(sf.audio_sendrecv_codecs() == no_codecs);
 
   // Test empty recv codecs list
   sf.set_audio_codecs(send_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());
+  EXPECT_TRUE(sf.audio_send_codecs() == send_codecs);
+  EXPECT_TRUE(sf.audio_recv_codecs() == no_codecs);
+  EXPECT_TRUE(sf.audio_sendrecv_codecs() == no_codecs);
 
   // Test all empty codec lists
   sf.set_audio_codecs(no_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());
+  EXPECT_TRUE(sf.audio_send_codecs() == no_codecs);
+  EXPECT_TRUE(sf.audio_recv_codecs() == no_codecs);
+  EXPECT_TRUE(sf.audio_sendrecv_codecs() == no_codecs);
 }
 
 namespace {
-// 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) {
+void TestAudioCodecsOffer(MediaContentDirection direction,
+                          bool add_legacy_stream) {
   TransportDescriptionFactory tdf;
   MediaSessionDescriptionFactory sf(&tdf);
   const std::vector<AudioCodec> send_codecs = MAKE_VECTOR(kAudioCodecs1);
@@ -3481,56 +3009,57 @@
   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;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", direction, kActive, &opts);
-
-  if (RtpTransceiverDirection::FromMediaContentDirection(direction).send) {
-    AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
-                               kMediaStream1, 1, &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);
 
   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(CodecsMatch<AudioCodec>(send_codecs, acd->codecs()));
+      EXPECT_TRUE(acd->codecs() == send_codecs);
     } else if (acd->direction() == cricket::MD_RECVONLY) {
-      EXPECT_TRUE(CodecsMatch<AudioCodec>(recv_codecs, acd->codecs()));
+      EXPECT_TRUE(acd->codecs() == recv_codecs);
     } else {
-      EXPECT_TRUE(CodecsMatch<AudioCodec>(sendrecv_codecs, acd->codecs()));
+      EXPECT_TRUE(acd->codecs() == sendrecv_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
@@ -3543,18 +3072,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]) {
@@ -3580,14 +3109,17 @@
       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;
-  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);
+  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);
   }
 
   std::unique_ptr<SessionDescription> offer(
@@ -3595,27 +3127,27 @@
   ASSERT_TRUE(offer.get() != NULL);
 
   MediaSessionOptions answer_opts;
-  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);
+  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);
   }
   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.
@@ -3625,20 +3157,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);
@@ -3646,7 +3178,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 << "{";
@@ -3667,31 +3199,36 @@
         << "; 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<MediaContentDirection> {};
+    : public ::testing::TestWithParam<::testing::tuple<MediaContentDirection,
+                                                       bool>> {
+};
 
 TEST_P(AudioCodecsOfferTest, TestCodecsInOffer) {
-  TestAudioCodecsOffer(GetParam());
+  TestAudioCodecsOffer(::testing::get<0>(GetParam()),
+                       ::testing::get<1>(GetParam()));
 }
 
 INSTANTIATE_TEST_CASE_P(MediaSessionDescriptionFactoryTest,
                         AudioCodecsOfferTest,
-                        ::testing::Values(cricket::MD_SENDONLY,
-                                          cricket::MD_RECVONLY,
-                                          cricket::MD_SENDRECV,
-                                          cricket::MD_INACTIVE));
+                        ::testing::Combine(
+                             ::testing::Values(cricket::MD_SENDONLY,
+                                               cricket::MD_RECVONLY,
+                                               cricket::MD_SENDRECV,
+                                               cricket::MD_INACTIVE),
+                             ::testing::Bool()));
 
 class AudioCodecsAnswerTest
     : public ::testing::TestWithParam<::testing::tuple<MediaContentDirection,
                                                        MediaContentDirection,
-                                                       bool>> {};
+                                                       bool>> {
+};
 
 TEST_P(AudioCodecsAnswerTest, TestCodecsInAnswer) {
   TestAudioCodecsAnswer(::testing::get<0>(GetParam()),
@@ -3699,15 +3236,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 d975ed9..3af5667 100644
--- a/webrtc/pc/peerconnection.cc
+++ b/webrtc/pc/peerconnection.cc
@@ -133,45 +133,32 @@
          (value <= Options::kMaxOfferToReceiveMedia);
 }
 
-// Add options to |[audio/video]_media_description_options| from |senders|.
-void AddRtpSenderOptions(
+// Add the stream and RTP data channel info to |session_options|.
+void AddSendStreams(
+    cricket::MediaSessionOptions* session_options,
     const std::vector<rtc::scoped_refptr<
         RtpSenderProxyWithInternal<RtpSenderInternal>>>& senders,
-    cricket::MediaDescriptionOptions* audio_media_description_options,
-    cricket::MediaDescriptionOptions* video_media_description_options) {
-  for (const auto& sender : senders) {
-    if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) {
-      if (audio_media_description_options) {
-        audio_media_description_options->AddAudioSender(
-            sender->id(), sender->internal()->stream_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;
+        rtp_data_channels) {
+  session_options->streams.clear();
+  for (const auto& sender : senders) {
+    session_options->AddSendStream(sender->media_type(), sender->id(),
+                                   sender->internal()->stream_id());
   }
+
   // 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) {
-      // 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());
+      // |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);
     }
   }
 }
@@ -327,62 +314,92 @@
   return cname;
 }
 
-bool ValidateOfferAnswerOptions(
-    const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options) {
-  return IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_audio) &&
-         IsValidOfferToReceiveMedia(rtc_options.offer_to_receive_video);
-}
-
-// 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(
+bool ExtractMediaSessionOptions(
     const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
+    bool is_offer,
     cricket::MediaSessionOptions* session_options) {
-  session_options->vad_enabled = rtc_options.voice_activity_detection;
-  session_options->bundle_enabled = rtc_options.use_rtp_mux;
-}
-
-bool ConvertConstraintsToOfferAnswerOptions(
-    const MediaConstraintsInterface* constraints,
-    PeerConnectionInterface::RTCOfferAnswerOptions* offer_answer_options) {
-  if (!constraints) {
-    return true;
+  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 ParseConstraintsForAnswer(const MediaConstraintsInterface* constraints,
+                               cricket::MediaSessionOptions* session_options) {
   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;
+  // kOfferToReceiveAudio defaults to true according to spec.
+  if (!FindConstraint(constraints,
+                      MediaConstraintsInterface::kOfferToReceiveAudio, &value,
+                      &mandatory_constraints_satisfied) ||
+      value) {
+    session_options->recv_audio = true;
   }
 
-  if (FindConstraint(constraints,
-                     MediaConstraintsInterface::kOfferToReceiveVideo, &value,
-                     &mandatory_constraints_satisfied)) {
-    offer_answer_options->offer_to_receive_video =
-        value ? PeerConnectionInterface::RTCOfferAnswerOptions::
-                    kOfferToReceiveMediaTrue
-              : 0;
+  // 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)) {
-    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;
+    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;
+  }
+
+  if (!constraints) {
+    return true;
+  }
   return mandatory_constraints_satisfied == constraints->GetMandatory().size();
 }
 
@@ -825,17 +842,49 @@
     LOG(LS_ERROR) << "CreateOffer - observer is NULL.";
     return;
   }
-  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);
+  RTCOfferAnswerOptions options;
 
-  CreateOffer(observer, 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);
 }
 
 void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer,
@@ -846,15 +895,14 @@
     return;
   }
 
-  if (!ValidateOfferAnswerOptions(options)) {
+  cricket::MediaSessionOptions session_options;
+  if (!GetOptionsForOffer(options, &session_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);
 }
 
@@ -867,26 +915,14 @@
     return;
   }
 
-  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)) {
+  cricket::MediaSessionOptions session_options;
+  if (!GetOptionsForAnswer(constraints, &session_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);
 }
 
@@ -899,7 +935,12 @@
   }
 
   cricket::MediaSessionOptions session_options;
-  GetOptionsForAnswer(options, &session_options);
+  if (!GetOptionsForAnswer(options, &session_options)) {
+    std::string error = "CreateAnswer called with invalid options.";
+    LOG(LS_ERROR) << error;
+    PostCreateSessionDescriptionFailure(observer, error);
+    return;
+  }
 
   session_->CreateAnswer(observer, session_options);
 }
@@ -1657,242 +1698,121 @@
                            MSG_CREATE_SESSIONDESCRIPTION_FAILED, msg);
 }
 
-void PeerConnection::GetOptionsForOffer(
+bool PeerConnection::GetOptionsForOffer(
     const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
     cricket::MediaSessionOptions* session_options) {
-  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.
+  // TODO(deadbeef): Once we have transceivers, enumerate them here instead of
+  // ContentInfos.
   if (session_->local_description()) {
-    GenerateMediaDescriptionOptions(
-        session_->local_description(),
-        cricket::RtpTransceiverDirection(send_audio, recv_audio),
-        cricket::RtpTransceiverDirection(send_video, recv_video), &audio_index,
-        &video_index, &data_index, session_options);
+    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;
   }
 
-  // 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;
+  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();
   }
-  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 (rtc_options.offer_to_receive_video == RTCOfferAnswerOptions::kUndefined) {
+    session_options->recv_video =
+        session_options->HasSendMediaStream(cricket::MEDIA_TYPE_VIDEO) ||
+        !remote_video_tracks_.empty();
   }
-  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 (!rtp_data_channels_.empty() ||
-      session_->data_channel_type() != cricket::DCT_RTP) {
+  if (HasDataChannels() && 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::GetOptionsForAnswer(
-    const RTCOfferAnswerOptions& rtc_options,
+void PeerConnection::InitializeOptionsForAnswer(
     cricket::MediaSessionOptions* session_options) {
-  ExtractSharedMediaSessionOptions(rtc_options, session_options);
+  session_options->recv_audio = false;
+  session_options->recv_video = false;
+  session_options->enable_ice_renomination =
+      configuration_.enable_ice_renomination;
+}
 
-  // 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);
+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();
+    }
   }
-  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);
-
+  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.
   // 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 (!rtp_data_channels_.empty() ||
-      session_->data_channel_type() != cricket::DCT_RTP) {
+  if (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;
 }
 
-void PeerConnection::GenerateMediaDescriptionOptions(
-    const SessionDescriptionInterface* session_desc,
-    cricket::RtpTransceiverDirection audio_direction,
-    cricket::RtpTransceiverDirection video_direction,
-    int* audio_index,
-    int* video_index,
-    int* data_index,
+bool PeerConnection::GetOptionsForAnswer(
+    const MediaConstraintsInterface* constraints,
     cricket::MediaSessionOptions* session_options) {
-  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;
-      }
-    }
+  InitializeOptionsForAnswer(session_options);
+  if (!ParseConstraintsForAnswer(constraints, session_options)) {
+    return false;
   }
+  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) {
@@ -2365,15 +2285,6 @@
   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 48e9628..f8b6a54 100644
--- a/webrtc/pc/peerconnection.h
+++ b/webrtc/pc/peerconnection.h
@@ -32,12 +32,28 @@
 class VideoRtpReceiver;
 class RtcEventLog;
 
-// TODO(zhihuang): Remove this declaration when the WebRtcSession tests don't
-// need it.
-void ExtractSharedMediaSessionOptions(
+// 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(
     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,
@@ -228,24 +244,26 @@
 
   // Returns a MediaSessionOptions struct with options decided by |options|,
   // the local MediaStreams and DataChannels.
-  void GetOptionsForOffer(
+  virtual bool GetOptionsForOffer(
       const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
       cricket::MediaSessionOptions* session_options);
 
   // Returns a MediaSessionOptions struct with options decided by
   // |constraints|, the local MediaStreams and DataChannels.
-  void GetOptionsForAnswer(const RTCOfferAnswerOptions& options,
-                           cricket::MediaSessionOptions* session_options);
+  // 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);
 
-  // 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,
+  void InitializeOptionsForAnswer(
+      cricket::MediaSessionOptions* session_options);
+
+  // Helper function for options processing.
+  // Deprecated.
+  virtual void FinishOptionsForAnswer(
       cricket::MediaSessionOptions* session_options);
 
   // Remove all local and remote tracks of type |media_type|.
@@ -343,7 +361,6 @@
   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 892615a..0e52c20 100644
--- a/webrtc/pc/peerconnectioninterface_unittest.cc
+++ b/webrtc/pc/peerconnectioninterface_unittest.cc
@@ -1184,57 +1184,6 @@
     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;
@@ -3545,224 +3494,6 @@
   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 {
@@ -3771,7 +3502,8 @@
   }
   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());
@@ -3853,6 +3585,177 @@
   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 bb1c877..f6d334e 100644
--- a/webrtc/pc/webrtcsession_unittest.cc
+++ b/webrtc/pc/webrtcsession_unittest.cc
@@ -143,9 +143,6 @@
 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 {
@@ -401,6 +398,7 @@
     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);
   }
 
@@ -509,215 +507,107 @@
     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;
-    local_send_audio_ = true;
-    local_send_video_ = true;
-    remote_send_audio_ = true;
-    remote_send_video_ = true;
+    send_audio_ = true;
+    send_video_ = true;
   }
 
   void SendAudioVideoStream2() {
     send_stream_1_ = false;
     send_stream_2_ = true;
-    local_send_audio_ = true;
-    local_send_video_ = true;
-    remote_send_audio_ = true;
-    remote_send_video_ = true;
+    send_audio_ = true;
+    send_video_ = true;
   }
 
   void SendAudioVideoStream1And2() {
     send_stream_1_ = true;
     send_stream_2_ = true;
-    local_send_audio_ = true;
-    local_send_video_ = true;
-    remote_send_audio_ = true;
-    remote_send_video_ = true;
+    send_audio_ = true;
+    send_video_ = true;
   }
 
   void SendNothing() {
     send_stream_1_ = false;
     send_stream_2_ = false;
-    local_send_audio_ = false;
-    local_send_video_ = false;
-    remote_send_audio_ = false;
-    remote_send_video_ = false;
+    send_audio_ = false;
+    send_video_ = false;
   }
 
   void SendAudioOnlyStream2() {
     send_stream_1_ = false;
     send_stream_2_ = true;
-    local_send_audio_ = true;
-    local_send_video_ = false;
-    remote_send_audio_ = true;
-    remote_send_video_ = false;
+    send_audio_ = true;
+    send_video_ = false;
   }
 
   void SendVideoOnlyStream2() {
     send_stream_1_ = false;
     send_stream_2_ = true;
-    local_send_audio_ = false;
-    local_send_video_ = true;
-    remote_send_audio_ = false;
-    remote_send_video_ = true;
+    send_audio_ = false;
+    send_video_ = true;
   }
 
-  // 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();
-      }
+  void AddStreamsToOptions(cricket::MediaSessionOptions* session_options) {
+    if (send_stream_1_ && send_audio_) {
+      session_options->AddSendStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack1,
+                                     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_1_ && send_video_) {
+      session_options->AddSendStream(cricket::MEDIA_TYPE_VIDEO, kVideoTrack1,
+                                     kStream1);
     }
-
-    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_audio_) {
+      session_options->AddSendStream(cricket::MEDIA_TYPE_AUDIO, kAudioTrack2,
+                                     kStream2);
     }
-
-    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);
+    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());
     }
   }
 
   void GetOptionsForOffer(
       const PeerConnectionInterface::RTCOfferAnswerOptions& rtc_options,
       cricket::MediaSessionOptions* session_options) {
-    ExtractSharedMediaSessionOptions(rtc_options, session_options);
+    ASSERT_TRUE(ExtractMediaSessionOptions(rtc_options, true, session_options));
 
-    // |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);
+    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);
+    }
     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_);
+    // ParseConstraintsForAnswer is used to set some defaults.
+    ASSERT_TRUE(webrtc::ParseConstraintsForAnswer(nullptr, session_options));
 
+    AddStreamsToOptions(session_options);
     session_options->bundle_enabled =
         session_options->bundle_enabled &&
         (session_options->has_audio() || session_options->has_video() ||
@@ -730,50 +620,6 @@
     session_options->crypto_options = crypto_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;
-
-    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() ||
-         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 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.
@@ -789,6 +635,7 @@
     PeerConnectionInterface::RTCOfferAnswerOptions options;
     options.offer_to_receive_audio =
         RTCOfferAnswerOptions::kOfferToReceiveMediaTrue;
+
     return CreateOffer(options);
   }
 
@@ -811,6 +658,9 @@
         = 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,
@@ -820,7 +670,8 @@
 
   SessionDescriptionInterface* CreateAnswer() {
     cricket::MediaSessionOptions options;
-    options.bundle_enabled = true;
+    options.recv_video = true;
+    options.recv_audio = true;
     return CreateAnswer(options);
   }
 
@@ -938,7 +789,7 @@
   void VerifyAnswerFromNonCryptoOffer() {
     // Create an SDP without Crypto.
     cricket::MediaSessionOptions options;
-    GetOptionsForRemoteOffer(&options);
+    options.recv_video = true;
     JsepSessionDescription* offer(
         CreateRemoteOffer(options, cricket::SEC_DISABLED));
     ASSERT_TRUE(offer != NULL);
@@ -952,7 +803,7 @@
 
   void VerifyAnswerFromCryptoOffer() {
     cricket::MediaSessionOptions options;
-    GetOptionsForRemoteOffer(&options);
+    options.recv_video = true;
     options.bundle_enabled = true;
     std::unique_ptr<JsepSessionDescription> offer(
         CreateRemoteOffer(options, cricket::SEC_REQUIRED));
@@ -1075,7 +926,7 @@
     SetLocalDescriptionWithoutError(answer);
   }
   void SetLocalDescriptionWithoutError(SessionDescriptionInterface* desc) {
-    ASSERT_TRUE(session_->SetLocalDescription(desc, nullptr));
+    EXPECT_TRUE(session_->SetLocalDescription(desc, NULL));
     session_->MaybeStartGathering();
   }
   void SetLocalDescriptionExpectState(SessionDescriptionInterface* desc,
@@ -1104,7 +955,7 @@
                                    expected_error, desc);
   }
   void SetRemoteDescriptionWithoutError(SessionDescriptionInterface* desc) {
-    ASSERT_TRUE(session_->SetRemoteDescription(desc, nullptr));
+    EXPECT_TRUE(session_->SetRemoteDescription(desc, NULL));
   }
   void SetRemoteDescriptionExpectState(SessionDescriptionInterface* desc,
                                        WebRtcSession::State expected_state) {
@@ -1141,26 +992,21 @@
       SessionDescriptionInterface** nocrypto_answer) {
     // Create a SDP without Crypto.
     cricket::MediaSessionOptions options;
-    GetOptionsForRemoteOffer(&options);
+    options.recv_video = true;
     options.bundle_enabled = true;
     *offer = CreateRemoteOffer(options, cricket::SEC_ENABLED);
     ASSERT_TRUE(*offer != NULL);
     VerifyCryptoParams((*offer)->description());
 
-    cricket::MediaSessionOptions answer_options;
-    GetOptionsForRemoteAnswer(&answer_options);
-    *nocrypto_answer =
-        CreateRemoteAnswer(*offer, answer_options, cricket::SEC_DISABLED);
+    *nocrypto_answer = CreateRemoteAnswer(*offer, options,
+                                          cricket::SEC_DISABLED);
     EXPECT_TRUE(*nocrypto_answer != NULL);
   }
 
   void CreateDtlsOfferAndNonDtlsAnswer(SessionDescriptionInterface** offer,
       SessionDescriptionInterface** nodtls_answer) {
     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_RECVONLY, kActive, &options);
+    options.recv_video = true;
     options.bundle_enabled = true;
 
     std::unique_ptr<SessionDescriptionInterface> temp_offer(
@@ -1222,7 +1068,8 @@
       const char* sctp_stream_name, int new_port,
       cricket::MediaSessionOptions options) {
     options.data_channel_type = cricket::DCT_SCTP;
-    GetOptionsForRemoteOffer(&options);
+    options.AddSendStream(cricket::MEDIA_TYPE_DATA, "datachannel",
+                          sctp_stream_name);
     return ChangeSDPSctpPort(new_port, CreateRemoteOffer(options));
   }
 
@@ -1250,7 +1097,7 @@
   // before this function to decide which streams to create.
   JsepSessionDescription* CreateRemoteOffer() {
     cricket::MediaSessionOptions options;
-    GetOptionsForRemoteOffer(&options);
+    GetOptionsForAnswer(&options);
     return CreateRemoteOffer(options, session_->remote_description());
   }
 
@@ -1285,7 +1132,6 @@
       const SessionDescriptionInterface* offer) {
     cricket::MediaSessionOptions options;
     GetOptionsForAnswer(&options);
-    options.bundle_enabled = true;
     return CreateRemoteAnswer(offer, options, cricket::SEC_REQUIRED);
   }
 
@@ -1609,7 +1455,6 @@
     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);
@@ -1617,19 +1462,16 @@
     }
 
     PeerConnectionInterface::RTCOfferAnswerOptions options;
-    cricket::MediaSessionOptions offer_session_options;
-    cricket::MediaSessionOptions answer_session_options;
-    GetOptionsForOffer(options, &offer_session_options);
-    GetOptionsForAnswer(&answer_session_options);
+    cricket::MediaSessionOptions 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, offer_session_options);
+        session_->CreateOffer(observers[i], options, session_options);
       } else {
-        session_->CreateAnswer(observers[i], answer_session_options);
+        session_->CreateAnswer(observers[i], session_options);
       }
     }
 
@@ -1687,15 +1529,8 @@
   // The following flags affect options created for CreateOffer/CreateAnswer.
   bool send_stream_1_ = false;
   bool send_stream_2_ = 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_;
+  bool send_audio_ = false;
+  bool send_video_ = false;
   rtc::scoped_refptr<DataChannel> data_channel_;
   // Last values received from data channel creation signal.
   std::string last_data_channel_label_;
@@ -1935,7 +1770,7 @@
 TEST_F(WebRtcSessionTest, TestSetNonSdesOfferWhenSdesOn) {
   Init();
   cricket::MediaSessionOptions options;
-  GetOptionsForRemoteOffer(&options);
+  options.recv_video = true;
   JsepSessionDescription* offer = CreateRemoteOffer(
       options, cricket::SEC_DISABLED);
   ASSERT_TRUE(offer != NULL);
@@ -1981,7 +1816,7 @@
   InitWithDtls(GetParam());
   SetFactoryDtlsSrtp();
   cricket::MediaSessionOptions options;
-  GetOptionsForRemoteOffer(&options);
+  options.recv_video = true;
   JsepSessionDescription* offer =
       CreateRemoteOffer(options, cricket::SEC_DISABLED);
   ASSERT_TRUE(offer != NULL);
@@ -2020,7 +1855,7 @@
   SetLocalDescriptionWithoutError(offer);
 
   cricket::MediaSessionOptions options;
-  GetOptionsForAnswer(&options);
+  options.recv_video = true;
   JsepSessionDescription* answer =
       CreateRemoteAnswer(offer, options, cricket::SEC_DISABLED);
   ASSERT_TRUE(answer != NULL);
@@ -2036,7 +1871,7 @@
 TEST_P(WebRtcSessionTest, TestReceiveNonDtlsOfferWhenDtlsOn) {
   InitWithDtls(GetParam());
   cricket::MediaSessionOptions options;
-  GetOptionsForRemoteOffer(&options);
+  options.recv_video = true;
   options.bundle_enabled = true;
   JsepSessionDescription* offer = CreateRemoteOffer(
       options, cricket::SEC_REQUIRED);
@@ -2074,16 +1909,12 @@
 TEST_P(WebRtcSessionTest, TestSetRemoteNonDtlsAnswerWhenDtlsOn) {
   InitWithDtls(GetParam());
   SessionDescriptionInterface* offer = CreateOffer();
-  cricket::MediaSessionOptions offer_options;
-  GetOptionsForRemoteOffer(&offer_options);
-
+  cricket::MediaSessionOptions options;
+  options.recv_video = true;
   std::unique_ptr<SessionDescriptionInterface> temp_offer(
-      CreateRemoteOffer(offer_options, cricket::SEC_ENABLED));
-
-  cricket::MediaSessionOptions answer_options;
-  GetOptionsForAnswer(&answer_options);
-  JsepSessionDescription* answer = CreateRemoteAnswer(
-      temp_offer.get(), answer_options, cricket::SEC_ENABLED);
+      CreateRemoteOffer(options, cricket::SEC_ENABLED));
+  JsepSessionDescription* answer =
+      CreateRemoteAnswer(temp_offer.get(), options, cricket::SEC_ENABLED);
 
   // SetRemoteDescription and SetLocalDescription will take the ownership of
   // the offer and answer.
@@ -2110,7 +1941,7 @@
   SetLocalDescriptionWithoutError(offer);
 
   cricket::MediaSessionOptions options;
-  GetOptionsForAnswer(&options);
+  options.recv_video = true;
   JsepSessionDescription* answer =
       CreateRemoteAnswer(offer, options, cricket::SEC_DISABLED);
   ASSERT_TRUE(answer != NULL);
@@ -2128,7 +1959,7 @@
   InitWithDtls(GetParam());
 
   cricket::MediaSessionOptions options;
-  GetOptionsForRemoteOffer(&options);
+  options.recv_video = true;
   JsepSessionDescription* offer =
       CreateRemoteOffer(options, cricket::SEC_DISABLED);
   ASSERT_TRUE(offer != NULL);
@@ -2161,7 +1992,7 @@
   SetLocalDescriptionWithoutError(offer);
 
   cricket::MediaSessionOptions options;
-  GetOptionsForAnswer(&options);
+  options.recv_video = true;
 
   // First, negotiate different SSL roles.
   SessionDescriptionInterface* answer =
@@ -2183,9 +2014,7 @@
                                        session_->remote_description());
   SetRemoteDescriptionWithoutError(offer);
 
-  cricket::MediaSessionOptions answer_options;
-  answer_options.bundle_enabled = true;
-  answer = CreateAnswer(answer_options);
+  answer = CreateAnswer();
   audio_transport_info = answer->description()->GetTransportInfoByName("audio");
   EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE,
             audio_transport_info->description.connection_role);
@@ -2202,7 +2031,7 @@
                                        kSessionVersion,
                                        session_->remote_description());
   SetRemoteDescriptionWithoutError(offer);
-  answer = CreateAnswer(answer_options);
+  answer = CreateAnswer();
   audio_transport_info = answer->description()->GetTransportInfoByName("audio");
   EXPECT_EQ(cricket::CONNECTIONROLE_PASSIVE,
             audio_transport_info->description.connection_role);
@@ -2563,7 +2392,6 @@
 
   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());
 
@@ -2589,27 +2417,42 @@
   // 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);
 
-  cricket::MediaSessionOptions answer_options;
-  answer_options.bundle_enabled = false;
-  SessionDescriptionInterface* answer = CreateAnswer(answer_options);
+  SessionDescriptionInterface* answer = CreateAnswer();
   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 " + std::to_string(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+            "audio_content_name " +
+                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 " + std::to_string(cricket::ICE_CANDIDATE_COMPONENT_RTP));
+            "video_content_name " +
+                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);
 }
@@ -2623,17 +2466,9 @@
   ASSERT_TRUE(offer != NULL);
   const cricket::ContentInfo* content =
       cricket::GetFirstAudioContent(offer->description());
-  ASSERT_TRUE(content != NULL);
-  EXPECT_EQ(
-      cricket::MD_RECVONLY,
-      static_cast<const cricket::AudioContentDescription*>(content->description)
-          ->direction());
+  EXPECT_TRUE(content != NULL);
   content = cricket::GetFirstVideoContent(offer->description());
-  ASSERT_TRUE(content != NULL);
-  EXPECT_EQ(
-      cricket::MD_RECVONLY,
-      static_cast<const cricket::VideoContentDescription*>(content->description)
-          ->direction());
+  EXPECT_TRUE(content == NULL);
 }
 
 // Test that an offer contains the correct media content descriptions based on
@@ -2646,34 +2481,17 @@
 
   const cricket::ContentInfo* content =
       cricket::GetFirstAudioContent(offer->description());
-  ASSERT_TRUE(content != NULL);
-  EXPECT_EQ(
-      cricket::MD_SENDRECV,
-      static_cast<const cricket::AudioContentDescription*>(content->description)
-          ->direction());
+  EXPECT_TRUE(content != NULL);
   content = cricket::GetFirstVideoContent(offer->description());
-  ASSERT_TRUE(content != NULL);
-  EXPECT_EQ(
-      cricket::MD_RECVONLY,
-      static_cast<const cricket::VideoContentDescription*>(content->description)
-          ->direction());
+  EXPECT_TRUE(content == NULL);
 
   // Test Audio / Video offer.
   SendAudioVideoStream1();
   offer.reset(CreateOffer());
   content = cricket::GetFirstAudioContent(offer->description());
-  ASSERT_TRUE(content != NULL);
-  EXPECT_EQ(
-      cricket::MD_SENDRECV,
-      static_cast<const cricket::AudioContentDescription*>(content->description)
-          ->direction());
-
+  EXPECT_TRUE(content != NULL);
   content = cricket::GetFirstVideoContent(offer->description());
-  ASSERT_TRUE(content != NULL);
-  EXPECT_EQ(
-      cricket::MD_SENDRECV,
-      static_cast<const cricket::VideoContentDescription*>(content->description)
-          ->direction());
+  EXPECT_TRUE(content != NULL);
 }
 
 // Test that an offer contains no media content descriptions if
@@ -2701,7 +2519,6 @@
   PeerConnectionInterface::RTCOfferAnswerOptions options;
   options.offer_to_receive_audio =
       RTCOfferAnswerOptions::kOfferToReceiveMediaTrue;
-  options.offer_to_receive_video = 0;
 
   std::unique_ptr<SessionDescriptionInterface> offer(CreateOffer(options));
 
@@ -2736,8 +2553,6 @@
   // 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());
@@ -2781,7 +2596,6 @@
   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);
@@ -2826,10 +2640,8 @@
   SetRemoteDescriptionWithoutError(offer.release());
 
   cricket::MediaSessionOptions session_options;
-  remote_send_audio_ = false;
-  remote_send_video_ = false;
-  local_recv_audio_ = false;
-  local_recv_video_ = false;
+  session_options.recv_audio = false;
+  session_options.recv_video = false;
   std::unique_ptr<SessionDescriptionInterface> answer(
       CreateAnswer(session_options));
 
@@ -2852,6 +2664,9 @@
   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));
@@ -2911,11 +2726,6 @@
   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
@@ -2926,7 +2736,7 @@
   video_channel_ = media_engine_->GetVideoChannel(0);
   voice_channel_ = media_engine_->GetVoiceChannel(0);
 
-  ASSERT_TRUE(video_channel_ == nullptr);
+  ASSERT_TRUE(video_channel_ == NULL);
 
   ASSERT_EQ(0u, voice_channel_->recv_streams().size());
   ASSERT_EQ(1u, voice_channel_->send_streams().size());
@@ -2934,14 +2744,13 @@
 
   // 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_ != nullptr);
-  ASSERT_TRUE(voice_channel_ != nullptr);
+  ASSERT_TRUE(video_channel_ != NULL);
+  ASSERT_TRUE(voice_channel_ != NULL);
 
   ASSERT_EQ(1u, video_channel_->recv_streams().size());
   ASSERT_EQ(1u, video_channel_->send_streams().size());
@@ -2953,17 +2762,10 @@
   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();
 
-  video_channel_ = media_engine_->GetVideoChannel(0);
-  voice_channel_ = media_engine_->GetVoiceChannel(0);
-
-  // The audio is expected to be rejected.
-  EXPECT_TRUE(video_channel_ == nullptr);
-
+  EXPECT_EQ(0u, video_channel_->recv_streams().size());
   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());
@@ -2980,13 +2782,10 @@
   SessionDescriptionInterface* offer = CreateOffer();
 
   cricket::MediaSessionOptions options;
-  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);
+  options.recv_audio = false;
+  options.recv_video = true;
+  SessionDescriptionInterface* answer = CreateRemoteAnswer(
+      offer, options, cricket::SEC_ENABLED);
 
   // SetLocalDescription and SetRemoteDescriptions takes ownership of offer
   // and answer.
@@ -3005,41 +2804,23 @@
 
   // Update the session descriptions, with Audio and Video.
   SendAudioVideoStream2();
-  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);
+  CreateAndSetRemoteOfferAndLocalAnswer();
 
   voice_channel_ = media_engine_->GetVoiceChannel(0);
-
   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();
-  remote_recv_audio_ = false;
-  SessionDescriptionInterface* offer3 = CreateRemoteOffer();
-  SetRemoteDescriptionWithoutError(offer3);
-  SessionDescriptionInterface* answer3 = CreateAnswer(answer_options);
-  SetLocalDescriptionWithoutError(answer3);
+  CreateAndSetRemoteOfferAndLocalAnswer();
 
   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());
@@ -3244,16 +3025,13 @@
   InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyBalanced);
   SendAudioVideoStream1();
 
-  cricket::MediaSessionOptions offer_options;
-  GetOptionsForRemoteOffer(&offer_options);
-  offer_options.bundle_enabled = true;
+  PeerConnectionInterface::RTCOfferAnswerOptions options;
+  options.use_rtp_mux = true;
 
-  SessionDescriptionInterface* offer = CreateRemoteOffer(offer_options);
+  SessionDescriptionInterface* offer = CreateRemoteOffer();
   SetRemoteDescriptionWithoutError(offer);
 
-  cricket::MediaSessionOptions answer_options;
-  answer_options.bundle_enabled = true;
-  SessionDescriptionInterface* answer = CreateAnswer(answer_options);
+  SessionDescriptionInterface* answer = CreateAnswer();
   SetLocalDescriptionWithoutError(answer);
 
   EXPECT_EQ(session_->voice_rtp_transport_channel(),
@@ -3418,11 +3196,10 @@
   EXPECT_EQ(session_->voice_rtp_transport_channel(),
             session_->video_rtp_transport_channel());
 
-  SendVideoOnlyStream2();
-  local_send_audio_ = false;
-  remote_recv_audio_ = false;
+  SendAudioVideoStream2();
   cricket::MediaSessionOptions recv_options;
-  GetOptionsForRemoteAnswer(&recv_options);
+  recv_options.recv_audio = false;
+  recv_options.recv_video = true;
   SessionDescriptionInterface* answer =
       CreateRemoteAnswer(session_->local_description(), recv_options);
   SetRemoteDescriptionWithoutError(answer);
@@ -3509,10 +3286,10 @@
   InitWithBundlePolicy(PeerConnectionInterface::kBundlePolicyMaxCompat);
   SendAudioVideoStream1();
 
-  PeerConnectionInterface::RTCOfferAnswerOptions rtc_options;
-  rtc_options.use_rtp_mux = true;
+  PeerConnectionInterface::RTCOfferAnswerOptions options;
+  options.use_rtp_mux = true;
 
-  SessionDescriptionInterface* offer = CreateOffer(rtc_options);
+  SessionDescriptionInterface* offer = CreateOffer(options);
   SetLocalDescriptionWithoutError(offer);
 
   EXPECT_NE(session_->voice_rtp_transport_channel(),
@@ -3868,7 +3645,7 @@
 TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewUfragAndPassword) {
   Init();
   cricket::MediaSessionOptions options;
-  GetOptionsForRemoteOffer(&options);
+  options.recv_video = true;
   std::unique_ptr<JsepSessionDescription> offer(CreateRemoteOffer(options));
   SetRemoteDescriptionWithoutError(offer.release());
 
@@ -3877,10 +3654,10 @@
   SetLocalDescriptionWithoutError(answer.release());
 
   // Receive an offer with new ufrag and password.
-  for (size_t i = 0; i < options.media_description_options.size(); ++i) {
-    options.media_description_options[i].transport_options.ice_restart = true;
+  for (const cricket::ContentInfo& content :
+       session_->local_description()->description()->contents()) {
+    options.transport_options[content.name].ice_restart = true;
   }
-
   std::unique_ptr<JsepSessionDescription> updated_offer1(
       CreateRemoteOffer(options, session_->remote_description()));
   SetRemoteDescriptionWithoutError(updated_offer1.release());
@@ -3908,7 +3685,8 @@
 TEST_F(WebRtcSessionTest, TestOfferChangingOnlyUfragOrPassword) {
   Init();
   cricket::MediaSessionOptions options;
-  GetOptionsForRemoteOffer(&options);
+  options.recv_audio = true;
+  options.recv_video = true;
   // Create an offer with audio and video.
   std::unique_ptr<JsepSessionDescription> offer(CreateRemoteOffer(options));
   SetIceUfragPwd(offer.get(), "original_ufrag", "original_password12345");
@@ -3948,7 +3726,7 @@
 TEST_F(WebRtcSessionTest, TestCreateAnswerWithOldUfragAndPassword) {
   Init();
   cricket::MediaSessionOptions options;
-  GetOptionsForRemoteOffer(&options);
+  options.recv_video = true;
   std::unique_ptr<JsepSessionDescription> offer(CreateRemoteOffer(options));
   SetRemoteDescriptionWithoutError(offer.release());
 
@@ -3975,7 +3753,8 @@
 TEST_F(WebRtcSessionTest, TestCreateAnswerWithNewAndOldUfragAndPassword) {
   Init();
   cricket::MediaSessionOptions options;
-  GetOptionsForRemoteOffer(&options);
+  options.recv_video = true;
+  options.recv_audio = true;
   options.bundle_enabled = false;
   std::unique_ptr<JsepSessionDescription> offer(CreateRemoteOffer(options));
 
@@ -4107,9 +3886,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());
@@ -4133,7 +3912,6 @@
   // 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);
@@ -4277,7 +4055,7 @@
   SetFactoryDtlsSrtp();
 
   cricket::MediaSessionOptions options;
-  GetOptionsForRemoteOffer(&options);
+  options.recv_video = true;
   std::unique_ptr<JsepSessionDescription> offer(
       CreateRemoteOffer(options, cricket::SEC_DISABLED));
   ASSERT_TRUE(offer.get() != NULL);
@@ -4351,7 +4129,6 @@
   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.
@@ -4395,7 +4172,7 @@
   SetRemoteDescriptionWithoutError(answer);
 
   cricket::MediaSessionOptions options;
-  GetOptionsForRemoteOffer(&options);
+  options.recv_video = true;
   offer = CreateRemoteOffer(options, cricket::SEC_DISABLED);
 
   cricket::Candidate candidate1;
@@ -4424,7 +4201,7 @@
   SetRemoteDescriptionWithoutError(answer);
 
   cricket::MediaSessionOptions options;
-  GetOptionsForRemoteOffer(&options);
+  options.recv_video = true;
   offer = CreateRemoteOffer(options, cricket::SEC_DISABLED);
   SetRemoteDescriptionWithoutError(offer);
 
@@ -4449,7 +4226,8 @@
   ASSERT_TRUE(offer->description());
   SetLocalDescriptionWithoutError(offer);
   cricket::MediaSessionOptions options;
-  GetOptionsForAnswer(&options);
+  options.recv_audio = true;
+  options.recv_video = true;
   SessionDescriptionInterface* answer =
       CreateRemoteAnswer(offer, options, cricket::SEC_DISABLED);
   ASSERT_TRUE(answer);
@@ -4462,8 +4240,7 @@
 // by local side.
 TEST_F(WebRtcSessionTest, TestRtxRemovedByCreateAnswer) {
   Init();
-  // Send video only to match the |kSdpWithRtx|.
-  SendVideoOnlyStream2();
+  SendAudioVideoStream1();
   std::string offer_sdp(kSdpWithRtx);
 
   SessionDescriptionInterface* offer =
@@ -4474,11 +4251,6 @@
   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"));
@@ -4544,7 +4316,8 @@
   options.offer_to_receive_audio =
       RTCOfferAnswerOptions::kOfferToReceiveMediaTrue;
   cricket::MediaSessionOptions session_options;
-  GetOptionsForOffer(options, &session_options);
+  session_options.recv_audio = true;
+
   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 beb8d1e..8eccd65 100644
--- a/webrtc/pc/webrtcsessiondescriptionfactory.cc
+++ b/webrtc/pc/webrtcsessiondescriptionfactory.cc
@@ -30,30 +30,24 @@
 
 static const uint64_t kInitSessionVersion = 2;
 
-static bool CompareSenderOptions(const cricket::SenderOptions& sender1,
-                                 const cricket::SenderOptions& sender2) {
-  return sender1.track_id < sender2.track_id;
+static bool CompareStream(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;
+static bool SameId(const MediaSessionOptions::Stream& stream1,
+                   const MediaSessionOptions::Stream& stream2) {
+  return stream1.id == stream2.id;
 }
 
-// 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();
+// 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();
 }
 
 enum {
@@ -134,6 +128,7 @@
       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);
@@ -242,8 +237,8 @@
     return;
   }
 
-  if (!ValidMediaSessionOptions(session_options)) {
-    error += " called with invalid session options";
+  if (!ValidStreams(session_options.streams)) {
+    error += " called with invalid media streams.";
     LOG(LS_ERROR) << error;
     PostCreateSessionDescriptionFailed(observer, error);
     return;
@@ -284,8 +279,8 @@
     return;
   }
 
-  if (!ValidMediaSessionOptions(session_options)) {
-    error += " called with invalid session options.";
+  if (!ValidStreams(session_options.streams)) {
+    error += " called with invalid media streams.";
     LOG(LS_ERROR) << error;
     PostCreateSessionDescriptionFailed(observer, error);
     return;
@@ -345,12 +340,13 @@
 void WebRtcSessionDescriptionFactory::InternalCreateOffer(
     CreateSessionDescriptionRequest request) {
   if (session_->local_description()) {
-    // 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;
+    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;
       }
     }
   }
@@ -379,11 +375,13 @@
     return;
   }
   if (session_->local_description()) {
-    for (const cricket::MediaDescriptionOptions& options :
-         request.options.media_description_options) {
-      if (!options.transport_options.ice_restart) {
+    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) {
         CopyCandidatesFromSessionDescription(session_->local_description(),
-                                             options.mid, offer);
+                                             content.name, offer);
       }
     }
   }
@@ -393,18 +391,18 @@
 void WebRtcSessionDescriptionFactory::InternalCreateAnswer(
     CreateSessionDescriptionRequest request) {
   if (session_->remote_description()) {
-    for (cricket::MediaDescriptionOptions& options :
-         request.options.media_description_options) {
+    for (const cricket::ContentInfo& content :
+         session_->remote_description()->description()->contents()) {
       // 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.
-      options.transport_options.ice_restart =
-          session_->IceRestartPending(options.mid);
+      request.options.transport_options[content.name].ice_restart =
+          session_->IceRestartPending(content.name);
       // 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(options.mid, &ssl_role)) {
-        options.transport_options.prefer_passive_role =
+      if (session_->GetSslRole(content.name, &ssl_role)) {
+        request.options.transport_options[content.name].prefer_passive_role =
             (rtc::SSL_SERVER == ssl_role);
       }
     }
@@ -435,13 +433,13 @@
     return;
   }
   if (session_->local_description()) {
-    // 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) {
+    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) {
         CopyCandidatesFromSessionDescription(session_->local_description(),
-                                             options.mid, answer);
+                                             content.name, answer);
       }
     }
   }