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}
This commit is contained in:
Harald Alvestrand 2024-05-07 11:28:44 +00:00 committed by WebRTC LUCI CQ
parent 30e3553229
commit 141f4c153f
3 changed files with 147 additions and 38 deletions

View File

@ -1036,10 +1036,7 @@ bool SetCodecsInAnswer(const MediaContentDescription* offer,
const webrtc::FieldTrialsView& field_trials) { const webrtc::FieldTrialsView& field_trials) {
RTC_DCHECK(offer->type() == MEDIA_TYPE_AUDIO || RTC_DCHECK(offer->type() == MEDIA_TYPE_AUDIO ||
offer->type() == MEDIA_TYPE_VIDEO); offer->type() == MEDIA_TYPE_VIDEO);
std::vector<Codec> negotiated_codecs; answer->AddCodecs(local_codecs);
NegotiateCodecs(local_codecs, offer->codecs(), &negotiated_codecs,
media_description_options.codec_preferences.empty());
answer->AddCodecs(negotiated_codecs);
answer->set_protocol(offer->protocol()); answer->set_protocol(offer->protocol());
if (!AddStreamParams(media_description_options.sender_options, if (!AddStreamParams(media_description_options.sender_options,
session_options.rtcp_cname, ssrc_generator, session_options.rtcp_cname, ssrc_generator,
@ -2013,6 +2010,10 @@ RTCError MediaSessionDescriptionFactory::AddTransportAnswer(
return RTCError::OK(); 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 // `codecs` = set of all possible codecs that can be used, with correct
// payload type mappings // payload type mappings
// //
@ -2039,17 +2040,23 @@ RTCError MediaSessionDescriptionFactory::AddRtpContentForOffer(
RTC_DCHECK(media_description_options.type == MEDIA_TYPE_AUDIO || RTC_DCHECK(media_description_options.type == MEDIA_TYPE_AUDIO ||
media_description_options.type == MEDIA_TYPE_VIDEO); media_description_options.type == MEDIA_TYPE_VIDEO);
const std::vector<Codec>& supported_codecs = std::vector<Codec> codecs_to_include;
media_description_options.type == MEDIA_TYPE_AUDIO if (media_description_options.codecs_to_include.empty()) {
? GetAudioCodecsForOffer(media_description_options.direction) const std::vector<Codec>& supported_codecs =
: GetVideoCodecsForOffer(media_description_options.direction); media_description_options.type == MEDIA_TYPE_AUDIO
webrtc::RTCErrorOr<std::vector<Codec>> error_or_filtered_codecs = ? GetAudioCodecsForOffer(media_description_options.direction)
GetNegotiatedCodecsForOffer(media_description_options, session_options, : GetVideoCodecsForOffer(media_description_options.direction);
current_content, codecs, supported_codecs); webrtc::RTCErrorOr<std::vector<Codec>> error_or_filtered_codecs =
if (!error_or_filtered_codecs.ok()) { GetNegotiatedCodecsForOffer(media_description_options, session_options,
return error_or_filtered_codecs.MoveError(); 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; std::unique_ptr<MediaContentDescription> content_description;
if (media_description_options.type == MEDIA_TYPE_AUDIO) { if (media_description_options.type == MEDIA_TYPE_AUDIO) {
content_description = std::make_unique<AudioContentDescription>(); content_description = std::make_unique<AudioContentDescription>();
@ -2058,10 +2065,9 @@ RTCError MediaSessionDescriptionFactory::AddRtpContentForOffer(
} }
auto error = CreateMediaContentOffer( auto error = CreateMediaContentOffer(
media_description_options, session_options, media_description_options, session_options, codecs_to_include,
error_or_filtered_codecs.MoveValue(), header_extensions, ssrc_generator(), header_extensions, ssrc_generator(), current_streams,
current_streams, content_description.get(), content_description.get(), transport_desc_factory_->trials());
transport_desc_factory_->trials());
if (!error.ok()) { if (!error.ok()) {
return error; return error;
} }
@ -2197,24 +2203,31 @@ RTCError MediaSessionDescriptionFactory::AddRtpContentForAnswer(
auto offer_rtd = offer_content_description->direction(); auto offer_rtd = offer_content_description->direction();
auto answer_rtd = NegotiateRtpTransceiverDirection(offer_rtd, wants_rtd); auto answer_rtd = NegotiateRtpTransceiverDirection(offer_rtd, wants_rtd);
const std::vector<Codec>& supported_codecs = std::vector<Codec> codecs_to_include;
media_description_options.type == MEDIA_TYPE_AUDIO bool negotiate;
? GetAudioCodecsForAnswer(offer_rtd, answer_rtd) if (media_description_options.codecs_to_include.empty()) {
: GetVideoCodecsForAnswer(offer_rtd, answer_rtd); const std::vector<Codec>& supported_codecs =
webrtc::RTCErrorOr<std::vector<Codec>> error_or_filtered_codecs = media_description_options.type == MEDIA_TYPE_AUDIO
GetNegotiatedCodecsForAnswer(media_description_options, session_options, ? GetAudioCodecsForAnswer(offer_rtd, answer_rtd)
current_content, codecs, supported_codecs); : GetVideoCodecsForAnswer(offer_rtd, answer_rtd);
if (!error_or_filtered_codecs.ok()) { webrtc::RTCErrorOr<std::vector<Codec>> error_or_filtered_codecs =
return error_or_filtered_codecs.MoveError(); 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. // Determine if we have media codecs in common.
bool has_common_media_codecs = bool has_usable_media_codecs =
std::find_if(filtered_codecs.begin(), filtered_codecs.end(), std::find_if(codecs_to_include.begin(), codecs_to_include.end(),
[](const Codec& c) { [](const Codec& c) {
return c.IsMediaCodec() && !IsComfortNoiseCodec(c); return c.IsMediaCodec() && !IsComfortNoiseCodec(c);
}) != filtered_codecs.end(); }) != codecs_to_include.end();
bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) && bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) &&
session_options.bundle_enabled; session_options.bundle_enabled;
@ -2224,10 +2237,19 @@ RTCError MediaSessionDescriptionFactory::AddRtpContentForAnswer(
} else { } else {
answer_content = std::make_unique<VideoContentDescription>(); answer_content = std::make_unique<VideoContentDescription>();
} }
if (!SetCodecsInAnswer( if (negotiate) {
offer_content_description, filtered_codecs, media_description_options, std::vector<Codec> negotiated_codecs;
session_options, ssrc_generator(), current_streams, NegotiateCodecs(codecs_to_include, offer_content_description->codecs(),
answer_content.get(), transport_desc_factory_->trials())) { &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, LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
"Failed to set codecs in answer"); "Failed to set codecs in answer");
} }
@ -2243,7 +2265,7 @@ RTCError MediaSessionDescriptionFactory::AddRtpContentForAnswer(
bool secure = bundle_transport ? bundle_transport->description.secure() bool secure = bundle_transport ? bundle_transport->description.secure()
: transport->secure(); : transport->secure();
bool rejected = media_description_options.stopped || bool rejected = media_description_options.stopped ||
offer_content->rejected || !has_common_media_codecs || offer_content->rejected || !has_usable_media_codecs ||
!IsMediaProtocolSupported(MEDIA_TYPE_AUDIO, !IsMediaProtocolSupported(MEDIA_TYPE_AUDIO,
answer_content->protocol(), secure); answer_content->protocol(), secure);
if (rejected) { if (rejected) {

View File

@ -90,6 +90,9 @@ struct MediaDescriptionOptions {
std::vector<SenderOptions> sender_options; std::vector<SenderOptions> sender_options;
std::vector<webrtc::RtpCodecCapability> codec_preferences; std::vector<webrtc::RtpCodecCapability> codec_preferences;
std::vector<webrtc::RtpHeaderExtensionCapability> header_extensions; 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: private:
// Doesn't DCHECK on `type`. // Doesn't DCHECK on `type`.

View File

@ -740,8 +740,92 @@ TEST_F(MediaSessionDescriptionFactoryTest, TestCreateVideoOffer) {
EXPECT_EQ(kMediaProtocolDtlsSavpf, vcd->protocol()); 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 // 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. // duplicate RTP payload types.
TEST_F(MediaSessionDescriptionFactoryTest, TestBundleOfferWithSameCodecPlType) { TEST_F(MediaSessionDescriptionFactoryTest, TestBundleOfferWithSameCodecPlType) {
const Codec& offered_video_codec = f2_.video_sendrecv_codecs()[0]; const Codec& offered_video_codec = f2_.video_sendrecv_codecs()[0];