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/api/rtp_transceiver_interface.cc b/api/rtp_transceiver_interface.cc
index 0504331..dc82fad 100644
--- a/api/rtp_transceiver_interface.cc
+++ b/api/rtp_transceiver_interface.cc
@@ -25,9 +25,15 @@
return absl::nullopt;
}
-void RtpTransceiverInterface::SetCodecPreferences(
+RTCError RtpTransceiverInterface::SetCodecPreferences(
rtc::ArrayView<RtpCodecCapability>) {
RTC_NOTREACHED() << "Not implemented";
+ return {};
+}
+
+std::vector<RtpCodecCapability> RtpTransceiverInterface::codec_preferences()
+ const {
+ return {};
}
} // namespace webrtc
diff --git a/api/rtp_transceiver_interface.h b/api/rtp_transceiver_interface.h
index 9b052d1..4606632 100644
--- a/api/rtp_transceiver_interface.h
+++ b/api/rtp_transceiver_interface.h
@@ -129,8 +129,9 @@
// The SetCodecPreferences method overrides the default codec preferences used
// by WebRTC for this transceiver.
// https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-setcodecpreferences
- // TODO(steveanton): Not implemented.
- virtual void SetCodecPreferences(rtc::ArrayView<RtpCodecCapability> codecs);
+ virtual RTCError SetCodecPreferences(
+ rtc::ArrayView<RtpCodecCapability> codecs);
+ virtual std::vector<RtpCodecCapability> codec_preferences() const;
protected:
~RtpTransceiverInterface() override = default;
diff --git a/media/base/codec.cc b/media/base/codec.cc
index 16ef65d..d0ca29b 100644
--- a/media/base/codec.cc
+++ b/media/base/codec.cc
@@ -99,6 +99,18 @@
: (absl::EqualsIgnoreCase(name, codec.name));
}
+bool Codec::MatchesCapability(
+ const webrtc::RtpCodecCapability& codec_capability) const {
+ webrtc::RtpCodecParameters codec_parameters = ToCodecParameters();
+
+ return codec_parameters.name == codec_capability.name &&
+ codec_parameters.kind == codec_capability.kind &&
+ (codec_parameters.name == cricket::kRtxCodecName ||
+ (codec_parameters.num_channels == codec_capability.num_channels &&
+ codec_parameters.clock_rate == codec_capability.clock_rate &&
+ codec_parameters.parameters == codec_capability.parameters));
+}
+
bool Codec::GetParam(const std::string& name, std::string* out) const {
CodecParameterMap::const_iterator iter = params.find(name);
if (iter == params.end())
diff --git a/media/base/codec.h b/media/base/codec.h
index b60d001..091adb6 100644
--- a/media/base/codec.h
+++ b/media/base/codec.h
@@ -73,6 +73,7 @@
// Indicates if this codec is compatible with the specified codec.
bool Matches(const Codec& codec) const;
+ bool MatchesCapability(const webrtc::RtpCodecCapability& capability) const;
// Find the parameter for |name| and write the value to |out|.
bool GetParam(const std::string& name, std::string* out) const;
diff --git a/media/base/fake_media_engine.cc b/media/base/fake_media_engine.cc
index 9724423..358b51c 100644
--- a/media/base/fake_media_engine.cc
+++ b/media/base/fake_media_engine.cc
@@ -495,7 +495,7 @@
FakeVoiceEngine::FakeVoiceEngine() : fail_create_channel_(false) {
// Add a fake audio codec. Note that the name must not be "" as there are
// sanity checks against that.
- codecs_.push_back(AudioCodec(101, "fake_audio_codec", 0, 0, 1));
+ SetCodecs({AudioCodec(101, "fake_audio_codec", 0, 0, 1)});
}
RtpCapabilities FakeVoiceEngine::GetCapabilities() const {
return RtpCapabilities();
@@ -524,13 +524,20 @@
channels_.erase(absl::c_find(channels_, channel));
}
const std::vector<AudioCodec>& FakeVoiceEngine::send_codecs() const {
- return codecs_;
+ return send_codecs_;
}
const std::vector<AudioCodec>& FakeVoiceEngine::recv_codecs() const {
- return codecs_;
+ return recv_codecs_;
}
void FakeVoiceEngine::SetCodecs(const std::vector<AudioCodec>& codecs) {
- codecs_ = codecs;
+ send_codecs_ = codecs;
+ recv_codecs_ = codecs;
+}
+void FakeVoiceEngine::SetRecvCodecs(const std::vector<AudioCodec>& codecs) {
+ recv_codecs_ = codecs;
+}
+void FakeVoiceEngine::SetSendCodecs(const std::vector<AudioCodec>& codecs) {
+ send_codecs_ = codecs;
}
int FakeVoiceEngine::GetInputLevel() {
return 0;
@@ -601,6 +608,14 @@
void FakeMediaEngine::SetAudioCodecs(const std::vector<AudioCodec>& codecs) {
voice_->SetCodecs(codecs);
}
+void FakeMediaEngine::SetAudioRecvCodecs(
+ const std::vector<AudioCodec>& codecs) {
+ voice_->SetRecvCodecs(codecs);
+}
+void FakeMediaEngine::SetAudioSendCodecs(
+ const std::vector<AudioCodec>& codecs) {
+ voice_->SetSendCodecs(codecs);
+}
void FakeMediaEngine::SetVideoCodecs(const std::vector<VideoCodec>& codecs) {
video_->SetCodecs(codecs);
}
diff --git a/media/base/fake_media_engine.h b/media/base/fake_media_engine.h
index f1192c5..b42f896 100644
--- a/media/base/fake_media_engine.h
+++ b/media/base/fake_media_engine.h
@@ -523,6 +523,8 @@
const std::vector<AudioCodec>& send_codecs() const override;
const std::vector<AudioCodec>& recv_codecs() const override;
void SetCodecs(const std::vector<AudioCodec>& codecs);
+ void SetRecvCodecs(const std::vector<AudioCodec>& codecs);
+ void SetSendCodecs(const std::vector<AudioCodec>& codecs);
int GetInputLevel();
bool StartAecDump(rtc::PlatformFile file, int64_t max_size_bytes) override;
void StopAecDump() override;
@@ -531,7 +533,8 @@
private:
std::vector<FakeVoiceMediaChannel*> channels_;
- std::vector<AudioCodec> codecs_;
+ std::vector<AudioCodec> recv_codecs_;
+ std::vector<AudioCodec> send_codecs_;
bool fail_create_channel_;
friend class FakeMediaEngine;
@@ -572,6 +575,8 @@
~FakeMediaEngine() override;
void SetAudioCodecs(const std::vector<AudioCodec>& codecs);
+ void SetAudioRecvCodecs(const std::vector<AudioCodec>& codecs);
+ void SetAudioSendCodecs(const std::vector<AudioCodec>& codecs);
void SetVideoCodecs(const std::vector<VideoCodec>& codecs);
FakeVoiceMediaChannel* GetVoiceChannel(size_t index);
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) {
diff --git a/pc/media_session.h b/pc/media_session.h
index 33c8c17..a369756 100644
--- a/pc/media_session.h
+++ b/pc/media_session.h
@@ -76,6 +76,7 @@
// Note: There's no equivalent "RtpReceiverOptions" because only send
// stream information goes in the local descriptions.
std::vector<SenderOptions> sender_options;
+ std::vector<webrtc::RtpCodecCapability> codec_preferences;
private:
// Doesn't DCHECK on |type|.
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index f7cfc9a..8a6d0e5 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -1703,7 +1703,8 @@
// could be invalid, but should not cause a crash).
RTC_DCHECK(!FindSenderById(sender->id()));
auto transceiver = RtpTransceiverProxyWithInternal<RtpTransceiver>::Create(
- signaling_thread(), new RtpTransceiver(sender, receiver));
+ signaling_thread(),
+ new RtpTransceiver(sender, receiver, channel_manager()));
transceivers_.push_back(transceiver);
transceiver->internal()->SignalNegotiationNeeded.connect(
this, &PeerConnection::OnNegotiationNeeded);
@@ -4397,6 +4398,8 @@
cricket::MediaDescriptionOptions media_description_options(
transceiver->media_type(), mid, transceiver->direction(),
transceiver->stopped());
+ media_description_options.codec_preferences =
+ transceiver->codec_preferences();
// This behavior is specified in JSEP. The gist is that:
// 1. The MSID is included if the RtpTransceiver's direction is sendonly or
// sendrecv.
diff --git a/pc/peer_connection_media_unittest.cc b/pc/peer_connection_media_unittest.cc
index c81d511..e9e3a00 100644
--- a/pc/peer_connection_media_unittest.cc
+++ b/pc/peer_connection_media_unittest.cc
@@ -73,11 +73,21 @@
return CreatePeerConnection(RTCConfiguration());
}
+ WrapperPtr CreatePeerConnection(const RTCConfiguration& config) {
+ return CreatePeerConnection(config, absl::make_unique<FakeMediaEngine>());
+ }
+
+ WrapperPtr CreatePeerConnection(
+ std::unique_ptr<FakeMediaEngine> media_engine) {
+ return CreatePeerConnection(RTCConfiguration(), std::move(media_engine));
+ }
+
// Creates PeerConnectionFactory and PeerConnection for given configuration.
// Note that PeerConnectionFactory is created with MediaTransportFactory,
// because some tests pass config.use_media_transport = true.
- WrapperPtr CreatePeerConnection(const RTCConfiguration& config) {
- auto media_engine = absl::make_unique<FakeMediaEngine>();
+ WrapperPtr CreatePeerConnection(
+ const RTCConfiguration& config,
+ std::unique_ptr<FakeMediaEngine> media_engine) {
auto* media_engine_ptr = media_engine.get();
PeerConnectionFactoryDependencies factory_dependencies;
@@ -125,6 +135,18 @@
return wrapper;
}
+ // Accepts the same arguments as CreatePeerConnection and adds default video
+ // track (but no audio).
+ template <typename... Args>
+ WrapperPtr CreatePeerConnectionWithVideo(Args&&... args) {
+ auto wrapper = CreatePeerConnection(std::forward<Args>(args)...);
+ if (!wrapper) {
+ return nullptr;
+ }
+ wrapper->AddVideoTrack("v");
+ return wrapper;
+ }
+
// Accepts the same arguments as CreatePeerConnection and adds default audio
// and video tracks.
template <typename... Args>
@@ -1239,6 +1261,498 @@
ASSERT_EQ(nullptr, callee_video->media_transport());
}
+template <typename C>
+bool CompareCodecs(const std::vector<webrtc::RtpCodecCapability>& capabilities,
+ const std::vector<C>& codecs) {
+ bool capability_has_rtx =
+ absl::c_any_of(capabilities, [](const webrtc::RtpCodecCapability& codec) {
+ return codec.name == cricket::kRtxCodecName;
+ });
+ bool codecs_has_rtx = absl::c_any_of(codecs, [](const C& codec) {
+ return codec.name == cricket::kRtxCodecName;
+ });
+
+ std::vector<C> codecs_no_rtx;
+ absl::c_copy_if(
+ codecs, std::back_inserter(codecs_no_rtx),
+ [](const C& codec) { return codec.name != cricket::kRtxCodecName; });
+
+ std::vector<webrtc::RtpCodecCapability> capabilities_no_rtx;
+ absl::c_copy_if(capabilities, std::back_inserter(capabilities_no_rtx),
+ [](const webrtc::RtpCodecCapability& codec) {
+ return codec.name != cricket::kRtxCodecName;
+ });
+
+ return capability_has_rtx == codecs_has_rtx &&
+ absl::c_equal(
+ capabilities_no_rtx, codecs_no_rtx,
+ [](const webrtc::RtpCodecCapability& capability, const C& codec) {
+ return codec.MatchesCapability(capability);
+ });
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan,
+ SetCodecPreferencesAudioMissingRecvCodec) {
+ auto fake_engine = absl::make_unique<FakeMediaEngine>();
+ auto send_codecs = fake_engine->voice().send_codecs();
+ send_codecs.push_back(cricket::AudioCodec(send_codecs.back().id + 1,
+ "send_only_codec", 0, 0, 1));
+ fake_engine->SetAudioSendCodecs(send_codecs);
+
+ auto caller = CreatePeerConnectionWithAudio(std::move(fake_engine));
+
+ auto transceiver = caller->pc()->GetTransceivers().front();
+ auto capabilities = caller->pc_factory()->GetRtpSenderCapabilities(
+ cricket::MediaType::MEDIA_TYPE_AUDIO);
+
+ std::vector<webrtc::RtpCodecCapability> codecs;
+ absl::c_copy_if(capabilities.codecs, std::back_inserter(codecs),
+ [](const webrtc::RtpCodecCapability& codec) {
+ return codec.name.find("_only_") != std::string::npos;
+ });
+
+ auto result = transceiver->SetCodecPreferences(codecs);
+ EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, result.type());
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan,
+ SetCodecPreferencesAudioMissingSendCodec) {
+ auto fake_engine = absl::make_unique<FakeMediaEngine>();
+ auto recv_codecs = fake_engine->voice().recv_codecs();
+ recv_codecs.push_back(cricket::AudioCodec(recv_codecs.back().id + 1,
+ "recv_only_codec", 0, 0, 1));
+ fake_engine->SetAudioRecvCodecs(recv_codecs);
+ auto caller = CreatePeerConnectionWithAudio(std::move(fake_engine));
+
+ auto transceiver = caller->pc()->GetTransceivers().front();
+ auto capabilities = caller->pc_factory()->GetRtpReceiverCapabilities(
+ cricket::MediaType::MEDIA_TYPE_AUDIO);
+
+ std::vector<webrtc::RtpCodecCapability> codecs;
+ absl::c_copy_if(capabilities.codecs, std::back_inserter(codecs),
+ [](const webrtc::RtpCodecCapability& codec) {
+ return codec.name.find("_only_") != std::string::npos;
+ });
+
+ auto result = transceiver->SetCodecPreferences(codecs);
+ EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, result.type());
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan,
+ SetCodecPreferencesAudioRejectsVideoCodec) {
+ auto caller = CreatePeerConnectionWithAudio();
+
+ auto transceiver = caller->pc()->GetTransceivers().front();
+ auto video_codecs =
+ caller->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_VIDEO)
+ .codecs;
+ auto codecs =
+ caller->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_AUDIO)
+ .codecs;
+ codecs.insert(codecs.end(), video_codecs.begin(), video_codecs.end());
+ auto result = transceiver->SetCodecPreferences(codecs);
+ EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, result.type());
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan,
+ SetCodecPreferencesAudioRejectsOnlyRtxRedFec) {
+ auto fake_engine = absl::make_unique<FakeMediaEngine>();
+ auto audio_codecs = fake_engine->voice().send_codecs();
+ audio_codecs.push_back(cricket::AudioCodec(audio_codecs.back().id + 1,
+ cricket::kRtxCodecName, 0, 0, 1));
+ audio_codecs.back().params[cricket::kCodecParamAssociatedPayloadType] =
+ std::to_string(audio_codecs.back().id - 1);
+ audio_codecs.push_back(cricket::AudioCodec(audio_codecs.back().id + 1,
+ cricket::kRedCodecName, 0, 0, 1));
+ audio_codecs.push_back(cricket::AudioCodec(
+ audio_codecs.back().id + 1, cricket::kUlpfecCodecName, 0, 0, 1));
+ fake_engine->SetAudioCodecs(audio_codecs);
+
+ auto caller = CreatePeerConnectionWithAudio(std::move(fake_engine));
+
+ auto transceiver = caller->pc()->GetTransceivers().front();
+ auto codecs =
+ caller->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_AUDIO)
+ .codecs;
+ auto codecs_only_rtx_red_fec = codecs;
+ auto it = std::remove_if(codecs_only_rtx_red_fec.begin(),
+ codecs_only_rtx_red_fec.end(),
+ [](const webrtc::RtpCodecCapability& codec) {
+ return !(codec.name == cricket::kRtxCodecName ||
+ codec.name == cricket::kRedCodecName ||
+ codec.name == cricket::kUlpfecCodecName);
+ });
+ codecs_only_rtx_red_fec.erase(it, codecs_only_rtx_red_fec.end());
+
+ auto result = transceiver->SetCodecPreferences(codecs_only_rtx_red_fec);
+ EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, result.type());
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan, SetCodecPreferencesAllAudioCodecs) {
+ auto caller = CreatePeerConnectionWithAudio();
+
+ auto sender_audio_codecs =
+ caller->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_AUDIO)
+ .codecs;
+
+ auto audio_transceiver = caller->pc()->GetTransceivers().front();
+
+ // Normal case, set all capabilities as preferences
+ EXPECT_TRUE(audio_transceiver->SetCodecPreferences(sender_audio_codecs).ok());
+ auto offer = caller->CreateOffer();
+ auto codecs = offer->description()
+ ->contents()[0]
+ .media_description()
+ ->as_audio()
+ ->codecs();
+ EXPECT_TRUE(CompareCodecs(sender_audio_codecs, codecs));
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan,
+ SetCodecPreferencesResetAudioCodecs) {
+ auto caller = CreatePeerConnectionWithAudio();
+
+ auto sender_audio_codecs =
+ caller->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_AUDIO)
+ .codecs;
+ std::vector<webrtc::RtpCodecCapability> empty_codecs = {};
+
+ auto audio_transceiver = caller->pc()->GetTransceivers().front();
+
+ // Normal case, reset codec preferences
+ EXPECT_TRUE(audio_transceiver->SetCodecPreferences(empty_codecs).ok());
+ auto offer = caller->CreateOffer();
+ auto codecs = offer->description()
+ ->contents()[0]
+ .media_description()
+ ->as_audio()
+ ->codecs();
+ EXPECT_TRUE(CompareCodecs(sender_audio_codecs, codecs));
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan,
+ SetCodecPreferencesVideoRejectsAudioCodec) {
+ auto caller = CreatePeerConnectionWithVideo();
+
+ auto transceiver = caller->pc()->GetTransceivers().front();
+ auto audio_codecs =
+ caller->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_AUDIO)
+ .codecs;
+ auto codecs =
+ caller->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_VIDEO)
+ .codecs;
+ codecs.insert(codecs.end(), audio_codecs.begin(), audio_codecs.end());
+ auto result = transceiver->SetCodecPreferences(codecs);
+ EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, result.type());
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan,
+ SetCodecPreferencesVideoRejectsOnlyRtxRedFec) {
+ auto fake_engine = absl::make_unique<FakeMediaEngine>();
+ auto video_codecs = fake_engine->video().codecs();
+ video_codecs.push_back(
+ cricket::VideoCodec(video_codecs.back().id + 1, cricket::kRtxCodecName));
+ video_codecs.push_back(
+ cricket::VideoCodec(video_codecs.back().id + 1, cricket::kRedCodecName));
+ video_codecs.push_back(cricket::VideoCodec(video_codecs.back().id + 1,
+ cricket::kUlpfecCodecName));
+ fake_engine->SetVideoCodecs(video_codecs);
+
+ auto caller = CreatePeerConnectionWithVideo(std::move(fake_engine));
+
+ auto transceiver = caller->pc()->GetTransceivers().front();
+ auto codecs =
+ caller->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_VIDEO)
+ .codecs;
+ auto codecs_only_rtx_red_fec = codecs;
+ auto it = std::remove_if(codecs_only_rtx_red_fec.begin(),
+ codecs_only_rtx_red_fec.end(),
+ [](const webrtc::RtpCodecCapability& codec) {
+ return !(codec.name == cricket::kRtxCodecName ||
+ codec.name == cricket::kRedCodecName ||
+ codec.name == cricket::kUlpfecCodecName);
+ });
+ codecs_only_rtx_red_fec.erase(it, codecs_only_rtx_red_fec.end());
+
+ auto result = transceiver->SetCodecPreferences(codecs_only_rtx_red_fec);
+ EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, result.type());
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan, SetCodecPreferencesAllVideoCodecs) {
+ auto caller = CreatePeerConnectionWithVideo();
+
+ auto sender_video_codecs =
+ caller->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
+ .codecs;
+
+ auto video_transceiver = caller->pc()->GetTransceivers().front();
+
+ // Normal case, setting preferences to normal capabilities
+ EXPECT_TRUE(video_transceiver->SetCodecPreferences(sender_video_codecs).ok());
+ auto offer = caller->CreateOffer();
+ auto codecs = offer->description()
+ ->contents()[0]
+ .media_description()
+ ->as_video()
+ ->codecs();
+ EXPECT_TRUE(CompareCodecs(sender_video_codecs, codecs));
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan,
+ SetCodecPreferencesResetVideoCodecs) {
+ auto caller = CreatePeerConnectionWithVideo();
+
+ auto sender_video_codecs =
+ caller->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
+ .codecs;
+
+ std::vector<webrtc::RtpCodecCapability> empty_codecs = {};
+
+ auto video_transceiver = caller->pc()->GetTransceivers().front();
+
+ // Normal case, resetting preferences with empty list of codecs
+ EXPECT_TRUE(video_transceiver->SetCodecPreferences(empty_codecs).ok());
+ auto offer = caller->CreateOffer();
+ auto codecs = offer->description()
+ ->contents()[0]
+ .media_description()
+ ->as_video()
+ ->codecs();
+ EXPECT_TRUE(CompareCodecs(sender_video_codecs, codecs));
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan,
+ SetCodecPreferencesVideoCodecDuplicatesRemoved) {
+ auto caller = CreatePeerConnectionWithVideo();
+
+ auto sender_video_codecs =
+ caller->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
+ .codecs;
+
+ auto video_transceiver = caller->pc()->GetTransceivers().front();
+
+ // Check duplicates are removed
+ auto single_codec = sender_video_codecs;
+ single_codec.resize(1);
+ auto duplicate_codec = single_codec;
+ duplicate_codec.push_back(duplicate_codec.front());
+ duplicate_codec.push_back(duplicate_codec.front());
+ duplicate_codec.push_back(duplicate_codec.front());
+
+ EXPECT_TRUE(video_transceiver->SetCodecPreferences(duplicate_codec).ok());
+ auto offer = caller->CreateOffer();
+ auto codecs = offer->description()
+ ->contents()[0]
+ .media_description()
+ ->as_video()
+ ->codecs();
+ EXPECT_TRUE(CompareCodecs(single_codec, codecs));
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan, SetCodecPreferencesVideoWithRtx) {
+ auto caller_fake_engine = absl::make_unique<FakeMediaEngine>();
+ auto caller_video_codecs = caller_fake_engine->video().codecs();
+ caller_video_codecs.push_back(cricket::VideoCodec(
+ caller_video_codecs.back().id + 1, cricket::kVp8CodecName));
+ caller_video_codecs.push_back(cricket::VideoCodec(
+ caller_video_codecs.back().id + 1, cricket::kRtxCodecName));
+ caller_video_codecs.back().params[cricket::kCodecParamAssociatedPayloadType] =
+ std::to_string(caller_video_codecs.back().id - 1);
+ caller_video_codecs.push_back(cricket::VideoCodec(
+ caller_video_codecs.back().id + 1, cricket::kVp9CodecName));
+ caller_video_codecs.push_back(cricket::VideoCodec(
+ caller_video_codecs.back().id + 1, cricket::kRtxCodecName));
+ caller_video_codecs.back().params[cricket::kCodecParamAssociatedPayloadType] =
+ std::to_string(caller_video_codecs.back().id - 1);
+ caller_fake_engine->SetVideoCodecs(caller_video_codecs);
+
+ auto caller = CreatePeerConnectionWithVideo(std::move(caller_fake_engine));
+
+ auto sender_video_codecs =
+ caller->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
+ .codecs;
+
+ auto video_transceiver = caller->pc()->GetTransceivers().front();
+
+ // Check that RTX codec is properly added
+ auto video_codecs_vpx_rtx = sender_video_codecs;
+ auto it =
+ std::remove_if(video_codecs_vpx_rtx.begin(), video_codecs_vpx_rtx.end(),
+ [](const webrtc::RtpCodecCapability& codec) {
+ return codec.name != cricket::kRtxCodecName &&
+ codec.name != cricket::kVp8CodecName &&
+ codec.name != cricket::kVp9CodecName;
+ });
+ video_codecs_vpx_rtx.erase(it, video_codecs_vpx_rtx.end());
+ absl::c_reverse(video_codecs_vpx_rtx);
+ EXPECT_EQ(video_codecs_vpx_rtx.size(), 3u); // VP8, VP9, RTX
+ EXPECT_TRUE(
+ video_transceiver->SetCodecPreferences(video_codecs_vpx_rtx).ok());
+ auto offer = caller->CreateOffer();
+ auto codecs = offer->description()
+ ->contents()[0]
+ .media_description()
+ ->as_video()
+ ->codecs();
+
+ EXPECT_TRUE(CompareCodecs(video_codecs_vpx_rtx, codecs));
+ EXPECT_EQ(codecs.size(), 4u);
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan,
+ SetCodecPreferencesVideoCodecsNegotiation) {
+ auto caller_fake_engine = absl::make_unique<FakeMediaEngine>();
+ auto caller_video_codecs = caller_fake_engine->video().codecs();
+ caller_video_codecs.push_back(cricket::VideoCodec(
+ caller_video_codecs.back().id + 1, cricket::kVp8CodecName));
+ caller_video_codecs.push_back(cricket::VideoCodec(
+ caller_video_codecs.back().id + 1, cricket::kRtxCodecName));
+ caller_video_codecs.back().params[cricket::kCodecParamAssociatedPayloadType] =
+ std::to_string(caller_video_codecs.back().id - 1);
+ caller_video_codecs.push_back(cricket::VideoCodec(
+ caller_video_codecs.back().id + 1, cricket::kVp9CodecName));
+ caller_video_codecs.push_back(cricket::VideoCodec(
+ caller_video_codecs.back().id + 1, cricket::kRtxCodecName));
+ caller_video_codecs.back().params[cricket::kCodecParamAssociatedPayloadType] =
+ std::to_string(caller_video_codecs.back().id - 1);
+ caller_fake_engine->SetVideoCodecs(caller_video_codecs);
+
+ auto callee_fake_engine = absl::make_unique<FakeMediaEngine>();
+ callee_fake_engine->SetVideoCodecs(caller_video_codecs);
+
+ auto caller = CreatePeerConnectionWithVideo(std::move(caller_fake_engine));
+ auto callee = CreatePeerConnection(std::move(callee_fake_engine));
+
+ auto video_codecs = caller->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
+ .codecs;
+
+ auto send_transceiver = caller->pc()->GetTransceivers().front();
+
+ auto video_codecs_vpx = video_codecs;
+ auto it = std::remove_if(video_codecs_vpx.begin(), video_codecs_vpx.end(),
+ [](const webrtc::RtpCodecCapability& codec) {
+ return codec.name != cricket::kVp8CodecName &&
+ codec.name != cricket::kVp9CodecName;
+ });
+ video_codecs_vpx.erase(it, video_codecs_vpx.end());
+ EXPECT_EQ(video_codecs_vpx.size(), 2u); // VP8, VP9
+ EXPECT_TRUE(send_transceiver->SetCodecPreferences(video_codecs_vpx).ok());
+
+ auto offer = caller->CreateOfferAndSetAsLocal();
+ auto codecs = offer->description()
+ ->contents()[0]
+ .media_description()
+ ->as_video()
+ ->codecs();
+
+ EXPECT_EQ(codecs.size(), 2u); // VP8, VP9
+ EXPECT_TRUE(CompareCodecs(video_codecs_vpx, codecs));
+
+ callee->SetRemoteDescription(std::move(offer));
+
+ auto recv_transceiver = callee->pc()->GetTransceivers().front();
+ auto video_codecs_vp8_rtx = video_codecs;
+ it = std::remove_if(video_codecs_vp8_rtx.begin(), video_codecs_vp8_rtx.end(),
+ [](const webrtc::RtpCodecCapability& codec) {
+ bool r = codec.name != cricket::kVp8CodecName &&
+ codec.name != cricket::kRtxCodecName;
+ return r;
+ });
+ video_codecs_vp8_rtx.erase(it, video_codecs_vp8_rtx.end());
+ EXPECT_EQ(video_codecs_vp8_rtx.size(), 2u); // VP8, RTX
+ recv_transceiver->SetCodecPreferences(video_codecs_vp8_rtx);
+
+ auto answer = callee->CreateAnswerAndSetAsLocal();
+
+ auto recv_codecs = answer->description()
+ ->contents()[0]
+ .media_description()
+ ->as_video()
+ ->codecs();
+ EXPECT_EQ(recv_codecs.size(), 1u); // VP8
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan,
+ SetCodecPreferencesVideoCodecsNegotiationReverseOrder) {
+ auto caller_fake_engine = absl::make_unique<FakeMediaEngine>();
+ auto caller_video_codecs = caller_fake_engine->video().codecs();
+ caller_video_codecs.push_back(cricket::VideoCodec(
+ caller_video_codecs.back().id + 1, cricket::kVp8CodecName));
+ caller_video_codecs.push_back(cricket::VideoCodec(
+ caller_video_codecs.back().id + 1, cricket::kRtxCodecName));
+ caller_video_codecs.back().params[cricket::kCodecParamAssociatedPayloadType] =
+ std::to_string(caller_video_codecs.back().id - 1);
+ caller_video_codecs.push_back(cricket::VideoCodec(
+ caller_video_codecs.back().id + 1, cricket::kVp9CodecName));
+ caller_video_codecs.push_back(cricket::VideoCodec(
+ caller_video_codecs.back().id + 1, cricket::kRtxCodecName));
+ caller_video_codecs.back().params[cricket::kCodecParamAssociatedPayloadType] =
+ std::to_string(caller_video_codecs.back().id - 1);
+ caller_fake_engine->SetVideoCodecs(caller_video_codecs);
+
+ auto callee_fake_engine = absl::make_unique<FakeMediaEngine>();
+ callee_fake_engine->SetVideoCodecs(caller_video_codecs);
+
+ auto caller = CreatePeerConnectionWithVideo(std::move(caller_fake_engine));
+ auto callee = CreatePeerConnection(std::move(callee_fake_engine));
+
+ auto video_codecs = caller->pc_factory()
+ ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO)
+ .codecs;
+
+ auto send_transceiver = caller->pc()->GetTransceivers().front();
+
+ auto video_codecs_vpx = video_codecs;
+ auto it = std::remove_if(video_codecs_vpx.begin(), video_codecs_vpx.end(),
+ [](const webrtc::RtpCodecCapability& codec) {
+ return codec.name != cricket::kVp8CodecName &&
+ codec.name != cricket::kVp9CodecName;
+ });
+ video_codecs_vpx.erase(it, video_codecs_vpx.end());
+ EXPECT_EQ(video_codecs_vpx.size(), 2u); // VP8, VP9
+ EXPECT_TRUE(send_transceiver->SetCodecPreferences(video_codecs_vpx).ok());
+
+ auto video_codecs_vpx_reverse = video_codecs_vpx;
+ absl::c_reverse(video_codecs_vpx_reverse);
+
+ auto offer = caller->CreateOfferAndSetAsLocal();
+ auto codecs = offer->description()
+ ->contents()[0]
+ .media_description()
+ ->as_video()
+ ->codecs();
+ EXPECT_EQ(codecs.size(), 2u); // VP9, VP8
+ EXPECT_TRUE(CompareCodecs(video_codecs_vpx, codecs));
+
+ callee->SetRemoteDescription(std::move(offer));
+
+ auto recv_transceiver = callee->pc()->GetTransceivers().front();
+ recv_transceiver->SetCodecPreferences(video_codecs_vpx_reverse);
+
+ auto answer = callee->CreateAnswerAndSetAsLocal();
+
+ auto recv_codecs = answer->description()
+ ->contents()[0]
+ .media_description()
+ ->as_video()
+ ->codecs();
+
+ EXPECT_TRUE(CompareCodecs(video_codecs_vpx_reverse, recv_codecs));
+}
+
INSTANTIATE_TEST_SUITE_P(PeerConnectionMediaTest,
PeerConnectionMediaTest,
Values(SdpSemantics::kPlanB,
diff --git a/pc/rtp_transceiver.cc b/pc/rtp_transceiver.cc
index a000b58..d8d1681 100644
--- a/pc/rtp_transceiver.cc
+++ b/pc/rtp_transceiver.cc
@@ -13,7 +13,9 @@
#include <string>
#include "absl/algorithm/container.h"
+#include "pc/channel_manager.h"
#include "pc/rtp_media_utils.h"
+#include "pc/rtp_parameters_conversion.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
@@ -28,8 +30,11 @@
RtpTransceiver::RtpTransceiver(
rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> sender,
rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
- receiver)
- : unified_plan_(true), media_type_(sender->media_type()) {
+ receiver,
+ cricket::ChannelManager* channel_manager)
+ : unified_plan_(true),
+ media_type_(sender->media_type()),
+ channel_manager_(channel_manager) {
RTC_DCHECK(media_type_ == cricket::MEDIA_TYPE_AUDIO ||
media_type_ == cricket::MEDIA_TYPE_VIDEO);
RTC_DCHECK_EQ(sender->media_type(), receiver->media_type());
@@ -223,10 +228,129 @@
current_direction_ = absl::nullopt;
}
-void RtpTransceiver::SetCodecPreferences(
- rtc::ArrayView<RtpCodecCapability> codecs) {
- // TODO(steveanton): Implement this.
- RTC_NOTREACHED() << "Not implemented";
+RTCError RtpTransceiver::SetCodecPreferences(
+ rtc::ArrayView<RtpCodecCapability> codec_capabilities) {
+ RTC_DCHECK(unified_plan_);
+
+ // 3. If codecs is an empty list, set transceiver's [[PreferredCodecs]] slot
+ // to codecs and abort these steps.
+ if (codec_capabilities.empty()) {
+ codec_preferences_.clear();
+ return RTCError::OK();
+ }
+
+ // 4. Remove any duplicate values in codecs.
+ std::vector<RtpCodecCapability> codecs;
+ absl::c_remove_copy_if(codec_capabilities, std::back_inserter(codecs),
+ [&codecs](const RtpCodecCapability& codec) {
+ return absl::c_linear_search(codecs, codec);
+ });
+
+ if (media_type_ == cricket::MEDIA_TYPE_AUDIO) {
+ std::vector<cricket::AudioCodec> audio_codecs;
+
+ std::vector<cricket::AudioCodec> recv_codecs, send_codecs;
+ channel_manager_->GetSupportedAudioReceiveCodecs(&recv_codecs);
+ channel_manager_->GetSupportedAudioSendCodecs(&send_codecs);
+
+ // 6. If the intersection between codecs and
+ // RTCRtpSender.getCapabilities(kind).codecs or the intersection between
+ // codecs and RTCRtpReceiver.getCapabilities(kind).codecs only contains RTX,
+ // RED or FEC codecs or is an empty set, throw InvalidModificationError.
+ // This ensures that we always have something to offer, regardless of
+ // transceiver.direction.
+
+ if (!absl::c_any_of(
+ codecs, [&recv_codecs](const RtpCodecCapability& codec) {
+ return codec.name != cricket::kRtxCodecName &&
+ codec.name != cricket::kRedCodecName &&
+ codec.name != cricket::kFlexfecCodecName &&
+ absl::c_any_of(
+ recv_codecs,
+ [&codec](const cricket::AudioCodec& recv_codec) {
+ return recv_codec.MatchesCapability(codec);
+ });
+ })) {
+ return RTCError(RTCErrorType::INVALID_MODIFICATION,
+ "Invalid codec preferences: Missing codec from recv "
+ "codec capabilities.");
+ }
+
+ if (!absl::c_any_of(
+ codecs, [&send_codecs](const RtpCodecCapability& codec) {
+ return codec.name != cricket::kRtxCodecName &&
+ codec.name != cricket::kRedCodecName &&
+ codec.name != cricket::kFlexfecCodecName &&
+ absl::c_any_of(
+ send_codecs,
+ [&codec](const cricket::AudioCodec& send_codec) {
+ return send_codec.MatchesCapability(codec);
+ });
+ })) {
+ return RTCError(RTCErrorType::INVALID_MODIFICATION,
+ "Invalid codec preferences: Missing codec from send "
+ "codec capabilities.");
+ }
+
+ // 7. Let codecCapabilities be the union of
+ // RTCRtpSender.getCapabilities(kind).codecs and
+ // RTCRtpReceiver.getCapabilities(kind).codecs. 8.1 For each codec in
+ // codecs, If codec is not in codecCapabilities, throw
+ // InvalidModificationError.
+ for (const auto& codec_preference : codecs) {
+ bool is_recv_codec = absl::c_any_of(
+ recv_codecs, [&codec_preference](const cricket::AudioCodec& codec) {
+ return codec.MatchesCapability(codec_preference);
+ });
+
+ bool is_send_codec = absl::c_any_of(
+ send_codecs, [&codec_preference](const cricket::AudioCodec& codec) {
+ return codec.MatchesCapability(codec_preference);
+ });
+
+ if (!is_recv_codec && !is_send_codec) {
+ return RTCError(
+ RTCErrorType::INVALID_MODIFICATION,
+ std::string(
+ "Invalid codec preferences: invalid codec with name \"") +
+ codec_preference.name + "\".");
+ }
+ }
+ } else if (media_type_ == cricket::MEDIA_TYPE_VIDEO) {
+ std::vector<cricket::VideoCodec> video_codecs;
+ // Video codecs are both for the receive and send side, so the checks are
+ // simpler than the audio ones.
+ channel_manager_->GetSupportedVideoCodecs(&video_codecs);
+
+ // Validate codecs
+ for (const auto& codec_preference : codecs) {
+ if (!absl::c_any_of(video_codecs, [&codec_preference](
+ const cricket::VideoCodec& codec) {
+ return codec.MatchesCapability(codec_preference);
+ })) {
+ return RTCError(
+ RTCErrorType::INVALID_MODIFICATION,
+ std::string(
+ "Invalid codec preferences: invalid codec with name \"") +
+ codec_preference.name + "\".");
+ }
+ }
+ }
+
+ // Check we have a real codec (not just rtx, red or fec)
+ if (absl::c_all_of(codecs, [](const RtpCodecCapability& codec) {
+ return codec.name == cricket::kRtxCodecName ||
+ codec.name == cricket::kRedCodecName ||
+ codec.name == cricket::kUlpfecCodecName;
+ })) {
+ return RTCError(RTCErrorType::INVALID_MODIFICATION,
+ "Invalid codec preferences: codec list must have a non "
+ "RTX, RED or FEC entry.");
+ }
+
+ codec_preferences_ = codecs;
+
+ return RTCError::OK();
}
} // namespace webrtc
diff --git a/pc/rtp_transceiver.h b/pc/rtp_transceiver.h
index 2c04bd7..990c3cc 100644
--- a/pc/rtp_transceiver.h
+++ b/pc/rtp_transceiver.h
@@ -16,6 +16,7 @@
#include "api/rtp_transceiver_interface.h"
#include "pc/channel_interface.h"
+#include "pc/channel_manager.h"
#include "pc/rtp_receiver.h"
#include "pc/rtp_sender.h"
@@ -66,7 +67,8 @@
RtpTransceiver(
rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>> sender,
rtc::scoped_refptr<RtpReceiverProxyWithInternal<RtpReceiverInternal>>
- receiver);
+ receiver,
+ cricket::ChannelManager* channel_manager);
~RtpTransceiver() override;
// Returns the Voice/VideoChannel set for this transceiver. May be null if
@@ -175,7 +177,11 @@
absl::optional<RtpTransceiverDirection> current_direction() const override;
absl::optional<RtpTransceiverDirection> fired_direction() const override;
void Stop() override;
- void SetCodecPreferences(rtc::ArrayView<RtpCodecCapability> codecs) override;
+ RTCError SetCodecPreferences(
+ rtc::ArrayView<RtpCodecCapability> codecs) override;
+ std::vector<RtpCodecCapability> codec_preferences() const override {
+ return codec_preferences_;
+ }
private:
void OnFirstPacketReceived(cricket::ChannelInterface* channel);
@@ -198,6 +204,8 @@
bool has_ever_been_used_to_send_ = false;
cricket::ChannelInterface* channel_ = nullptr;
+ cricket::ChannelManager* channel_manager_ = nullptr;
+ std::vector<RtpCodecCapability> codec_preferences_;
};
BEGIN_SIGNALING_PROXY_MAP(RtpTransceiver)
@@ -212,7 +220,10 @@
PROXY_CONSTMETHOD0(absl::optional<RtpTransceiverDirection>, current_direction)
PROXY_CONSTMETHOD0(absl::optional<RtpTransceiverDirection>, fired_direction)
PROXY_METHOD0(void, Stop)
-PROXY_METHOD1(void, SetCodecPreferences, rtc::ArrayView<RtpCodecCapability>)
+PROXY_METHOD1(webrtc::RTCError,
+ SetCodecPreferences,
+ rtc::ArrayView<RtpCodecCapability>)
+PROXY_CONSTMETHOD0(std::vector<RtpCodecCapability>, codec_preferences)
END_PROXY_MAP()
} // namespace webrtc