/* * Copyright 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ // This file contains tests for `RtpTransceiver`. #include "pc/rtp_transceiver.h" #include #include #include #include "absl/strings/string_view.h" #include "api/environment/environment_factory.h" #include "api/peer_connection_interface.h" #include "api/rtp_parameters.h" #include "api/test/rtc_error_matchers.h" #include "media/base/codec_comparators.h" #include "media/base/fake_media_engine.h" #include "pc/rtp_parameters_conversion.h" #include "pc/test/enable_fake_media.h" #include "pc/test/mock_channel_interface.h" #include "pc/test/mock_rtp_receiver_internal.h" #include "pc/test/mock_rtp_sender_internal.h" #include "rtc_base/thread.h" #include "test/gmock.h" #include "test/gtest.h" using ::testing::_; using ::testing::ElementsAre; using ::testing::Field; using ::testing::NiceMock; using ::testing::Optional; using ::testing::Property; using ::testing::Return; using ::testing::ReturnRef; using ::testing::SizeIs; namespace webrtc { namespace { class RtpTransceiverTest : public testing::Test { public: RtpTransceiverTest() : dependencies_(MakeDependencies()), context_( ConnectionContext::Create(CreateEnvironment(), &dependencies_)) {} protected: cricket::FakeMediaEngine* media_engine() { // We know this cast is safe because we supplied the fake implementation // in MakeDependencies(). return static_cast(context_->media_engine()); } ConnectionContext* context() { return context_.get(); } private: rtc::AutoThread main_thread_; static PeerConnectionFactoryDependencies MakeDependencies() { PeerConnectionFactoryDependencies d; d.network_thread = rtc::Thread::Current(); d.worker_thread = rtc::Thread::Current(); d.signaling_thread = rtc::Thread::Current(); EnableFakeMedia(d, std::make_unique()); return d; } PeerConnectionFactoryDependencies dependencies_; rtc::scoped_refptr context_; }; // Checks that a channel cannot be set on a stopped `RtpTransceiver`. TEST_F(RtpTransceiverTest, CannotSetChannelOnStoppedTransceiver) { const std::string content_name("my_mid"); auto transceiver = rtc::make_ref_counted( cricket::MediaType::MEDIA_TYPE_AUDIO, context()); auto channel1 = std::make_unique>(); EXPECT_CALL(*channel1, media_type()) .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO)); EXPECT_CALL(*channel1, mid()).WillRepeatedly(ReturnRef(content_name)); EXPECT_CALL(*channel1, SetFirstPacketReceivedCallback(_)); EXPECT_CALL(*channel1, SetRtpTransport(_)).WillRepeatedly(Return(true)); auto channel1_ptr = channel1.get(); transceiver->SetChannel(std::move(channel1), [&](const std::string& mid) { EXPECT_EQ(mid, content_name); return nullptr; }); EXPECT_EQ(channel1_ptr, transceiver->channel()); // Stop the transceiver. transceiver->StopInternal(); EXPECT_EQ(channel1_ptr, transceiver->channel()); auto channel2 = std::make_unique>(); EXPECT_CALL(*channel2, media_type()) .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO)); // Clear the current channel - required to allow SetChannel() EXPECT_CALL(*channel1_ptr, SetFirstPacketReceivedCallback(_)); transceiver->ClearChannel(); ASSERT_EQ(nullptr, transceiver->channel()); // Channel can no longer be set, so this call should be a no-op. transceiver->SetChannel(std::move(channel2), [](const std::string&) { return nullptr; }); EXPECT_EQ(nullptr, transceiver->channel()); } // Checks that a channel can be unset on a stopped `RtpTransceiver` TEST_F(RtpTransceiverTest, CanUnsetChannelOnStoppedTransceiver) { const std::string content_name("my_mid"); auto transceiver = rtc::make_ref_counted( cricket::MediaType::MEDIA_TYPE_VIDEO, context()); auto channel = std::make_unique>(); EXPECT_CALL(*channel, media_type()) .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_VIDEO)); EXPECT_CALL(*channel, mid()).WillRepeatedly(ReturnRef(content_name)); EXPECT_CALL(*channel, SetFirstPacketReceivedCallback(_)) .WillRepeatedly(testing::Return()); EXPECT_CALL(*channel, SetRtpTransport(_)).WillRepeatedly(Return(true)); auto channel_ptr = channel.get(); transceiver->SetChannel(std::move(channel), [&](const std::string& mid) { EXPECT_EQ(mid, content_name); return nullptr; }); EXPECT_EQ(channel_ptr, transceiver->channel()); // Stop the transceiver. transceiver->StopInternal(); EXPECT_EQ(channel_ptr, transceiver->channel()); // Set the channel to `nullptr`. transceiver->ClearChannel(); EXPECT_EQ(nullptr, transceiver->channel()); } class RtpTransceiverUnifiedPlanTest : public RtpTransceiverTest { public: static rtc::scoped_refptr MockReceiver( cricket::MediaType media_type) { auto receiver = rtc::make_ref_counted>(); EXPECT_CALL(*receiver.get(), media_type()) .WillRepeatedly(Return(media_type)); return receiver; } static rtc::scoped_refptr MockSender( cricket::MediaType media_type) { auto sender = rtc::make_ref_counted>(); EXPECT_CALL(*sender.get(), media_type()).WillRepeatedly(Return(media_type)); return sender; } rtc::scoped_refptr CreateTransceiver( rtc::scoped_refptr sender, rtc::scoped_refptr receiver) { return rtc::make_ref_counted( RtpSenderProxyWithInternal::Create( rtc::Thread::Current(), std::move(sender)), RtpReceiverProxyWithInternal::Create( rtc::Thread::Current(), rtc::Thread::Current(), std::move(receiver)), context(), media_engine()->voice().GetRtpHeaderExtensions(), /* on_negotiation_needed= */ [] {}); } protected: rtc::AutoThread main_thread_; }; // Basic tests for Stop() TEST_F(RtpTransceiverUnifiedPlanTest, StopSetsDirection) { rtc::scoped_refptr receiver = MockReceiver(cricket::MediaType::MEDIA_TYPE_AUDIO); rtc::scoped_refptr sender = MockSender(cricket::MediaType::MEDIA_TYPE_AUDIO); rtc::scoped_refptr transceiver = CreateTransceiver(sender, receiver); EXPECT_CALL(*receiver.get(), Stop()); EXPECT_CALL(*receiver.get(), SetMediaChannel(_)); EXPECT_CALL(*sender.get(), SetTransceiverAsStopped()); EXPECT_CALL(*sender.get(), Stop()); EXPECT_EQ(RtpTransceiverDirection::kInactive, transceiver->direction()); EXPECT_FALSE(transceiver->current_direction()); transceiver->StopStandard(); EXPECT_EQ(RtpTransceiverDirection::kStopped, transceiver->direction()); EXPECT_FALSE(transceiver->current_direction()); transceiver->StopTransceiverProcedure(); EXPECT_TRUE(transceiver->current_direction()); EXPECT_EQ(RtpTransceiverDirection::kStopped, transceiver->direction()); EXPECT_EQ(RtpTransceiverDirection::kStopped, *transceiver->current_direction()); } class RtpTransceiverFilteredCodecPreferencesTest : public RtpTransceiverUnifiedPlanTest { public: RtpTransceiverFilteredCodecPreferencesTest() : transceiver_(CreateTransceiver( MockSender(cricket::MediaType::MEDIA_TYPE_VIDEO), MockReceiver(cricket::MediaType::MEDIA_TYPE_VIDEO))) {} struct H264CodecCapabilities { cricket::Codec cricket_sendrecv_codec; RtpCodecCapability sendrecv_codec; cricket::Codec cricket_sendonly_codec; RtpCodecCapability sendonly_codec; cricket::Codec cricket_recvonly_codec; RtpCodecCapability recvonly_codec; cricket::Codec cricket_rtx_codec; RtpCodecCapability rtx_codec; }; // For H264, the profile and level IDs are entangled. This function uses // profile-level-id values that are not equal even when levels are ignored. H264CodecCapabilities ConfigureH264CodecCapabilities() { cricket::Codec cricket_sendrecv_codec = cricket::CreateVideoCodec( SdpVideoFormat("H264", {{"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, {"profile-level-id", "42f00b"}}, {ScalabilityMode::kL1T1})); cricket::Codec cricket_sendonly_codec = cricket::CreateVideoCodec( SdpVideoFormat("H264", {{"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, {"profile-level-id", "640034"}}, {ScalabilityMode::kL1T1})); cricket::Codec cricket_recvonly_codec = cricket::CreateVideoCodec( SdpVideoFormat("H264", {{"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, {"profile-level-id", "f4001f"}}, {ScalabilityMode::kL1T1})); cricket::Codec cricket_rtx_codec = cricket::CreateVideoRtxCodec( cricket::Codec::kIdNotSet, cricket::Codec::kIdNotSet); media_engine()->SetVideoSendCodecs( {cricket_sendrecv_codec, cricket_sendonly_codec, cricket_rtx_codec}); media_engine()->SetVideoRecvCodecs( {cricket_sendrecv_codec, cricket_recvonly_codec, cricket_rtx_codec}); H264CodecCapabilities capabilities = { .cricket_sendrecv_codec = cricket_sendrecv_codec, .sendrecv_codec = ToRtpCodecCapability(cricket_sendrecv_codec), .cricket_sendonly_codec = cricket_sendonly_codec, .sendonly_codec = ToRtpCodecCapability(cricket_sendonly_codec), .cricket_recvonly_codec = cricket_recvonly_codec, .recvonly_codec = ToRtpCodecCapability(cricket_recvonly_codec), .cricket_rtx_codec = cricket_rtx_codec, .rtx_codec = ToRtpCodecCapability(cricket_rtx_codec), }; EXPECT_FALSE(IsSameRtpCodecIgnoringLevel( capabilities.cricket_sendrecv_codec, capabilities.sendonly_codec)); EXPECT_FALSE(IsSameRtpCodecIgnoringLevel( capabilities.cricket_sendrecv_codec, capabilities.recvonly_codec)); EXPECT_FALSE(IsSameRtpCodecIgnoringLevel( capabilities.cricket_sendonly_codec, capabilities.recvonly_codec)); return capabilities; } #ifdef RTC_ENABLE_H265 struct H265CodecCapabilities { // The level-id from sender getCapabilities() or receiver getCapabilities(). static constexpr const char* kSendOnlyLevel = "180"; static constexpr const char* kRecvOnlyLevel = "156"; // A valid H265 level-id, but one not present in either getCapabilities(). static constexpr const char* kLevelNotInCapabilities = "135"; cricket::Codec cricket_sendonly_codec; RtpCodecCapability sendonly_codec; cricket::Codec cricket_recvonly_codec; RtpCodecCapability recvonly_codec; }; // For H265, the profile and level IDs are separate and are ignored by // IsSameRtpCodecIgnoringLevel(). H265CodecCapabilities ConfigureH265CodecCapabilities() { cricket::Codec cricket_sendonly_codec = cricket::CreateVideoCodec( SdpVideoFormat("H265", {{"profile-id", "1"}, {"tier-flag", "0"}, {"level-id", H265CodecCapabilities::kSendOnlyLevel}, {"tx-mode", "SRST"}}, {ScalabilityMode::kL1T1})); cricket::Codec cricket_recvonly_codec = cricket::CreateVideoCodec( SdpVideoFormat("H265", {{"profile-id", "1"}, {"tier-flag", "0"}, {"level-id", H265CodecCapabilities::kRecvOnlyLevel}, {"tx-mode", "SRST"}}, {ScalabilityMode::kL1T1})); media_engine()->SetVideoSendCodecs({cricket_sendonly_codec}); media_engine()->SetVideoRecvCodecs({cricket_recvonly_codec}); return { .cricket_sendonly_codec = cricket_sendonly_codec, .sendonly_codec = ToRtpCodecCapability(cricket_sendonly_codec), .cricket_recvonly_codec = cricket_recvonly_codec, .recvonly_codec = ToRtpCodecCapability(cricket_recvonly_codec), }; } #endif // RTC_ENABLE_H265 protected: rtc::scoped_refptr transceiver_; }; TEST_F(RtpTransceiverFilteredCodecPreferencesTest, EmptyByDefault) { ConfigureH264CodecCapabilities(); EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0)); EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0)); EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0)); EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kInactive), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0)); } TEST_F(RtpTransceiverFilteredCodecPreferencesTest, OrderIsMaintained) { const auto codecs = ConfigureH264CodecCapabilities(); std::vector codec_capabilities = {codecs.sendrecv_codec, codecs.rtx_codec}; EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), ElementsAre(codec_capabilities[0], codec_capabilities[1])); // Reverse order. codec_capabilities = {codecs.rtx_codec, codecs.sendrecv_codec}; EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), ElementsAre(codec_capabilities[0], codec_capabilities[1])); } TEST_F(RtpTransceiverFilteredCodecPreferencesTest, FiltersCodecsBasedOnDirection) { const auto codecs = ConfigureH264CodecCapabilities(); std::vector codec_capabilities = { codecs.sendonly_codec, codecs.sendrecv_codec, codecs.recvonly_codec}; EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk()); EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), ElementsAre(codecs.sendrecv_codec)); EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), ElementsAre(codecs.sendonly_codec, codecs.sendrecv_codec)); EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), ElementsAre(codecs.sendrecv_codec, codecs.recvonly_codec)); EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kInactive), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), ElementsAre(codecs.sendrecv_codec)); } TEST_F(RtpTransceiverFilteredCodecPreferencesTest, RtxIsIncludedAfterFiltering) { const auto codecs = ConfigureH264CodecCapabilities(); std::vector codec_capabilities = {codecs.recvonly_codec, codecs.rtx_codec}; EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk()); EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), ElementsAre(codecs.recvonly_codec, codecs.rtx_codec)); } TEST_F(RtpTransceiverFilteredCodecPreferencesTest, NoMediaIsTheSameAsNoPreference) { const auto codecs = ConfigureH264CodecCapabilities(); std::vector codec_capabilities = {codecs.recvonly_codec, codecs.rtx_codec}; EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk()); EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly), IsRtcOk()); // After filtering the only codec that remains is RTX which is not a media // codec, this is the same as not having any preferences. EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0)); // But the preferences are remembered in case the direction changes such that // we do have a media codec. EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), ElementsAre(codecs.recvonly_codec, codecs.rtx_codec)); } TEST_F(RtpTransceiverFilteredCodecPreferencesTest, H264LevelIdsIgnoredByFilter) { // Baseline 3.1 and 5.2 are compatible when ignoring level IDs. cricket::Codec baseline_3_1 = cricket::CreateVideoCodec( SdpVideoFormat("H264", {{"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, {"profile-level-id", "42001f"}}, {ScalabilityMode::kL1T1})); cricket::Codec baseline_5_2 = cricket::CreateVideoCodec( SdpVideoFormat("H264", {{"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, {"profile-level-id", "420034"}}, {ScalabilityMode::kL1T1})); // High is NOT compatible with baseline. cricket::Codec high_3_1 = cricket::CreateVideoCodec( SdpVideoFormat("H264", {{"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, {"profile-level-id", "64001f"}}, {ScalabilityMode::kL1T1})); // Configure being able to both send and receive Baseline but using different // level IDs in either direction, while the High profile is "truly" recvonly. media_engine()->SetVideoSendCodecs({baseline_3_1}); media_engine()->SetVideoRecvCodecs({baseline_5_2, high_3_1}); // Prefer to "sendrecv" Baseline 5.2. Even though we can only send 3.1 this // codec is not filtered out due to 5.2 and 3.1 being compatible when ignoring // level IDs. std::vector codec_capabilities = { ToRtpCodecCapability(baseline_5_2)}; EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk()); EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), ElementsAre(codec_capabilities[0])); // Prefer to "sendrecv" High 3.1. This gets filtered out because we cannot // send it (Baseline 3.1 is not compatible with it). codec_capabilities = {ToRtpCodecCapability(high_3_1)}; EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0)); // Change direction to "recvonly" to avoid High 3.1 being filtered out. EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), ElementsAre(codec_capabilities[0])); } #ifdef RTC_ENABLE_H265 TEST_F(RtpTransceiverFilteredCodecPreferencesTest, H265LevelIdIsIgnoredByFilter) { const auto codecs = ConfigureH265CodecCapabilities(); std::vector codec_capabilities = {codecs.sendonly_codec, codecs.recvonly_codec}; EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk()); // Regardless of direction, both codecs are preferred due to ignoring levels. EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), ElementsAre(codec_capabilities[0], codec_capabilities[1])); EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), ElementsAre(codec_capabilities[0], codec_capabilities[1])); EXPECT_THAT( transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv), IsRtcOk()); EXPECT_THAT(transceiver_->filtered_codec_preferences(), ElementsAre(codec_capabilities[0], codec_capabilities[1])); } TEST_F(RtpTransceiverFilteredCodecPreferencesTest, H265LevelIdHasToBeFromSenderOrReceiverCapabilities) { ConfigureH265CodecCapabilities(); cricket::Codec cricket_codec = cricket::CreateVideoCodec(SdpVideoFormat( "H265", {{"profile-id", "1"}, {"tier-flag", "0"}, {"level-id", H265CodecCapabilities::kLevelNotInCapabilities}, {"tx-mode", "SRST"}}, {ScalabilityMode::kL1T1})); std::vector codec_capabilities = { ToRtpCodecCapability(cricket_codec)}; EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcErrorWithTypeAndMessage( RTCErrorType::INVALID_MODIFICATION, "Invalid codec preferences: Missing codec from codec " "capabilities.")); } #endif // RTC_ENABLE_H265 class RtpTransceiverTestForHeaderExtensions : public RtpTransceiverUnifiedPlanTest { public: RtpTransceiverTestForHeaderExtensions() : extensions_( {RtpHeaderExtensionCapability("uri1", 1, RtpTransceiverDirection::kSendOnly), RtpHeaderExtensionCapability("uri2", 2, RtpTransceiverDirection::kRecvOnly), RtpHeaderExtensionCapability(RtpExtension::kMidUri, 3, RtpTransceiverDirection::kSendRecv), RtpHeaderExtensionCapability(RtpExtension::kVideoRotationUri, 4, RtpTransceiverDirection::kSendRecv)}), transceiver_(rtc::make_ref_counted( RtpSenderProxyWithInternal::Create( rtc::Thread::Current(), sender_), RtpReceiverProxyWithInternal::Create( rtc::Thread::Current(), rtc::Thread::Current(), receiver_), context(), extensions_, /* on_negotiation_needed= */ [] {})) {} void ClearChannel() { EXPECT_CALL(*sender_.get(), SetMediaChannel(_)); transceiver_->ClearChannel(); } rtc::scoped_refptr receiver_ = MockReceiver(cricket::MediaType::MEDIA_TYPE_AUDIO); rtc::scoped_refptr sender_ = MockSender(cricket::MediaType::MEDIA_TYPE_AUDIO); std::vector extensions_; rtc::scoped_refptr transceiver_; }; TEST_F(RtpTransceiverTestForHeaderExtensions, OffersChannelManagerList) { EXPECT_CALL(*receiver_.get(), Stop()); EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)); EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped()); EXPECT_CALL(*sender_.get(), Stop()); EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_); } TEST_F(RtpTransceiverTestForHeaderExtensions, ModifiesDirection) { EXPECT_CALL(*receiver_.get(), Stop()); EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)); EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped()); EXPECT_CALL(*sender_.get(), Stop()); auto modified_extensions = extensions_; modified_extensions[0].direction = RtpTransceiverDirection::kSendOnly; EXPECT_TRUE( transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok()); EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), modified_extensions); modified_extensions[0].direction = RtpTransceiverDirection::kRecvOnly; EXPECT_TRUE( transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok()); EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), modified_extensions); modified_extensions[0].direction = RtpTransceiverDirection::kSendRecv; EXPECT_TRUE( transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok()); EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), modified_extensions); modified_extensions[0].direction = RtpTransceiverDirection::kInactive; EXPECT_TRUE( transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok()); EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), modified_extensions); } TEST_F(RtpTransceiverTestForHeaderExtensions, AcceptsStoppedExtension) { EXPECT_CALL(*receiver_.get(), Stop()); EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)); EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped()); EXPECT_CALL(*sender_.get(), Stop()); auto modified_extensions = extensions_; modified_extensions[0].direction = RtpTransceiverDirection::kStopped; EXPECT_TRUE( transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions).ok()); EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), modified_extensions); } TEST_F(RtpTransceiverTestForHeaderExtensions, RejectsDifferentSize) { EXPECT_CALL(*receiver_.get(), Stop()); EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)); EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped()); EXPECT_CALL(*sender_.get(), Stop()); auto modified_extensions = extensions_; modified_extensions.pop_back(); EXPECT_THAT(transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions), Property(&RTCError::type, RTCErrorType::INVALID_MODIFICATION)); EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_); } TEST_F(RtpTransceiverTestForHeaderExtensions, RejectsChangedUri) { EXPECT_CALL(*receiver_.get(), Stop()); EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)); EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped()); EXPECT_CALL(*sender_.get(), Stop()); auto modified_extensions = extensions_; ASSERT_TRUE(!modified_extensions.empty()); modified_extensions[0].uri = "http://webrtc.org"; EXPECT_THAT(transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions), Property(&RTCError::type, RTCErrorType::INVALID_MODIFICATION)); EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_); } TEST_F(RtpTransceiverTestForHeaderExtensions, RejectsReorder) { EXPECT_CALL(*receiver_.get(), Stop()); EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)); EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped()); EXPECT_CALL(*sender_.get(), Stop()); auto modified_extensions = extensions_; ASSERT_GE(modified_extensions.size(), 2u); std::swap(modified_extensions[0], modified_extensions[1]); EXPECT_THAT(transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions), Property(&RTCError::type, RTCErrorType::INVALID_MODIFICATION)); EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_); } TEST_F(RtpTransceiverTestForHeaderExtensions, RejectsStoppedMandatoryExtensions) { EXPECT_CALL(*receiver_.get(), Stop()); EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)); EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped()); EXPECT_CALL(*sender_.get(), Stop()); std::vector modified_extensions = extensions_; // Attempting to stop the mandatory MID extension. modified_extensions[2].direction = RtpTransceiverDirection::kStopped; EXPECT_THAT(transceiver_->SetHeaderExtensionsToNegotiate(modified_extensions), Property(&RTCError::type, RTCErrorType::INVALID_MODIFICATION)); EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_); } TEST_F(RtpTransceiverTestForHeaderExtensions, NoNegotiatedHdrExtsWithoutChannel) { EXPECT_CALL(*receiver_.get(), Stop()); EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)); EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped()); EXPECT_CALL(*sender_.get(), Stop()); EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(), ElementsAre(Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped))); } TEST_F(RtpTransceiverTestForHeaderExtensions, NoNegotiatedHdrExtsWithChannelWithoutNegotiation) { const std::string content_name("my_mid"); EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)).WillRepeatedly(Return()); EXPECT_CALL(*receiver_.get(), Stop()).WillRepeatedly(Return()); EXPECT_CALL(*sender_.get(), SetMediaChannel(_)); EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped()); EXPECT_CALL(*sender_.get(), Stop()); auto mock_channel = std::make_unique>(); auto mock_channel_ptr = mock_channel.get(); EXPECT_CALL(*mock_channel, SetFirstPacketReceivedCallback(_)); EXPECT_CALL(*mock_channel, media_type()) .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO)); EXPECT_CALL(*mock_channel, voice_media_send_channel()) .WillRepeatedly(Return(nullptr)); EXPECT_CALL(*mock_channel, mid()).WillRepeatedly(ReturnRef(content_name)); EXPECT_CALL(*mock_channel, SetRtpTransport(_)).WillRepeatedly(Return(true)); transceiver_->SetChannel(std::move(mock_channel), [](const std::string&) { return nullptr; }); EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(), ElementsAre(Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped))); EXPECT_CALL(*mock_channel_ptr, SetFirstPacketReceivedCallback(_)); ClearChannel(); } TEST_F(RtpTransceiverTestForHeaderExtensions, ReturnsNegotiatedHdrExts) { const std::string content_name("my_mid"); EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)).WillRepeatedly(Return()); EXPECT_CALL(*receiver_.get(), Stop()).WillRepeatedly(Return()); EXPECT_CALL(*sender_.get(), SetMediaChannel(_)); EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped()); EXPECT_CALL(*sender_.get(), Stop()); auto mock_channel = std::make_unique>(); auto mock_channel_ptr = mock_channel.get(); EXPECT_CALL(*mock_channel, SetFirstPacketReceivedCallback(_)); EXPECT_CALL(*mock_channel, media_type()) .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO)); EXPECT_CALL(*mock_channel, voice_media_send_channel()) .WillRepeatedly(Return(nullptr)); EXPECT_CALL(*mock_channel, mid()).WillRepeatedly(ReturnRef(content_name)); EXPECT_CALL(*mock_channel, SetRtpTransport(_)).WillRepeatedly(Return(true)); cricket::RtpHeaderExtensions extensions = {RtpExtension("uri1", 1), RtpExtension("uri2", 2)}; cricket::AudioContentDescription description; description.set_rtp_header_extensions(extensions); transceiver_->OnNegotiationUpdate(SdpType::kAnswer, &description); transceiver_->SetChannel(std::move(mock_channel), [](const std::string&) { return nullptr; }); EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(), ElementsAre(Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kSendRecv), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kSendRecv), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped))); EXPECT_CALL(*mock_channel_ptr, SetFirstPacketReceivedCallback(_)); ClearChannel(); } TEST_F(RtpTransceiverTestForHeaderExtensions, ReturnsNegotiatedHdrExtsSecondTime) { EXPECT_CALL(*receiver_.get(), Stop()); EXPECT_CALL(*receiver_.get(), SetMediaChannel(_)); EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped()); EXPECT_CALL(*sender_.get(), Stop()); cricket::RtpHeaderExtensions extensions = {RtpExtension("uri1", 1), RtpExtension("uri2", 2)}; cricket::AudioContentDescription description; description.set_rtp_header_extensions(extensions); transceiver_->OnNegotiationUpdate(SdpType::kAnswer, &description); EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(), ElementsAre(Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kSendRecv), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kSendRecv), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped))); extensions = {RtpExtension("uri3", 4), RtpExtension("uri5", 6)}; description.set_rtp_header_extensions(extensions); transceiver_->OnNegotiationUpdate(SdpType::kAnswer, &description); EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(), ElementsAre(Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped))); } TEST_F(RtpTransceiverTestForHeaderExtensions, SimulcastOrSvcEnablesExtensionsByDefault) { std::vector extensions = { {RtpExtension::kDependencyDescriptorUri, 1, RtpTransceiverDirection::kStopped}, {RtpExtension::kVideoLayersAllocationUri, 2, RtpTransceiverDirection::kStopped}, }; // Default is stopped. auto sender = rtc::make_ref_counted>(); auto transceiver = rtc::make_ref_counted( RtpSenderProxyWithInternal::Create( rtc::Thread::Current(), sender), RtpReceiverProxyWithInternal::Create( rtc::Thread::Current(), rtc::Thread::Current(), receiver_), context(), extensions, /* on_negotiation_needed= */ [] {}); std::vector header_extensions = transceiver->GetHeaderExtensionsToNegotiate(); ASSERT_EQ(header_extensions.size(), 2u); EXPECT_EQ(header_extensions[0].uri, RtpExtension::kDependencyDescriptorUri); EXPECT_EQ(header_extensions[0].direction, RtpTransceiverDirection::kStopped); EXPECT_EQ(header_extensions[1].uri, RtpExtension::kVideoLayersAllocationUri); EXPECT_EQ(header_extensions[1].direction, RtpTransceiverDirection::kStopped); // Simulcast, i.e. more than one encoding. RtpParameters simulcast_parameters; simulcast_parameters.encodings.resize(2); auto simulcast_sender = rtc::make_ref_counted>(); EXPECT_CALL(*simulcast_sender, GetParametersInternal()) .WillRepeatedly(Return(simulcast_parameters)); auto simulcast_transceiver = rtc::make_ref_counted( RtpSenderProxyWithInternal::Create( rtc::Thread::Current(), simulcast_sender), RtpReceiverProxyWithInternal::Create( rtc::Thread::Current(), rtc::Thread::Current(), receiver_), context(), extensions, /* on_negotiation_needed= */ [] {}); auto simulcast_extensions = simulcast_transceiver->GetHeaderExtensionsToNegotiate(); ASSERT_EQ(simulcast_extensions.size(), 2u); EXPECT_EQ(simulcast_extensions[0].uri, RtpExtension::kDependencyDescriptorUri); EXPECT_EQ(simulcast_extensions[0].direction, RtpTransceiverDirection::kSendRecv); EXPECT_EQ(simulcast_extensions[1].uri, RtpExtension::kVideoLayersAllocationUri); EXPECT_EQ(simulcast_extensions[1].direction, RtpTransceiverDirection::kSendRecv); // SVC, a single encoding with a scalabilityMode other than L1T1. webrtc::RtpParameters svc_parameters; svc_parameters.encodings.resize(1); svc_parameters.encodings[0].scalability_mode = "L3T3"; auto svc_sender = rtc::make_ref_counted>(); EXPECT_CALL(*svc_sender, GetParametersInternal()) .WillRepeatedly(Return(svc_parameters)); auto svc_transceiver = rtc::make_ref_counted( RtpSenderProxyWithInternal::Create( rtc::Thread::Current(), svc_sender), RtpReceiverProxyWithInternal::Create( rtc::Thread::Current(), rtc::Thread::Current(), receiver_), context(), extensions, /* on_negotiation_needed= */ [] {}); std::vector svc_extensions = svc_transceiver->GetHeaderExtensionsToNegotiate(); ASSERT_EQ(svc_extensions.size(), 2u); EXPECT_EQ(svc_extensions[0].uri, RtpExtension::kDependencyDescriptorUri); EXPECT_EQ(svc_extensions[0].direction, RtpTransceiverDirection::kSendRecv); EXPECT_EQ(svc_extensions[1].uri, RtpExtension::kVideoLayersAllocationUri); EXPECT_EQ(svc_extensions[1].direction, RtpTransceiverDirection::kSendRecv); } } // namespace } // namespace webrtc