Provide mechanism to make codec decisions per-transceiver

This provides a way to tell the SDP generator to use a specific list
of codecs, rather than trying to compute what list to send.

Preparatory to making codec decisions per-transceiver.

Bug: webrtc:42226302
Change-Id: I1b7d4e55ed7a0546394b74820b4e51434ef86ad9
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/349620
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Tony Herre <herre@google.com>
Cr-Commit-Position: refs/heads/main@{#42247}
diff --git a/pc/media_session.cc b/pc/media_session.cc
index bd36c37..e18a0eb 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -1036,10 +1036,7 @@
                        const webrtc::FieldTrialsView& field_trials) {
   RTC_DCHECK(offer->type() == MEDIA_TYPE_AUDIO ||
              offer->type() == MEDIA_TYPE_VIDEO);
-  std::vector<Codec> negotiated_codecs;
-  NegotiateCodecs(local_codecs, offer->codecs(), &negotiated_codecs,
-                  media_description_options.codec_preferences.empty());
-  answer->AddCodecs(negotiated_codecs);
+  answer->AddCodecs(local_codecs);
   answer->set_protocol(offer->protocol());
   if (!AddStreamParams(media_description_options.sender_options,
                        session_options.rtcp_cname, ssrc_generator,
@@ -2013,6 +2010,10 @@
   return RTCError::OK();
 }
 
+// Add the RTP description to the SessionDescription.
+// If media_description_options.codecs_to_include is set, those codecs are used.
+//
+// If it is not set, the codecs used are computed based on:
 // `codecs` = set of all possible codecs that can be used, with correct
 // payload type mappings
 //
@@ -2039,17 +2040,23 @@
   RTC_DCHECK(media_description_options.type == MEDIA_TYPE_AUDIO ||
              media_description_options.type == MEDIA_TYPE_VIDEO);
 
-  const std::vector<Codec>& supported_codecs =
-      media_description_options.type == MEDIA_TYPE_AUDIO
-          ? GetAudioCodecsForOffer(media_description_options.direction)
-          : GetVideoCodecsForOffer(media_description_options.direction);
-  webrtc::RTCErrorOr<std::vector<Codec>> error_or_filtered_codecs =
-      GetNegotiatedCodecsForOffer(media_description_options, session_options,
-                                  current_content, codecs, supported_codecs);
-  if (!error_or_filtered_codecs.ok()) {
-    return error_or_filtered_codecs.MoveError();
+  std::vector<Codec> codecs_to_include;
+  if (media_description_options.codecs_to_include.empty()) {
+    const std::vector<Codec>& supported_codecs =
+        media_description_options.type == MEDIA_TYPE_AUDIO
+            ? GetAudioCodecsForOffer(media_description_options.direction)
+            : GetVideoCodecsForOffer(media_description_options.direction);
+    webrtc::RTCErrorOr<std::vector<Codec>> error_or_filtered_codecs =
+        GetNegotiatedCodecsForOffer(media_description_options, session_options,
+                                    current_content, codecs, supported_codecs);
+    if (!error_or_filtered_codecs.ok()) {
+      return error_or_filtered_codecs.MoveError();
+    }
+    codecs_to_include = error_or_filtered_codecs.MoveValue();
+  } else {
+    // Ignore both the codecs argument and the Get*CodecsForOffer results.
+    codecs_to_include = media_description_options.codecs_to_include;
   }
-
   std::unique_ptr<MediaContentDescription> content_description;
   if (media_description_options.type == MEDIA_TYPE_AUDIO) {
     content_description = std::make_unique<AudioContentDescription>();
@@ -2058,10 +2065,9 @@
   }
 
   auto error = CreateMediaContentOffer(
-      media_description_options, session_options,
-      error_or_filtered_codecs.MoveValue(), header_extensions, ssrc_generator(),
-      current_streams, content_description.get(),
-      transport_desc_factory_->trials());
+      media_description_options, session_options, codecs_to_include,
+      header_extensions, ssrc_generator(), current_streams,
+      content_description.get(), transport_desc_factory_->trials());
   if (!error.ok()) {
     return error;
   }
@@ -2197,24 +2203,31 @@
   auto offer_rtd = offer_content_description->direction();
   auto answer_rtd = NegotiateRtpTransceiverDirection(offer_rtd, wants_rtd);
 
-  const std::vector<Codec>& supported_codecs =
-      media_description_options.type == MEDIA_TYPE_AUDIO
-          ? GetAudioCodecsForAnswer(offer_rtd, answer_rtd)
-          : GetVideoCodecsForAnswer(offer_rtd, answer_rtd);
-  webrtc::RTCErrorOr<std::vector<Codec>> error_or_filtered_codecs =
-      GetNegotiatedCodecsForAnswer(media_description_options, session_options,
-                                   current_content, codecs, supported_codecs);
-  if (!error_or_filtered_codecs.ok()) {
-    return error_or_filtered_codecs.MoveError();
+  std::vector<Codec> codecs_to_include;
+  bool negotiate;
+  if (media_description_options.codecs_to_include.empty()) {
+    const std::vector<Codec>& supported_codecs =
+        media_description_options.type == MEDIA_TYPE_AUDIO
+            ? GetAudioCodecsForAnswer(offer_rtd, answer_rtd)
+            : GetVideoCodecsForAnswer(offer_rtd, answer_rtd);
+    webrtc::RTCErrorOr<std::vector<Codec>> error_or_filtered_codecs =
+        GetNegotiatedCodecsForAnswer(media_description_options, session_options,
+                                     current_content, codecs, supported_codecs);
+    if (!error_or_filtered_codecs.ok()) {
+      return error_or_filtered_codecs.MoveError();
+    }
+    codecs_to_include = error_or_filtered_codecs.MoveValue();
+    negotiate = true;
+  } else {
+    codecs_to_include = media_description_options.codecs_to_include;
+    negotiate = false;  // Don't filter against remote codecs
   }
-  auto filtered_codecs = error_or_filtered_codecs.MoveValue();
-
   // Determine if we have media codecs in common.
-  bool has_common_media_codecs =
-      std::find_if(filtered_codecs.begin(), filtered_codecs.end(),
+  bool has_usable_media_codecs =
+      std::find_if(codecs_to_include.begin(), codecs_to_include.end(),
                    [](const Codec& c) {
                      return c.IsMediaCodec() && !IsComfortNoiseCodec(c);
-                   }) != filtered_codecs.end();
+                   }) != codecs_to_include.end();
 
   bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) &&
                         session_options.bundle_enabled;
@@ -2224,10 +2237,19 @@
   } else {
     answer_content = std::make_unique<VideoContentDescription>();
   }
-  if (!SetCodecsInAnswer(
-          offer_content_description, filtered_codecs, media_description_options,
-          session_options, ssrc_generator(), current_streams,
-          answer_content.get(), transport_desc_factory_->trials())) {
+  if (negotiate) {
+    std::vector<Codec> negotiated_codecs;
+    NegotiateCodecs(codecs_to_include, offer_content_description->codecs(),
+                    &negotiated_codecs,
+                    media_description_options.codec_preferences.empty());
+    codecs_to_include = negotiated_codecs;
+  }
+
+  if (!SetCodecsInAnswer(offer_content_description, codecs_to_include,
+                         media_description_options, session_options,
+                         ssrc_generator(), current_streams,
+                         answer_content.get(),
+                         transport_desc_factory_->trials())) {
     LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
                          "Failed to set codecs in answer");
   }
@@ -2243,7 +2265,7 @@
   bool secure = bundle_transport ? bundle_transport->description.secure()
                                  : transport->secure();
   bool rejected = media_description_options.stopped ||
-                  offer_content->rejected || !has_common_media_codecs ||
+                  offer_content->rejected || !has_usable_media_codecs ||
                   !IsMediaProtocolSupported(MEDIA_TYPE_AUDIO,
                                             answer_content->protocol(), secure);
   if (rejected) {
diff --git a/pc/media_session.h b/pc/media_session.h
index 4e7bf67..55c9531 100644
--- a/pc/media_session.h
+++ b/pc/media_session.h
@@ -90,6 +90,9 @@
   std::vector<SenderOptions> sender_options;
   std::vector<webrtc::RtpCodecCapability> codec_preferences;
   std::vector<webrtc::RtpHeaderExtensionCapability> header_extensions;
+  // Codecs to include in a generated offer or answer.
+  // If this is used, session-level codec lists MUST be ignored.
+  std::vector<Codec> codecs_to_include;
 
  private:
   // Doesn't DCHECK on `type`.
diff --git a/pc/media_session_unittest.cc b/pc/media_session_unittest.cc
index 19f7898..75b6d71 100644
--- a/pc/media_session_unittest.cc
+++ b/pc/media_session_unittest.cc
@@ -740,8 +740,92 @@
   EXPECT_EQ(kMediaProtocolDtlsSavpf, vcd->protocol());
 }
 
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateOfferWithCustomCodecs) {
+  MediaSessionOptions opts;
+
+  webrtc::SdpAudioFormat audio_format("custom-audio", 8000, 2);
+  Codec custom_audio_codec = CreateAudioCodec(audio_format);
+  auto audio_options = MediaDescriptionOptions(
+      MEDIA_TYPE_AUDIO, "0", RtpTransceiverDirection::kSendRecv, kActive);
+  audio_options.codecs_to_include.push_back(custom_audio_codec);
+  opts.media_description_options.push_back(audio_options);
+
+  Codec custom_video_codec = CreateVideoCodec("custom-video");
+  auto video_options = MediaDescriptionOptions(
+      MEDIA_TYPE_VIDEO, "1", RtpTransceiverDirection::kSendRecv, kActive);
+  video_options.codecs_to_include.push_back(custom_video_codec);
+  opts.media_description_options.push_back(video_options);
+
+  std::unique_ptr<SessionDescription> offer =
+      f1_.CreateOfferOrError(opts, nullptr).MoveValue();
+  ASSERT_TRUE(offer.get());
+  const ContentInfo* ac = offer->GetContentByName("0");
+  const ContentInfo* vc = offer->GetContentByName("1");
+  ASSERT_TRUE(ac);
+  ASSERT_TRUE(vc);
+  EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
+  EXPECT_EQ(MediaProtocolType::kRtp, vc->type);
+  const MediaContentDescription* acd = ac->media_description();
+  const MediaContentDescription* vcd = vc->media_description();
+  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+  ASSERT_EQ(acd->codecs().size(), 1U);
+  // Fields in codec are set during the gen process, so simple compare
+  // does not work.
+  EXPECT_EQ(acd->codecs()[0].name, custom_audio_codec.name);
+
+  EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
+  ASSERT_EQ(vcd->codecs().size(), 1U);
+  EXPECT_EQ(vcd->codecs()[0].name, custom_video_codec.name);
+}
+
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateAnswerWithCustomCodecs) {
+  MediaSessionOptions offer_opts;
+  MediaSessionOptions answer_opts;
+
+  AddAudioVideoSections(RtpTransceiverDirection::kSendRecv, &offer_opts);
+  // Create custom codecs and add to answer. These will override
+  // the normally generated codec list in the answer.
+  // This breaks O/A rules - the responsibility for obeying those is
+  // on the caller, not on this function.
+  webrtc::SdpAudioFormat audio_format("custom-audio", 8000, 2);
+  Codec custom_audio_codec = CreateAudioCodec(audio_format);
+  auto audio_options = MediaDescriptionOptions(
+      MEDIA_TYPE_AUDIO, "audio", RtpTransceiverDirection::kSendRecv, kActive);
+  audio_options.codecs_to_include.push_back(custom_audio_codec);
+  answer_opts.media_description_options.push_back(audio_options);
+
+  Codec custom_video_codec = CreateVideoCodec("custom-video");
+  auto video_options = MediaDescriptionOptions(
+      MEDIA_TYPE_VIDEO, "video", RtpTransceiverDirection::kSendRecv, kActive);
+  video_options.codecs_to_include.push_back(custom_video_codec);
+  answer_opts.media_description_options.push_back(video_options);
+
+  std::unique_ptr<SessionDescription> offer =
+      f1_.CreateOfferOrError(offer_opts, nullptr).MoveValue();
+  ASSERT_TRUE(offer.get());
+  std::unique_ptr<SessionDescription> answer =
+      f1_.CreateAnswerOrError(offer.get(), answer_opts, nullptr).MoveValue();
+  const ContentInfo* ac = answer->GetContentByName("audio");
+  const ContentInfo* vc = answer->GetContentByName("video");
+  ASSERT_TRUE(ac);
+  ASSERT_TRUE(vc);
+  EXPECT_EQ(MediaProtocolType::kRtp, ac->type);
+  EXPECT_EQ(MediaProtocolType::kRtp, vc->type);
+  const MediaContentDescription* acd = ac->media_description();
+  const MediaContentDescription* vcd = vc->media_description();
+  EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
+  ASSERT_EQ(acd->codecs().size(), 1U);
+  // Fields in codec are set during the gen process, so simple compare
+  // does not work.
+  EXPECT_EQ(acd->codecs()[0].name, custom_audio_codec.name);
+
+  EXPECT_EQ(MEDIA_TYPE_VIDEO, vcd->type());
+  ASSERT_EQ(vcd->codecs().size(), 1U);
+  EXPECT_EQ(vcd->codecs()[0].name, custom_video_codec.name);
+}
+
 // Test creating an offer with bundle where the Codecs have the same dynamic
-// RTP playlod type. The test verifies that the offer don't contain the
+// RTP paylod type. The test verifies that the offer don't contain the
 // duplicate RTP payload types.
 TEST_F(MediaSessionDescriptionFactoryTest, TestBundleOfferWithSameCodecPlType) {
   const Codec& offered_video_codec = f2_.video_sendrecv_codecs()[0];