/* * 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 #include #include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" #include "api/audio_options.h" #include "api/crypto/crypto_options.h" #include "api/environment/environment.h" #include "api/jsep.h" #include "api/make_ref_counted.h" #include "api/media_types.h" #include "api/peer_connection_interface.h" #include "api/rtc_error.h" #include "api/rtp_header_extension_id.h" #include "api/rtp_parameters.h" #include "api/rtp_transceiver_direction.h" #include "api/scoped_refptr.h" #include "api/test/rtc_error_matchers.h" #include "api/video_codecs/scalability_mode.h" #include "api/video_codecs/sdp_video_format.h" #include "call/call.h" #include "media/base/codec.h" #include "media/base/codec_comparators.h" #include "media/base/fake_media_engine.h" #include "media/base/media_channel.h" #include "media/base/media_config.h" #include "media/engine/fake_webrtc_call.h" #include "p2p/dtls/fake_dtls_transport.h" #include "pc/codec_vendor.h" #include "pc/connection_context.h" #include "pc/dtls_transport.h" #include "pc/rtp_parameters_conversion.h" #include "pc/rtp_receiver.h" #include "pc/rtp_receiver_proxy.h" #include "pc/rtp_sender.h" #include "pc/rtp_sender_proxy.h" #include "pc/rtp_transport.h" #include "pc/rtp_transport_internal.h" #include "pc/scoped_operations_batcher.h" #include "pc/session_description.h" #include "pc/simulcast_description.h" #include "pc/test/enable_fake_media.h" #include "pc/test/fake_codec_lookup_helper.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/checks.h" #include "rtc_base/logging.h" #include "rtc_base/network_route.h" #include "rtc_base/thread.h" #include "test/create_test_environment.h" #include "test/gmock.h" #include "test/gtest.h" #include "test/run_loop.h" using ::testing::_; using ::testing::ElementsAre; using ::testing::Field; using ::testing::IsNull; using ::testing::NiceMock; using ::testing::Optional; using ::testing::Property; using ::testing::Return; using ::testing::ReturnRef; using ::testing::SizeIs; namespace webrtc { namespace { scoped_refptr CreateAudioTransceiverWithChannel( const Environment& env, Call* call, ConnectionContext* context, CodecLookupHelper* codec_lookup_helper, const AudioOptions& audio_options, absl::AnyInvocable transport_lookup = []() { return nullptr; }) { auto transceiver = make_ref_counted( env, call, MediaConfig(), "sender", "receiver", MediaType::AUDIO, nullptr, std::vector(), std::vector(), context, codec_lookup_helper, nullptr, nullptr, audio_options, VideoOptions(), CryptoOptions(), nullptr, std::vector(), false, std::vector(), [] {}); ScopedOperationsBatcher worker_tasks(context->worker_thread()); ScopedOperationsBatcher network_tasks(context->network_thread()); transceiver->CreateChannel("0", call, MediaConfig(), false, CryptoOptions(), nullptr, std::move(transport_lookup), worker_tasks, network_tasks); // CreateChannel does not schedule any worker tasks when the transceiver is // constructed with pre-allocated channels, so `worker_tasks` should be empty. RTC_CHECK(worker_tasks.IsEmpty()); RTC_CHECK(network_tasks.Run().ok()); return transceiver; } scoped_refptr CreateVideoTransceiverWithChannel( const Environment& env, Call* call, ConnectionContext* context, CodecLookupHelper* codec_lookup_helper, const VideoOptions& video_options, absl::AnyInvocable transport_lookup = []() { return nullptr; }) { auto transceiver = make_ref_counted( env, call, MediaConfig(), "sender", "receiver", MediaType::VIDEO, nullptr, std::vector(), std::vector(), context, codec_lookup_helper, nullptr, nullptr, AudioOptions(), video_options, CryptoOptions(), nullptr, std::vector(), false, std::vector(), [] {}); ScopedOperationsBatcher worker_tasks(context->worker_thread()); ScopedOperationsBatcher network_tasks(context->network_thread()); transceiver->CreateChannel("0", call, MediaConfig(), false, CryptoOptions(), nullptr, std::move(transport_lookup), worker_tasks, network_tasks); // CreateChannel does not schedule any worker tasks when the transceiver is // constructed with pre-allocated channels, so `worker_tasks` should be empty. RTC_CHECK(worker_tasks.IsEmpty()); RTC_CHECK(network_tasks.Run().ok()); return transceiver; } class RtpTransceiverTest : public testing::Test { public: RtpTransceiverTest() : env_(CreateTestEnvironment()), dependencies_(MakeDependencies()), context_(ConnectionContext::Create(env_, &dependencies_)), media_engine_ref_(context_), codec_lookup_helper_(context_.get(), env_.field_trials()) {} protected: const Environment& env() const { return env_; } FakeMediaEngine* media_engine() { // We know this cast is safe because we supplied the fake implementation // in MakeDependencies(). return static_cast(media_engine_ref_.media_engine()); } ConnectionContext* context() { return context_.get(); } CodecLookupHelper* codec_lookup_helper() { return &codec_lookup_helper_; } FakeCodecLookupHelper* fake_codec_lookup_helper() { return &codec_lookup_helper_; } protected: test::RunLoop main_thread_; private: static PeerConnectionFactoryDependencies MakeDependencies() { PeerConnectionFactoryDependencies d; d.network_thread = Thread::Current(); d.worker_thread = Thread::Current(); d.signaling_thread = Thread::Current(); EnableFakeMedia(d, std::make_unique()); return d; } Environment env_; PeerConnectionFactoryDependencies dependencies_; scoped_refptr context_; ConnectionContext::MediaEngineReference media_engine_ref_; FakeCodecLookupHelper codec_lookup_helper_; }; class RtpTransceiverDoubleThreadTest : public testing::Test { public: RtpTransceiverDoubleThreadTest() : env_(CreateTestEnvironment()), network_thread_(Thread::Create()), dependencies_(MakeDependencies(network_thread_.get())), context_(ConnectionContext::Create(env_, &dependencies_)), codec_lookup_helper_(context_.get(), env_.field_trials()) { network_thread_->Start(); context_->worker_thread()->BlockingCall([&]() { media_engine_ref_ = std::make_unique(context_); }); } ~RtpTransceiverDoubleThreadTest() override { context_->worker_thread()->BlockingCall( [&]() { media_engine_ref_.reset(); }); } protected: const Environment& env() const { return env_; } FakeMediaEngine* media_engine() { return static_cast(media_engine_ref_->media_engine()); } ConnectionContext* context() { return context_.get(); } CodecLookupHelper* codec_lookup_helper() { return &codec_lookup_helper_; } private: static PeerConnectionFactoryDependencies MakeDependencies( Thread* network_thread) { PeerConnectionFactoryDependencies d; d.network_thread = network_thread; d.signaling_thread = Thread::Current(); RTC_LOG(LS_INFO) << "MakeDependencies signaling_thread=" << d.signaling_thread; EnableFakeMedia(d, std::make_unique()); return d; } test::RunLoop main_thread_; Environment env_; std::unique_ptr network_thread_; PeerConnectionFactoryDependencies dependencies_; scoped_refptr context_; std::unique_ptr media_engine_ref_; FakeCodecLookupHelper codec_lookup_helper_; }; class RtpTransceiverTestWithFakeCall : public RtpTransceiverTest { public: RtpTransceiverTestWithFakeCall() : call_(std::make_unique(env(), Thread::Current(), Thread::Current())) {} protected: std::unique_ptr call_; }; // Checks that a channel cannot be set on a stopped `RtpTransceiver`. TEST_F(RtpTransceiverTest, CannotSetChannelOnStoppedTransceiver) { const std::string content_name("my_mid"); auto transceiver = make_ref_counted( env(), MediaType::AUDIO, context(), codec_lookup_helper(), nullptr, AudioOptions(), VideoOptions()); transceiver->set_mid(content_name); auto channel1 = std::make_unique>(); EXPECT_CALL(*channel1, media_type()).WillRepeatedly(Return(MediaType::AUDIO)); EXPECT_CALL(*channel1, mid()).WillRepeatedly(ReturnRef(content_name)); EXPECT_CALL(*channel1, SetRtpTransport(_)).WillRepeatedly(Return(true)); transceiver->SetChannelForTest(std::move(channel1)); EXPECT_TRUE(transceiver->HasChannel()); // Stop the transceiver. transceiver->StopInternal(); EXPECT_TRUE(transceiver->HasChannel()); auto channel2 = std::make_unique>(); EXPECT_CALL(*channel2, media_type()).WillRepeatedly(Return(MediaType::AUDIO)); // Clear the current channel - required to allow SetChannelForTest() transceiver->ClearChannel(); ASSERT_FALSE(transceiver->HasChannel()); // Channel can no longer be set, so this call should be a no-op. transceiver->SetChannelForTest(std::move(channel2)); EXPECT_FALSE(transceiver->HasChannel()); } // Checks that a channel can be unset on a stopped `RtpTransceiver` TEST_F(RtpTransceiverTest, CanUnsetChannelOnStoppedTransceiver) { const std::string content_name("my_mid"); auto transceiver = make_ref_counted( env(), MediaType::VIDEO, context(), codec_lookup_helper(), nullptr, AudioOptions(), VideoOptions()); transceiver->set_mid(content_name); auto channel = std::make_unique>(); EXPECT_CALL(*channel, media_type()).WillRepeatedly(Return(MediaType::VIDEO)); EXPECT_CALL(*channel, mid()).WillRepeatedly(ReturnRef(content_name)); EXPECT_CALL(*channel, SetRtpTransport(_)).WillRepeatedly(Return(true)); transceiver->SetChannelForTest(std::move(channel)); EXPECT_TRUE(transceiver->HasChannel()); // Stop the transceiver. transceiver->StopInternal(); EXPECT_TRUE(transceiver->HasChannel()); // Set the channel to `nullptr`. transceiver->ClearChannel(); EXPECT_FALSE(transceiver->HasChannel()); } TEST_F(RtpTransceiverTestWithFakeCall, TransportNameIsUpdated) { const std::string content_name("my_mid"); auto fake_dtls = std::make_unique(env(), "test_transport", false); auto rtp_transport = std::make_unique(/*rtcp_mux_enabled=*/true, env().field_trials()); rtp_transport->SetRtpPacketTransport(fake_dtls.get()); auto transceiver = CreateAudioTransceiverWithChannel( env(), call_.get(), context(), codec_lookup_helper(), AudioOptions(), [&]() -> RtpTransportInternal* { return rtp_transport.get(); }); EXPECT_TRUE(transceiver->HasChannel()); EXPECT_EQ(transceiver->transport_name(), "test_transport"); auto dtls_transport = make_ref_counted(fake_dtls.get()); transceiver->SetTransport(dtls_transport, "updated_transport"); EXPECT_EQ(transceiver->transport_name(), "updated_transport"); // Setting null transport should clear the name. transceiver->SetTransport(nullptr, std::nullopt); EXPECT_EQ(transceiver->transport_name(), std::nullopt); EXPECT_TRUE(transceiver->HasChannel()); // Clearing the channel should reset the transport name to nullopt. transceiver->SetTransport(dtls_transport, "yadt"); EXPECT_EQ(transceiver->transport_name(), "yadt"); transceiver->ClearChannel(); EXPECT_FALSE(transceiver->transport_name().has_value()); } class RtpTransceiverUnifiedPlanTest : public RtpTransceiverTest { public: static scoped_refptr MockReceiver( MediaType media_type) { auto receiver = make_ref_counted>(); EXPECT_CALL(*receiver.get(), media_type()) .WillRepeatedly(Return(media_type)); return receiver; } static scoped_refptr MockSender(MediaType media_type) { auto sender = make_ref_counted>(); EXPECT_CALL(*sender.get(), media_type()).WillRepeatedly(Return(media_type)); return sender; } scoped_refptr CreateTransceiver( scoped_refptr sender, scoped_refptr receiver) { return make_ref_counted( env(), RtpSenderProxyWithInternal::Create( Thread::Current(), std::move(sender)), RtpReceiverProxyWithInternal::Create( Thread::Current(), Thread::Current(), std::move(receiver)), context(), codec_lookup_helper(), media_engine()->voice().GetRtpHeaderExtensions(&env().field_trials()), /* on_negotiation_needed= */ [] {}, AudioOptions(), VideoOptions()); } }; // Basic tests for Stop() TEST_F(RtpTransceiverUnifiedPlanTest, StopSetsDirection) { scoped_refptr receiver = MockReceiver(MediaType::AUDIO); scoped_refptr sender = MockSender(MediaType::AUDIO); scoped_refptr transceiver = CreateTransceiver(sender, receiver); EXPECT_EQ(RtpTransceiverDirection::kInactive, transceiver->direction()); EXPECT_FALSE(transceiver->current_direction()); transceiver->StopStandard(); EXPECT_EQ(RtpTransceiverDirection::kStopped, transceiver->direction()); EXPECT_FALSE(transceiver->current_direction()); auto stop_task = transceiver->GetStopTransceiverProcedure(); if (stop_task) { std::move(stop_task)(); } 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(MediaType::VIDEO), MockReceiver(MediaType::VIDEO))) {} struct H264CodecCapabilities { Codec cricket_sendrecv_codec; RtpCodecCapability sendrecv_codec; Codec cricket_sendonly_codec; RtpCodecCapability sendonly_codec; Codec cricket_recvonly_codec; RtpCodecCapability recvonly_codec; Codec cricket_rtx_codec; RtpCodecCapability rtx_codec; }; // This function must be called after modifying the media factory's // capabilities, since the transceiver picks up codecs from the factory // at transceiver create time. void RecreateTransceiver() { fake_codec_lookup_helper()->Reset(); transceiver_ = CreateTransceiver(MockSender(MediaType::VIDEO), MockReceiver(MediaType::VIDEO)); } // 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() { Codec cricket_sendrecv_codec = CreateVideoCodec(SdpVideoFormat("H264", {{"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, {"profile-level-id", "42f00b"}}, {ScalabilityMode::kL1T1})); Codec cricket_sendonly_codec = CreateVideoCodec(SdpVideoFormat("H264", {{"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, {"profile-level-id", "640034"}}, {ScalabilityMode::kL1T1})); Codec cricket_recvonly_codec = CreateVideoCodec(SdpVideoFormat("H264", {{"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, {"profile-level-id", "f4001f"}}, {ScalabilityMode::kL1T1})); Codec cricket_rtx_codec = CreateVideoRtxCodec(Codec::kIdNotSet, 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)); // Because RtpTransceiver buffers codec information in a CodecVendor, // we must recreate it after changing the supported codecs. RecreateTransceiver(); 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"; Codec cricket_sendonly_codec; RtpCodecCapability sendonly_codec; Codec cricket_recvonly_codec; RtpCodecCapability recvonly_codec; }; // For H265, the profile and level IDs are separate and are ignored by // IsSameRtpCodecIgnoringLevel(). H265CodecCapabilities ConfigureH265CodecCapabilities() { Codec cricket_sendonly_codec = CreateVideoCodec( SdpVideoFormat("H265", {{"profile-id", "1"}, {"tier-flag", "0"}, {"level-id", H265CodecCapabilities::kSendOnlyLevel}, {"tx-mode", "SRST"}}, {ScalabilityMode::kL1T1})); Codec cricket_recvonly_codec = 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}); // Because RtpTransceiver buffers codec information in a CodecVendor, // we must recreate it after changing the supported codecs. RecreateTransceiver(); 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: 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. Codec baseline_3_1 = CreateVideoCodec(SdpVideoFormat("H264", {{"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, {"profile-level-id", "42001f"}}, {ScalabilityMode::kL1T1})); Codec baseline_5_2 = CreateVideoCodec(SdpVideoFormat("H264", {{"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, {"profile-level-id", "420034"}}, {ScalabilityMode::kL1T1})); // High is NOT compatible with baseline. Codec high_3_1 = 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}); // Because RtpTransceiver buffers codec information in a CodecVendor, // we must recreate it after changing the supported codecs. RecreateTransceiver(); // 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(); Codec cricket_codec = 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", RtpTransceiverDirection::kSendOnly), RtpHeaderExtensionCapability("uri2", RtpTransceiverDirection::kRecvOnly), RtpHeaderExtensionCapability(RtpExtension::kMidUri, RtpTransceiverDirection::kSendRecv), RtpHeaderExtensionCapability(RtpExtension::kVideoRotationUri, RtpTransceiverDirection::kSendRecv)}), transceiver_(make_ref_counted( env(), RtpSenderProxyWithInternal::Create( Thread::Current(), sender_), RtpReceiverProxyWithInternal::Create( Thread::Current(), Thread::Current(), receiver_), context(), codec_lookup_helper(), extensions_, /* on_negotiation_needed= */ [] {}, AudioOptions(), VideoOptions())) {} void ClearChannel() { EXPECT_CALL(*sender_.get(), SetMediaChannel(IsNull())) .WillRepeatedly(Return()); transceiver_->ClearChannel(); } protected: scoped_refptr receiver_ = MockReceiver(MediaType::AUDIO); scoped_refptr sender_ = MockSender(MediaType::AUDIO); std::vector extensions_; scoped_refptr transceiver_; }; TEST_F(RtpTransceiverTestForHeaderExtensions, OffersChannelManagerList) { EXPECT_EQ(transceiver_->GetHeaderExtensionsToNegotiate(), extensions_); } TEST_F(RtpTransceiverTestForHeaderExtensions, ModifiesDirection) { 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) { 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) { 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) { 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) { 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) { 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_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"); transceiver_->set_mid(content_name); auto mock_channel = std::make_unique>(); // Raw ptr for updating expectations later since `mock_channel` will be moved // to `SetChannel`. EXPECT_CALL(*mock_channel, media_type()) .WillRepeatedly(Return(MediaType::AUDIO)); EXPECT_CALL(*mock_channel, mid()).WillRepeatedly(ReturnRef(content_name)); EXPECT_CALL(*mock_channel, SetRtpTransport(_)).WillRepeatedly(Return(true)); transceiver_->SetChannelForTest(std::move(mock_channel)); 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))); ClearChannel(); } TEST_F(RtpTransceiverTestForHeaderExtensions, ReturnsNegotiatedHdrExts) { const std::string content_name("my_mid"); transceiver_->set_mid(content_name); auto mock_channel = std::make_unique>(); EXPECT_CALL(*mock_channel, media_type()) .WillRepeatedly(Return(MediaType::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)); RtpHeaderExtensions extensions = { RtpExtension("uri1", RtpHeaderExtensionId(1)), RtpExtension("uri2", RtpHeaderExtensionId(2))}; AudioContentDescription description; description.set_rtp_header_extensions(extensions); transceiver_->OnNegotiationUpdate(SdpType::kAnswer, &description); transceiver_->SetChannelForTest(std::move(mock_channel)); 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))); ClearChannel(); } TEST_F(RtpTransceiverTestForHeaderExtensions, ReturnsNegotiatedHdrExtsOnPrAnswer) { const std::string content_name("my_mid"); transceiver_->set_mid(content_name); auto mock_channel = std::make_unique>(); EXPECT_CALL(*mock_channel, media_type()) .WillRepeatedly(Return(MediaType::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)); RtpHeaderExtensions extensions = { RtpExtension("uri1", RtpHeaderExtensionId(1)), RtpExtension("uri2", RtpHeaderExtensionId(2))}; AudioContentDescription description; description.set_rtp_header_extensions(extensions); transceiver_->OnNegotiationUpdate(SdpType::kPrAnswer, &description); transceiver_->SetChannelForTest(std::move(mock_channel)); 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))); ClearChannel(); } TEST_F(RtpTransceiverTestForHeaderExtensions, AnswerCanUseOtherHdrExtensionsThanPrAnswer) { const std::string content_name("my_mid"); transceiver_->set_mid(content_name); auto mock_channel = std::make_unique>(); EXPECT_CALL(*mock_channel, media_type()) .WillRepeatedly(Return(MediaType::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_->SetChannelForTest(std::move(mock_channel)); AudioContentDescription description_pr_answer; description_pr_answer.set_rtp_header_extensions( {RtpExtension("uri1", RtpHeaderExtensionId(1))}); transceiver_->OnNegotiationUpdate(SdpType::kPrAnswer, &description_pr_answer); EXPECT_THAT(transceiver_->GetNegotiatedHeaderExtensions(), ElementsAre(Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kSendRecv), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped), Field(&RtpHeaderExtensionCapability::direction, RtpTransceiverDirection::kStopped))); AudioContentDescription description_answer; description_answer.set_rtp_header_extensions( {RtpExtension("uri1", RtpHeaderExtensionId(1)), RtpExtension("uri2", RtpHeaderExtensionId(2))}); transceiver_->OnNegotiationUpdate(SdpType::kAnswer, &description_answer); 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))); ClearChannel(); } TEST_F(RtpTransceiverTestForHeaderExtensions, ReturnsNegotiatedHdrExtsSecondTime) { RtpHeaderExtensions extensions = { RtpExtension("uri1", RtpHeaderExtensionId(1)), RtpExtension("uri2", RtpHeaderExtensionId(2))}; 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", RtpHeaderExtensionId(4)), RtpExtension("uri5", RtpHeaderExtensionId(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, RtpTransceiverDirection::kStopped}, {RtpExtension::kVideoLayersAllocationUri, RtpTransceiverDirection::kStopped}, }; // Default is stopped. auto sender = MockSender(MediaType::VIDEO); auto receiver = MockReceiver(MediaType::VIDEO); auto transceiver = make_ref_counted( env(), RtpSenderProxyWithInternal::Create(Thread::Current(), sender), RtpReceiverProxyWithInternal::Create( Thread::Current(), Thread::Current(), receiver), context(), codec_lookup_helper(), extensions, /* on_negotiation_needed= */ [] {}, AudioOptions(), VideoOptions()); ASSERT_EQ(sender->media_type(), MediaType::VIDEO); 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 = MockSender(MediaType::VIDEO); EXPECT_CALL(*simulcast_sender, GetParametersInternal(_, _)) .WillRepeatedly(Return(simulcast_parameters)); auto simulcast_transceiver = make_ref_counted( env(), RtpSenderProxyWithInternal::Create(Thread::Current(), simulcast_sender), RtpReceiverProxyWithInternal::Create( Thread::Current(), Thread::Current(), receiver), context(), codec_lookup_helper(), extensions, /* on_negotiation_needed= */ [] {}, AudioOptions(), VideoOptions()); 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. RtpParameters svc_parameters; svc_parameters.encodings.resize(1); svc_parameters.encodings[0].scalability_mode = "L3T3"; auto svc_sender = MockSender(MediaType::VIDEO); EXPECT_CALL(*svc_sender, GetParametersInternal(_, _)) .WillRepeatedly(Return(svc_parameters)); auto svc_transceiver = make_ref_counted( env(), RtpSenderProxyWithInternal::Create(Thread::Current(), svc_sender), RtpReceiverProxyWithInternal::Create( Thread::Current(), Thread::Current(), receiver), context(), codec_lookup_helper(), extensions, /* on_negotiation_needed= */ [] {}, AudioOptions(), VideoOptions()); 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); } TEST_F(RtpTransceiverTestWithFakeCall, SetChannelAppliesAudioOptionsToSendChannel) { AudioOptions audio_options; audio_options.audio_network_adaptor = true; auto transceiver = CreateAudioTransceiverWithChannel( env(), call_.get(), context(), codec_lookup_helper(), audio_options); ASSERT_TRUE(transceiver->HasChannel()); auto* voice_channel = transceiver->voice_media_send_channel(); ASSERT_TRUE(voice_channel); auto* fake_channel = static_cast(voice_channel); EXPECT_TRUE(fake_channel->options().audio_network_adaptor); ScopedOperationsBatcher worker_tasks(context()->worker_thread()); ScopedOperationsBatcher network_tasks(context()->network_thread()); network_tasks.Add(transceiver->GetClearChannelNetworkTask()); worker_tasks.Add( transceiver->GetDeleteChannelWorkerTask(/*stop_senders=*/true)); } TEST_F(RtpTransceiverTestWithFakeCall, OnNetworkRouteChangedForwardsToChannel) { const std::string content_name("my_mid"); auto fake_dtls = std::make_unique(env(), "test_transport", false); auto rtp_transport = std::make_unique(/*rtcp_mux_enabled=*/true, env().field_trials()); rtp_transport->SetRtpPacketTransport(fake_dtls.get()); auto transceiver = CreateAudioTransceiverWithChannel( env(), call_.get(), context(), codec_lookup_helper(), AudioOptions(), [&]() -> RtpTransportInternal* { return rtp_transport.get(); }); ASSERT_TRUE(transceiver->HasChannel()); auto* voice_channel = transceiver->voice_media_send_channel(); ASSERT_TRUE(voice_channel); auto* fake_channel = static_cast(voice_channel); NetworkRoute network_route; network_route.connected = true; network_route.local = RouteEndpoint::CreateWithNetworkId(1); network_route.remote = RouteEndpoint::CreateWithNetworkId(2); network_route.last_sent_packet_id = 100; network_route.packet_overhead = 28; // Trigger network route change on the transport on the network thread. context()->network_thread()->BlockingCall([&]() { fake_dtls->ice_transport()->NotifyNetworkRouteChanged( std::optional(network_route)); }); // Verify that the channel received it. EXPECT_EQ(1, fake_channel->num_network_route_changes()); EXPECT_TRUE(fake_channel->last_network_route().connected); EXPECT_EQ(1, fake_channel->last_network_route().local.network_id()); EXPECT_EQ(2, fake_channel->last_network_route().remote.network_id()); EXPECT_EQ(100, fake_channel->last_network_route().last_sent_packet_id); EXPECT_EQ(28, fake_channel->transport_overhead_per_packet()); ScopedOperationsBatcher worker_tasks(context()->worker_thread()); ScopedOperationsBatcher network_tasks(context()->network_thread()); network_tasks.Add(transceiver->GetClearChannelNetworkTask()); worker_tasks.Add( transceiver->GetDeleteChannelWorkerTask(/*stop_senders=*/true)); } TEST_F(RtpTransceiverTestWithFakeCall, OnNetworkRouteChangedForwardsToVideoChannel) { const std::string content_name("my_mid"); auto fake_dtls = std::make_unique(env(), "test_transport", false); auto rtp_transport = std::make_unique(/*rtcp_mux_enabled=*/true, env().field_trials()); rtp_transport->SetRtpPacketTransport(fake_dtls.get()); auto transceiver = CreateVideoTransceiverWithChannel( env(), call_.get(), context(), codec_lookup_helper(), VideoOptions(), [&]() -> RtpTransportInternal* { return rtp_transport.get(); }); ASSERT_TRUE(transceiver->HasChannel()); auto* video_channel = transceiver->video_media_send_channel(); ASSERT_TRUE(video_channel); auto* fake_channel = static_cast(video_channel); NetworkRoute network_route; network_route.connected = true; network_route.local = RouteEndpoint::CreateWithNetworkId(1); network_route.remote = RouteEndpoint::CreateWithNetworkId(2); network_route.last_sent_packet_id = 100; network_route.packet_overhead = 28; // Trigger network route change on the transport on the network thread. context()->network_thread()->BlockingCall([&]() { fake_dtls->ice_transport()->NotifyNetworkRouteChanged( std::optional(network_route)); }); // Verify that the channel received it. EXPECT_EQ(1, fake_channel->num_network_route_changes()); EXPECT_TRUE(fake_channel->last_network_route().connected); EXPECT_EQ(1, fake_channel->last_network_route().local.network_id()); EXPECT_EQ(2, fake_channel->last_network_route().remote.network_id()); EXPECT_EQ(100, fake_channel->last_network_route().last_sent_packet_id); EXPECT_EQ(28, fake_channel->transport_overhead_per_packet()); ScopedOperationsBatcher worker_tasks(context()->worker_thread()); ScopedOperationsBatcher network_tasks(context()->network_thread()); network_tasks.Add(transceiver->GetClearChannelNetworkTask()); worker_tasks.Add( transceiver->GetDeleteChannelWorkerTask(/*stop_senders=*/true)); } TEST_F(RtpTransceiverDoubleThreadTest, OnNetworkRouteChangedForwardsToChannel_DoubleThread) { const std::string content_name("my_mid"); RTC_LOG(LS_INFO) << "TestBody current_thread=" << Thread::Current(); RTC_LOG(LS_INFO) << "TestBody signaling_thread=" << context()->signaling_thread(); RTC_LOG(LS_INFO) << "TestBody network_thread=" << context()->network_thread(); std::unique_ptr fake_dtls; auto rtp_transport = std::make_unique(/*rtcp_mux_enabled=*/true, env().field_trials()); context()->network_thread()->BlockingCall([&]() { fake_dtls = std::make_unique( env(), "test_transport", 0, context()->network_thread()); rtp_transport->SetRtpPacketTransport(fake_dtls.get()); }); auto call = std::make_unique(env(), context()->worker_thread(), context()->network_thread()); auto transceiver = CreateAudioTransceiverWithChannel( env(), call.get(), context(), codec_lookup_helper(), AudioOptions(), [&]() -> RtpTransportInternal* { return rtp_transport.get(); }); ASSERT_TRUE(transceiver->HasChannel()); auto* voice_channel = transceiver->voice_media_send_channel(); ASSERT_TRUE(voice_channel); auto* fake_channel = static_cast(voice_channel); NetworkRoute network_route; network_route.connected = true; network_route.local = RouteEndpoint::CreateWithNetworkId(1); network_route.remote = RouteEndpoint::CreateWithNetworkId(2); network_route.last_sent_packet_id = 100; network_route.packet_overhead = 28; // Trigger network route change on the transport on the network thread. context()->network_thread()->BlockingCall([&]() { fake_dtls->ice_transport()->NotifyNetworkRouteChanged( std::optional(network_route)); }); // Verify that the channel received it. int changes = context()->network_thread()->BlockingCall( [&]() { return fake_channel->num_network_route_changes(); }); EXPECT_EQ(1, changes); NetworkRoute last_route = context()->network_thread()->BlockingCall( [&]() { return fake_channel->last_network_route(); }); EXPECT_TRUE(last_route.connected); EXPECT_EQ(1, last_route.local.network_id()); EXPECT_EQ(2, last_route.remote.network_id()); EXPECT_EQ(100, last_route.last_sent_packet_id); int overhead = context()->network_thread()->BlockingCall( [&]() { return fake_channel->transport_overhead_per_packet(); }); EXPECT_EQ(28, overhead); ScopedOperationsBatcher worker_tasks(context()->worker_thread()); ScopedOperationsBatcher network_tasks(context()->network_thread()); network_tasks.Add(transceiver->GetClearChannelNetworkTask()); worker_tasks.Add( transceiver->GetDeleteChannelWorkerTask(/*stop_senders=*/true)); RTC_CHECK(network_tasks.Run().ok()); RTC_CHECK(worker_tasks.Run().ok()); context()->network_thread()->BlockingCall([&]() { fake_dtls.reset(); }); } // Sframe tests TEST_F(RtpTransceiverUnifiedPlanTest, SframeEnabledIsNulloptByDefault) { scoped_refptr transceiver = CreateTransceiver( MockSender(MediaType::AUDIO), MockReceiver(MediaType::AUDIO)); EXPECT_EQ(transceiver->SframeEnabled(), std::nullopt); } TEST_F(RtpTransceiverUnifiedPlanTest, TryToEnableSframeSetsValueToTrue) { scoped_refptr transceiver = CreateTransceiver( MockSender(MediaType::AUDIO), MockReceiver(MediaType::AUDIO)); EXPECT_TRUE(transceiver->TryToEnableSframe().ok()); EXPECT_THAT(transceiver->SframeEnabled(), Optional(true)); } TEST_F(RtpTransceiverUnifiedPlanTest, TryToEnableSframeCanBeCalledMultipleTimes) { scoped_refptr transceiver = CreateTransceiver( MockSender(MediaType::AUDIO), MockReceiver(MediaType::AUDIO)); EXPECT_TRUE(transceiver->TryToEnableSframe().ok()); EXPECT_TRUE(transceiver->TryToEnableSframe().ok()); EXPECT_THAT(transceiver->SframeEnabled(), Optional(true)); } TEST_F(RtpTransceiverUnifiedPlanTest, TryToEnableSframeFailsAfterExplicitlySetToFalse) { scoped_refptr transceiver = CreateTransceiver( MockSender(MediaType::AUDIO), MockReceiver(MediaType::AUDIO)); // Simulate the transceiver having been explicitly set to false (e.g. via // ApplySframeEnabled during SDP application). transceiver->ApplySframeEnabled(false); EXPECT_THAT(transceiver->SframeEnabled(), Optional(false)); RTCError error = transceiver->TryToEnableSframe(); EXPECT_FALSE(error.ok()); EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION); EXPECT_THAT(transceiver->SframeEnabled(), Optional(false)); } TEST_F(RtpTransceiverUnifiedPlanTest, ApplySframeEnabledTrueSetsState) { scoped_refptr transceiver = CreateTransceiver( MockSender(MediaType::AUDIO), MockReceiver(MediaType::AUDIO)); transceiver->ApplySframeEnabled(true); EXPECT_THAT(transceiver->SframeEnabled(), Optional(true)); } TEST_F(RtpTransceiverUnifiedPlanTest, ApplySframeEnabledFalseSetsState) { scoped_refptr transceiver = CreateTransceiver( MockSender(MediaType::AUDIO), MockReceiver(MediaType::AUDIO)); transceiver->ApplySframeEnabled(false); EXPECT_THAT(transceiver->SframeEnabled(), Optional(false)); } TEST_F(RtpTransceiverUnifiedPlanTest, ApplySframeEnabledTrueEnablesSframeOnChannel) { scoped_refptr transceiver = CreateTransceiver( MockSender(MediaType::AUDIO), MockReceiver(MediaType::AUDIO)); const std::string content_name = "audio"; transceiver->set_mid(content_name); auto mock_channel = std::make_unique>(); ON_CALL(*mock_channel, media_type).WillByDefault(Return(MediaType::AUDIO)); ON_CALL(*mock_channel, mid).WillByDefault(ReturnRef(content_name)); ON_CALL(*mock_channel, SetRtpTransport).WillByDefault(Return(true)); EXPECT_CALL(*mock_channel, EnableSframe); transceiver->SetChannelForTest(std::move(mock_channel)); transceiver->ApplySframeEnabled(true); transceiver->ClearChannel(); } TEST_F(RtpTransceiverUnifiedPlanTest, ApplySframeEnabledFalseDoesNotEnableSframeOnChannel) { scoped_refptr transceiver = CreateTransceiver( MockSender(MediaType::AUDIO), MockReceiver(MediaType::AUDIO)); const std::string content_name = "audio"; transceiver->set_mid(content_name); auto mock_channel = std::make_unique>(); ON_CALL(*mock_channel, media_type).WillByDefault(Return(MediaType::AUDIO)); ON_CALL(*mock_channel, mid).WillByDefault(ReturnRef(content_name)); ON_CALL(*mock_channel, SetRtpTransport).WillByDefault(Return(true)); EXPECT_CALL(*mock_channel, EnableSframe).Times(0); transceiver->SetChannelForTest(std::move(mock_channel)); transceiver->ApplySframeEnabled(false); transceiver->ClearChannel(); } } // namespace } // namespace webrtc