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];