Negotiating Simulcast in the initial offer/answer - Part1.

This change adds Simulcast negotiation to media session offers/answers.
Next step is to add negotiation logic to PeerConnection.

Bug: webrtc:10075
Change-Id: Iea3a1084c16058f0efbc974cf623ec05c3c7a74f
Reviewed-on: https://webrtc-review.googlesource.com/c/115790
Reviewed-by: Seth Hampson <shampson@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Commit-Queue: Amit Hilbuch <amithi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26115}
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 2a07e71..268176c 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -66,6 +66,8 @@
     "srtptransport.h",
     "transportstats.cc",
     "transportstats.h",
+    "unique_id_generator.cc",
+    "unique_id_generator.h",
   ]
 
   deps = [
@@ -90,6 +92,7 @@
     "../rtc_base:checks",
     "../rtc_base:rtc_base",
     "../rtc_base:rtc_task_queue",
+    "../rtc_base:stringutils",
     "../rtc_base/third_party/base64",
     "../rtc_base/third_party/sigslot",
     "../system_wrappers:metrics",
@@ -257,6 +260,7 @@
       "srtpsession_unittest.cc",
       "srtptestutil.h",
       "srtptransport_unittest.cc",
+      "unique_id_generator_unittest.cc",
     ]
 
     include_dirs = [ "//third_party/libsrtp/srtp" ]
diff --git a/pc/mediasession.cc b/pc/mediasession.cc
index 4898ed9..4f7fae1 100644
--- a/pc/mediasession.cc
+++ b/pc/mediasession.cc
@@ -28,6 +28,7 @@
 #include "pc/channelmanager.h"
 #include "pc/rtpmediautils.h"
 #include "pc/srtpfilter.h"
+#include "pc/unique_id_generator.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/helpers.h"
 #include "rtc_base/logging.h"
@@ -36,6 +37,7 @@
 namespace {
 
 using webrtc::RtpTransceiverDirection;
+using webrtc::UniqueRandomIdGenerator;
 
 const char kInline[] = "inline:";
 
@@ -272,22 +274,6 @@
   return false;
 }
 
-// Generate random SSRC values that are not already present in |params_vec|.
-// The generated values are added to |ssrcs|.
-// |num_ssrcs| is the number of the SSRC will be generated.
-static void GenerateSsrcs(const StreamParamsVec& params_vec,
-                          int num_ssrcs,
-                          std::vector<uint32_t>* ssrcs) {
-  for (int i = 0; i < num_ssrcs; i++) {
-    uint32_t candidate;
-    do {
-      candidate = rtc::CreateRandomNonZeroId();
-    } while (GetStreamBySsrc(params_vec, candidate) ||
-             std::count(ssrcs->begin(), ssrcs->end(), candidate) > 0);
-    ssrcs->push_back(candidate);
-  }
-}
-
 // Finds all StreamParams of all media types and attach them to stream_params.
 static StreamParamsVec GetCurrentStreamParams(
     const std::vector<const ContentInfo*>& active_local_contents) {
@@ -401,6 +387,135 @@
  private:
 };
 
+static StreamParams CreateStreamParamsForNewSenderWithSsrcs(
+    const SenderOptions& sender,
+    const std::string& rtcp_cname,
+    const StreamParamsVec& current_streams,
+    bool include_rtx_streams,
+    bool include_flexfec_stream) {
+  StreamParams result;
+  result.id = sender.track_id;
+
+  std::vector<uint32_t> known_ssrcs;
+  for (const StreamParams& params : current_streams) {
+    for (uint32_t ssrc : params.ssrcs) {
+      known_ssrcs.push_back(ssrc);
+    }
+  }
+
+  UniqueRandomIdGenerator ssrc_generator(known_ssrcs);
+  // We need to keep |primary_ssrcs| separate from |result.ssrcs|  because
+  // iterators are invalidated when rtx and flexfec ssrcs are added to the list.
+  std::vector<uint32_t> primary_ssrcs;
+  for (int i = 0; i < sender.num_sim_layers; ++i) {
+    primary_ssrcs.push_back(ssrc_generator());
+  }
+  result.ssrcs = primary_ssrcs;
+
+  if (sender.num_sim_layers > 1) {
+    SsrcGroup group(kSimSsrcGroupSemantics, result.ssrcs);
+    result.ssrc_groups.push_back(group);
+  }
+  // Generate an RTX ssrc for every ssrc in the group.
+  if (include_rtx_streams) {
+    for (uint32_t ssrc : primary_ssrcs) {
+      result.AddFidSsrc(ssrc, ssrc_generator());
+    }
+  }
+  // Generate extra ssrc for include_flexfec_stream case.
+  if (include_flexfec_stream) {
+    // TODO(brandtr): Update when we support multistream protection.
+    if (result.ssrcs.size() == 1) {
+      for (uint32_t ssrc : primary_ssrcs) {
+        result.AddFecFrSsrc(ssrc, ssrc_generator());
+      }
+    } else if (!result.ssrcs.empty()) {
+      RTC_LOG(LS_WARNING)
+          << "Our FlexFEC implementation only supports protecting "
+             "a single media streams. This session has multiple "
+             "media streams however, so no FlexFEC SSRC will be generated.";
+    }
+  }
+  result.cname = rtcp_cname;
+  result.set_stream_ids(sender.stream_ids);
+
+  return result;
+}
+
+static bool ValidateSimulcastLayers(
+    const std::vector<RidDescription>& rids,
+    const SimulcastLayerList& simulcast_layers) {
+  std::vector<SimulcastLayer> all_layers = simulcast_layers.GetAllLayers();
+  return std::all_of(all_layers.begin(), all_layers.end(),
+                     [&rids](const SimulcastLayer& layer) {
+                       return std::find_if(rids.begin(), rids.end(),
+                                           [&layer](const RidDescription& rid) {
+                                             return rid.rid == layer.rid;
+                                           }) != rids.end();
+                     });
+}
+
+static StreamParams CreateStreamParamsForNewSenderWithRids(
+    const SenderOptions& sender,
+    const std::string& rtcp_cname) {
+  RTC_DCHECK(!sender.rids.empty());
+  RTC_DCHECK_EQ(sender.num_sim_layers, 0)
+      << "RIDs are the compliant way to indicate simulcast.";
+  RTC_DCHECK(ValidateSimulcastLayers(sender.rids, sender.simulcast_layers));
+  StreamParams result;
+  result.id = sender.track_id;
+  result.cname = rtcp_cname;
+  result.set_stream_ids(sender.stream_ids);
+
+  // More than one rid should be signaled.
+  if (sender.rids.size() > 1) {
+    result.set_rids(sender.rids);
+  }
+
+  return result;
+}
+
+// Adds SimulcastDescription if indicated by the media description options.
+// MediaContentDescription should already be set up with the send rids.
+static void AddSimulcastToMediaDescription(
+    const MediaDescriptionOptions& media_description_options,
+    MediaContentDescription* description) {
+  RTC_DCHECK(description);
+
+  // Check if we are using RIDs in this scenario.
+  if (std::all_of(
+          description->streams().begin(), description->streams().end(),
+          [](const StreamParams& params) { return !params.has_rids(); })) {
+    return;
+  }
+
+  RTC_DCHECK_EQ(1, description->streams().size())
+      << "RIDs are only supported in Unified Plan semantics.";
+  RTC_DCHECK_EQ(1, media_description_options.sender_options.size());
+  RTC_DCHECK(description->type() == MediaType::MEDIA_TYPE_AUDIO ||
+             description->type() == MediaType::MEDIA_TYPE_VIDEO);
+
+  // One RID or less indicates that simulcast is not needed.
+  if (description->streams()[0].rids().size() <= 1) {
+    return;
+  }
+
+  RTC_DCHECK(!media_description_options.receive_rids.empty());
+  RTC_DCHECK(ValidateSimulcastLayers(
+      media_description_options.receive_rids,
+      media_description_options.receive_simulcast_layers));
+  StreamParams receive_stream;
+  receive_stream.set_rids(media_description_options.receive_rids);
+  description->set_receive_stream(receive_stream);
+
+  SimulcastDescription simulcast;
+  simulcast.send_layers() =
+      media_description_options.sender_options[0].simulcast_layers;
+  simulcast.receive_layers() =
+      media_description_options.receive_simulcast_layers;
+  description->set_simulcast_description(simulcast);
+}
+
 // Adds a StreamParams for each SenderOptions in |sender_options| to
 // content_description.
 // |current_params| - All currently known StreamParams of any media type.
@@ -428,44 +543,17 @@
         GetStreamByIds(*current_streams, "" /*group_id*/, sender.track_id);
     if (!param) {
       // This is a new sender.
-      std::vector<uint32_t> ssrcs;
-      GenerateSsrcs(*current_streams, sender.num_sim_layers, &ssrcs);
-      StreamParams stream_param;
-      stream_param.id = sender.track_id;
-      // Add the generated ssrc.
-      for (size_t i = 0; i < ssrcs.size(); ++i) {
-        stream_param.ssrcs.push_back(ssrcs[i]);
-      }
-      if (sender.num_sim_layers > 1) {
-        SsrcGroup group(kSimSsrcGroupSemantics, stream_param.ssrcs);
-        stream_param.ssrc_groups.push_back(group);
-      }
-      // Generate extra ssrcs for include_rtx_streams case.
-      if (include_rtx_streams) {
-        // Generate an RTX ssrc for every ssrc in the group.
-        std::vector<uint32_t> rtx_ssrcs;
-        GenerateSsrcs(*current_streams, static_cast<int>(ssrcs.size()),
-                      &rtx_ssrcs);
-        for (size_t i = 0; i < ssrcs.size(); ++i) {
-          stream_param.AddFidSsrc(ssrcs[i], rtx_ssrcs[i]);
-        }
-      }
-      // Generate extra ssrc for include_flexfec_stream case.
-      if (include_flexfec_stream) {
-        // TODO(brandtr): Update when we support multistream protection.
-        if (ssrcs.size() == 1) {
-          std::vector<uint32_t> flexfec_ssrcs;
-          GenerateSsrcs(*current_streams, 1, &flexfec_ssrcs);
-          stream_param.AddFecFrSsrc(ssrcs[0], flexfec_ssrcs[0]);
-        } else if (!ssrcs.empty()) {
-          RTC_LOG(LS_WARNING)
-              << "Our FlexFEC implementation only supports protecting "
-                 "a single media streams. This session has multiple "
-                 "media streams however, so no FlexFEC SSRC will be generated.";
-        }
-      }
-      stream_param.cname = rtcp_cname;
-      stream_param.set_stream_ids(sender.stream_ids);
+      StreamParams stream_param =
+          sender.rids.empty()
+              ?
+              // Signal SSRCs and legacy simulcast (if requested).
+              CreateStreamParamsForNewSenderWithSsrcs(
+                  sender, rtcp_cname, *current_streams, include_rtx_streams,
+                  include_flexfec_stream)
+              :
+              // Signal RIDs and spec-compliant simulcast (if requested).
+              CreateStreamParamsForNewSenderWithRids(sender, rtcp_cname);
+
       content_description->AddStream(stream_param);
 
       // Store the new StreamParams in current_streams.
@@ -692,7 +780,7 @@
 // offer.
 template <class C>
 static bool CreateMediaContentOffer(
-    const std::vector<SenderOptions>& sender_options,
+    const MediaDescriptionOptions& media_description_options,
     const MediaSessionOptions& session_options,
     const std::vector<C>& codecs,
     const SecurePolicy& secure_policy,
@@ -709,11 +797,13 @@
   }
   offer->set_rtp_header_extensions(rtp_extensions);
 
-  if (!AddStreamParams(sender_options, session_options.rtcp_cname,
-                       current_streams, offer)) {
+  if (!AddStreamParams(media_description_options.sender_options,
+                       session_options.rtcp_cname, current_streams, offer)) {
     return false;
   }
 
+  AddSimulcastToMediaDescription(media_description_options, offer);
+
   if (secure_policy != SEC_DISABLED) {
     if (current_cryptos) {
       AddMediaCryptos(*current_cryptos, offer);
@@ -1117,6 +1207,8 @@
     return false;  // Something went seriously wrong.
   }
 
+  AddSimulcastToMediaDescription(media_description_options, answer);
+
   answer->set_direction(NegotiateRtpTransceiverDirection(
       offer->direction(), media_description_options.direction));
   return true;
@@ -1200,15 +1292,21 @@
     const std::string& track_id,
     const std::vector<std::string>& stream_ids) {
   RTC_DCHECK(type == MEDIA_TYPE_AUDIO);
-  AddSenderInternal(track_id, stream_ids, 1);
+  AddSenderInternal(track_id, stream_ids, {}, SimulcastLayerList(), 1);
 }
 
 void MediaDescriptionOptions::AddVideoSender(
     const std::string& track_id,
     const std::vector<std::string>& stream_ids,
+    const std::vector<RidDescription>& rids,
+    const SimulcastLayerList& simulcast_layers,
     int num_sim_layers) {
   RTC_DCHECK(type == MEDIA_TYPE_VIDEO);
-  AddSenderInternal(track_id, stream_ids, num_sim_layers);
+  RTC_DCHECK(rids.empty() || num_sim_layers == 0)
+      << "RIDs are the compliant way to indicate simulcast.";
+  RTC_DCHECK(ValidateSimulcastLayers(rids, simulcast_layers));
+  AddSenderInternal(track_id, stream_ids, rids, simulcast_layers,
+                    num_sim_layers);
 }
 
 void MediaDescriptionOptions::AddRtpDataChannel(const std::string& track_id,
@@ -1216,16 +1314,24 @@
   RTC_DCHECK(type == MEDIA_TYPE_DATA);
   // TODO(steveanton): Is it the case that RtpDataChannel will never have more
   // than one stream?
-  AddSenderInternal(track_id, {stream_id}, 1);
+  AddSenderInternal(track_id, {stream_id}, {}, SimulcastLayerList(), 1);
 }
 
 void MediaDescriptionOptions::AddSenderInternal(
     const std::string& track_id,
     const std::vector<std::string>& stream_ids,
+    const std::vector<RidDescription>& rids,
+    const SimulcastLayerList& simulcast_layers,
     int num_sim_layers) {
   // TODO(steveanton): Support any number of stream ids.
   RTC_CHECK(stream_ids.size() == 1U);
-  sender_options.push_back(SenderOptions{track_id, stream_ids, num_sim_layers});
+  SenderOptions options;
+  options.track_id = track_id;
+  options.stream_ids = stream_ids;
+  options.simulcast_layers = simulcast_layers;
+  options.rids = rids;
+  options.num_sim_layers = num_sim_layers;
+  sender_options.push_back(options);
 }
 
 bool MediaSessionOptions::HasMediaDescription(MediaType type) const {
@@ -1928,9 +2034,9 @@
   GetSupportedAudioSdesCryptoSuiteNames(session_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())) {
+          media_description_options, session_options, filtered_codecs,
+          sdes_policy, GetCryptos(current_content), crypto_suites,
+          audio_rtp_extensions, current_streams, audio.get())) {
     return false;
   }
 
@@ -1998,9 +2104,9 @@
   }
 
   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())) {
+          media_description_options, session_options, filtered_codecs,
+          sdes_policy, GetCryptos(current_content), crypto_suites,
+          video_rtp_extensions, current_streams, video.get())) {
     return false;
   }
 
@@ -2066,9 +2172,9 @@
 
   // 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())) {
+          media_description_options, session_options, data_codecs, sdes_policy,
+          GetCryptos(current_content), crypto_suites, RtpHeaderExtensions(),
+          current_streams, data.get())) {
     return false;
   }
 
diff --git a/pc/mediasession.h b/pc/mediasession.h
index d5c26f4..f732cb2 100644
--- a/pc/mediasession.h
+++ b/pc/mediasession.h
@@ -35,9 +35,14 @@
 const char kDefaultRtcpCname[] = "DefaultRtcpCname";
 
 // Options for an RtpSender contained with an media description/"m=" section.
+// Note: Spec-compliant Simulcast and legacy simulcast are mutually exclusive.
 struct SenderOptions {
   std::string track_id;
   std::vector<std::string> stream_ids;
+  // Use RIDs and Simulcast Layers to indicate spec-compliant Simulcast.
+  std::vector<RidDescription> rids;
+  SimulcastLayerList simulcast_layers;
+  // Use |num_sim_layers| to indicate legacy simulcast.
   int num_sim_layers;
 };
 
@@ -55,6 +60,8 @@
                       const std::vector<std::string>& stream_ids);
   void AddVideoSender(const std::string& track_id,
                       const std::vector<std::string>& stream_ids,
+                      const std::vector<RidDescription>& rids,
+                      const SimulcastLayerList& simulcast_layers,
                       int num_sim_layers);
 
   // Internally just uses sender_options.
@@ -69,11 +76,22 @@
   // Note: There's no equivalent "RtpReceiverOptions" because only send
   // stream information goes in the local descriptions.
   std::vector<SenderOptions> sender_options;
+  // |receive_rids| and |receive_simulcast_layers| are used with spec-compliant
+  // simulcast. When Simulcast is used, they should both not be empty.
+  // All RIDs in |receive_simulcast_layers| must appear in receive_rids as well.
+  // |receive_rids| could also be used outside of simulcast. It is possible to
+  // add restrictions on the incoming stream during negotiation outside the
+  // simulcast scenario. This is currently not fully supported, as meaningful
+  // restrictions are not handled by this library.
+  std::vector<RidDescription> receive_rids;
+  SimulcastLayerList receive_simulcast_layers;
 
  private:
   // Doesn't DCHECK on |type|.
   void AddSenderInternal(const std::string& track_id,
                          const std::vector<std::string>& stream_ids,
+                         const std::vector<RidDescription>& rids,
+                         const SimulcastLayerList& simulcast_layers,
                          int num_sim_layers);
 };
 
diff --git a/pc/mediasession_unittest.cc b/pc/mediasession_unittest.cc
index 2af14d2..16be74f 100644
--- a/pc/mediasession_unittest.cc
+++ b/pc/mediasession_unittest.cc
@@ -36,45 +36,58 @@
 
 typedef std::vector<cricket::Candidate> Candidates;
 
+using cricket::AudioCodec;
+using cricket::AudioContentDescription;
+using cricket::ContentInfo;
+using cricket::CryptoParamsVec;
+using cricket::DataCodec;
+using cricket::DataContentDescription;
+using cricket::GetFirstAudioContent;
+using cricket::GetFirstAudioContentDescription;
+using cricket::GetFirstDataContent;
+using cricket::GetFirstDataContentDescription;
+using cricket::GetFirstVideoContent;
+using cricket::GetFirstVideoContentDescription;
+using cricket::kAutoBandwidth;
+using cricket::MEDIA_TYPE_AUDIO;
+using cricket::MEDIA_TYPE_DATA;
+using cricket::MEDIA_TYPE_VIDEO;
 using cricket::MediaContentDescription;
-using cricket::MediaSessionDescriptionFactory;
 using cricket::MediaDescriptionOptions;
+using cricket::MediaProtocolType;
+using cricket::MediaSessionDescriptionFactory;
 using cricket::MediaSessionOptions;
 using cricket::MediaType;
-using cricket::MediaProtocolType;
+using cricket::RidDescription;
+using cricket::RidDirection;
+using cricket::SEC_DISABLED;
+using cricket::SEC_ENABLED;
+using cricket::SEC_REQUIRED;
 using cricket::SessionDescription;
+using cricket::SimulcastDescription;
+using cricket::SimulcastLayer;
+using cricket::SimulcastLayerList;
 using cricket::SsrcGroup;
 using cricket::StreamParams;
 using cricket::StreamParamsVec;
 using cricket::TransportDescription;
 using cricket::TransportDescriptionFactory;
 using cricket::TransportInfo;
-using cricket::ContentInfo;
-using cricket::CryptoParamsVec;
-using cricket::AudioContentDescription;
-using cricket::VideoContentDescription;
-using cricket::DataContentDescription;
-using cricket::GetFirstAudioContent;
-using cricket::GetFirstVideoContent;
-using cricket::GetFirstDataContent;
-using cricket::GetFirstAudioContentDescription;
-using cricket::GetFirstVideoContentDescription;
-using cricket::GetFirstDataContentDescription;
-using cricket::kAutoBandwidth;
-using cricket::AudioCodec;
 using cricket::VideoCodec;
-using cricket::DataCodec;
-using cricket::MEDIA_TYPE_AUDIO;
-using cricket::MEDIA_TYPE_VIDEO;
-using cricket::MEDIA_TYPE_DATA;
-using cricket::SEC_DISABLED;
-using cricket::SEC_ENABLED;
-using cricket::SEC_REQUIRED;
-using rtc::CS_AES_CM_128_HMAC_SHA1_32;
-using rtc::CS_AES_CM_128_HMAC_SHA1_80;
+using cricket::VideoContentDescription;
 using rtc::CS_AEAD_AES_128_GCM;
 using rtc::CS_AEAD_AES_256_GCM;
+using rtc::CS_AES_CM_128_HMAC_SHA1_32;
+using rtc::CS_AES_CM_128_HMAC_SHA1_80;
+using testing::Each;
 using testing::ElementsAreArray;
+using testing::Eq;
+using testing::Field;
+using testing::IsEmpty;
+using testing::IsFalse;
+using testing::Ne;
+using testing::Pointwise;
+using testing::SizeIs;
 using webrtc::RtpExtension;
 using webrtc::RtpTransceiverDirection;
 
@@ -223,8 +236,8 @@
 static const char* kDefaultSrtpCryptoSuite = CS_AES_CM_128_HMAC_SHA1_80;
 static const char* kDefaultSrtpCryptoSuiteGcm = CS_AEAD_AES_256_GCM;
 
-// These constants are used to make the code using "AddMediaSection" more
-// readable.
+// These constants are used to make the code using "AddMediaDescriptionOptions"
+// more readable.
 static constexpr bool kStopped = true;
 static constexpr bool kActive = false;
 
@@ -276,33 +289,37 @@
 }
 
 // Add a media section to the |session_options|.
-static void AddMediaSection(MediaType type,
-                            const std::string& mid,
-                            RtpTransceiverDirection direction,
-                            bool stopped,
-                            MediaSessionOptions* opts) {
+static void AddMediaDescriptionOptions(MediaType type,
+                                       const std::string& mid,
+                                       RtpTransceiverDirection direction,
+                                       bool stopped,
+                                       MediaSessionOptions* opts) {
   opts->media_description_options.push_back(
       MediaDescriptionOptions(type, mid, direction, stopped));
 }
 
 static void AddAudioVideoSections(RtpTransceiverDirection direction,
                                   MediaSessionOptions* opts) {
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", direction, kActive, opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", direction, kActive, opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio", direction, kActive,
+                             opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video", direction, kActive,
+                             opts);
 }
 
 static void AddDataSection(cricket::DataChannelType dct,
                            RtpTransceiverDirection direction,
                            MediaSessionOptions* opts) {
   opts->data_channel_type = dct;
-  AddMediaSection(MEDIA_TYPE_DATA, "data", direction, kActive, opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_DATA, "data", direction, kActive, opts);
 }
 
-static void AttachSenderToMediaSection(
+static void AttachSenderToMediaDescriptionOptions(
     const std::string& mid,
     MediaType type,
     const std::string& track_id,
     const std::vector<std::string>& stream_ids,
+    const std::vector<RidDescription>& rids,
+    const SimulcastLayerList& simulcast_layers,
     int num_sim_layer,
     MediaSessionOptions* session_options) {
   auto it = FindFirstMediaDescriptionByMid(mid, session_options);
@@ -311,7 +328,8 @@
       it->AddAudioSender(track_id, stream_ids);
       break;
     case MEDIA_TYPE_VIDEO:
-      it->AddVideoSender(track_id, stream_ids, num_sim_layer);
+      it->AddVideoSender(track_id, stream_ids, rids, simulcast_layers,
+                         num_sim_layer);
       break;
     case MEDIA_TYPE_DATA:
       RTC_CHECK(stream_ids.size() == 1U);
@@ -322,6 +340,18 @@
   }
 }
 
+static void AttachSenderToMediaDescriptionOptions(
+    const std::string& mid,
+    MediaType type,
+    const std::string& track_id,
+    const std::vector<std::string>& stream_ids,
+    int num_sim_layer,
+    MediaSessionOptions* session_options) {
+  AttachSenderToMediaDescriptionOptions(mid, type, track_id, stream_ids, {},
+                                        SimulcastLayerList(), num_sim_layer,
+                                        session_options);
+}
+
 static void DetachSenderFromMediaSection(const std::string& mid,
                                          const std::string& track_id,
                                          MediaSessionOptions* session_options) {
@@ -340,8 +370,9 @@
 // (https://tools.ietf.org/html/draft-uberti-rtcweb-plan-00).
 static MediaSessionOptions CreatePlanBMediaSessionOptions() {
   MediaSessionOptions session_options;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &session_options);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &session_options);
   return session_options;
 }
 
@@ -754,10 +785,12 @@
   f1_.set_secure(SEC_ENABLED);
   f2_.set_secure(SEC_ENABLED);
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kInactive,
-                  kStopped, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kInactive, kStopped,
+                             &opts);
   opts.data_channel_type = cricket::DCT_NONE;
   opts.bundle_enabled = true;
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
@@ -879,10 +912,10 @@
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSendOnlyOffer) {
   MediaSessionOptions opts;
   AddAudioVideoSections(RtpTransceiverDirection::kSendOnly, &opts);
-  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
-                             {kMediaStream1}, 1, &opts);
-  AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
-                             {kMediaStream1}, 1, &opts);
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                                        {kMediaStream1}, 1, &opts);
+  AttachSenderToMediaDescriptionOptions("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
+                                        {kMediaStream1}, 1, &opts);
 
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
   ASSERT_TRUE(offer.get() != NULL);
@@ -907,8 +940,9 @@
   EXPECT_EQ(1u, offer1->contents().size());
   EXPECT_TRUE(IsMediaContentOfType(&offer1->contents()[0], MEDIA_TYPE_DATA));
 
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
   std::unique_ptr<SessionDescription> offer2(
       f1_.CreateOffer(opts, offer1.get()));
   ASSERT_TRUE(offer2.get() != NULL);
@@ -916,8 +950,9 @@
   EXPECT_TRUE(IsMediaContentOfType(&offer2->contents()[0], MEDIA_TYPE_DATA));
   EXPECT_TRUE(IsMediaContentOfType(&offer2->contents()[1], MEDIA_TYPE_VIDEO));
 
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
   std::unique_ptr<SessionDescription> offer3(
       f1_.CreateOffer(opts, offer2.get()));
   ASSERT_TRUE(offer3.get() != NULL);
@@ -1176,15 +1211,17 @@
   ASSERT_TRUE(offer1.get() != NULL);
 
   // Appends audio to the offer.
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
   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", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
   std::unique_ptr<SessionDescription> offer3(
       f1_.CreateOffer(opts, offer2.get()));
   ASSERT_TRUE(offer3.get() != NULL);
@@ -1517,10 +1554,12 @@
 // Create an audio-only answer to a video offer.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAudioAnswerToVideo) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
   ASSERT_TRUE(offer.get() != NULL);
 
@@ -1539,8 +1578,9 @@
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateNoDataAnswerToDataOffer) {
   MediaSessionOptions opts = CreatePlanBMediaSessionOptions();
   opts.data_channel_type = cricket::DCT_RTP;
-  AddMediaSection(MEDIA_TYPE_DATA, "data", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_DATA, "data",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
   ASSERT_TRUE(offer.get() != NULL);
 
@@ -1655,18 +1695,18 @@
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoOffer) {
   MediaSessionOptions opts;
   AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &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);
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                                        {kMediaStream1}, 1, &opts);
+  AttachSenderToMediaDescriptionOptions("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
+                                        {kMediaStream1}, 1, &opts);
+  AttachSenderToMediaDescriptionOptions("audio", MEDIA_TYPE_AUDIO, kAudioTrack2,
+                                        {kMediaStream1}, 1, &opts);
 
   AddDataSection(cricket::DCT_RTP, RtpTransceiverDirection::kSendRecv, &opts);
-  AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack1,
-                             {kMediaStream1}, 1, &opts);
-  AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack2,
-                             {kMediaStream1}, 1, &opts);
+  AttachSenderToMediaDescriptionOptions("data", MEDIA_TYPE_DATA, kDataTrack1,
+                                        {kMediaStream1}, 1, &opts);
+  AttachSenderToMediaDescriptionOptions("data", MEDIA_TYPE_DATA, kDataTrack2,
+                                        {kMediaStream1}, 1, &opts);
 
   f1_.set_secure(SEC_ENABLED);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
@@ -1730,14 +1770,14 @@
 
   // 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);
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, kVideoTrack2,
+                                        {kMediaStream2}, 1, &opts);
   DetachSenderFromMediaSection("audio", kAudioTrack2, &opts);
-  AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack3,
-                             {kMediaStream1}, 1, &opts);
+  AttachSenderToMediaDescriptionOptions("audio", MEDIA_TYPE_AUDIO, kAudioTrack3,
+                                        {kMediaStream1}, 1, &opts);
   DetachSenderFromMediaSection("data", kDataTrack2, &opts);
-  AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack3,
-                             {kMediaStream1}, 1, &opts);
+  AttachSenderToMediaDescriptionOptions("data", MEDIA_TYPE_DATA, kDataTrack3,
+                                        {kMediaStream1}, 1, &opts);
   std::unique_ptr<SessionDescription> updated_offer(
       f1_.CreateOffer(opts, offer.get()));
 
@@ -1799,13 +1839,15 @@
 // Create an offer with simulcast video stream.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateSimulcastVideoOffer) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kSendRecv,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
   const int num_sim_layers = 3;
-  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
-                             {kMediaStream1}, num_sim_layers, &opts);
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                                        {kMediaStream1}, num_sim_layers, &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
 
   ASSERT_TRUE(offer.get() != NULL);
@@ -1822,6 +1864,190 @@
   EXPECT_EQ(static_cast<size_t>(num_sim_layers), sim_ssrc_group->ssrcs.size());
 }
 
+MATCHER(RidDescriptionEquals, "Verifies that two RidDescriptions are equal.") {
+  const RidDescription& rid1 = ::testing::get<0>(arg);
+  const RidDescription& rid2 = ::testing::get<1>(arg);
+  return rid1.rid == rid2.rid && rid1.direction == rid2.direction;
+}
+
+static void CheckSimulcastInSessionDescription(
+    const SessionDescription* description,
+    const std::string& content_name,
+    const std::vector<RidDescription>& send_rids,
+    const SimulcastLayerList& send_layers,
+    const RidDescription& receive_rid,
+    const SimulcastLayer& receive_layer) {
+  ASSERT_NE(description, nullptr);
+  const ContentInfo* content = description->GetContentByName(content_name);
+  ASSERT_NE(content, nullptr);
+  const MediaContentDescription* cd = content->media_description();
+  ASSERT_NE(cd, nullptr);
+  const StreamParamsVec& streams = cd->streams();
+  ASSERT_THAT(streams, SizeIs(1));
+  const StreamParams& stream = streams[0];
+  ASSERT_THAT(stream.ssrcs, IsEmpty());
+  EXPECT_TRUE(stream.has_rids());
+  const std::vector<RidDescription> rids = stream.rids();
+
+  EXPECT_THAT(rids, Pointwise(RidDescriptionEquals(), send_rids));
+
+  ASSERT_TRUE(cd->has_receive_stream());
+  const StreamParams& receive_stream = cd->receive_stream();
+  EXPECT_THAT(receive_stream.ssrcs, IsEmpty());
+  EXPECT_TRUE(receive_stream.has_rids());
+  ASSERT_THAT(receive_stream.rids(), SizeIs(1));
+
+  EXPECT_EQ(receive_rid.rid, receive_stream.rids()[0].rid);
+  EXPECT_EQ(receive_rid.direction, receive_stream.rids()[0].direction);
+
+  EXPECT_TRUE(cd->HasSimulcast());
+  const SimulcastDescription& simulcast = cd->simulcast_description();
+  EXPECT_THAT(simulcast.send_layers(), SizeIs(send_layers.size()));
+  EXPECT_THAT(simulcast.send_layers(), Pointwise(Eq(), send_layers));
+
+  ASSERT_THAT(simulcast.receive_layers().GetAllLayers(), SizeIs(1));
+  EXPECT_EQ(receive_layer, simulcast.receive_layers().GetAllLayers()[0]);
+}
+
+// Create an offer with spec-compliant simulcast video stream.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateCompliantSimulcastOffer) {
+  MediaSessionOptions opts;
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  RidDescription receive_rid("1", RidDirection::kReceive);
+  SimulcastLayer receive_layer(receive_rid.rid, false);
+  opts.media_description_options[0].receive_rids = {receive_rid};
+  opts.media_description_options[0].receive_simulcast_layers.AddLayer(
+      receive_layer);
+  std::vector<RidDescription> send_rids;
+  send_rids.push_back(RidDescription("f", RidDirection::kSend));
+  send_rids.push_back(RidDescription("h", RidDirection::kSend));
+  send_rids.push_back(RidDescription("q", RidDirection::kSend));
+  SimulcastLayerList simulcast_layers;
+  simulcast_layers.AddLayer(SimulcastLayer(send_rids[0].rid, false));
+  simulcast_layers.AddLayer(SimulcastLayer(send_rids[1].rid, true));
+  simulcast_layers.AddLayer(SimulcastLayer(send_rids[2].rid, false));
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                                        {kMediaStream1}, send_rids,
+                                        simulcast_layers, 0, &opts);
+  std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
+
+  CheckSimulcastInSessionDescription(offer.get(), "video", send_rids,
+                                     simulcast_layers, receive_rid,
+                                     receive_layer);
+}
+
+// Create an offer that signals RIDs (not SSRCs) without Simulcast.
+// In this scenario, RIDs do not need to be negotiated (there is only one).
+TEST_F(MediaSessionDescriptionFactoryTest, TestOfferWithRidsNoSimulcast) {
+  MediaSessionOptions opts;
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  RidDescription rid("f", RidDirection::kSend);
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                                        {kMediaStream1}, {rid},
+                                        SimulcastLayerList(), 0, &opts);
+  std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
+
+  ASSERT_NE(offer.get(), nullptr);
+  const ContentInfo* content = offer->GetContentByName("video");
+  ASSERT_NE(content, nullptr);
+  const MediaContentDescription* cd = content->media_description();
+  ASSERT_NE(cd, nullptr);
+  EXPECT_FALSE(cd->has_receive_stream());
+  const StreamParamsVec& streams = cd->streams();
+  ASSERT_THAT(streams, SizeIs(1));
+  const StreamParams& stream = streams[0];
+  ASSERT_THAT(stream.ssrcs, IsEmpty());
+  EXPECT_FALSE(stream.has_rids());
+  EXPECT_FALSE(cd->HasSimulcast());
+}
+
+// Create an answer with spec-compliant simulcast video stream.
+// In this scenario, the SFU is the caller requesting that we send Simulcast.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateCompliantSimulcastAnswer) {
+  MediaSessionOptions offer_opts;
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &offer_opts);
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                                        {kMediaStream1}, 1, &offer_opts);
+  std::unique_ptr<SessionDescription> offer =
+      f1_.CreateOffer(offer_opts, nullptr);
+
+  MediaSessionOptions answer_opts;
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &answer_opts);
+
+  RidDescription receive_rid("1", RidDirection::kReceive);
+  SimulcastLayer receive_layer(receive_rid.rid, false);
+  answer_opts.media_description_options[0].receive_rids = {receive_rid};
+  answer_opts.media_description_options[0].receive_simulcast_layers.AddLayer(
+      receive_layer);
+  std::vector<RidDescription> rid_descriptions{
+      RidDescription("f", RidDirection::kSend),
+      RidDescription("h", RidDirection::kSend),
+      RidDescription("q", RidDirection::kSend),
+  };
+  SimulcastLayerList simulcast_layers;
+  simulcast_layers.AddLayer(SimulcastLayer(rid_descriptions[0].rid, false));
+  simulcast_layers.AddLayer(SimulcastLayer(rid_descriptions[1].rid, true));
+  simulcast_layers.AddLayer(SimulcastLayer(rid_descriptions[2].rid, false));
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                                        {kMediaStream1}, rid_descriptions,
+                                        simulcast_layers, 0, &answer_opts);
+  std::unique_ptr<SessionDescription> answer =
+      f2_.CreateAnswer(offer.get(), answer_opts, nullptr);
+
+  CheckSimulcastInSessionDescription(answer.get(), "video", rid_descriptions,
+                                     simulcast_layers, receive_rid,
+                                     receive_layer);
+}
+
+// Create an answer that signals RIDs (not SSRCs) without Simulcast.
+// In this scenario, RIDs do not need to be negotiated (there is only one).
+// Note that RID Direction is not the same as the transceiver direction.
+TEST_F(MediaSessionDescriptionFactoryTest, TestAnswerWithRidsNoSimulcast) {
+  MediaSessionOptions offer_opts;
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &offer_opts);
+  RidDescription rid_offer("f", RidDirection::kSend);
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                                        {kMediaStream1}, {rid_offer},
+                                        SimulcastLayerList(), 0, &offer_opts);
+  std::unique_ptr<SessionDescription> offer =
+      f1_.CreateOffer(offer_opts, nullptr);
+
+  MediaSessionOptions answer_opts;
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &answer_opts);
+
+  RidDescription rid_answer("f", RidDirection::kReceive);
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                                        {kMediaStream1}, {rid_answer},
+                                        SimulcastLayerList(), 0, &answer_opts);
+  std::unique_ptr<SessionDescription> answer =
+      f2_.CreateAnswer(offer.get(), answer_opts, nullptr);
+
+  ASSERT_NE(answer.get(), nullptr);
+  const ContentInfo* content = offer->GetContentByName("video");
+  ASSERT_NE(content, nullptr);
+  const MediaContentDescription* cd = content->media_description();
+  ASSERT_NE(cd, nullptr);
+  EXPECT_FALSE(cd->has_receive_stream());
+  const StreamParamsVec& streams = cd->streams();
+  ASSERT_THAT(streams, SizeIs(1));
+  const StreamParams& stream = streams[0];
+  ASSERT_THAT(stream.ssrcs, IsEmpty());
+  EXPECT_FALSE(stream.has_rids());
+  EXPECT_FALSE(cd->HasSimulcast());
+}
+
 // Create an audio and video answer to a standard video offer with:
 // - one video track
 // - two audio tracks
@@ -1830,35 +2056,41 @@
 // adding a new video track and removes one of the audio tracks.
 TEST_F(MediaSessionDescriptionFactoryTest, TestCreateMultiStreamVideoAnswer) {
   MediaSessionOptions offer_opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &offer_opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &offer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &offer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &offer_opts);
   offer_opts.data_channel_type = cricket::DCT_RTP;
-  AddMediaSection(MEDIA_TYPE_DATA, "data", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &offer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_DATA, "data",
+                             RtpTransceiverDirection::kRecvOnly, 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", RtpTransceiverDirection::kSendRecv,
-                  kActive, &answer_opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kSendRecv,
-                  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);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &answer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &answer_opts);
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, kVideoTrack1,
+                                        {kMediaStream1}, 1, &answer_opts);
+  AttachSenderToMediaDescriptionOptions("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
+                                        {kMediaStream1}, 1, &answer_opts);
+  AttachSenderToMediaDescriptionOptions("audio", MEDIA_TYPE_AUDIO, kAudioTrack2,
+                                        {kMediaStream1}, 1, &answer_opts);
 
-  AddMediaSection(MEDIA_TYPE_DATA, "data", RtpTransceiverDirection::kSendRecv,
-                  kActive, &answer_opts);
-  AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack1,
-                             {kMediaStream1}, 1, &answer_opts);
-  AttachSenderToMediaSection("data", MEDIA_TYPE_DATA, kDataTrack2,
-                             {kMediaStream1}, 1, &answer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_DATA, "data",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &answer_opts);
+  AttachSenderToMediaDescriptionOptions("data", MEDIA_TYPE_DATA, kDataTrack1,
+                                        {kMediaStream1}, 1, &answer_opts);
+  AttachSenderToMediaDescriptionOptions("data", MEDIA_TYPE_DATA, kDataTrack2,
+                                        {kMediaStream1}, 1, &answer_opts);
   answer_opts.data_channel_type = cricket::DCT_RTP;
 
   std::unique_ptr<SessionDescription> answer =
@@ -1923,8 +2155,8 @@
 
   // 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);
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, kVideoTrack2,
+                                        {kMediaStream2}, 1, &answer_opts);
   DetachSenderFromMediaSection("audio", kAudioTrack2, &answer_opts);
   DetachSenderFromMediaSection("data", kDataTrack2, &answer_opts);
   std::unique_ptr<SessionDescription> updated_answer(
@@ -2030,8 +2262,9 @@
   f2_.set_video_codecs({});
 
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "a0", RtpTransceiverDirection::kSendRecv,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "a0",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, nullptr);
@@ -2056,8 +2289,9 @@
   f2_.set_audio_codecs({}, {});
 
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "v0", RtpTransceiverDirection::kSendRecv,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "v0",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
   auto answer = f2_.CreateAnswer(offer.get(), opts, nullptr);
 
@@ -2083,8 +2317,9 @@
   // Perform initial offer/answer in reverse (|f2_| as offerer) so that the
   // second offer/answer is forward (|f1_| as offerer).
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "a0", RtpTransceiverDirection::kSendRecv,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "a0",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
   std::unique_ptr<SessionDescription> offer = f2_.CreateOffer(opts, nullptr);
   std::unique_ptr<SessionDescription> answer =
       f1_.CreateAnswer(offer.get(), opts, nullptr);
@@ -2113,8 +2348,9 @@
   // Perform initial offer/answer in reverse (|f2_| as offerer) so that the
   // second offer/answer is forward (|f1_| as offerer).
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "v0", RtpTransceiverDirection::kSendRecv,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "v0",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
   std::unique_ptr<SessionDescription> offer = f2_.CreateOffer(opts, nullptr);
   std::unique_ptr<SessionDescription> answer =
       f1_.CreateAnswer(offer.get(), opts, nullptr);
@@ -2139,8 +2375,9 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        RespondentCreatesOfferAfterCreatingAnswerWithRtx) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
   std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
   // This creates rtx for H264 with the payload type |f1_| uses.
   AddRtxCodec(VideoCodec::CreateRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs);
@@ -2188,8 +2425,9 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        RespondentCreatesOfferAfterCreatingAnswerWithRemappedRtxPayloadType) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
   // We specifically choose different preferred payload types for VP8 to
   // trigger the issue.
   cricket::VideoCodec vp8_offerer(100, "VP8");
@@ -2246,8 +2484,9 @@
   f1_.set_video_codecs(f1_codecs);
 
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
 
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, NULL);
   std::unique_ptr<SessionDescription> answer =
@@ -2336,8 +2575,9 @@
 // Test that RTX is ignored when there is no associated payload type parameter.
 TEST_F(MediaSessionDescriptionFactoryTest, RtxWithoutApt) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
   std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
   // This creates RTX without associated payload type parameter.
   AddRtxCodec(VideoCodec(126, cricket::kRtxCodecName), &f1_codecs);
@@ -2379,8 +2619,9 @@
 // type doesn't match the local value.
 TEST_F(MediaSessionDescriptionFactoryTest, FilterOutRtxIfAptDoesntMatch) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
   std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
   // This creates RTX for H264 in sender.
   AddRtxCodec(VideoCodec::CreateRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs);
@@ -2409,8 +2650,9 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        FilterOutUnsupportedRtxWhenCreatingAnswer) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
   std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
   // This creates RTX for H264-SVC in sender.
   AddRtxCodec(VideoCodec::CreateRtxCodec(125, kVideoCodecs1[0].id), &f1_codecs);
@@ -2444,8 +2686,9 @@
 // to add another.
 TEST_F(MediaSessionDescriptionFactoryTest, AddSecondRtxInNewOffer) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
   std::vector<VideoCodec> f1_codecs = MAKE_VECTOR(kVideoCodecs1);
   // This creates RTX for H264 for the offerer.
   AddRtxCodec(VideoCodec::CreateRtxCodec(126, kVideoCodecs1[1].id), &f1_codecs);
@@ -2479,11 +2722,12 @@
 // generated for each simulcast ssrc and correctly grouped.
 TEST_F(MediaSessionDescriptionFactoryTest, SimSsrcsGenerateMultipleRtxSsrcs) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kSendRecv,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
   // Add simulcast streams.
-  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, "stream1",
-                             {"stream1label"}, 3, &opts);
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, "stream1",
+                                        {"stream1label"}, 3, &opts);
 
   // Use a single real codec, and then add RTX for it.
   std::vector<VideoCodec> f1_codecs;
@@ -2520,11 +2764,12 @@
 // together with a FEC-FR grouping.
 TEST_F(MediaSessionDescriptionFactoryTest, GenerateFlexfecSsrc) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kSendRecv,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
   // Add single stream.
-  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, "stream1",
-                             {"stream1label"}, 1, &opts);
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, "stream1",
+                                        {"stream1label"}, 1, &opts);
 
   // Use a single real codec, and then add FlexFEC for it.
   std::vector<VideoCodec> f1_codecs;
@@ -2560,11 +2805,12 @@
 // multiple FlexfecSenders, or through multistream protection.
 TEST_F(MediaSessionDescriptionFactoryTest, SimSsrcsGenerateNoFlexfecSsrcs) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kSendRecv,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
   // Add simulcast streams.
-  AttachSenderToMediaSection("video", MEDIA_TYPE_VIDEO, "stream1",
-                             {"stream1label"}, 3, &opts);
+  AttachSenderToMediaDescriptionOptions("video", MEDIA_TYPE_VIDEO, "stream1",
+                                        {"stream1label"}, 3, &opts);
 
   // Use a single real codec, and then add FlexFEC for it.
   std::vector<VideoCodec> f1_codecs;
@@ -2767,16 +3013,18 @@
 // ensure the TransportInfo in the SessionDescription matches what we expect.
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferAudio) {
   MediaSessionOptions options;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &options);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &options);
   TestTransportInfo(true, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestTransportInfoOfferIceRenomination) {
   MediaSessionOptions options;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &options);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &options);
   options.media_description_options[0]
       .transport_options.enable_ice_renomination = true;
   TestTransportInfo(true, options, false);
@@ -2784,8 +3032,9 @@
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoOfferAudioCurrent) {
   MediaSessionOptions options;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &options);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &options);
   TestTransportInfo(true, options, true);
 }
 
@@ -2827,16 +3076,18 @@
 
 TEST_F(MediaSessionDescriptionFactoryTest, TestTransportInfoAnswerAudio) {
   MediaSessionOptions options;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &options);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &options);
   TestTransportInfo(false, options, false);
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestTransportInfoAnswerIceRenomination) {
   MediaSessionOptions options;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &options);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &options);
   options.media_description_options[0]
       .transport_options.enable_ice_renomination = true;
   TestTransportInfo(false, options, false);
@@ -2845,8 +3096,9 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        TestTransportInfoAnswerAudioCurrent) {
   MediaSessionOptions options;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kRecvOnly,
-                  kActive, &options);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &options);
   TestTransportInfo(false, options, true);
 }
 
@@ -3139,13 +3391,16 @@
 // Test that the generated MIDs match the existing offer.
 TEST_F(MediaSessionDescriptionFactoryTest, TestMIDsMatchesExistingOffer) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio_modified",
-                  RtpTransceiverDirection::kRecvOnly, kActive, &opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video_modified",
-                  RtpTransceiverDirection::kRecvOnly, kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio_modified",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video_modified",
+                             RtpTransceiverDirection::kRecvOnly, kActive,
+                             &opts);
   opts.data_channel_type = cricket::DCT_SCTP;
-  AddMediaSection(MEDIA_TYPE_DATA, "data_modified",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_DATA, "data_modified",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
   // Create offer.
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
   std::unique_ptr<SessionDescription> updated_offer(
@@ -3168,25 +3423,29 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        CreateOfferWithMultipleAVMediaSections) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio_1",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
-  AttachSenderToMediaSection("audio_1", MEDIA_TYPE_AUDIO, kAudioTrack1,
-                             {kMediaStream1}, 1, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio_1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  AttachSenderToMediaDescriptionOptions(
+      "audio_1", MEDIA_TYPE_AUDIO, kAudioTrack1, {kMediaStream1}, 1, &opts);
 
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video_1",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
-  AttachSenderToMediaSection("video_1", MEDIA_TYPE_VIDEO, kVideoTrack1,
-                             {kMediaStream1}, 1, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video_1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  AttachSenderToMediaDescriptionOptions(
+      "video_1", MEDIA_TYPE_VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);
 
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio_2",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
-  AttachSenderToMediaSection("audio_2", MEDIA_TYPE_AUDIO, kAudioTrack2,
-                             {kMediaStream2}, 1, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio_2",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  AttachSenderToMediaDescriptionOptions(
+      "audio_2", MEDIA_TYPE_AUDIO, kAudioTrack2, {kMediaStream2}, 1, &opts);
 
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video_2",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
-  AttachSenderToMediaSection("video_2", MEDIA_TYPE_VIDEO, kVideoTrack2,
-                             {kMediaStream2}, 1, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video_2",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  AttachSenderToMediaDescriptionOptions(
+      "video_2", MEDIA_TYPE_VIDEO, kVideoTrack2, {kMediaStream2}, 1, &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
   ASSERT_TRUE(offer);
 
@@ -3223,25 +3482,29 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        CreateAnswerWithMultipleAVMediaSections) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio_1",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
-  AttachSenderToMediaSection("audio_1", MEDIA_TYPE_AUDIO, kAudioTrack1,
-                             {kMediaStream1}, 1, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio_1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  AttachSenderToMediaDescriptionOptions(
+      "audio_1", MEDIA_TYPE_AUDIO, kAudioTrack1, {kMediaStream1}, 1, &opts);
 
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video_1",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
-  AttachSenderToMediaSection("video_1", MEDIA_TYPE_VIDEO, kVideoTrack1,
-                             {kMediaStream1}, 1, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video_1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  AttachSenderToMediaDescriptionOptions(
+      "video_1", MEDIA_TYPE_VIDEO, kVideoTrack1, {kMediaStream1}, 1, &opts);
 
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio_2",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
-  AttachSenderToMediaSection("audio_2", MEDIA_TYPE_AUDIO, kAudioTrack2,
-                             {kMediaStream2}, 1, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio_2",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  AttachSenderToMediaDescriptionOptions(
+      "audio_2", MEDIA_TYPE_AUDIO, kAudioTrack2, {kMediaStream2}, 1, &opts);
 
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video_2",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
-  AttachSenderToMediaSection("video_2", MEDIA_TYPE_VIDEO, kVideoTrack2,
-                             {kMediaStream2}, 1, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video_2",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  AttachSenderToMediaDescriptionOptions(
+      "video_2", MEDIA_TYPE_VIDEO, kVideoTrack2, {kMediaStream2}, 1, &opts);
 
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
   ASSERT_TRUE(offer);
@@ -3282,10 +3545,12 @@
        CreateOfferWithMediaSectionStoppedByOfferer) {
   // Create an offer with two audio sections and one of them is stopped.
   MediaSessionOptions offer_opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio1",
-                  RtpTransceiverDirection::kSendRecv, kActive, &offer_opts);
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio2",
-                  RtpTransceiverDirection::kInactive, kStopped, &offer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &offer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio2",
+                             RtpTransceiverDirection::kInactive, kStopped,
+                             &offer_opts);
   std::unique_ptr<SessionDescription> offer =
       f1_.CreateOffer(offer_opts, nullptr);
   ASSERT_TRUE(offer);
@@ -3300,10 +3565,12 @@
        CreateAnswerWithMediaSectionStoppedByOfferer) {
   // Create an offer with two audio sections and one of them is stopped.
   MediaSessionOptions offer_opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio1",
-                  RtpTransceiverDirection::kSendRecv, kActive, &offer_opts);
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio2",
-                  RtpTransceiverDirection::kInactive, kStopped, &offer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &offer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio2",
+                             RtpTransceiverDirection::kInactive, kStopped,
+                             &offer_opts);
   std::unique_ptr<SessionDescription> offer =
       f1_.CreateOffer(offer_opts, nullptr);
   ASSERT_TRUE(offer);
@@ -3313,10 +3580,12 @@
 
   // Create an answer based on the offer.
   MediaSessionOptions answer_opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio1",
-                  RtpTransceiverDirection::kSendRecv, kActive, &answer_opts);
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio2",
-                  RtpTransceiverDirection::kSendRecv, kActive, &answer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &answer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio2",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &answer_opts);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), answer_opts, nullptr);
   ASSERT_EQ(2u, answer->contents().size());
@@ -3330,10 +3599,12 @@
        CreateAnswerWithMediaSectionRejectedByAnswerer) {
   // Create an offer with two audio sections.
   MediaSessionOptions offer_opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio1",
-                  RtpTransceiverDirection::kSendRecv, kActive, &offer_opts);
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio2",
-                  RtpTransceiverDirection::kSendRecv, kActive, &offer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &offer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio2",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &offer_opts);
   std::unique_ptr<SessionDescription> offer =
       f1_.CreateOffer(offer_opts, nullptr);
   ASSERT_TRUE(offer);
@@ -3343,10 +3614,12 @@
 
   // The answerer rejects one of the audio sections.
   MediaSessionOptions answer_opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio1",
-                  RtpTransceiverDirection::kSendRecv, kActive, &answer_opts);
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio2",
-                  RtpTransceiverDirection::kInactive, kStopped, &answer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &answer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio2",
+                             RtpTransceiverDirection::kInactive, kStopped,
+                             &answer_opts);
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), answer_opts, nullptr);
   ASSERT_EQ(2u, answer->contents().size());
@@ -3365,10 +3638,12 @@
   MediaSessionOptions opts;
   // This tests put video section first because normally audio comes first by
   // default.
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kSendRecv,
-                  kActive, &opts);
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kSendRecv,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
 
   ASSERT_TRUE(offer);
@@ -3382,10 +3657,12 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        PayloadTypesSharedByMediaSectionsOfSameType) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video1",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video2",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video2",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
   // Create an offer with two video sections using same codecs.
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
   ASSERT_TRUE(offer);
@@ -3419,10 +3696,12 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        CreateOfferRespectsCodecPreferenceOrder) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video1",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video2",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video2",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
   // Create an offer with two video sections using same codecs.
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
   ASSERT_TRUE(offer);
@@ -3453,10 +3732,12 @@
 TEST_F(MediaSessionDescriptionFactoryTest,
        CreateAnswerRespectsCodecPreferenceOrder) {
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video1",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video2",
-                  RtpTransceiverDirection::kSendRecv, kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video1",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video2",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
   // Create an offer with two video sections using same codecs.
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
   ASSERT_TRUE(offer);
@@ -3509,10 +3790,12 @@
   f2_.set_video_codecs(video_codecs2);
 
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kSendRecv,
-                  kActive, &opts);
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kSendRecv,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
 
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
   ASSERT_TRUE(offer);
@@ -3558,8 +3841,9 @@
   f2_.set_video_codecs({h264_pm1});
 
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kSendRecv,
-                  kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
+                             RtpTransceiverDirection::kSendRecv, kActive,
+                             &opts);
 
   std::unique_ptr<SessionDescription> offer = f1_.CreateOffer(opts, nullptr);
   ASSERT_TRUE(offer);
@@ -3764,12 +4048,13 @@
   sf.set_audio_codecs(send_codecs, recv_codecs);
 
   MediaSessionOptions opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", direction, kActive, &opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio", direction, kActive,
+                             &opts);
 
   if (direction == RtpTransceiverDirection::kSendRecv ||
       direction == RtpTransceiverDirection::kSendOnly) {
-    AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
-                               {kMediaStream1}, 1, &opts);
+    AttachSenderToMediaDescriptionOptions(
+        "audio", MEDIA_TYPE_AUDIO, kAudioTrack1, {kMediaStream1}, 1, &opts);
   }
 
   std::unique_ptr<SessionDescription> offer = sf.CreateOffer(opts, NULL);
@@ -3862,12 +4147,13 @@
       VectorFromIndices(kOfferAnswerCodecs, kAnswerRecvCodecs));
 
   MediaSessionOptions offer_opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", offer_direction, kActive,
-                  &offer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio", offer_direction,
+                             kActive, &offer_opts);
 
   if (webrtc::RtpTransceiverDirectionHasSend(offer_direction)) {
-    AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
-                               {kMediaStream1}, 1, &offer_opts);
+    AttachSenderToMediaDescriptionOptions("audio", MEDIA_TYPE_AUDIO,
+                                          kAudioTrack1, {kMediaStream1}, 1,
+                                          &offer_opts);
   }
 
   std::unique_ptr<SessionDescription> offer =
@@ -3875,12 +4161,13 @@
   ASSERT_TRUE(offer.get() != NULL);
 
   MediaSessionOptions answer_opts;
-  AddMediaSection(MEDIA_TYPE_AUDIO, "audio", answer_direction, kActive,
-                  &answer_opts);
+  AddMediaDescriptionOptions(MEDIA_TYPE_AUDIO, "audio", answer_direction,
+                             kActive, &answer_opts);
 
   if (webrtc::RtpTransceiverDirectionHasSend(answer_direction)) {
-    AttachSenderToMediaSection("audio", MEDIA_TYPE_AUDIO, kAudioTrack1,
-                               {kMediaStream1}, 1, &answer_opts);
+    AttachSenderToMediaDescriptionOptions("audio", MEDIA_TYPE_AUDIO,
+                                          kAudioTrack1, {kMediaStream1}, 1,
+                                          &answer_opts);
   }
   std::unique_ptr<SessionDescription> answer =
       answer_factory.CreateAnswer(offer.get(), answer_opts, NULL);
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index a70ab2e..7f7cebd 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -58,8 +58,9 @@
 using cricket::ContentInfo;
 using cricket::ContentInfos;
 using cricket::MediaContentDescription;
-using cricket::SessionDescription;
 using cricket::MediaProtocolType;
+using cricket::SessionDescription;
+using cricket::SimulcastLayerList;
 using cricket::TransportInfo;
 
 using cricket::LOCAL_PORT_TYPE;
@@ -170,7 +171,7 @@
 }
 
 // Add options to |[audio/video]_media_description_options| from |senders|.
-void AddRtpSenderOptions(
+void AddPlanBRtpSenderOptions(
     const std::vector<rtc::scoped_refptr<
         RtpSenderProxyWithInternal<RtpSenderInternal>>>& senders,
     cricket::MediaDescriptionOptions* audio_media_description_options,
@@ -186,8 +187,8 @@
       RTC_DCHECK(sender->media_type() == cricket::MEDIA_TYPE_VIDEO);
       if (video_media_description_options) {
         video_media_description_options->AddVideoSender(
-            sender->id(), sender->internal()->stream_ids(),
-            num_sim_layers);
+            sender->id(), sender->internal()->stream_ids(), {},
+            SimulcastLayerList(), num_sim_layers);
       }
     }
   }
@@ -4014,9 +4015,10 @@
       !video_index ? nullptr
                    : &session_options->media_description_options[*video_index];
 
-  AddRtpSenderOptions(GetSendersInternal(), audio_media_description_options,
-                      video_media_description_options,
-                      offer_answer_options.num_simulcast_layers);
+  AddPlanBRtpSenderOptions(GetSendersInternal(),
+                           audio_media_description_options,
+                           video_media_description_options,
+                           offer_answer_options.num_simulcast_layers);
 }
 
 static cricket::MediaDescriptionOptions
@@ -4241,9 +4243,10 @@
       !video_index ? nullptr
                    : &session_options->media_description_options[*video_index];
 
-  AddRtpSenderOptions(GetSendersInternal(), audio_media_description_options,
-                      video_media_description_options,
-                      offer_answer_options.num_simulcast_layers);
+  AddPlanBRtpSenderOptions(GetSendersInternal(),
+                           audio_media_description_options,
+                           video_media_description_options,
+                           offer_answer_options.num_simulcast_layers);
 }
 
 void PeerConnection::GetOptionsForUnifiedPlanAnswer(
diff --git a/pc/simulcastdescription.cc b/pc/simulcastdescription.cc
index 8fff520..61849ce 100644
--- a/pc/simulcastdescription.cc
+++ b/pc/simulcastdescription.cc
@@ -20,6 +20,10 @@
   RTC_DCHECK(!rid.empty());
 }
 
+bool SimulcastLayer::operator==(const SimulcastLayer& other) const {
+  return rid == other.rid && is_paused == other.is_paused;
+}
+
 void SimulcastLayerList::AddLayer(const SimulcastLayer& layer) {
   list_.push_back({layer});
 }
diff --git a/pc/simulcastdescription.h b/pc/simulcastdescription.h
index 9f15f78..0781a6a 100644
--- a/pc/simulcastdescription.h
+++ b/pc/simulcastdescription.h
@@ -25,6 +25,7 @@
 
   SimulcastLayer(const SimulcastLayer& other) = default;
   SimulcastLayer& operator=(const SimulcastLayer& other) = default;
+  bool operator==(const SimulcastLayer& other) const;
 
   std::string rid;
   bool is_paused;
@@ -48,6 +49,12 @@
 //            {SimulcastLayer("4", false), SimulcastLayer("5", false});
 class SimulcastLayerList final {
  public:
+  // Type definitions required by a container.
+  typedef size_t size_type;
+  typedef std::vector<SimulcastLayer> value_type;
+  typedef std::vector<std::vector<SimulcastLayer>>::const_iterator
+      const_iterator;
+
   // Use to add a layer when there will be no alternatives.
   void AddLayer(const SimulcastLayer& layer);
 
@@ -57,13 +64,9 @@
 
   // Read-only access to the contents.
   // Note: This object does not allow removal of layers.
-  std::vector<std::vector<SimulcastLayer>>::const_iterator begin() const {
-    return list_.begin();
-  }
+  const_iterator begin() const { return list_.begin(); }
 
-  std::vector<std::vector<SimulcastLayer>>::const_iterator end() const {
-    return list_.end();
-  }
+  const_iterator end() const { return list_.end(); }
 
   const std::vector<SimulcastLayer>& operator[](size_t index) const;
 
diff --git a/pc/unique_id_generator.cc b/pc/unique_id_generator.cc
new file mode 100644
index 0000000..b381d44
--- /dev/null
+++ b/pc/unique_id_generator.cc
@@ -0,0 +1,64 @@
+/*
+ *  Copyright 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "pc/unique_id_generator.h"
+
+#include <limits>
+#include <vector>
+
+#include "rtc_base/helpers.h"
+#include "rtc_base/string_to_number.h"
+#include "rtc_base/stringencode.h"
+
+namespace webrtc {
+
+namespace {
+UniqueNumberGenerator<uint32_t> CreateUniqueNumberGenerator(
+    rtc::ArrayView<std::string> known_ids) {
+  std::vector<uint32_t> known_ints;
+  for (const std::string& str : known_ids) {
+    absl::optional<uint32_t> value = rtc::StringToNumber<uint32_t>(str);
+    if (value.has_value()) {
+      known_ints.push_back(value.value());
+    }
+  }
+  return UniqueNumberGenerator<uint32_t>(known_ints);
+}
+}  // namespace
+
+UniqueRandomIdGenerator::UniqueRandomIdGenerator() : known_ids_() {}
+UniqueRandomIdGenerator::UniqueRandomIdGenerator(
+    rtc::ArrayView<uint32_t> known_ids)
+    : known_ids_(known_ids.begin(), known_ids.end()) {}
+
+UniqueRandomIdGenerator::~UniqueRandomIdGenerator() = default;
+
+uint32_t UniqueRandomIdGenerator::GenerateId() {
+  while (true) {
+    RTC_CHECK_LT(known_ids_.size(), std::numeric_limits<uint32_t>::max());
+    auto pair = known_ids_.insert(rtc::CreateRandomNonZeroId());
+    if (pair.second) {
+      return *pair.first;
+    }
+  }
+}
+
+UniqueStringGenerator::UniqueStringGenerator() : unique_number_generator_() {}
+UniqueStringGenerator::UniqueStringGenerator(
+    rtc::ArrayView<std::string> known_ids)
+    : unique_number_generator_(CreateUniqueNumberGenerator(known_ids)) {}
+
+UniqueStringGenerator::~UniqueStringGenerator() = default;
+
+std::string UniqueStringGenerator::GenerateString() {
+  return rtc::ToString(unique_number_generator_.GenerateNumber());
+}
+
+}  // namespace webrtc
diff --git a/pc/unique_id_generator.h b/pc/unique_id_generator.h
new file mode 100644
index 0000000..15ba794
--- /dev/null
+++ b/pc/unique_id_generator.h
@@ -0,0 +1,122 @@
+/*
+ *  Copyright 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef PC_UNIQUE_ID_GENERATOR_H_
+#define PC_UNIQUE_ID_GENERATOR_H_
+
+#include <limits>
+#include <set>
+#include <string>
+
+#include "api/array_view.h"
+
+namespace webrtc {
+
+// This class will generate numbers. A common use case is for identifiers.
+// The generated numbers will be unique, in the local scope of the generator.
+// This means that a generator will never generate the same number twice.
+// The generator can also be initialized with a sequence of known ids.
+// In such a case, it will never generate an id from that list.
+// Recommendedations:
+//  * Prefer unsigned types.
+//  * Prefer larger types (uint8_t will run out quickly).
+template <typename TIntegral>
+class UniqueNumberGenerator {
+ public:
+  typedef TIntegral value_type;
+  UniqueNumberGenerator();
+  // Creates a generator that will never return any value from the given list.
+  explicit UniqueNumberGenerator(rtc::ArrayView<TIntegral> known_ids);
+  ~UniqueNumberGenerator();
+
+  // Generates a number that this generator has never produced before.
+  // If there are no available numbers to generate, this method will fail
+  // with an |RTC_CHECK|.
+  TIntegral GenerateNumber();
+  TIntegral operator()() { return GenerateNumber(); }
+
+ private:
+  static_assert(std::is_integral<TIntegral>::value, "Must be integral type.");
+  TIntegral counter_;
+  // This class can be further optimized by removing the known_ids_ set when
+  // the generator was created without a sequence of ids to ignore.
+  // In such a case, the implementation uses a counter which is sufficient to
+  // prevent repetitions of the generated values.
+  std::set<TIntegral> known_ids_;
+};
+
+// This class will generate unique ids. Ids are 32 bit unsigned integers.
+// The generated ids will be unique, in the local scope of the generator.
+// This means that a generator will never generate the same id twice.
+// The generator can also be initialized with a sequence of known ids.
+// In such a case, it will never generate an id from that list.
+class UniqueRandomIdGenerator {
+ public:
+  typedef uint32_t value_type;
+  UniqueRandomIdGenerator();
+  // Create a generator that will never return any value from the given list.
+  explicit UniqueRandomIdGenerator(rtc::ArrayView<uint32_t> known_ids);
+  ~UniqueRandomIdGenerator();
+
+  // Generates a random id that this generator has never produced before.
+  // This method becomes more expensive with each use, as the probability of
+  // collision for the randomly generated numbers increases.
+  uint32_t GenerateId();
+  uint32_t operator()() { return GenerateId(); }
+
+ private:
+  std::set<uint32_t> known_ids_;
+};
+
+// This class will generate strings. A common use case is for identifiers.
+// The generated strings will be unique, in the local scope of the generator.
+// This means that a generator will never generate the same string twice.
+// The generator can also be initialized with a sequence of known ids.
+// In such a case, it will never generate an id from that list.
+class UniqueStringGenerator {
+ public:
+  typedef std::string value_type;
+  UniqueStringGenerator();
+  explicit UniqueStringGenerator(rtc::ArrayView<std::string> known_ids);
+  ~UniqueStringGenerator();
+
+  std::string GenerateString();
+  std::string operator()() { return GenerateString(); }
+
+ private:
+  // This implementation will be simple and will generate "0", "1", ...
+  UniqueNumberGenerator<uint32_t> unique_number_generator_;
+};
+
+template <typename TIntegral>
+UniqueNumberGenerator<TIntegral>::UniqueNumberGenerator() : counter_(0) {}
+
+template <typename TIntegral>
+UniqueNumberGenerator<TIntegral>::UniqueNumberGenerator(
+    rtc::ArrayView<TIntegral> known_ids)
+    : counter_(0), known_ids_(known_ids.begin(), known_ids.end()) {}
+
+template <typename TIntegral>
+UniqueNumberGenerator<TIntegral>::~UniqueNumberGenerator() {}
+
+template <typename TIntegral>
+TIntegral UniqueNumberGenerator<TIntegral>::GenerateNumber() {
+  while (true) {
+    RTC_CHECK_LT(counter_, std::numeric_limits<TIntegral>::max());
+    auto pair = known_ids_.insert(counter_++);
+    if (pair.second) {
+      return *pair.first;
+    }
+  }
+}
+
+}  // namespace webrtc
+
+#endif  // PC_UNIQUE_ID_GENERATOR_H_
diff --git a/pc/unique_id_generator_unittest.cc b/pc/unique_id_generator_unittest.cc
new file mode 100644
index 0000000..86d831b
--- /dev/null
+++ b/pc/unique_id_generator_unittest.cc
@@ -0,0 +1,79 @@
+/*
+ *  Copyright 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "algorithm"
+#include "string"
+#include "vector"
+
+#include "api/array_view.h"
+#include "pc/unique_id_generator.h"
+#include "rtc_base/gunit.h"
+#include "rtc_base/helpers.h"
+#include "test/gmock.h"
+
+using ::testing::IsEmpty;
+using ::testing::Test;
+
+namespace webrtc {
+
+template <typename Generator>
+class UniqueIdGeneratorTest : public Test {};
+
+using test_types = ::testing::Types<UniqueNumberGenerator<uint8_t>,
+                                    UniqueNumberGenerator<uint16_t>,
+                                    UniqueNumberGenerator<uint32_t>,
+                                    UniqueRandomIdGenerator,
+                                    UniqueStringGenerator>;
+
+TYPED_TEST_CASE(UniqueIdGeneratorTest, test_types);
+
+TYPED_TEST(UniqueIdGeneratorTest, ElementsDoNotRepeat) {
+  typedef TypeParam Generator;
+  const size_t num_elements = 255;
+  Generator generator;
+  std::vector<typename Generator::value_type> values;
+  for (size_t i = 0; i < num_elements; i++) {
+    values.push_back(generator());
+  }
+
+  EXPECT_EQ(num_elements, values.size());
+  // Use a set to check uniqueness.
+  std::set<typename Generator::value_type> set(values.begin(), values.end());
+  EXPECT_EQ(values.size(), set.size()) << "Returned values were not unique.";
+}
+
+TYPED_TEST(UniqueIdGeneratorTest, KnownElementsAreNotGenerated) {
+  typedef TypeParam Generator;
+  const size_t num_elements = 100;
+  rtc::InitRandom(0);
+  Generator generator1;
+  std::vector<typename Generator::value_type> known_values;
+  for (size_t i = 0; i < num_elements; i++) {
+    known_values.push_back(generator1());
+  }
+  EXPECT_EQ(num_elements, known_values.size());
+
+  rtc::InitRandom(0);
+  Generator generator2(known_values);
+
+  std::vector<typename Generator::value_type> values;
+  for (size_t i = 0; i < num_elements; i++) {
+    values.push_back(generator2());
+  }
+  EXPECT_THAT(values, ::testing::SizeIs(num_elements));
+  std::sort(values.begin(), values.end());
+  std::sort(known_values.begin(), known_values.end());
+  std::vector<typename Generator::value_type> intersection;
+  std::set_intersection(values.begin(), values.end(), known_values.begin(),
+                        known_values.end(), std::back_inserter(intersection));
+  EXPECT_THAT(intersection, IsEmpty());
+}
+
+}  // namespace webrtc