Add support for enabling simulcast in "Plan B" using MediaConstraints.

BUG=webrtc:9655

Change-Id: Ieb5fe5d97b6d4381608a51593bca5423979d1b9f
Reviewed-on: https://webrtc-review.googlesource.com/95481
Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
Reviewed-by: Seth Hampson <shampson@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#24424}
diff --git a/api/mediaconstraintsinterface.cc b/api/mediaconstraintsinterface.cc
index c892606..80c447d 100644
--- a/api/mediaconstraintsinterface.cc
+++ b/api/mediaconstraintsinterface.cc
@@ -146,6 +146,9 @@
     "googCpuOveruseDetection";
 const char MediaConstraintsInterface::kPayloadPadding[] = "googPayloadPadding";
 
+const char MediaConstraintsInterface::kNumSimulcastLayers[] =
+    "googNumSimulcastLayers";
+
 // Set |value| to the value associated with the first appearance of |key|, or
 // return false if |key| is not found.
 bool MediaConstraintsInterface::Constraints::FindFirst(
@@ -301,6 +304,13 @@
     offer_answer_options->ice_restart = value;
   }
 
+  int layers;
+  if (FindConstraint(constraints,
+                     MediaConstraintsInterface::kNumSimulcastLayers,
+                     &layers, &mandatory_constraints_satisfied)) {
+    offer_answer_options->num_simulcast_layers = layers;
+  }
+
   return mandatory_constraints_satisfied == constraints->GetMandatory().size();
 }
 
diff --git a/api/mediaconstraintsinterface.h b/api/mediaconstraintsinterface.h
index 3c85dfb..6128e6a 100644
--- a/api/mediaconstraintsinterface.h
+++ b/api/mediaconstraintsinterface.h
@@ -119,6 +119,11 @@
   // stripped by Chrome before passed down to Libjingle.
   static const char kInternalConstraintPrefix[];
 
+  // Specifies number of simulcast layers for all video tracks
+  // with a Plan B offer/answer
+  // (see RTCOfferAnswerOptions::num_simulcast_layers).
+  static const char kNumSimulcastLayers[];
+
   virtual ~MediaConstraintsInterface() = default;
 
   virtual const Constraints& GetMandatory() const = 0;
diff --git a/api/peerconnectioninterface.h b/api/peerconnectioninterface.h
index 84a0501..2b94ee8 100644
--- a/api/peerconnectioninterface.h
+++ b/api/peerconnectioninterface.h
@@ -596,6 +596,9 @@
     // confused with RTCP mux (multiplexing RTP and RTCP together).
     bool use_rtp_mux = true;
 
+    // This will apply to all video tracks with a Plan B SDP offer/answer.
+    int num_simulcast_layers = 1;
+
     RTCOfferAnswerOptions() = default;
 
     RTCOfferAnswerOptions(int offer_to_receive_video,
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index 9155b71..7bc2570 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -173,7 +173,8 @@
     const std::vector<rtc::scoped_refptr<
         RtpSenderProxyWithInternal<RtpSenderInternal>>>& senders,
     cricket::MediaDescriptionOptions* audio_media_description_options,
-    cricket::MediaDescriptionOptions* video_media_description_options) {
+    cricket::MediaDescriptionOptions* video_media_description_options,
+    int num_sim_layers) {
   for (const auto& sender : senders) {
     if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) {
       if (audio_media_description_options) {
@@ -184,7 +185,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(), 1);
+            sender->id(), sender->internal()->stream_ids(),
+            num_sim_layers);
       }
     }
   }
@@ -3686,7 +3688,8 @@
                    : &session_options->media_description_options[*video_index];
 
   AddRtpSenderOptions(GetSendersInternal(), audio_media_description_options,
-                      video_media_description_options);
+                      video_media_description_options,
+                      offer_answer_options.num_simulcast_layers);
 }
 
 // Find a new MID that is not already in |used_mids|, then add it to |used_mids|
@@ -3910,7 +3913,8 @@
                    : &session_options->media_description_options[*video_index];
 
   AddRtpSenderOptions(GetSendersInternal(), audio_media_description_options,
-                      video_media_description_options);
+                      video_media_description_options,
+                      offer_answer_options.num_simulcast_layers);
 }
 
 void PeerConnection::GetOptionsForUnifiedPlanAnswer(
diff --git a/pc/peerconnection_media_unittest.cc b/pc/peerconnection_media_unittest.cc
index cc7ae9a..6f4fe1e 100644
--- a/pc/peerconnection_media_unittest.cc
+++ b/pc/peerconnection_media_unittest.cc
@@ -273,6 +273,56 @@
   EXPECT_EQ(0u, callee_video->recv_streams().size());
 }
 
+// Test enabling of simulcast with Plan B semantics.
+// This test creating an offer.
+TEST_F(PeerConnectionMediaTestPlanB, SimulcastOffer) {
+  auto caller = CreatePeerConnection();
+  auto caller_video_track = caller->AddVideoTrack("v");
+  RTCOfferAnswerOptions options;
+  options.num_simulcast_layers = 3;
+  auto offer = caller->CreateOffer(options);
+  auto* description = cricket::GetFirstMediaContent(
+      offer->description(),
+      cricket::MEDIA_TYPE_VIDEO)->media_description();
+  ASSERT_EQ(1u, description->streams().size());
+  ASSERT_TRUE(description->streams()[0].get_ssrc_group("SIM"));
+  EXPECT_EQ(3u, description->streams()[0].get_ssrc_group("SIM")->ssrcs.size());
+
+  // Check that it actually creates simulcast aswell.
+  caller->SetLocalDescription(std::move(offer));
+  auto senders = caller->pc()->GetSenders();
+  ASSERT_EQ(1u, senders.size());
+  EXPECT_EQ(cricket::MediaType::MEDIA_TYPE_VIDEO, senders[0]->media_type());
+  EXPECT_EQ(3u, senders[0]->GetParameters().encodings.size());
+}
+
+// Test enabling of simulcast with Plan B semantics.
+// This test creating an answer.
+TEST_F(PeerConnectionMediaTestPlanB, SimulcastAnswer) {
+  auto caller = CreatePeerConnection();
+  caller->AddVideoTrack("v0");
+  auto offer = caller->CreateOffer();
+  auto callee = CreatePeerConnection();
+  auto callee_video_track = callee->AddVideoTrack("v1");
+  ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
+  RTCOfferAnswerOptions options;
+  options.num_simulcast_layers = 3;
+  auto answer = callee->CreateAnswer(options);
+  auto* description = cricket::GetFirstMediaContent(
+      answer->description(),
+      cricket::MEDIA_TYPE_VIDEO)->media_description();
+  ASSERT_EQ(1u, description->streams().size());
+  ASSERT_TRUE(description->streams()[0].get_ssrc_group("SIM"));
+  EXPECT_EQ(3u, description->streams()[0].get_ssrc_group("SIM")->ssrcs.size());
+
+  // Check that it actually creates simulcast aswell.
+  callee->SetLocalDescription(std::move(answer));
+  auto senders = callee->pc()->GetSenders();
+  ASSERT_EQ(1u, senders.size());
+  EXPECT_EQ(cricket::MediaType::MEDIA_TYPE_VIDEO, senders[0]->media_type());
+  EXPECT_EQ(3u, senders[0]->GetParameters().encodings.size());
+}
+
 // Test that stopping the callee transceivers causes the media channels to be
 // destroyed on the callee after calling SetLocalDescription on the local
 // answer.