From 2d9d82eceff99dc31d33136d15fc15a0267b1044 Mon Sep 17 00:00:00 2001 From: Florent Castelli Date: Tue, 23 Apr 2019 19:25:51 +0200 Subject: [PATCH] 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 Reviewed-by: Seth Hampson Commit-Queue: Florent Castelli Cr-Commit-Position: refs/heads/master@{#27817} --- api/rtp_transceiver_interface.cc | 8 +- api/rtp_transceiver_interface.h | 5 +- media/base/codec.cc | 12 + media/base/codec.h | 1 + media/base/fake_media_engine.cc | 23 +- media/base/fake_media_engine.h | 7 +- pc/media_session.cc | 280 +++++++++------ pc/media_session.h | 1 + pc/peer_connection.cc | 5 +- pc/peer_connection_media_unittest.cc | 518 ++++++++++++++++++++++++++- pc/rtp_transceiver.cc | 136 ++++++- pc/rtp_transceiver.h | 17 +- 12 files changed, 892 insertions(+), 121 deletions(-) diff --git a/api/rtp_transceiver_interface.cc b/api/rtp_transceiver_interface.cc index 050433148b..dc82fadd39 100644 --- a/api/rtp_transceiver_interface.cc +++ b/api/rtp_transceiver_interface.cc @@ -25,9 +25,15 @@ RtpTransceiverInterface::fired_direction() const { return absl::nullopt; } -void RtpTransceiverInterface::SetCodecPreferences( +RTCError RtpTransceiverInterface::SetCodecPreferences( rtc::ArrayView) { RTC_NOTREACHED() << "Not implemented"; + return {}; +} + +std::vector RtpTransceiverInterface::codec_preferences() + const { + return {}; } } // namespace webrtc diff --git a/api/rtp_transceiver_interface.h b/api/rtp_transceiver_interface.h index 9b052d1b73..4606632808 100644 --- a/api/rtp_transceiver_interface.h +++ b/api/rtp_transceiver_interface.h @@ -129,8 +129,9 @@ class RtpTransceiverInterface : public rtc::RefCountInterface { // 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 codecs); + virtual RTCError SetCodecPreferences( + rtc::ArrayView codecs); + virtual std::vector codec_preferences() const; protected: ~RtpTransceiverInterface() override = default; diff --git a/media/base/codec.cc b/media/base/codec.cc index 16ef65dff5..d0ca29b6f5 100644 --- a/media/base/codec.cc +++ b/media/base/codec.cc @@ -99,6 +99,18 @@ bool Codec::Matches(const Codec& codec) const { : (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 b60d001de0..091adb6cfa 100644 --- a/media/base/codec.h +++ b/media/base/codec.h @@ -73,6 +73,7 @@ struct RTC_EXPORT Codec { // 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 9724423e0b..358b51c7de 100644 --- a/media/base/fake_media_engine.cc +++ b/media/base/fake_media_engine.cc @@ -495,7 +495,7 @@ bool FakeDataMediaChannel::SetMaxSendBandwidth(int bps) { 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 @@ void FakeVoiceEngine::UnregisterChannel(VoiceMediaChannel* channel) { channels_.erase(absl::c_find(channels_, channel)); } const std::vector& FakeVoiceEngine::send_codecs() const { - return codecs_; + return send_codecs_; } const std::vector& FakeVoiceEngine::recv_codecs() const { - return codecs_; + return recv_codecs_; } void FakeVoiceEngine::SetCodecs(const std::vector& codecs) { - codecs_ = codecs; + send_codecs_ = codecs; + recv_codecs_ = codecs; +} +void FakeVoiceEngine::SetRecvCodecs(const std::vector& codecs) { + recv_codecs_ = codecs; +} +void FakeVoiceEngine::SetSendCodecs(const std::vector& codecs) { + send_codecs_ = codecs; } int FakeVoiceEngine::GetInputLevel() { return 0; @@ -601,6 +608,14 @@ FakeMediaEngine::~FakeMediaEngine() {} void FakeMediaEngine::SetAudioCodecs(const std::vector& codecs) { voice_->SetCodecs(codecs); } +void FakeMediaEngine::SetAudioRecvCodecs( + const std::vector& codecs) { + voice_->SetRecvCodecs(codecs); +} +void FakeMediaEngine::SetAudioSendCodecs( + const std::vector& codecs) { + voice_->SetSendCodecs(codecs); +} void FakeMediaEngine::SetVideoCodecs(const std::vector& codecs) { video_->SetCodecs(codecs); } diff --git a/media/base/fake_media_engine.h b/media/base/fake_media_engine.h index f1192c51ff..b42f896b80 100644 --- a/media/base/fake_media_engine.h +++ b/media/base/fake_media_engine.h @@ -523,6 +523,8 @@ class FakeVoiceEngine : public VoiceEngineInterface { const std::vector& send_codecs() const override; const std::vector& recv_codecs() const override; void SetCodecs(const std::vector& codecs); + void SetRecvCodecs(const std::vector& codecs); + void SetSendCodecs(const std::vector& codecs); int GetInputLevel(); bool StartAecDump(rtc::PlatformFile file, int64_t max_size_bytes) override; void StopAecDump() override; @@ -531,7 +533,8 @@ class FakeVoiceEngine : public VoiceEngineInterface { private: std::vector channels_; - std::vector codecs_; + std::vector recv_codecs_; + std::vector send_codecs_; bool fail_create_channel_; friend class FakeMediaEngine; @@ -572,6 +575,8 @@ class FakeMediaEngine : public CompositeMediaEngine { ~FakeMediaEngine() override; void SetAudioCodecs(const std::vector& codecs); + void SetAudioRecvCodecs(const std::vector& codecs); + void SetAudioSendCodecs(const std::vector& codecs); void SetVideoCodecs(const std::vector& codecs); FakeVoiceMediaChannel* GetVoiceChannel(size_t index); diff --git a/pc/media_session.cc b/pc/media_session.cc index 8377f1003f..0eace22c6a 100644 --- a/pc/media_session.cc +++ b/pc/media_session.cc @@ -799,7 +799,8 @@ static bool ReferencedCodecsMatch(const std::vector& codecs1, template static void NegotiateCodecs(const std::vector& local_codecs, const std::vector& offered_codecs, - std::vector* negotiated_codecs) { + std::vector* 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 @@ static void NegotiateCodecs(const std::vector& local_codecs, 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 payload_type_preferences; - int preference = static_cast(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 payload_type_preferences; + int preference = static_cast(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 @@ static void MergeCodecs(const std::vector& reference_codecs, } } +template +static Codecs MatchCodecPreference( + const std::vector& codec_preferences, + const Codecs& codecs) { + Codecs filtered_codecs; + std::set 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 @@ static bool CreateMediaContentAnswer( bool bundle_enabled, MediaContentDescriptionImpl* answer) { std::vector 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 @@ bool MediaSessionDescriptionFactory::AddAudioContentForOffer( 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(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(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(supported_audio_codecs, audio_codecs, - codec, &found_codec) && - !FindMatchingCodec(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(supported_audio_codecs, audio_codecs, + codec, &found_codec) && + !FindMatchingCodec(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 @@ bool MediaSessionDescriptionFactory::AddVideoContentForOffer( &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(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(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(video_codecs_, video_codecs, codec, - &found_codec) && - !FindMatchingCodec(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(video_codecs_, video_codecs, codec, + &found_codec) && + !FindMatchingCodec(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 @@ bool MediaSessionDescriptionFactory::AddAudioContentForAnswer( 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(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(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(supported_audio_codecs, audio_codecs, - codec, nullptr) && - !FindMatchingCodec(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(supported_audio_codecs, audio_codecs, + codec, nullptr) && + !FindMatchingCodec(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 @@ bool MediaSessionDescriptionFactory::AddVideoContentForAnswer( } 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(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(vcd->codecs(), video_codecs, codec, + nullptr)) { + filtered_codecs.push_back(codec); + } } } - } - // Add other supported video codecs. - for (const VideoCodec& codec : video_codecs_) { - if (FindMatchingCodec(video_codecs_, video_codecs, codec, - nullptr) && - !FindMatchingCodec(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(video_codecs_, video_codecs, codec, + nullptr) && + !FindMatchingCodec(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 @@ void MediaSessionDescriptionFactory::ComputeAudioCodecsIntersectionAndUnion() { // 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 33c8c17d37..a369756964 100644 --- a/pc/media_session.h +++ b/pc/media_session.h @@ -76,6 +76,7 @@ struct MediaDescriptionOptions { // Note: There's no equivalent "RtpReceiverOptions" because only send // stream information goes in the local descriptions. std::vector sender_options; + std::vector codec_preferences; private: // Doesn't DCHECK on |type|. diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc index f7cfc9a71a..8a6d0e57ac 100644 --- a/pc/peer_connection.cc +++ b/pc/peer_connection.cc @@ -1703,7 +1703,8 @@ PeerConnection::CreateAndAddTransceiver( // could be invalid, but should not cause a crash). RTC_DCHECK(!FindSenderById(sender->id())); auto transceiver = RtpTransceiverProxyWithInternal::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 @@ GetMediaDescriptionOptionsForTransceiver( 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 c81d5111b7..e9e3a00a13 100644 --- a/pc/peer_connection_media_unittest.cc +++ b/pc/peer_connection_media_unittest.cc @@ -73,11 +73,21 @@ class PeerConnectionMediaBaseTest : public ::testing::Test { return CreatePeerConnection(RTCConfiguration()); } + WrapperPtr CreatePeerConnection(const RTCConfiguration& config) { + return CreatePeerConnection(config, absl::make_unique()); + } + + WrapperPtr CreatePeerConnection( + std::unique_ptr 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(); + WrapperPtr CreatePeerConnection( + const RTCConfiguration& config, + std::unique_ptr media_engine) { auto* media_engine_ptr = media_engine.get(); PeerConnectionFactoryDependencies factory_dependencies; @@ -125,6 +135,18 @@ class PeerConnectionMediaBaseTest : public ::testing::Test { return wrapper; } + // Accepts the same arguments as CreatePeerConnection and adds default video + // track (but no audio). + template + WrapperPtr CreatePeerConnectionWithVideo(Args&&... args) { + auto wrapper = CreatePeerConnection(std::forward(args)...); + if (!wrapper) { + return nullptr; + } + wrapper->AddVideoTrack("v"); + return wrapper; + } + // Accepts the same arguments as CreatePeerConnection and adds default audio // and video tracks. template @@ -1239,6 +1261,498 @@ TEST_P(PeerConnectionMediaTest, MediaTransportNotPropagatedToVoiceEngine) { ASSERT_EQ(nullptr, callee_video->media_transport()); } +template +bool CompareCodecs(const std::vector& capabilities, + const std::vector& 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 codecs_no_rtx; + absl::c_copy_if( + codecs, std::back_inserter(codecs_no_rtx), + [](const C& codec) { return codec.name != cricket::kRtxCodecName; }); + + std::vector 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(); + 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 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(); + 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 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(); + 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 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(); + 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 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(); + 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(); + 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(); + 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(); + 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(); + 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 a000b5809d..d8d168191e 100644 --- a/pc/rtp_transceiver.cc +++ b/pc/rtp_transceiver.cc @@ -13,7 +13,9 @@ #include #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(cricket::MediaType media_type) RtpTransceiver::RtpTransceiver( rtc::scoped_refptr> sender, rtc::scoped_refptr> - 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 @@ void RtpTransceiver::Stop() { current_direction_ = absl::nullopt; } -void RtpTransceiver::SetCodecPreferences( - rtc::ArrayView codecs) { - // TODO(steveanton): Implement this. - RTC_NOTREACHED() << "Not implemented"; +RTCError RtpTransceiver::SetCodecPreferences( + rtc::ArrayView 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 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 audio_codecs; + + std::vector 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 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 2c04bd7c80..990c3cc09a 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 @@ class RtpTransceiver final RtpTransceiver( rtc::scoped_refptr> sender, rtc::scoped_refptr> - receiver); + receiver, + cricket::ChannelManager* channel_manager); ~RtpTransceiver() override; // Returns the Voice/VideoChannel set for this transceiver. May be null if @@ -175,7 +177,11 @@ class RtpTransceiver final absl::optional current_direction() const override; absl::optional fired_direction() const override; void Stop() override; - void SetCodecPreferences(rtc::ArrayView codecs) override; + RTCError SetCodecPreferences( + rtc::ArrayView codecs) override; + std::vector codec_preferences() const override { + return codec_preferences_; + } private: void OnFirstPacketReceived(cricket::ChannelInterface* channel); @@ -198,6 +204,8 @@ class RtpTransceiver final bool has_ever_been_used_to_send_ = false; cricket::ChannelInterface* channel_ = nullptr; + cricket::ChannelManager* channel_manager_ = nullptr; + std::vector codec_preferences_; }; BEGIN_SIGNALING_PROXY_MAP(RtpTransceiver) @@ -212,7 +220,10 @@ PROXY_METHOD1(void, SetDirection, RtpTransceiverDirection) PROXY_CONSTMETHOD0(absl::optional, current_direction) PROXY_CONSTMETHOD0(absl::optional, fired_direction) PROXY_METHOD0(void, Stop) -PROXY_METHOD1(void, SetCodecPreferences, rtc::ArrayView) +PROXY_METHOD1(webrtc::RTCError, + SetCodecPreferences, + rtc::ArrayView) +PROXY_CONSTMETHOD0(std::vector, codec_preferences) END_PROXY_MAP() } // namespace webrtc