Implement RTCRtpTransceiver.setCodecPreferences
SetCodecPreferences allows clients to filter and reorder codecs in their
SDP offer and answer.
Bug: webrtc:9777
Change-Id: I716bed9b06496629b45210883b286f599c875239
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/129727
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Seth Hampson <shampson@webrtc.org>
Commit-Queue: Florent Castelli <orphis@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27817}
diff --git a/pc/media_session.cc b/pc/media_session.cc
index 8377f10..0eace22 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -799,7 +799,8 @@
template <class C>
static void NegotiateCodecs(const std::vector<C>& local_codecs,
const std::vector<C>& offered_codecs,
- std::vector<C>* negotiated_codecs) {
+ std::vector<C>* negotiated_codecs,
+ bool keep_offer_order) {
for (const C& ours : local_codecs) {
C theirs;
// Note that we intentionally only find one matching codec for each of our
@@ -823,19 +824,22 @@
negotiated_codecs->push_back(std::move(negotiated));
}
}
- // RFC3264: Although the answerer MAY list the formats in their desired
- // order of preference, it is RECOMMENDED that unless there is a
- // specific reason, the answerer list formats in the same relative order
- // they were present in the offer.
- std::unordered_map<int, int> payload_type_preferences;
- int preference = static_cast<int>(offered_codecs.size() + 1);
- for (const C& codec : offered_codecs) {
- payload_type_preferences[codec.id] = preference--;
+ if (keep_offer_order) {
+ // RFC3264: Although the answerer MAY list the formats in their desired
+ // order of preference, it is RECOMMENDED that unless there is a
+ // specific reason, the answerer list formats in the same relative order
+ // they were present in the offer.
+ // This can be skipped when the transceiver has any codec preferences.
+ std::unordered_map<int, int> payload_type_preferences;
+ int preference = static_cast<int>(offered_codecs.size() + 1);
+ for (const C& codec : offered_codecs) {
+ payload_type_preferences[codec.id] = preference--;
+ }
+ absl::c_sort(*negotiated_codecs, [&payload_type_preferences](const C& a,
+ const C& b) {
+ return payload_type_preferences[a.id] > payload_type_preferences[b.id];
+ });
}
- absl::c_sort(
- *negotiated_codecs, [&payload_type_preferences](const C& a, const C& b) {
- return payload_type_preferences[a.id] > payload_type_preferences[b.id];
- });
}
// Finds a codec in |codecs2| that matches |codec_to_match|, which is
@@ -954,6 +958,51 @@
}
}
+template <typename Codecs>
+static Codecs MatchCodecPreference(
+ const std::vector<webrtc::RtpCodecCapability>& codec_preferences,
+ const Codecs& codecs) {
+ Codecs filtered_codecs;
+ std::set<std::string> kept_codecs_ids;
+ bool want_rtx = false;
+
+ for (const auto& codec_preference : codec_preferences) {
+ auto found_codec = absl::c_find_if(
+ codecs, [&codec_preference](const typename Codecs::value_type& codec) {
+ webrtc::RtpCodecParameters codec_parameters =
+ codec.ToCodecParameters();
+ return codec_parameters.name == codec_preference.name &&
+ codec_parameters.kind == codec_preference.kind &&
+ codec_parameters.num_channels ==
+ codec_preference.num_channels &&
+ codec_parameters.clock_rate == codec_preference.clock_rate &&
+ codec_parameters.parameters == codec_preference.parameters;
+ });
+
+ if (found_codec != codecs.end()) {
+ filtered_codecs.push_back(*found_codec);
+ kept_codecs_ids.insert(std::to_string(found_codec->id));
+ } else if (IsRtxCodec(codec_preference)) {
+ want_rtx = true;
+ }
+ }
+
+ if (want_rtx) {
+ for (const auto& codec : codecs) {
+ if (IsRtxCodec(codec)) {
+ const auto apt =
+ codec.params.find(cricket::kCodecParamAssociatedPayloadType);
+ if (apt != codec.params.end() &&
+ kept_codecs_ids.count(apt->second) > 0) {
+ filtered_codecs.push_back(codec);
+ }
+ }
+ }
+ }
+
+ return filtered_codecs;
+}
+
static bool FindByUriAndEncryption(const RtpHeaderExtensions& extensions,
const webrtc::RtpExtension& ext_to_match,
webrtc::RtpExtension* found_extension) {
@@ -1159,7 +1208,8 @@
bool bundle_enabled,
MediaContentDescriptionImpl<C>* answer) {
std::vector<C> negotiated_codecs;
- NegotiateCodecs(local_codecs, offer->codecs(), &negotiated_codecs);
+ NegotiateCodecs(local_codecs, offer->codecs(), &negotiated_codecs,
+ media_description_options.codec_preferences.empty());
answer->AddCodecs(negotiated_codecs);
answer->set_protocol(offer->protocol());
@@ -2009,30 +2059,38 @@
GetAudioCodecsForOffer(media_description_options.direction);
AudioCodecs filtered_codecs;
- // Add the codecs from current content if it exists and is not rejected nor
- // recycled.
- if (current_content && !current_content->rejected &&
- current_content->name == media_description_options.mid) {
- RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO));
- const AudioContentDescription* acd =
- current_content->media_description()->as_audio();
- for (const AudioCodec& codec : acd->codecs()) {
- if (FindMatchingCodec<AudioCodec>(acd->codecs(), audio_codecs, codec,
- nullptr)) {
- filtered_codecs.push_back(codec);
+
+ if (!media_description_options.codec_preferences.empty()) {
+ // Add the codecs from the current transceiver's codec preferences.
+ // They override any existing codecs from previous negotiations.
+ filtered_codecs = MatchCodecPreference(
+ media_description_options.codec_preferences, supported_audio_codecs);
+ } else {
+ // Add the codecs from current content if it exists and is not rejected nor
+ // recycled.
+ if (current_content && !current_content->rejected &&
+ current_content->name == media_description_options.mid) {
+ RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO));
+ const AudioContentDescription* acd =
+ current_content->media_description()->as_audio();
+ for (const AudioCodec& codec : acd->codecs()) {
+ if (FindMatchingCodec<AudioCodec>(acd->codecs(), audio_codecs, codec,
+ nullptr)) {
+ filtered_codecs.push_back(codec);
+ }
}
}
- }
- // Add other supported audio codecs.
- AudioCodec found_codec;
- for (const AudioCodec& codec : supported_audio_codecs) {
- if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs,
- codec, &found_codec) &&
- !FindMatchingCodec<AudioCodec>(supported_audio_codecs, filtered_codecs,
- codec, nullptr)) {
- // Use the |found_codec| from |audio_codecs| because it has the correctly
- // mapped payload type.
- filtered_codecs.push_back(found_codec);
+ // Add other supported audio codecs.
+ AudioCodec found_codec;
+ for (const AudioCodec& codec : supported_audio_codecs) {
+ if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs,
+ codec, &found_codec) &&
+ !FindMatchingCodec<AudioCodec>(supported_audio_codecs,
+ filtered_codecs, codec, nullptr)) {
+ // Use the |found_codec| from |audio_codecs| because it has the
+ // correctly mapped payload type.
+ filtered_codecs.push_back(found_codec);
+ }
}
}
@@ -2088,30 +2146,38 @@
&crypto_suites);
VideoCodecs filtered_codecs;
- // Add the codecs from current content if it exists and is not rejected nor
- // recycled.
- if (current_content && !current_content->rejected &&
- current_content->name == media_description_options.mid) {
- RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO));
- const VideoContentDescription* vcd =
- current_content->media_description()->as_video();
- for (const VideoCodec& codec : vcd->codecs()) {
- if (FindMatchingCodec<VideoCodec>(vcd->codecs(), video_codecs, codec,
- nullptr)) {
- filtered_codecs.push_back(codec);
+
+ if (!media_description_options.codec_preferences.empty()) {
+ // Add the codecs from the current transceiver's codec preferences.
+ // They override any existing codecs from previous negotiations.
+ filtered_codecs = MatchCodecPreference(
+ media_description_options.codec_preferences, video_codecs_);
+ } else {
+ // Add the codecs from current content if it exists and is not rejected nor
+ // recycled.
+ if (current_content && !current_content->rejected &&
+ current_content->name == media_description_options.mid) {
+ RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO));
+ const VideoContentDescription* vcd =
+ current_content->media_description()->as_video();
+ for (const VideoCodec& codec : vcd->codecs()) {
+ if (FindMatchingCodec<VideoCodec>(vcd->codecs(), video_codecs, codec,
+ nullptr)) {
+ filtered_codecs.push_back(codec);
+ }
}
}
- }
- // Add other supported video codecs.
- VideoCodec found_codec;
- for (const VideoCodec& codec : video_codecs_) {
- if (FindMatchingCodec<VideoCodec>(video_codecs_, video_codecs, codec,
- &found_codec) &&
- !FindMatchingCodec<VideoCodec>(video_codecs_, filtered_codecs, codec,
- nullptr)) {
- // Use the |found_codec| from |video_codecs| because it has the correctly
- // mapped payload type.
- filtered_codecs.push_back(found_codec);
+ // Add other supported video codecs.
+ VideoCodec found_codec;
+ for (const VideoCodec& codec : video_codecs_) {
+ if (FindMatchingCodec<VideoCodec>(video_codecs_, video_codecs, codec,
+ &found_codec) &&
+ !FindMatchingCodec<VideoCodec>(video_codecs_, filtered_codecs, codec,
+ nullptr)) {
+ // Use the |found_codec| from |video_codecs| because it has the
+ // correctly mapped payload type.
+ filtered_codecs.push_back(found_codec);
+ }
}
}
@@ -2254,29 +2320,35 @@
GetAudioCodecsForAnswer(offer_rtd, answer_rtd);
AudioCodecs filtered_codecs;
- // Add the codecs from current content if it exists and is not rejected nor
- // recycled.
- if (current_content && !current_content->rejected &&
- current_content->name == media_description_options.mid) {
- RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO));
- const AudioContentDescription* acd =
- current_content->media_description()->as_audio();
- for (const AudioCodec& codec : acd->codecs()) {
- if (FindMatchingCodec<AudioCodec>(acd->codecs(), audio_codecs, codec,
- nullptr)) {
- filtered_codecs.push_back(codec);
+
+ if (!media_description_options.codec_preferences.empty()) {
+ filtered_codecs = MatchCodecPreference(
+ media_description_options.codec_preferences, supported_audio_codecs);
+ } else {
+ // Add the codecs from current content if it exists and is not rejected nor
+ // recycled.
+ if (current_content && !current_content->rejected &&
+ current_content->name == media_description_options.mid) {
+ RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_AUDIO));
+ const AudioContentDescription* acd =
+ current_content->media_description()->as_audio();
+ for (const AudioCodec& codec : acd->codecs()) {
+ if (FindMatchingCodec<AudioCodec>(acd->codecs(), audio_codecs, codec,
+ nullptr)) {
+ filtered_codecs.push_back(codec);
+ }
}
}
- }
- // Add other supported audio codecs.
- for (const AudioCodec& codec : supported_audio_codecs) {
- if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs,
- codec, nullptr) &&
- !FindMatchingCodec<AudioCodec>(supported_audio_codecs, filtered_codecs,
- codec, nullptr)) {
- // We should use the local codec with local parameters and the codec id
- // would be correctly mapped in |NegotiateCodecs|.
- filtered_codecs.push_back(codec);
+ // Add other supported audio codecs.
+ for (const AudioCodec& codec : supported_audio_codecs) {
+ if (FindMatchingCodec<AudioCodec>(supported_audio_codecs, audio_codecs,
+ codec, nullptr) &&
+ !FindMatchingCodec<AudioCodec>(supported_audio_codecs,
+ filtered_codecs, codec, nullptr)) {
+ // We should use the local codec with local parameters and the codec id
+ // would be correctly mapped in |NegotiateCodecs|.
+ filtered_codecs.push_back(codec);
+ }
}
}
@@ -2342,29 +2414,35 @@
}
VideoCodecs filtered_codecs;
- // Add the codecs from current content if it exists and is not rejected nor
- // recycled.
- if (current_content && !current_content->rejected &&
- current_content->name == media_description_options.mid) {
- RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO));
- const VideoContentDescription* vcd =
- current_content->media_description()->as_video();
- for (const VideoCodec& codec : vcd->codecs()) {
- if (FindMatchingCodec<VideoCodec>(vcd->codecs(), video_codecs, codec,
- nullptr)) {
- filtered_codecs.push_back(codec);
+
+ if (!media_description_options.codec_preferences.empty()) {
+ filtered_codecs = MatchCodecPreference(
+ media_description_options.codec_preferences, video_codecs_);
+ } else {
+ // Add the codecs from current content if it exists and is not rejected nor
+ // recycled.
+ if (current_content && !current_content->rejected &&
+ current_content->name == media_description_options.mid) {
+ RTC_CHECK(IsMediaContentOfType(current_content, MEDIA_TYPE_VIDEO));
+ const VideoContentDescription* vcd =
+ current_content->media_description()->as_video();
+ for (const VideoCodec& codec : vcd->codecs()) {
+ if (FindMatchingCodec<VideoCodec>(vcd->codecs(), video_codecs, codec,
+ nullptr)) {
+ filtered_codecs.push_back(codec);
+ }
}
}
- }
- // Add other supported video codecs.
- for (const VideoCodec& codec : video_codecs_) {
- if (FindMatchingCodec<VideoCodec>(video_codecs_, video_codecs, codec,
- nullptr) &&
- !FindMatchingCodec<VideoCodec>(video_codecs_, filtered_codecs, codec,
- nullptr)) {
- // We should use the local codec with local parameters and the codec id
- // would be correctly mapped in |NegotiateCodecs|.
- filtered_codecs.push_back(codec);
+ // Add other supported video codecs.
+ for (const VideoCodec& codec : video_codecs_) {
+ if (FindMatchingCodec<VideoCodec>(video_codecs_, video_codecs, codec,
+ nullptr) &&
+ !FindMatchingCodec<VideoCodec>(video_codecs_, filtered_codecs, codec,
+ nullptr)) {
+ // We should use the local codec with local parameters and the codec id
+ // would be correctly mapped in |NegotiateCodecs|.
+ filtered_codecs.push_back(codec);
+ }
}
}
@@ -2499,7 +2577,7 @@
// expensive than decoding, and prioritizing a codec in the send list probably
// means it's a codec we can handle efficiently.
NegotiateCodecs(audio_recv_codecs_, audio_send_codecs_,
- &audio_sendrecv_codecs_);
+ &audio_sendrecv_codecs_, true);
}
bool IsMediaContent(const ContentInfo* content) {