/* * Copyright 2023 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. */ #include #include #include #include #include #include #include #include #include #include #include #include "absl/algorithm/container.h" #include "absl/strings/match.h" #include "absl/strings/string_view.h" #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" #include "api/audio_options.h" #include "api/environment/environment.h" #include "api/field_trials_view.h" #include "api/jsep.h" #include "api/make_ref_counted.h" #include "api/media_stream_interface.h" #include "api/media_types.h" #include "api/rtc_error.h" #include "api/rtp_parameters.h" #include "api/rtp_sender_interface.h" #include "api/rtp_transceiver_direction.h" #include "api/rtp_transceiver_interface.h" #include "api/scoped_refptr.h" #include "api/stats/rtc_stats_report.h" #include "api/stats/rtcstats_objects.h" #include "api/test/rtc_error_matchers.h" #include "api/units/data_rate.h" #include "api/units/time_delta.h" #include "api/video_codecs/scalability_mode.h" #include "api/video_codecs/sdp_video_format.h" #include "media/base/codec.h" #include "media/engine/fake_webrtc_video_engine.h" #include "pc/session_description.h" #include "pc/simulcast_description.h" #include "pc/test/mock_peer_connection_observers.h" #include "pc/test/peer_connection_test_wrapper.h" #include "pc/test/simulcast_layer_util.h" #include "rtc_base/checks.h" #include "rtc_base/containers/flat_map.h" #include "rtc_base/logging.h" #include "rtc_base/physical_socket_server.h" #include "rtc_base/thread.h" #include "test/create_test_environment.h" #include "test/create_test_field_trials.h" #include "test/gmock.h" #include "test/gtest.h" #include "test/wait_until.h" using ::testing::AllOf; using ::testing::AnyOf; using ::testing::Contains; using ::testing::Each; using ::testing::Eq; using ::testing::Field; using ::testing::Gt; using ::testing::HasSubstr; using ::testing::IsSupersetOf; using ::testing::IsTrue; using ::testing::Key; using ::testing::Le; using ::testing::Matcher; using ::testing::Ne; using ::testing::NotNull; using ::testing::Optional; using ::testing::Pair; using ::testing::Pointer; using ::testing::ResultOf; using ::testing::SizeIs; using ::testing::StrCaseEq; using ::testing::StrEq; using ::testing::UnorderedElementsAre; namespace webrtc { namespace { // Most tests pass in 20-30 seconds, but some tests take longer such as AV1 // requiring additional ramp-up time (https://crbug.com/webrtc/15006) or SVC // (LxTx_KEY) being slower than simulcast to send top spatial layer. // TODO(https://crbug.com/webrtc/15076): Remove need for long ramp-up timeouts // by using simulated time. constexpr TimeDelta kLongTimeoutForRampingUp = TimeDelta::Minutes(1); // The max bitrate 1500 kbps may be subject to change in the future. What we're // interested in here is that all code paths that result in L1T3 result in the // same target bitrate which does not exceed this limit. constexpr DataRate kVp9ExpectedMaxBitrateForL1T3 = DataRate::KilobitsPerSec(1500); auto EncoderImplementationIs(absl::string_view impl) { return Field("encoder_implementation", &RTCOutboundRtpStreamStats::encoder_implementation, Optional(StrEq(impl))); } template auto ScalabilityModeIs(M matcher) { return Field("scalability_mode", &RTCOutboundRtpStreamStats::scalability_mode, matcher); } template auto CodecIs(M matcher) { return Field("codec_id", &RTCOutboundRtpStreamStats::codec_id, matcher); } template auto RidIs(M matcher) { return Field("rid", &RTCOutboundRtpStreamStats::rid, matcher); } template auto ResolutionIs(WidthMatcher width_matcher, HeightMatcher height_matcher) { return AllOf(Field("frame_width", &RTCOutboundRtpStreamStats::frame_width, width_matcher), Field("frame_height", &RTCOutboundRtpStreamStats::frame_height, height_matcher)); } template auto HeightIs(M matcher) { return Field("frame_height", &RTCOutboundRtpStreamStats::frame_height, matcher); } template auto BytesSentIs(M matcher) { return Field("bytes_sent", &RTCOutboundRtpStreamStats::bytes_sent, matcher); } template auto FramesEncodedIs(M matcher) { return Field("frames_encoded", &RTCOutboundRtpStreamStats::frames_encoded, matcher); } auto Active() { return Field("active", &RTCOutboundRtpStreamStats::active, true); } Matcher> OutboundRtpStatsAre( Matcher> matcher) { return Pointer(ResultOf( "outbound_rtp", [&](const RTCStatsReport* report) { std::vector stats = report->GetStatsOfType(); // Copy to a new vector. std::vector stats_copy; stats_copy.reserve(stats.size()); for (const auto* stat : stats) { stats_copy.emplace_back(*stat); } return stats_copy; }, matcher)); } auto HasOutboundRtpBytesSent(size_t num_layers, size_t num_active_layers) { return OutboundRtpStatsAre(AllOf( SizeIs(num_layers), testing::Contains( Field("bytes_sent", &RTCOutboundRtpStreamStats::bytes_sent, Gt(0))) .Times(num_active_layers))); } auto HasOutboundRtpBytesSent(size_t num_layers) { return HasOutboundRtpBytesSent(num_layers, num_layers); } flat_map GetOutboundRtpStreamStatsByRid( scoped_refptr report) { flat_map result; auto stats = report->GetStatsOfType(); for (const auto* outbound_rtp : stats) { result.emplace( std::make_pair(outbound_rtp->rid.value_or(""), *outbound_rtp)); } return result; } struct StringParamToString { std::string operator()(const ::testing::TestParamInfo& info) { return info.param; } }; std::string GetCurrentCodecMimeType( scoped_refptr report, const RTCOutboundRtpStreamStats& outbound_rtp) { return outbound_rtp.codec_id.has_value() ? *report->GetAs(*outbound_rtp.codec_id)->mime_type : ""; } const RTCOutboundRtpStreamStats* FindOutboundRtpByRid( const std::vector& outbound_rtps, const absl::string_view& rid) { for (const auto* outbound_rtp : outbound_rtps) { if (outbound_rtp->rid.has_value() && *outbound_rtp->rid == rid) { return outbound_rtp; } } return nullptr; } } // namespace class PeerConnectionEncodingsIntegrationTest : public ::testing::Test { public: PeerConnectionEncodingsIntegrationTest() : env_(CreateTestEnvironment()), background_thread_(std::make_unique(&pss_)) { RTC_CHECK(background_thread_->Start()); } scoped_refptr CreatePc( absl::string_view field_trials = "") { auto pc_wrapper = make_ref_counted( "pc", env_, &pss_, background_thread_.get(), background_thread_.get()); pc_wrapper->CreatePc({}, CreateBuiltinAudioEncoderFactory(), CreateBuiltinAudioDecoderFactory(), CreateTestFieldTrialsPtr(field_trials)); return pc_wrapper; } scoped_refptr AddTransceiverWithSimulcastLayers( scoped_refptr local, scoped_refptr remote, std::vector init_layers) { scoped_refptr stream = local->GetUserMedia( /*audio=*/false, AudioOptions(), /*video=*/true, {.width = 1280, .height = 720}); scoped_refptr track = stream->GetVideoTracks()[0]; RTCErrorOr> transceiver_or_error = local->pc()->AddTransceiver(track, CreateTransceiverInit(init_layers)); EXPECT_TRUE(transceiver_or_error.ok()); return transceiver_or_error.value(); } bool HasReceiverVideoCodecCapability( scoped_refptr pc_wrapper, absl::string_view codec_name) { std::vector codecs = pc_wrapper->pc_factory() ->GetRtpReceiverCapabilities(MediaType::VIDEO) .codecs; return std::find_if(codecs.begin(), codecs.end(), [&codec_name](const RtpCodecCapability& codec) { return absl::EqualsIgnoreCase(codec.name, codec_name); }) != codecs.end(); } std::vector GetCapabilitiesAndRestrictToCodec( scoped_refptr pc_wrapper, absl::string_view codec_name) { std::vector codecs = pc_wrapper->pc_factory() ->GetRtpReceiverCapabilities(MediaType::VIDEO) .codecs; std::erase_if(codecs, [&codec_name](const RtpCodecCapability& codec) { return !codec.IsResiliencyCodec() && !absl::EqualsIgnoreCase(codec.name, codec_name); }); RTC_DCHECK(std::find_if(codecs.begin(), codecs.end(), [&codec_name](const RtpCodecCapability& codec) { return absl::EqualsIgnoreCase(codec.name, codec_name); }) != codecs.end()); return codecs; } void ExchangeIceCandidates( scoped_refptr local_pc_wrapper, scoped_refptr remote_pc_wrapper) { local_pc_wrapper->SubscribeOnIceCandidateReady( [remote_pc = remote_pc_wrapper.get()](const std::string& mid, int index, const std::string& candidate) { remote_pc->AddIceCandidate(mid, index, candidate); }); remote_pc_wrapper->SubscribeOnIceCandidateReady( [local_pc = local_pc_wrapper.get()](const std::string& mid, int index, const std::string& candidate) { local_pc->AddIceCandidate(mid, index, candidate); }); } // Negotiate without any tweaks (does not work for simulcast loopback). void Negotiate(scoped_refptr local_pc_wrapper, scoped_refptr remote_pc_wrapper) { std::unique_ptr offer = CreateOffer(local_pc_wrapper); scoped_refptr p1 = SetLocalDescription(local_pc_wrapper, offer.get()); scoped_refptr p2 = SetRemoteDescription(remote_pc_wrapper, offer.get()); EXPECT_TRUE(Await({p1, p2})); std::unique_ptr answer = CreateAnswer(remote_pc_wrapper); p1 = SetLocalDescription(remote_pc_wrapper, answer.get()); p2 = SetRemoteDescription(local_pc_wrapper, answer.get()); EXPECT_TRUE(Await({p1, p2})); } void NegotiateWithSimulcastTweaks( scoped_refptr local_pc_wrapper, scoped_refptr remote_pc_wrapper) { // Create and set offer for `local_pc_wrapper`. std::unique_ptr offer = CreateOffer(local_pc_wrapper); scoped_refptr p1 = SetLocalDescription(local_pc_wrapper, offer.get()); // Modify the offer before handoff because `remote_pc_wrapper` only supports // receiving singlecast. SimulcastDescription simulcast_description = RemoveSimulcast(offer.get()); scoped_refptr p2 = SetRemoteDescription(remote_pc_wrapper, offer.get()); EXPECT_TRUE(Await({p1, p2})); // Create and set answer for `remote_pc_wrapper`. std::unique_ptr answer = CreateAnswer(remote_pc_wrapper); EXPECT_TRUE(answer); p1 = SetLocalDescription(remote_pc_wrapper, answer.get()); // Modify the answer before handoff because `local_pc_wrapper` should still // send simulcast. MediaContentDescription* mcd_answer = answer->description()->contents()[0].media_description(); mcd_answer->mutable_streams().clear(); std::vector simulcast_layers = simulcast_description.send_layers().GetAllLayers(); SimulcastLayerList& receive_layers = mcd_answer->simulcast_description().receive_layers(); for (const auto& layer : simulcast_layers) { receive_layers.AddLayer(layer); } p2 = SetRemoteDescription(local_pc_wrapper, answer.get()); EXPECT_TRUE(Await({p1, p2})); } scoped_refptr GetStats( scoped_refptr pc_wrapper) { auto callback = make_ref_counted(); pc_wrapper->pc()->GetStats(callback.get()); RTC_CHECK(WaitUntil([&]() { return callback->called(); }, testing::IsTrue()) .ok()); return callback->report(); } [[nodiscard]] RTCErrorOr> GetStatsUntil( scoped_refptr pc_wrapper, Matcher> matcher, WaitUntilSettings settings = {}) { return WaitUntil([&]() { return GetStats(pc_wrapper); }, std::move(matcher), settings); } protected: std::unique_ptr CreateOffer( scoped_refptr pc_wrapper) { auto observer = make_ref_counted(); pc_wrapper->pc()->CreateOffer(observer.get(), {}); EXPECT_THAT(WaitUntil([&] { return observer->called(); }, IsTrue()), IsRtcOk()); return observer->MoveDescription(); } std::unique_ptr CreateAnswer( scoped_refptr pc_wrapper) { auto observer = make_ref_counted(); pc_wrapper->pc()->CreateAnswer(observer.get(), {}); EXPECT_THAT(WaitUntil([&] { return observer->called(); }, IsTrue()), IsRtcOk()); return observer->MoveDescription(); } scoped_refptr SetLocalDescription( scoped_refptr pc_wrapper, SessionDescriptionInterface* sdp) { auto observer = make_ref_counted(); pc_wrapper->pc()->SetLocalDescription(observer.get(), sdp->Clone().release()); return observer; } scoped_refptr SetRemoteDescription( scoped_refptr pc_wrapper, SessionDescriptionInterface* sdp) { auto observer = make_ref_counted(); pc_wrapper->pc()->SetRemoteDescription(observer.get(), sdp->Clone().release()); return observer; } // To avoid ICE candidates arriving before the remote endpoint has received // the offer it is important to SetLocalDescription() and // SetRemoteDescription() are kicked off without awaiting in-between. This // helper is used to await multiple observers. bool Await( std::vector> observers) { for (auto& observer : observers) { auto result = WaitUntil([&] { return observer->called(); }, IsTrue()); if (!result.ok() || !observer->result()) { return false; } } return true; } const Environment env_; PhysicalSocketServer pss_; std::unique_ptr background_thread_; }; TEST_F(PeerConnectionEncodingsIntegrationTest, VP8_SingleEncodingDefaultsToL1T1) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP8"); transceiver->SetCodecPreferences(codecs); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Wait until media is flowing. auto stats_result = GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(1)); ASSERT_THAT(stats_result, IsRtcOk()); EXPECT_THAT(GetOutboundRtpStreamStatsByRid(stats_result.value()), ElementsAre(Pair("", ResolutionIs(1280, 720)))); // Verify codec and scalability mode. scoped_refptr report = stats_result.value(); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(1u)); EXPECT_THAT(outbound_rtps, Contains(ResolutionIs(Le(1280), Le(720)))); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]), StrCaseEq("video/VP8")); EXPECT_THAT(*outbound_rtps[0]->scalability_mode, StrEq("L1T1")); } TEST_F(PeerConnectionEncodingsIntegrationTest, VP8_RejectsSvcAndDefaultsToL1T1) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); // Restricting the local receive codecs will restrict what we offer and // hence the answer if it is a subset of our offer. std::vector codecs = GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP8"); transceiver->SetCodecPreferences(codecs); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); // Attempt SVC (L3T3_KEY). This is not possible because only VP8 is up for // negotiation and VP8 does not support it. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_EQ(parameters.encodings.size(), 1u); parameters.encodings[0].scalability_mode = "L3T3_KEY"; parameters.encodings[0].scale_resolution_down_by = 1; EXPECT_FALSE(sender->SetParameters(parameters).ok()); // `scalability_mode` remains unset because SetParameters() failed. parameters = sender->GetParameters(); ASSERT_EQ(parameters.encodings.size(), 1u); EXPECT_THAT(parameters.encodings[0].scalability_mode, Eq(std::nullopt)); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Wait until media is flowing. ASSERT_THAT(GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(1)), IsRtcOk()); // When `scalability_mode` is not set, VP8 defaults to L1T1. scoped_refptr report = GetStats(local_pc_wrapper); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(1u)); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]), StrCaseEq("video/VP8")); EXPECT_THAT(*outbound_rtps[0]->scalability_mode, StrEq("L1T1")); // GetParameters() confirms `scalability_mode` is still not set. parameters = sender->GetParameters(); ASSERT_EQ(parameters.encodings.size(), 1u); EXPECT_THAT(parameters.encodings[0].scalability_mode, Eq(std::nullopt)); } TEST_F(PeerConnectionEncodingsIntegrationTest, SetParametersWithScalabilityModeNotSupportedBySubsequentNegotiation) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); // Restricting the local receive codecs will restrict what we offer and // hence the answer if it is a subset of our offer. std::vector codecs = GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP8"); transceiver->SetCodecPreferences(codecs); // Attempt SVC (L3T3_KEY). This is still possible because VP9 might be // available from the remote end. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_EQ(parameters.encodings.size(), 1u); parameters.encodings[0].scalability_mode = "L3T3_KEY"; parameters.encodings[0].scale_resolution_down_by = 1; EXPECT_TRUE(sender->SetParameters(parameters).ok()); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); // `scalability_mode` is set to the VP8 default since that is what was // negotiated. parameters = sender->GetParameters(); ASSERT_EQ(parameters.encodings.size(), 1u); EXPECT_THAT(parameters.encodings[0].scalability_mode, Eq("L1T2")); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Wait until media is flowing. auto error_or_stats = GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(1)); ASSERT_THAT(error_or_stats, IsRtcOk()); // When `scalability_mode` is not set, VP8 defaults to L1T1. scoped_refptr report = error_or_stats.value(); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(1u)); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]), StrCaseEq("video/VP8")); EXPECT_THAT(*outbound_rtps[0]->scalability_mode, StrEq("L1T2")); // GetParameters() confirms `scalability_mode` is still not set. parameters = sender->GetParameters(); ASSERT_EQ(parameters.encodings.size(), 1u); EXPECT_THAT(parameters.encodings[0].scalability_mode, Eq("L1T2")); } TEST_F(PeerConnectionEncodingsIntegrationTest, VP8_FallbackFromSvcResultsInL1T2) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); // Verify test assumption that VP8 is first in the list, but don't modify the // codec preferences because we want the sender to think SVC is a possibility. std::vector codecs = local_pc_wrapper->pc_factory() ->GetRtpSenderCapabilities(MediaType::VIDEO) .codecs; EXPECT_THAT(codecs[0].name, StrCaseEq("VP8")); // Attempt SVC (L3T3_KEY), which is not possible with VP8, but the sender does // not yet know which codec we'll use so the parameters will be accepted. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_EQ(parameters.encodings.size(), 1u); parameters.encodings[0].scalability_mode = "L3T3_KEY"; parameters.encodings[0].scale_resolution_down_by = 1; EXPECT_TRUE(sender->SetParameters(parameters).ok()); // Verify fallback has not happened yet. parameters = sender->GetParameters(); ASSERT_EQ(parameters.encodings.size(), 1u); EXPECT_THAT(parameters.encodings[0].scalability_mode, Optional(std::string("L3T3_KEY"))); // Negotiate, this results in VP8 being picked and fallback happening. NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // `scalaiblity_mode` is assigned the fallback value "L1T2" which is different // than the default of std::nullopt. parameters = sender->GetParameters(); ASSERT_EQ(parameters.encodings.size(), 1u); EXPECT_THAT(parameters.encodings[0].scalability_mode, Optional(std::string("L1T2"))); // Wait until media is flowing, no significant time needed because we only // have one layer. ASSERT_THAT(GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(1u)), IsRtcOk()); // GetStats() confirms "L1T2" is used which is different than the "L1T1" // default or the "L3T3_KEY" that was attempted. scoped_refptr report = GetStats(local_pc_wrapper); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(1u)); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]), StrCaseEq("video/VP8")); EXPECT_THAT(*outbound_rtps[0]->scalability_mode, StrEq("L1T2")); // Now that we know VP8 is used, try setting L3T3 which should fail. parameters = sender->GetParameters(); ASSERT_EQ(parameters.encodings.size(), 1u); parameters.encodings[0].scalability_mode = "L3T3_KEY"; parameters.encodings[0].scale_resolution_down_by = 1; EXPECT_FALSE(sender->SetParameters(parameters).ok()); } // The legacy SVC path is triggered when VP9 us used, but `scalability_mode` has // not been specified. // TODO(https://crbug.com/webrtc/14889): When legacy VP9 SVC path has been // deprecated and removed, update this test to assert that simulcast is used // (i.e. VP9 is not treated differently than VP8). TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_LegacySvcWhenScalabilityModeNotSpecified) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f", "h", "q"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); transceiver->SetCodecPreferences(codecs); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Wait until media is flowing. We only expect a single RTP stream. // We expect to see bytes flowing almost immediately on the lowest layer. ASSERT_THAT(GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(1u)), IsRtcOk()); // Wait until scalability mode is reported and expected resolution reached. // Ramp up time may be significant. ASSERT_THAT( GetStatsUntil( local_pc_wrapper, OutboundRtpStatsAre(Contains( AllOf(RidIs("f"), ScalabilityModeIs("L3T3_KEY"), HeightIs(720)))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); // Despite SVC being used on a single RTP stream, GetParameters() returns the // three encodings that we configured earlier (this is not spec-compliant but // it is how legacy SVC behaves). scoped_refptr sender = transceiver->sender(); std::vector encodings = sender->GetParameters().encodings; ASSERT_EQ(encodings.size(), 3u); // When legacy SVC is used, `scalability_mode` is not specified. EXPECT_FALSE(encodings[0].scalability_mode.has_value()); EXPECT_FALSE(encodings[1].scalability_mode.has_value()); EXPECT_FALSE(encodings[2].scalability_mode.has_value()); } // The spec-compliant way to configure SVC for a single stream. The expected // outcome is the same as for the legacy SVC case except that we only have one // encoding in GetParameters(). TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_StandardSvcWithOnlyOneEncoding) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); transceiver->SetCodecPreferences(codecs); // Configure SVC, a.k.a. "L3T3_KEY". scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_EQ(parameters.encodings.size(), 1u); parameters.encodings[0].scalability_mode = "L3T3_KEY"; parameters.encodings[0].scale_resolution_down_by = 1; EXPECT_TRUE(sender->SetParameters(parameters).ok()); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Wait until media is flowing. We only expect a single RTP stream. // We expect to see bytes flowing almost immediately on the lowest layer. auto error_or_stats = GetStatsUntil(local_pc_wrapper, AllOf(HasOutboundRtpBytesSent(1u), OutboundRtpStatsAre(Contains(HeightIs(720)))), {.timeout = kLongTimeoutForRampingUp}); ASSERT_THAT(error_or_stats, IsRtcOk()); // Verify codec and scalability mode. scoped_refptr report = error_or_stats.value(); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(1u)); EXPECT_THAT(outbound_rtps[0], ResolutionIs(1280, 720)); EXPECT_THAT(outbound_rtps[0], RidIs(std::nullopt)); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]), StrCaseEq("video/VP9")); EXPECT_THAT(*outbound_rtps[0]->scalability_mode, StrEq("L3T3_KEY")); // GetParameters() is consistent with what we asked for and got. parameters = sender->GetParameters(); ASSERT_EQ(parameters.encodings.size(), 1u); EXPECT_THAT(parameters.encodings[0].scalability_mode, Optional(std::string("L3T3_KEY"))); } // The {active,inactive,inactive} case is technically simulcast but since we // only have one active stream, we're able to do SVC (multiple spatial layers // is not supported if multiple encodings are active). The expected outcome is // the same as above except we end up with two inactive RTP streams which are // observable in GetStats(). TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_StandardSvcWithSingleActiveEncoding) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f", "h", "q"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); transceiver->SetCodecPreferences(codecs); // Configure SVC, a.k.a. "L3T3_KEY". scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); parameters.encodings[0].scalability_mode = "L3T3_KEY"; parameters.encodings[0].scale_resolution_down_by = 1; parameters.encodings[1].active = false; parameters.encodings[2].active = false; EXPECT_TRUE(sender->SetParameters(parameters).ok()); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Since the standard API is configuring simulcast we get three outbound-rtps, // but only one is active. // Wait until scalability mode is reported and expected resolution reached. // Ramp up time is significant. ASSERT_THAT(GetStatsUntil(local_pc_wrapper, AllOf(HasOutboundRtpBytesSent(3, 1), OutboundRtpStatsAre(Contains(AllOf( RidIs("f"), ScalabilityModeIs("L3T3_KEY"), HeightIs(720))))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); // GetParameters() is consistent with what we asked for and got. parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); EXPECT_THAT(parameters.encodings[0].scalability_mode, Optional(StrEq("L3T3_KEY"))); EXPECT_FALSE(parameters.encodings[1].scalability_mode.has_value()); EXPECT_FALSE(parameters.encodings[2].scalability_mode.has_value()); } // Exercise common path where `scalability_mode` is not specified until after // negotiation, requiring us to recreate the stream when the number of streams // changes from 1 (legacy SVC) to 3 (standard simulcast). TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_SwitchFromLegacySvcToStandardSingleActiveEncodingSvc) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f", "h", "q"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); transceiver->SetCodecPreferences(codecs); // The original negotiation triggers legacy SVC because we didn't specify // any scalability mode. NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Switch to the standard mode. Despite only having a single active stream in // both cases, this internally reconfigures from 1 stream to 3 streams. // Test coverage for https://crbug.com/webrtc/15016. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); parameters.encodings[0].active = true; parameters.encodings[0].scalability_mode = "L2T2_KEY"; parameters.encodings[0].scale_resolution_down_by = 2.0; parameters.encodings[1].active = false; parameters.encodings[1].scalability_mode = std::nullopt; parameters.encodings[2].active = false; parameters.encodings[2].scalability_mode = std::nullopt; sender->SetParameters(parameters); // Since the standard API is configuring simulcast we get three outbound-rtps, // but only one is active. // Wait until scalability mode is reported and expected resolution reached. // Ramp up time may be significant. ASSERT_THAT(GetStatsUntil(local_pc_wrapper, AllOf(HasOutboundRtpBytesSent(3, 1), OutboundRtpStatsAre(Contains(AllOf( RidIs("f"), ScalabilityModeIs("L2T2_KEY"), HeightIs(720 / 2))))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); // GetParameters() does not report any fallback. parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); EXPECT_THAT(parameters.encodings[0].scalability_mode, Optional(std::string("L2T2_KEY"))); EXPECT_FALSE(parameters.encodings[1].scalability_mode.has_value()); EXPECT_FALSE(parameters.encodings[2].scalability_mode.has_value()); } TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_SimulcastDeactiveActiveLayer_StandardSvc) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"q", "h", "f"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); constexpr absl::string_view kCodec = "VP9"; std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, kCodec); transceiver->SetCodecPreferences(codecs); // Switch to the standard mode. Despite only having a single active stream in // both cases, this internally reconfigures from 1 stream to 3 streams. // Test coverage for https://crbug.com/webrtc/15016. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); parameters.encodings[0].active = true; parameters.encodings[0].scalability_mode = "L1T3"; parameters.encodings[0].scale_resolution_down_by = 4.0; parameters.encodings[1].active = true; parameters.encodings[1].scalability_mode = "L1T1"; parameters.encodings[1].scale_resolution_down_by = 2.0; parameters.encodings[2].active = true; parameters.encodings[2].scalability_mode = "L1T1"; parameters.encodings[2].scale_resolution_down_by = 1.0; EXPECT_TRUE(sender->SetParameters(parameters).ok()); // The original negotiation triggers legacy SVC because we didn't specify // any scalability mode. NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Since the standard API is configuring simulcast we get three outbound-rtps, // and two are active. ASSERT_THAT( WaitUntil( [&] { std::vector outbound_rtps = GetStats(local_pc_wrapper) ->GetStatsOfType(); std::vector bytes_sent; bytes_sent.reserve(outbound_rtps.size()); for (const auto* outbound_rtp : outbound_rtps) { bytes_sent.push_back(outbound_rtp->bytes_sent.value_or(0)); } return bytes_sent; }, AllOf(SizeIs(3), Each(Gt(0))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); scoped_refptr report = GetStats(local_pc_wrapper); ASSERT_TRUE(report); std::vector outbound_rtps = report->GetStatsOfType(); EXPECT_THAT(outbound_rtps, Each(EncoderImplementationIs( "SimulcastEncoderAdapter (libvpx, libvpx, libvpx)"))); // GetParameters() does not report any fallback. parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); EXPECT_THAT(parameters.encodings[0].scalability_mode, Optional(StrEq("L1T3"))); EXPECT_THAT(parameters.encodings[1].scalability_mode, Optional(StrEq("L1T1"))); EXPECT_THAT(parameters.encodings[2].scalability_mode, Optional(StrEq("L1T1"))); EXPECT_THAT(parameters.encodings[2].scale_resolution_down_by, Eq(1.0)); EXPECT_THAT(parameters.encodings[1].scale_resolution_down_by, Eq(2.0)); EXPECT_THAT(parameters.encodings[0].scale_resolution_down_by, Eq(4.0)); // Deactivate the active layer. parameters.encodings[2].active = false; EXPECT_TRUE(sender->SetParameters(parameters).ok()); ASSERT_THAT(WaitUntil( [&]() { return GetStats(local_pc_wrapper) ->GetStatsOfType(); }, AllOf(Each(EncoderImplementationIs( "SimulcastEncoderAdapter (libvpx, libvpx)")), UnorderedElementsAre(ScalabilityModeIs("L1T3"), ScalabilityModeIs("L1T1"), ScalabilityModeIs(std::nullopt)))), IsRtcOk()); } TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_SimulcastMultiplLayersActive_StandardSvc) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"q", "h", "f"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); transceiver->SetCodecPreferences(codecs); // Switch to the standard mode. Despite only having a single active stream in // both cases, this internally reconfigures from 1 stream to 3 streams. // Test coverage for https://crbug.com/webrtc/15016. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); parameters.encodings[0].active = true; parameters.encodings[0].scalability_mode = "L1T3"; parameters.encodings[0].scale_resolution_down_by = 4.0; parameters.encodings[1].active = true; parameters.encodings[1].scalability_mode = "L1T1"; parameters.encodings[1].scale_resolution_down_by = 2.0; parameters.encodings[2].active = false; parameters.encodings[2].scalability_mode = std::nullopt; EXPECT_TRUE(sender->SetParameters(parameters).ok()); // The original negotiation triggers legacy SVC because we didn't specify // any scalability mode. NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Since the standard API is configuring simulcast we get three outbound-rtps, // and two are active. // Wait until scalability mode is reported and expected resolution reached. // Ramp up time may be significant. auto error_or_stats = GetStatsUntil( local_pc_wrapper, OutboundRtpStatsAre( IsSupersetOf({AllOf(RidIs("q"), ScalabilityModeIs("L1T3"), HeightIs(720 / 4), BytesSentIs(Gt(0))), AllOf(RidIs("h"), ScalabilityModeIs("L1T1"), HeightIs(720 / 2), BytesSentIs(Gt(0)))})), {.timeout = kLongTimeoutForRampingUp}); ASSERT_THAT(error_or_stats, IsRtcOk()); scoped_refptr report = error_or_stats.value(); std::vector outbound_rtps = report->GetStatsOfType(); EXPECT_THAT(outbound_rtps, Each(EncoderImplementationIs( "SimulcastEncoderAdapter (libvpx, libvpx)"))); // GetParameters() does not report any fallback. parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); EXPECT_THAT(parameters.encodings[0].scalability_mode, Optional(StrEq("L1T3"))); EXPECT_THAT(parameters.encodings[1].scalability_mode, Optional(StrEq("L1T1"))); EXPECT_THAT(parameters.encodings[2].scalability_mode, Eq(std::nullopt)); } TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_Simulcast_SwitchToLegacySvc) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f", "h", "q"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); transceiver->SetCodecPreferences(codecs); // Switch to the standard mode. Despite only having a single active stream in // both cases, this internally reconfigures from 1 stream to 3 streams. // Test coverage for https://crbug.com/webrtc/15016. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); parameters.encodings[0].active = false; parameters.encodings[1].active = true; parameters.encodings[1].scalability_mode = "L1T1"; parameters.encodings[1].scale_resolution_down_by = 2.0; parameters.encodings[2].active = true; parameters.encodings[2].scalability_mode = "L1T3"; parameters.encodings[2].scale_resolution_down_by = 4.0; EXPECT_TRUE(sender->SetParameters(parameters).ok()); // The original negotiation triggers legacy SVC because we didn't specify // any scalability mode. NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Since the standard API is configuring simulcast we get three outbound-rtps, // and two are active. // Wait until scalability mode is reported and expected resolution reached. // Ramp up time may be significant. auto error_or_stats = GetStatsUntil( local_pc_wrapper, OutboundRtpStatsAre(UnorderedElementsAre( AllOf(RidIs("q"), ScalabilityModeIs("L1T3"), HeightIs(720 / 4)), AllOf(RidIs("h"), ScalabilityModeIs("L1T1"), HeightIs(720 / 2)), AllOf(RidIs("f"), BytesSentIs(AnyOf(0, std::nullopt))))), {.timeout = kLongTimeoutForRampingUp}); ASSERT_THAT(error_or_stats, IsRtcOk()); // GetParameters() does not report any fallback. parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); EXPECT_THAT(parameters.encodings[0].scalability_mode, Eq(std::nullopt)); EXPECT_THAT(parameters.encodings[1].scalability_mode, Optional(StrEq("L1T1"))); EXPECT_THAT(parameters.encodings[2].scalability_mode, Optional(StrEq("L1T3"))); // Switch to legacy SVC mode. parameters.encodings[0].active = true; parameters.encodings[0].scalability_mode = std::nullopt; parameters.encodings[0].scale_resolution_down_by = std::nullopt; parameters.encodings[1].active = true; parameters.encodings[1].scalability_mode = std::nullopt; parameters.encodings[1].scale_resolution_down_by = std::nullopt; parameters.encodings[2].active = false; parameters.encodings[2].scalability_mode = std::nullopt; parameters.encodings[2].scale_resolution_down_by = std::nullopt; EXPECT_TRUE(sender->SetParameters(parameters).ok()); // Ensure that we are getting VGA at L1T3 from the "f" rid. EXPECT_THAT( GetStatsUntil( local_pc_wrapper, OutboundRtpStatsAre(Contains(AllOf( RidIs("f"), ScalabilityModeIs("L2T3_KEY"), HeightIs(720 / 2)))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); } TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_OneLayerActive_LegacySvc) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f", "h", "q"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); transceiver->SetCodecPreferences(codecs); // Sending L1T3 with legacy SVC mode means setting 1 layer active. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); parameters.encodings[0].active = true; parameters.encodings[1].active = false; parameters.encodings[2].active = false; sender->SetParameters(parameters); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Ensure that we are getting 180P at L1T3 from the "f" rid. EXPECT_THAT( GetStatsUntil( local_pc_wrapper, OutboundRtpStatsAre(Contains( AllOf(RidIs("f"), ScalabilityModeIs("L1T3"), HeightIs(720 / 4)))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); } TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_AllLayersInactive_LegacySvc) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f", "h", "q"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); transceiver->SetCodecPreferences(codecs); // Legacy SVC mode and all layers inactive. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); parameters.encodings[0].active = false; parameters.encodings[1].active = false; parameters.encodings[2].active = false; sender->SetParameters(parameters); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Ensure no media is flowing (1 second should be enough). Thread::Current()->SleepMs(1000); scoped_refptr report = GetStats(local_pc_wrapper); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(1u)); EXPECT_EQ(*outbound_rtps[0]->bytes_sent, 0u); } TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_AllLayersInactive_StandardSvc) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f", "h", "q"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); transceiver->SetCodecPreferences(codecs); // Standard mode and all layers inactive. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); parameters.encodings[0].scalability_mode = "L3T3_KEY"; parameters.encodings[0].scale_resolution_down_by = 1; parameters.encodings[0].active = false; parameters.encodings[1].active = false; parameters.encodings[2].active = false; sender->SetParameters(parameters); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Ensure no media is flowing (1 second should be enough). Thread::Current()->SleepMs(1000); scoped_refptr report = GetStats(local_pc_wrapper); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(3u)); EXPECT_EQ(*outbound_rtps[0]->bytes_sent, 0u); EXPECT_EQ(*outbound_rtps[1]->bytes_sent, 0u); EXPECT_EQ(*outbound_rtps[2]->bytes_sent, 0u); } TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_TargetBitrate_LegacyL1T3) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f", "h", "q"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); transceiver->SetCodecPreferences(codecs); // In legacy SVC, disabling the bottom two layers encodings is interpreted as // disabling the bottom two spatial layers resulting in L1T3. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); parameters.encodings[0].active = false; parameters.encodings[1].active = false; parameters.encodings[2].active = true; sender->SetParameters(parameters); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Wait until 720p L1T3 has ramped up to 720p. It may take additional time // for the target bitrate to reach its maximum. ASSERT_THAT( GetStatsUntil(local_pc_wrapper, OutboundRtpStatsAre(Contains(AllOf( RidIs("f"), ScalabilityModeIs("L1T3"), HeightIs(720)))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); // The target bitrate typically reaches `kVp9ExpectedMaxBitrateForL1T3` // in a short period of time. However to reduce risk of flakiness in bot // environments, this test only fails if we we exceed the expected target. Thread::Current()->SleepMs(1000); scoped_refptr report = GetStats(local_pc_wrapper); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(1)); DataRate target_bitrate = DataRate::BitsPerSec(*outbound_rtps[0]->target_bitrate); EXPECT_LE(target_bitrate.kbps(), kVp9ExpectedMaxBitrateForL1T3.kbps()); } // Test coverage for https://crbug.com/1455039. TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_TargetBitrate_StandardL1T3) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f", "h", "q"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); transceiver->SetCodecPreferences(codecs); // With standard APIs, L1T3 is explicitly specified and the encodings refers // to the RTP streams, not the spatial layers. The end result should be // equivalent to the legacy L1T3 case. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); parameters.encodings[0].active = true; parameters.encodings[0].scale_resolution_down_by = 1.0; parameters.encodings[0].scalability_mode = "L1T3"; parameters.encodings[1].active = false; parameters.encodings[2].active = false; sender->SetParameters(parameters); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Wait until 720p L1T3 has ramped up to 720p. It may take additional time // for the target bitrate to reach its maximum. ASSERT_THAT( GetStatsUntil(local_pc_wrapper, OutboundRtpStatsAre(Contains(AllOf( RidIs("f"), ScalabilityModeIs("L1T3"), HeightIs(720)))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); // The target bitrate typically reaches `kVp9ExpectedMaxBitrateForL1T3` // in a short period of time. However to reduce risk of flakiness in bot // environments, this test only fails if we we exceed the expected target. Thread::Current()->SleepMs(1000); scoped_refptr report = GetStats(local_pc_wrapper); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(3)); auto* outbound_rtp = FindOutboundRtpByRid(outbound_rtps, "f"); ASSERT_TRUE(outbound_rtp); DataRate target_bitrate = DataRate::BitsPerSec(*outbound_rtp->target_bitrate); EXPECT_LE(target_bitrate.kbps(), kVp9ExpectedMaxBitrateForL1T3.kbps()); } TEST_F(PeerConnectionEncodingsIntegrationTest, SimulcastProducesUniqueSsrcAndRtxSsrcsWhenConnected) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f", "h", "q"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP8"); transceiver->SetCodecPreferences(codecs); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Wait until media is flowing on all three layers. // Ramp up time is needed before all three layers are sending. auto stats = GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(3u), {.timeout = kLongTimeoutForRampingUp}); ASSERT_THAT(stats, IsRtcOk()); // Verify SSRCs and RTX SSRCs. scoped_refptr report = stats.MoveValue(); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(3u)); std::set ssrcs; std::set rtx_ssrcs; for (const auto& outbound_rtp : outbound_rtps) { ASSERT_TRUE(outbound_rtp->ssrc.has_value()); ASSERT_TRUE(outbound_rtp->rtx_ssrc.has_value()); ssrcs.insert(*outbound_rtp->ssrc); rtx_ssrcs.insert(*outbound_rtp->rtx_ssrc); } EXPECT_EQ(ssrcs.size(), 3u); EXPECT_EQ(rtx_ssrcs.size(), 3u); } // Similar to the above test, but we never exchange ICE candidates such that // simulcast never has a chance to "ramp up". Despite this, we should see one // outbound-rtp per encoding. TEST_F(PeerConnectionEncodingsIntegrationTest, SimulcastProducesUniqueSsrcAndRtxSsrcsWhenDisconnected) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); std::vector layers = CreateLayers({"f", "h", "q"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); // Inactive the middle layer. auto sender = transceiver->sender(); auto parameters = sender->GetParameters(); parameters.encodings[0].active = true; parameters.encodings[1].active = false; parameters.encodings[2].active = true; EXPECT_TRUE(sender->SetParameters(parameters).ok()); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); // We have three outbound-rtps. scoped_refptr report = GetStats(local_pc_wrapper); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_EQ(outbound_rtps.size(), 3u); // SSRC and RTX is unique. std::set ssrcs; std::set rtx_ssrcs; for (const auto& outbound_rtp : outbound_rtps) { ASSERT_TRUE(outbound_rtp->ssrc.has_value()); ASSERT_TRUE(outbound_rtp->rtx_ssrc.has_value()); ssrcs.insert(*outbound_rtp->ssrc); rtx_ssrcs.insert(*outbound_rtp->rtx_ssrc); } EXPECT_EQ(ssrcs.size(), 3u); EXPECT_EQ(rtx_ssrcs.size(), 3u); // RIDs and active are set. auto outbound_rtp_by_rid = GetOutboundRtpStreamStatsByRid(report); EXPECT_THAT( outbound_rtp_by_rid, UnorderedElementsAre(Pair("q", Active()), Pair("h", Not(Active())), Pair("f", Active()))); } TEST_F(PeerConnectionEncodingsIntegrationTest, EncodingParameterCodecIsEmptyWhenCreatedAudio) { scoped_refptr local_pc_wrapper = CreatePc(); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::AUDIO); scoped_refptr audio_transceiver = transceiver_or_error.MoveValue(); RtpParameters parameters = audio_transceiver->sender()->GetParameters(); EXPECT_FALSE(parameters.encodings[0].codec.has_value()); } TEST_F(PeerConnectionEncodingsIntegrationTest, EncodingParameterCodecIsEmptyWhenCreatedVideo) { scoped_refptr local_pc_wrapper = CreatePc(); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO); scoped_refptr video_transceiver = transceiver_or_error.MoveValue(); RtpParameters parameters = video_transceiver->sender()->GetParameters(); EXPECT_FALSE(parameters.encodings[0].codec.has_value()); } TEST_F(PeerConnectionEncodingsIntegrationTest, EncodingParameterCodecIsSetByAddTransceiverAudio) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); scoped_refptr stream = local_pc_wrapper->GetUserMedia( /*audio=*/true, {}, /*video=*/false, {}); scoped_refptr track = stream->GetAudioTracks()[0]; std::optional pcmu = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::AUDIO, "pcmu"); ASSERT_TRUE(pcmu); RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kSendOnly; RtpEncodingParameters encoding_parameters; encoding_parameters.codec = pcmu; init.send_encodings.push_back(encoding_parameters); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track, init); scoped_refptr audio_transceiver = transceiver_or_error.MoveValue(); RtpParameters parameters = audio_transceiver->sender()->GetParameters(); EXPECT_EQ(*parameters.encodings[0].codec, *pcmu); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); ASSERT_TRUE(local_pc_wrapper->WaitForConnection()); ASSERT_TRUE(remote_pc_wrapper->WaitForConnection()); scoped_refptr report = GetStats(local_pc_wrapper); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_EQ(outbound_rtps.size(), 1u); std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]); EXPECT_STRCASEEQ(("audio/" + pcmu->name).c_str(), codec_name.c_str()); } TEST_F(PeerConnectionEncodingsIntegrationTest, EncodingParameterCodecIsSetByAddTransceiverVideo) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); scoped_refptr stream = local_pc_wrapper->GetUserMedia( /*audio=*/false, {}, /*video=*/true, {.width = 1280, .height = 720}); scoped_refptr track = stream->GetVideoTracks()[0]; std::optional vp9 = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp9"); ASSERT_TRUE(vp9); RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kSendOnly; RtpEncodingParameters encoding_parameters; encoding_parameters.codec = vp9; encoding_parameters.scalability_mode = "L3T3"; init.send_encodings.push_back(encoding_parameters); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track, init); scoped_refptr audio_transceiver = transceiver_or_error.MoveValue(); RtpParameters parameters = audio_transceiver->sender()->GetParameters(); EXPECT_EQ(*parameters.encodings[0].codec, *vp9); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); auto error_or_stats = GetStatsUntil(local_pc_wrapper, OutboundRtpStatsAre(Contains(ScalabilityModeIs("L3T3")))); ASSERT_THAT(error_or_stats, IsRtcOk()); scoped_refptr report = error_or_stats.MoveValue(); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_EQ(outbound_rtps.size(), 1u); std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]); EXPECT_STRCASEEQ(("video/" + vp9->name).c_str(), codec_name.c_str()); EXPECT_EQ(outbound_rtps[0]->scalability_mode.value(), "L3T3"); } TEST_F(PeerConnectionEncodingsIntegrationTest, EncodingParameterCodecIsSetBySetParametersBeforeNegotiationAudio) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); scoped_refptr stream = local_pc_wrapper->GetUserMedia( /*audio=*/true, {}, /*video=*/false, {}); scoped_refptr track = stream->GetAudioTracks()[0]; std::optional pcmu = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::AUDIO, "pcmu"); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track); scoped_refptr audio_transceiver = transceiver_or_error.MoveValue(); RtpParameters parameters = audio_transceiver->sender()->GetParameters(); parameters.encodings[0].codec = pcmu; EXPECT_TRUE(audio_transceiver->sender()->SetParameters(parameters).ok()); parameters = audio_transceiver->sender()->GetParameters(); EXPECT_EQ(parameters.encodings[0].codec, pcmu); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); scoped_refptr report = GetStats(local_pc_wrapper); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_EQ(outbound_rtps.size(), 1u); std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]); EXPECT_STRCASEEQ(("audio/" + pcmu->name).c_str(), codec_name.c_str()); } TEST_F(PeerConnectionEncodingsIntegrationTest, EncodingParameterCodecIsSetBySetParametersAfterNegotiationAudio) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); scoped_refptr stream = local_pc_wrapper->GetUserMedia( /*audio=*/true, {}, /*video=*/false, {}); scoped_refptr track = stream->GetAudioTracks()[0]; std::optional pcmu = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::AUDIO, "pcmu"); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track); scoped_refptr audio_transceiver = transceiver_or_error.MoveValue(); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); scoped_refptr report = GetStats(local_pc_wrapper); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_EQ(outbound_rtps.size(), 1u); std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]); EXPECT_STRCASENE(("audio/" + pcmu->name).c_str(), codec_name.c_str()); std::string last_codec_id = outbound_rtps[0]->codec_id.value(); RtpParameters parameters = audio_transceiver->sender()->GetParameters(); parameters.encodings[0].codec = pcmu; EXPECT_TRUE(audio_transceiver->sender()->SetParameters(parameters).ok()); parameters = audio_transceiver->sender()->GetParameters(); EXPECT_EQ(parameters.encodings[0].codec, pcmu); auto error_or_stats = GetStatsUntil(local_pc_wrapper, OutboundRtpStatsAre(Contains(CodecIs(Ne(last_codec_id))))); ASSERT_THAT(error_or_stats, IsRtcOk()); report = error_or_stats.MoveValue(); outbound_rtps = report->GetStatsOfType(); ASSERT_EQ(outbound_rtps.size(), 1u); codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]); EXPECT_STRCASEEQ(("audio/" + pcmu->name).c_str(), codec_name.c_str()); } TEST_F(PeerConnectionEncodingsIntegrationTest, EncodingParameterCodecIsSetBySetParametersBeforeNegotiationVideo) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); scoped_refptr stream = local_pc_wrapper->GetUserMedia( /*audio=*/false, {}, /*video=*/true, {.width = 1280, .height = 720}); scoped_refptr track = stream->GetVideoTracks()[0]; std::optional vp9 = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp9"); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track); scoped_refptr video_transceiver = transceiver_or_error.MoveValue(); RtpParameters parameters = video_transceiver->sender()->GetParameters(); parameters.encodings[0].codec = vp9; parameters.encodings[0].scalability_mode = "L3T3"; EXPECT_TRUE(video_transceiver->sender()->SetParameters(parameters).ok()); parameters = video_transceiver->sender()->GetParameters(); EXPECT_EQ(parameters.encodings[0].codec, vp9); EXPECT_EQ(parameters.encodings[0].scalability_mode, "L3T3"); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); auto error_or_stats = GetStatsUntil( local_pc_wrapper, OutboundRtpStatsAre(Contains(AllOf( ScalabilityModeIs("L3T3"), CodecIs(Ne("")))))); ASSERT_THAT(error_or_stats, IsRtcOk()); scoped_refptr report = error_or_stats.MoveValue(); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_EQ(outbound_rtps.size(), 1u); std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]); EXPECT_STRCASEEQ(("video/" + vp9->name).c_str(), codec_name.c_str()); EXPECT_EQ(outbound_rtps[0]->scalability_mode.value_or(""), "L3T3"); } TEST_F(PeerConnectionEncodingsIntegrationTest, EncodingParameterCodecIsSetBySetParametersAfterNegotiationVideo) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); scoped_refptr stream = local_pc_wrapper->GetUserMedia( /*audio=*/false, {}, /*video=*/true, {.width = 1280, .height = 720}); scoped_refptr track = stream->GetVideoTracks()[0]; std::optional vp9 = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp9"); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track); scoped_refptr video_transceiver = transceiver_or_error.MoveValue(); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); scoped_refptr report = GetStats(local_pc_wrapper); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_EQ(outbound_rtps.size(), 1u); std::string codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]); EXPECT_STRCASENE(("audio/" + vp9->name).c_str(), codec_name.c_str()); std::string last_codec_id = outbound_rtps[0]->codec_id.value(); RtpParameters parameters = video_transceiver->sender()->GetParameters(); parameters.encodings[0].codec = vp9; parameters.encodings[0].scalability_mode = "L3T3"; EXPECT_TRUE(video_transceiver->sender()->SetParameters(parameters).ok()); parameters = video_transceiver->sender()->GetParameters(); EXPECT_EQ(parameters.encodings[0].codec, vp9); EXPECT_EQ(parameters.encodings[0].scalability_mode, "L3T3"); auto error_or_stats = GetStatsUntil( local_pc_wrapper, OutboundRtpStatsAre(Contains( AllOf(ScalabilityModeIs("L3T3"), CodecIs(Ne(last_codec_id)))))); ASSERT_THAT(error_or_stats, IsRtcOk()); report = error_or_stats.MoveValue(); outbound_rtps = report->GetStatsOfType(); ASSERT_EQ(outbound_rtps.size(), 1u); codec_name = GetCurrentCodecMimeType(report, *outbound_rtps[0]); EXPECT_STRCASEEQ(("video/" + vp9->name).c_str(), codec_name.c_str()); EXPECT_EQ(outbound_rtps[0]->scalability_mode.value(), "L3T3"); } TEST_F(PeerConnectionEncodingsIntegrationTest, AddTransceiverRejectsUnknownCodecParameterAudio) { scoped_refptr local_pc_wrapper = CreatePc(); RtpCodec dummy_codec; dummy_codec.kind = MediaType::AUDIO; dummy_codec.name = "FOOBAR"; dummy_codec.clock_rate = 90000; dummy_codec.num_channels = 2; RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kSendOnly; RtpEncodingParameters encoding_parameters; encoding_parameters.codec = dummy_codec; init.send_encodings.push_back(encoding_parameters); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::AUDIO, init); EXPECT_FALSE(transceiver_or_error.ok()); EXPECT_EQ(transceiver_or_error.error().type(), RTCErrorType::UNSUPPORTED_OPERATION); } TEST_F(PeerConnectionEncodingsIntegrationTest, AddTransceiverRejectsUnknownCodecParameterVideo) { scoped_refptr local_pc_wrapper = CreatePc(); RtpCodec dummy_codec; dummy_codec.kind = MediaType::VIDEO; dummy_codec.name = "FOOBAR"; dummy_codec.clock_rate = 90000; RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kSendOnly; RtpEncodingParameters encoding_parameters; encoding_parameters.codec = dummy_codec; init.send_encodings.push_back(encoding_parameters); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO, init); EXPECT_FALSE(transceiver_or_error.ok()); EXPECT_EQ(transceiver_or_error.error().type(), RTCErrorType::UNSUPPORTED_OPERATION); } TEST_F(PeerConnectionEncodingsIntegrationTest, SetParametersRejectsUnknownCodecParameterAudio) { scoped_refptr local_pc_wrapper = CreatePc(); RtpCodec dummy_codec; dummy_codec.kind = MediaType::AUDIO; dummy_codec.name = "FOOBAR"; dummy_codec.clock_rate = 90000; dummy_codec.num_channels = 2; auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::AUDIO); ASSERT_TRUE(transceiver_or_error.ok()); scoped_refptr audio_transceiver = transceiver_or_error.MoveValue(); RtpParameters parameters = audio_transceiver->sender()->GetParameters(); parameters.encodings[0].codec = dummy_codec; RTCError error = audio_transceiver->sender()->SetParameters(parameters); EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION); } TEST_F(PeerConnectionEncodingsIntegrationTest, SetParametersRejectsUnknownCodecParameterVideo) { scoped_refptr local_pc_wrapper = CreatePc(); RtpCodec dummy_codec; dummy_codec.kind = MediaType::VIDEO; dummy_codec.name = "FOOBAR"; dummy_codec.clock_rate = 90000; auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO); ASSERT_TRUE(transceiver_or_error.ok()); scoped_refptr video_transceiver = transceiver_or_error.MoveValue(); RtpParameters parameters = video_transceiver->sender()->GetParameters(); parameters.encodings[0].codec = dummy_codec; RTCError error = video_transceiver->sender()->SetParameters(parameters); EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION); } TEST_F(PeerConnectionEncodingsIntegrationTest, SetParametersRejectsNonNegotiatedCodecParameterAudio) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::optional opus = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::AUDIO, "opus"); ASSERT_TRUE(opus); std::vector not_opus_codecs = local_pc_wrapper->pc_factory() ->GetRtpSenderCapabilities(MediaType::AUDIO) .codecs; std::erase_if(not_opus_codecs, [&](const auto& codec) { return absl::EqualsIgnoreCase(codec.name, opus->name); }); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::AUDIO); ASSERT_TRUE(transceiver_or_error.ok()); scoped_refptr audio_transceiver = transceiver_or_error.MoveValue(); ASSERT_TRUE(audio_transceiver->SetCodecPreferences(not_opus_codecs).ok()); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); RtpParameters parameters = audio_transceiver->sender()->GetParameters(); parameters.encodings[0].codec = opus; RTCError error = audio_transceiver->sender()->SetParameters(parameters); EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION); } TEST_F(PeerConnectionEncodingsIntegrationTest, SetParametersRejectsNonRemotelyNegotiatedCodecParameterAudio) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::optional opus = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::AUDIO, "opus"); ASSERT_TRUE(opus); std::vector not_opus_codecs = local_pc_wrapper->pc_factory() ->GetRtpSenderCapabilities(MediaType::AUDIO) .codecs; std::erase_if(not_opus_codecs, [&](const auto& codec) { return absl::EqualsIgnoreCase(codec.name, opus->name); }); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::AUDIO); ASSERT_TRUE(transceiver_or_error.ok()); scoped_refptr audio_transceiver = transceiver_or_error.MoveValue(); // Negotiation, create offer and apply it std::unique_ptr offer = CreateOffer(local_pc_wrapper); scoped_refptr p1 = SetLocalDescription(local_pc_wrapper, offer.get()); scoped_refptr p2 = SetRemoteDescription(remote_pc_wrapper, offer.get()); EXPECT_TRUE(Await({p1, p2})); // Update the remote transceiver to reject Opus std::vector> remote_transceivers = remote_pc_wrapper->pc()->GetTransceivers(); ASSERT_TRUE(!remote_transceivers.empty()); scoped_refptr remote_audio_transceiver = remote_transceivers[0]; ASSERT_TRUE( remote_audio_transceiver->SetCodecPreferences(not_opus_codecs).ok()); // Create answer and apply it std::unique_ptr answer = CreateAnswer(remote_pc_wrapper); p1 = SetLocalDescription(remote_pc_wrapper, answer.get()); p2 = SetRemoteDescription(local_pc_wrapper, answer.get()); EXPECT_TRUE(Await({p1, p2})); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); RtpParameters parameters = audio_transceiver->sender()->GetParameters(); parameters.encodings[0].codec = opus; RTCError error = audio_transceiver->sender()->SetParameters(parameters); EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION); } // Test coverage for https://crbug.com/webrtc/391340599. // Some web apps add non-standard FMTP parameters to video codecs and because // they get successfully negotiated due to being ignored by SDP rules, they show // up in GetParameters().codecs. Using SetParameters() with such codecs should // still work. TEST_F(PeerConnectionEncodingsIntegrationTest, SetParametersAcceptsMungedCodecFromGetParameters) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO); ASSERT_TRUE(transceiver_or_error.ok()); scoped_refptr video_transceiver = transceiver_or_error.MoveValue(); std::unique_ptr offer = CreateOffer(local_pc_wrapper); // Munge a new parameter for VP8 in the offer. auto* mcd = offer->description()->contents()[0].media_description(); ASSERT_THAT(mcd, NotNull()); std::vector codecs = mcd->codecs(); ASSERT_THAT(codecs, Contains(Field(&Codec::name, "VP8"))); auto vp8_codec = absl::c_find_if( codecs, [](const Codec& codec) { return codec.name == "VP8"; }); vp8_codec->params.emplace("non-standard-param", "true"); mcd->set_codecs(codecs); scoped_refptr p1 = SetLocalDescription(local_pc_wrapper, offer.get()); scoped_refptr p2 = SetRemoteDescription(remote_pc_wrapper, offer.get()); EXPECT_TRUE(Await({p1, p2})); // Create answer and apply it std::unique_ptr answer = CreateAnswer(remote_pc_wrapper); mcd = answer->description()->contents()[0].media_description(); ASSERT_THAT(mcd, NotNull()); codecs = mcd->codecs(); ASSERT_THAT(codecs, Contains(Field(&Codec::name, "VP8"))); vp8_codec = absl::c_find_if( codecs, [](const Codec& codec) { return codec.name == "VP8"; }); vp8_codec->params.emplace("non-standard-param", "true"); mcd->set_codecs(codecs); p1 = SetLocalDescription(remote_pc_wrapper, answer.get()); p2 = SetRemoteDescription(local_pc_wrapper, answer.get()); EXPECT_TRUE(Await({p1, p2})); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); RtpParameters parameters = video_transceiver->sender()->GetParameters(); auto it = absl::c_find_if( parameters.codecs, [](const auto& codec) { return codec.name == "VP8"; }); ASSERT_NE(it, parameters.codecs.end()); RtpCodecParameters& vp8_codec_from_parameters = *it; EXPECT_THAT(vp8_codec_from_parameters.parameters, Contains(Pair("non-standard-param", "true"))); parameters.encodings[0].codec = vp8_codec_from_parameters; EXPECT_THAT(video_transceiver->sender()->SetParameters(parameters), IsRtcOk()); } TEST_F(PeerConnectionEncodingsIntegrationTest, SetParametersRejectsNonNegotiatedCodecParameterVideo) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::optional vp8 = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp8"); ASSERT_TRUE(vp8); std::vector not_vp8_codecs = local_pc_wrapper->pc_factory() ->GetRtpSenderCapabilities(MediaType::VIDEO) .codecs; std::erase_if(not_vp8_codecs, [&](const auto& codec) { return absl::EqualsIgnoreCase(codec.name, vp8->name); }); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO); ASSERT_TRUE(transceiver_or_error.ok()); scoped_refptr video_transceiver = transceiver_or_error.MoveValue(); ASSERT_TRUE(video_transceiver->SetCodecPreferences(not_vp8_codecs).ok()); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); RtpParameters parameters = video_transceiver->sender()->GetParameters(); parameters.encodings[0].codec = vp8; RTCError error = video_transceiver->sender()->SetParameters(parameters); EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION); } TEST_F(PeerConnectionEncodingsIntegrationTest, SetParametersRejectsNonRemotelyNegotiatedCodecParameterVideo) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::optional vp8 = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp8"); ASSERT_TRUE(vp8); std::vector not_vp8_codecs = local_pc_wrapper->pc_factory() ->GetRtpSenderCapabilities(MediaType::VIDEO) .codecs; std::erase_if(not_vp8_codecs, [&](const auto& codec) { return absl::EqualsIgnoreCase(codec.name, vp8->name); }); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO); ASSERT_TRUE(transceiver_or_error.ok()); scoped_refptr video_transceiver = transceiver_or_error.MoveValue(); // Negotiation, create offer and apply it std::unique_ptr offer = CreateOffer(local_pc_wrapper); scoped_refptr p1 = SetLocalDescription(local_pc_wrapper, offer.get()); scoped_refptr p2 = SetRemoteDescription(remote_pc_wrapper, offer.get()); EXPECT_TRUE(Await({p1, p2})); // Update the remote transceiver to reject VP8 std::vector> remote_transceivers = remote_pc_wrapper->pc()->GetTransceivers(); ASSERT_TRUE(!remote_transceivers.empty()); scoped_refptr remote_video_transceiver = remote_transceivers[0]; ASSERT_TRUE( remote_video_transceiver->SetCodecPreferences(not_vp8_codecs).ok()); // Create answer and apply it std::unique_ptr answer = CreateAnswer(remote_pc_wrapper); p1 = SetLocalDescription(remote_pc_wrapper, answer.get()); p2 = SetRemoteDescription(local_pc_wrapper, answer.get()); EXPECT_TRUE(Await({p1, p2})); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); RtpParameters parameters = video_transceiver->sender()->GetParameters(); parameters.encodings[0].codec = vp8; RTCError error = video_transceiver->sender()->SetParameters(parameters); EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION); } TEST_F(PeerConnectionEncodingsIntegrationTest, EncodingParametersCodecRemovedAfterNegotiationAudio) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::optional opus = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::AUDIO, "opus"); ASSERT_TRUE(opus); std::vector not_opus_codecs = local_pc_wrapper->pc_factory() ->GetRtpSenderCapabilities(MediaType::AUDIO) .codecs; std::erase_if(not_opus_codecs, [&](const auto& codec) { return absl::EqualsIgnoreCase(codec.name, opus->name); }); RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kSendOnly; RtpEncodingParameters encoding_parameters; encoding_parameters.codec = opus; init.send_encodings.push_back(encoding_parameters); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::AUDIO, init); ASSERT_TRUE(transceiver_or_error.ok()); scoped_refptr audio_transceiver = transceiver_or_error.MoveValue(); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); RtpParameters parameters = audio_transceiver->sender()->GetParameters(); EXPECT_EQ(parameters.encodings[0].codec, opus); ASSERT_TRUE(audio_transceiver->SetCodecPreferences(not_opus_codecs).ok()); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); parameters = audio_transceiver->sender()->GetParameters(); EXPECT_FALSE(parameters.encodings[0].codec); } TEST_F(PeerConnectionEncodingsIntegrationTest, EncodingParametersRedEnabledBeforeNegotiationAudio) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector send_codecs = local_pc_wrapper->pc_factory() ->GetRtpSenderCapabilities(MediaType::AUDIO) .codecs; std::optional opus = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::AUDIO, "opus"); ASSERT_TRUE(opus); std::optional red = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::AUDIO, "red"); ASSERT_TRUE(red); RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kSendOnly; RtpEncodingParameters encoding_parameters; encoding_parameters.codec = opus; init.send_encodings.push_back(encoding_parameters); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::AUDIO, init); ASSERT_TRUE(transceiver_or_error.ok()); scoped_refptr audio_transceiver = transceiver_or_error.MoveValue(); // Preferring RED over Opus should enable RED with Opus encoding. send_codecs[0] = red.value(); send_codecs[1] = opus.value(); ASSERT_TRUE(audio_transceiver->SetCodecPreferences(send_codecs).ok()); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); RtpParameters parameters = audio_transceiver->sender()->GetParameters(); EXPECT_EQ(parameters.encodings[0].codec, opus); EXPECT_EQ(parameters.codecs[0].name, red->name); // Check that it's possible to switch back to Opus without RED. send_codecs[0] = opus.value(); send_codecs[1] = red.value(); ASSERT_TRUE(audio_transceiver->SetCodecPreferences(send_codecs).ok()); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); parameters = audio_transceiver->sender()->GetParameters(); EXPECT_EQ(parameters.encodings[0].codec, opus); EXPECT_EQ(parameters.codecs[0].name, opus->name); } TEST_F(PeerConnectionEncodingsIntegrationTest, SetParametersRejectsScalabilityModeForSelectedCodec) { scoped_refptr local_pc_wrapper = CreatePc(); std::optional vp8 = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp8"); ASSERT_TRUE(vp8); RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kSendOnly; RtpEncodingParameters encoding_parameters; encoding_parameters.codec = vp8; encoding_parameters.scalability_mode = "L1T3"; init.send_encodings.push_back(encoding_parameters); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO, init); ASSERT_TRUE(transceiver_or_error.ok()); scoped_refptr video_transceiver = transceiver_or_error.MoveValue(); RtpParameters parameters = video_transceiver->sender()->GetParameters(); parameters.encodings[0].scalability_mode = "L3T3"; RTCError error = video_transceiver->sender()->SetParameters(parameters); EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION); } TEST_F(PeerConnectionEncodingsIntegrationTest, EncodingParametersCodecRemovedByNegotiationVideo) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::optional vp8 = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp8"); ASSERT_TRUE(vp8); std::vector not_vp8_codecs = local_pc_wrapper->pc_factory() ->GetRtpSenderCapabilities(MediaType::VIDEO) .codecs; std::erase_if(not_vp8_codecs, [&](const auto& codec) { return absl::EqualsIgnoreCase(codec.name, vp8->name); }); RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kSendOnly; RtpEncodingParameters encoding_parameters; encoding_parameters.rid = "h"; encoding_parameters.codec = vp8; encoding_parameters.scale_resolution_down_by = 2; init.send_encodings.push_back(encoding_parameters); encoding_parameters.rid = "f"; encoding_parameters.scale_resolution_down_by = 1; init.send_encodings.push_back(encoding_parameters); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO, init); ASSERT_TRUE(transceiver_or_error.ok()); scoped_refptr video_transceiver = transceiver_or_error.MoveValue(); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); RtpParameters parameters = video_transceiver->sender()->GetParameters(); ASSERT_EQ(parameters.encodings.size(), 2u); EXPECT_EQ(parameters.encodings[0].codec, vp8); EXPECT_EQ(parameters.encodings[1].codec, vp8); ASSERT_TRUE(video_transceiver->SetCodecPreferences(not_vp8_codecs).ok()); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); parameters = video_transceiver->sender()->GetParameters(); EXPECT_FALSE(parameters.encodings[0].codec); EXPECT_FALSE(parameters.encodings[1].codec); } TEST_F(PeerConnectionEncodingsIntegrationTest, AddTransceiverRejectsMixedCodecSimulcast) { // Mixed Codec Simulcast is not yet supported, so we ensure that we reject // such parameters. scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::optional vp8 = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp8"); ASSERT_TRUE(vp8); std::optional vp9 = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp9"); ASSERT_TRUE(vp9); RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kSendOnly; RtpEncodingParameters encoding_parameters; encoding_parameters.rid = "h"; encoding_parameters.codec = vp8; encoding_parameters.scale_resolution_down_by = 2; init.send_encodings.push_back(encoding_parameters); encoding_parameters.rid = "f"; encoding_parameters.codec = vp9; encoding_parameters.scale_resolution_down_by = 1; init.send_encodings.push_back(encoding_parameters); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO, init); ASSERT_FALSE(transceiver_or_error.ok()); EXPECT_EQ(transceiver_or_error.error().type(), RTCErrorType::UNSUPPORTED_OPERATION); } TEST_F(PeerConnectionEncodingsIntegrationTest, AddTransceiverAcceptsMixedCodecSimulcast) { // Enable WIP mixed codec simulcast support std::string field_trials = "WebRTC-MixedCodecSimulcast/Enabled/"; scoped_refptr local_pc_wrapper = CreatePc(field_trials); scoped_refptr remote_pc_wrapper = CreatePc(field_trials); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::optional vp8 = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp8"); ASSERT_TRUE(vp8); std::optional vp9 = local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp9"); ASSERT_TRUE(vp9); RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kSendOnly; RtpEncodingParameters encoding_parameters; encoding_parameters.rid = "h"; encoding_parameters.codec = vp8; encoding_parameters.scale_resolution_down_by = 2; init.send_encodings.push_back(encoding_parameters); encoding_parameters.rid = "f"; encoding_parameters.codec = vp9; encoding_parameters.scale_resolution_down_by = 1; init.send_encodings.push_back(encoding_parameters); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO, init); ASSERT_TRUE(transceiver_or_error.ok()); } TEST_F(PeerConnectionEncodingsIntegrationTest, ScaleToParameterChecking) { scoped_refptr pc_wrapper = CreatePc(); // AddTransceiver: If `scale_resolution_down_to` is specified on any encoding // it must be specified on all encodings. RtpTransceiverInit init; RtpEncodingParameters encoding; encoding.scale_resolution_down_to = std::nullopt; init.send_encodings.push_back(encoding); encoding.scale_resolution_down_to = {.width = 1280, .height = 720}; init.send_encodings.push_back(encoding); auto transceiver_or_error = pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO, init); EXPECT_FALSE(transceiver_or_error.ok()); EXPECT_EQ(transceiver_or_error.error().type(), RTCErrorType::UNSUPPORTED_OPERATION); // AddTransceiver: Width and height must not be zero. init.send_encodings[0].scale_resolution_down_to = {.width = 1280, .height = 0}; init.send_encodings[1].scale_resolution_down_to = {.width = 0, .height = 720}; transceiver_or_error = pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO, init); EXPECT_FALSE(transceiver_or_error.ok()); EXPECT_EQ(transceiver_or_error.error().type(), RTCErrorType::UNSUPPORTED_OPERATION); // AddTransceiver: Specifying both `scale_resolution_down_to` and // `scale_resolution_down_by` is allowed (the latter is ignored). init.send_encodings[0].scale_resolution_down_to = {.width = 640, .height = 480}; init.send_encodings[0].scale_resolution_down_by = 1.0; init.send_encodings[1].scale_resolution_down_to = {.width = 1280, .height = 720}; init.send_encodings[1].scale_resolution_down_by = 2.0; transceiver_or_error = pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO, init); ASSERT_TRUE(transceiver_or_error.ok()); // SetParameters: If `scale_resolution_down_to` is specified on any active // encoding it must be specified on all active encodings. auto sender = transceiver_or_error.value()->sender(); auto parameters = sender->GetParameters(); parameters.encodings[0].scale_resolution_down_to = {.width = 640, .height = 480}; parameters.encodings[1].scale_resolution_down_to = std::nullopt; auto error = sender->SetParameters(parameters); EXPECT_FALSE(error.ok()); EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION); // But it's OK not to specify `scale_resolution_down_to` on an inactive // encoding. parameters = sender->GetParameters(); parameters.encodings[0].scale_resolution_down_to = {.width = 640, .height = 480}; parameters.encodings[1].active = false; parameters.encodings[1].scale_resolution_down_to = std::nullopt; error = sender->SetParameters(parameters); EXPECT_TRUE(error.ok()); // SetParameters: Width and height must not be zero. sender = transceiver_or_error.value()->sender(); parameters = sender->GetParameters(); parameters.encodings[0].scale_resolution_down_to = {.width = 1280, .height = 0}; parameters.encodings[1].active = true; parameters.encodings[1].scale_resolution_down_to = {.width = 0, .height = 720}; error = sender->SetParameters(parameters); EXPECT_FALSE(error.ok()); EXPECT_EQ(error.type(), RTCErrorType::INVALID_MODIFICATION); // SetParameters: Specifying both `scale_resolution_down_to` and // `scale_resolution_down_by` is allowed (the latter is ignored). parameters = sender->GetParameters(); parameters.encodings[0].scale_resolution_down_to = {.width = 640, .height = 480}; parameters.encodings[0].scale_resolution_down_by = 2.0; parameters.encodings[1].scale_resolution_down_to = {.width = 1280, .height = 720}; parameters.encodings[1].scale_resolution_down_by = 1.0; error = sender->SetParameters(parameters); EXPECT_TRUE(error.ok()); } TEST_F(PeerConnectionEncodingsIntegrationTest, ScaleResolutionDownByIsIgnoredWhenScaleToIsSpecified) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); scoped_refptr stream = local_pc_wrapper->GetUserMedia( /*audio=*/false, {}, /*video=*/true, {.width = 640, .height = 360}); scoped_refptr track = stream->GetVideoTracks()[0]; // Configure contradicting scaling factors (180p vs 360p). RtpTransceiverInit init; RtpEncodingParameters encoding; encoding.scale_resolution_down_by = 2.0; encoding.scale_resolution_down_to = {.width = 640, .height = 360}; init.send_encodings.push_back(encoding); auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track, init); // Negotiate singlecast. ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); Negotiate(local_pc_wrapper, remote_pc_wrapper); // Confirm 640x360 is sent. // If `scale_resolution_down_by` was not ignored we would never ramp up to // full resolution. EXPECT_THAT( GetStatsUntil(local_pc_wrapper, OutboundRtpStatsAre(ElementsAre(ResolutionIs(640, 360))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); } // Tests that use the standard path (specifying both `scalability_mode` and // `scale_resolution_down_by` or `scale_resolution_down_to`) should pass for all // codecs. class PeerConnectionEncodingsIntegrationParameterizedTest : public PeerConnectionEncodingsIntegrationTest, public ::testing::WithParamInterface { public: PeerConnectionEncodingsIntegrationParameterizedTest() : codec_name_(GetParam()), mime_type_("video/" + codec_name_) {} // Work-around for the fact that whether or not AV1 is supported is not known // at compile-time so we have to skip tests early if missing. // TODO(https://crbug.com/webrtc/15011): Increase availability of AV1 or make // it possible to check support at compile-time. bool SkipTestDueToAv1Missing( scoped_refptr local_pc_wrapper) { if (codec_name_ == "AV1" && !HasReceiverVideoCodecCapability(local_pc_wrapper, "AV1")) { RTC_LOG(LS_WARNING) << "\n***\nAV1 is not available, skipping test.\n***"; return true; } return false; } protected: const std::string codec_name_; // E.g. "VP9" const std::string mime_type_; // E.g. "video/VP9" }; TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest, AllLayersInactive) { scoped_refptr local_pc_wrapper = CreatePc(); if (SkipTestDueToAv1Missing(local_pc_wrapper)) { return; } scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f", "h", "q"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_); transceiver->SetCodecPreferences(codecs); // Standard mode and all layers inactive. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); parameters.encodings[0].scalability_mode = "L1T3"; parameters.encodings[0].scale_resolution_down_by = 1; parameters.encodings[0].active = false; parameters.encodings[1].active = false; parameters.encodings[2].active = false; sender->SetParameters(parameters); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Ensure no media is flowing (1 second should be enough). Thread::Current()->SleepMs(1000); scoped_refptr report = GetStats(local_pc_wrapper); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(3u)); EXPECT_EQ(*outbound_rtps[0]->bytes_sent, 0u); EXPECT_EQ(*outbound_rtps[1]->bytes_sent, 0u); EXPECT_EQ(*outbound_rtps[2]->bytes_sent, 0u); } // Configure 4:2:1 using `scale_resolution_down_by`. TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest, Simulcast) { scoped_refptr local_pc_wrapper = CreatePc(); if (SkipTestDueToAv1Missing(local_pc_wrapper)) { return; } scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"q", "h", "f"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_); transceiver->SetCodecPreferences(codecs); scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); parameters.encodings[0].scalability_mode = "L1T3"; parameters.encodings[0].scale_resolution_down_by = 4; parameters.encodings[1].scalability_mode = "L1T3"; parameters.encodings[1].scale_resolution_down_by = 2; parameters.encodings[2].scalability_mode = "L1T3"; parameters.encodings[2].scale_resolution_down_by = 1; sender->SetParameters(parameters); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // GetParameters() does not report any fallback. parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); EXPECT_TRUE(parameters.encodings[0].scalability_mode.has_value()); EXPECT_THAT(parameters.encodings[0].scalability_mode, Optional(std::string("L1T3"))); EXPECT_TRUE(parameters.encodings[1].scalability_mode.has_value()); EXPECT_THAT(parameters.encodings[1].scalability_mode, Optional(std::string("L1T3"))); EXPECT_TRUE(parameters.encodings[2].scalability_mode.has_value()); EXPECT_THAT(parameters.encodings[2].scalability_mode, Optional(std::string("L1T3"))); // Wait until media is flowing on all three layers. // Ramp up time is needed before all three layers are sending. auto error_or_report = GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(3u), {.timeout = kLongTimeoutForRampingUp}); ASSERT_THAT(error_or_report, IsRtcOk()); // Verify codec and scalability mode. scoped_refptr report = error_or_report.value(); auto outbound_rtp_by_rid = GetOutboundRtpStreamStatsByRid(report); EXPECT_THAT(outbound_rtp_by_rid, UnorderedElementsAre(Pair("q", ResolutionIs(320, 180)), Pair("h", ResolutionIs(640, 360)), Pair("f", ResolutionIs(1280, 720)))); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(3u)); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]), StrCaseEq(mime_type_)); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[1]), StrCaseEq(mime_type_)); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[2]), StrCaseEq(mime_type_)); EXPECT_THAT(*outbound_rtps[0]->scalability_mode, StrEq("L1T3")); EXPECT_THAT(*outbound_rtps[1]->scalability_mode, StrEq("L1T3")); EXPECT_THAT(*outbound_rtps[2]->scalability_mode, StrEq("L1T3")); } // Configure 4:2:1 using `scale_resolution_down_to`. TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest, SimulcastWithScaleTo) { scoped_refptr local_pc_wrapper = CreatePc(); if (SkipTestDueToAv1Missing(local_pc_wrapper)) { return; } scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"q", "h", "f"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_); transceiver->SetCodecPreferences(codecs); scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); parameters.encodings[0].scalability_mode = "L1T3"; parameters.encodings[0].scale_resolution_down_to = {.width = 320, .height = 180}; parameters.encodings[1].scalability_mode = "L1T3"; parameters.encodings[1].scale_resolution_down_to = {.width = 640, .height = 360}; parameters.encodings[2].scalability_mode = "L1T3"; parameters.encodings[2].scale_resolution_down_to = {.width = 1280, .height = 720}; sender->SetParameters(parameters); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // GetParameters() does not report any fallback. parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); EXPECT_THAT(parameters.encodings[0].scalability_mode, Optional(std::string("L1T3"))); EXPECT_THAT(parameters.encodings[1].scalability_mode, Optional(std::string("L1T3"))); EXPECT_THAT(parameters.encodings[2].scalability_mode, Optional(std::string("L1T3"))); // Wait until media is flowing on all three layers. // Ramp up time is needed before all three layers are sending. auto error_or_report = GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(3u), {.timeout = kLongTimeoutForRampingUp}); ASSERT_THAT(error_or_report, IsRtcOk()); // Verify codec and scalability mode. scoped_refptr report = error_or_report.value(); auto outbound_rtp_by_rid = GetOutboundRtpStreamStatsByRid(report); EXPECT_THAT(outbound_rtp_by_rid, UnorderedElementsAre(Pair("q", ResolutionIs(320, 180)), Pair("h", ResolutionIs(640, 360)), Pair("f", ResolutionIs(1280, 720)))); // Verify codec and scalability mode. std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(3u)); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]), StrCaseEq(mime_type_)); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[1]), StrCaseEq(mime_type_)); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[2]), StrCaseEq(mime_type_)); EXPECT_THAT(*outbound_rtps[0]->scalability_mode, StrEq("L1T3")); EXPECT_THAT(*outbound_rtps[1]->scalability_mode, StrEq("L1T3")); EXPECT_THAT(*outbound_rtps[2]->scalability_mode, StrEq("L1T3")); } // Simulcast starting in 720p 4:2:1 then changing to {180p, 360p, 540p} using // the `scale_resolution_down_by` API. TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest, SimulcastScaleDownByNoLongerPowerOfTwo) { scoped_refptr local_pc_wrapper = CreatePc(); if (SkipTestDueToAv1Missing(local_pc_wrapper)) { return; } scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"q", "h", "f"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_); transceiver->SetCodecPreferences(codecs); scoped_refptr sender = transceiver->sender(); // Configure {180p, 360p, 720p}. RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); parameters.encodings[0].scalability_mode = "L1T1"; parameters.encodings[0].scale_resolution_down_by = 4.0; parameters.encodings[1].scalability_mode = "L1T1"; parameters.encodings[1].scale_resolution_down_by = 2.0; parameters.encodings[2].scalability_mode = "L1T1"; parameters.encodings[2].scale_resolution_down_by = 1.0; sender->SetParameters(parameters); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Wait for media to flow on all layers. // Needed repro step of https://crbug.com/webrtc/369654168: When the same // LibvpxVp9Encoder instance was used to first produce simulcast and later for // a single encoding, the previously used simulcast index (= 2) would still be // set when producing 180p since non-simulcast config does not reset this, // resulting in the 180p encoding freezing and the 540p encoding having double // frame rate and toggling between 180p and 540p in resolution. ASSERT_THAT(GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(3u), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); // Configure {180p, 360p, 540p}. parameters = sender->GetParameters(); parameters.encodings[0].scale_resolution_down_by = 4.0; parameters.encodings[1].scale_resolution_down_by = 2.0; parameters.encodings[2].scale_resolution_down_by = 1.333333; sender->SetParameters(parameters); // Wait for the new resolutions to be produced. auto encoding_resolutions_result = WaitUntil([&] { return GetStats(local_pc_wrapper); }, OutboundRtpStatsAre(UnorderedElementsAre( AllOf(RidIs("q"), ResolutionIs(320, 180)), AllOf(RidIs("h"), ResolutionIs(640, 360)), AllOf(RidIs("f"), ResolutionIs(960, 540)))), {.timeout = kLongTimeoutForRampingUp}); ASSERT_THAT(encoding_resolutions_result, IsRtcOk()); auto outbound_rtp_by_rid = GetOutboundRtpStreamStatsByRid(encoding_resolutions_result.value()); ASSERT_THAT(outbound_rtp_by_rid, UnorderedElementsAre(Key("q"), Key("h"), Key("f"))); // Ensure frames continue to be encoded post reconfiguration. uint64_t frames_encoded_q = outbound_rtp_by_rid.at("q").frames_encoded.value(); uint64_t frames_encoded_h = outbound_rtp_by_rid.at("h").frames_encoded.value(); uint64_t frames_encoded_f = outbound_rtp_by_rid.at("f").frames_encoded.value(); EXPECT_THAT( GetStatsUntil( local_pc_wrapper, OutboundRtpStatsAre(UnorderedElementsAre( AllOf(RidIs("q"), FramesEncodedIs(Gt(frames_encoded_q))), AllOf(RidIs("h"), FramesEncodedIs(Gt(frames_encoded_h))), AllOf(RidIs("f"), FramesEncodedIs(Gt(frames_encoded_f))))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); } // Simulcast starting in 720p 4:2:1 then changing to {180p, 360p, 540p} using // the `scale_resolution_down_to` API. TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest, SimulcastScaleToNoLongerPowerOfTwo) { scoped_refptr local_pc_wrapper = CreatePc(); if (SkipTestDueToAv1Missing(local_pc_wrapper)) { return; } scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"q", "h", "f"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_); transceiver->SetCodecPreferences(codecs); scoped_refptr sender = transceiver->sender(); // Configure {180p, 360p, 720p}. RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); parameters.encodings[0].scalability_mode = "L1T1"; parameters.encodings[0].scale_resolution_down_to = {.width = 320, .height = 180}; parameters.encodings[1].scalability_mode = "L1T1"; parameters.encodings[1].scale_resolution_down_to = {.width = 640, .height = 360}; parameters.encodings[2].scalability_mode = "L1T1"; parameters.encodings[2].scale_resolution_down_to = {.width = 1280, .height = 720}; sender->SetParameters(parameters); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Wait for media to flow on all layers. // Needed repro step of https://crbug.com/webrtc/369654168: When the same // LibvpxVp9Encoder instance was used to first produce simulcast and later for // a single encoding, the previously used simulcast index (= 2) would still be // set when producing 180p since non-simulcast config does not reset this, // resulting in the 180p encoding freezing and the 540p encoding having double // frame rate and toggling between 180p and 540p in resolution. ASSERT_THAT(GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(3u), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); // Configure {180p, 360p, 540p}. parameters = sender->GetParameters(); parameters.encodings[0].scale_resolution_down_to = {.width = 320, .height = 180}; parameters.encodings[1].scale_resolution_down_to = {.width = 640, .height = 360}; parameters.encodings[2].scale_resolution_down_to = {.width = 960, .height = 540}; sender->SetParameters(parameters); // Wait for the new resolutions to be produced. auto error_or_stats = GetStatsUntil(local_pc_wrapper, OutboundRtpStatsAre(UnorderedElementsAre( AllOf(RidIs("q"), ResolutionIs(320, 180)), AllOf(RidIs("h"), ResolutionIs(640, 360)), AllOf(RidIs("f"), ResolutionIs(960, 540)))), {.timeout = kLongTimeoutForRampingUp}); ASSERT_THAT(error_or_stats, IsRtcOk()); auto outbound_rtp_by_rid = GetOutboundRtpStreamStatsByRid(error_or_stats.value()); ASSERT_THAT(outbound_rtp_by_rid, UnorderedElementsAre(Pair("q", BytesSentIs(Ne(std::nullopt))), Pair("h", BytesSentIs(Ne(std::nullopt))), Pair("f", BytesSentIs(Ne(std::nullopt))))); // Ensure frames continue to be encoded post reconfiguration. EXPECT_THAT( GetStatsUntil( local_pc_wrapper, OutboundRtpStatsAre(UnorderedElementsAre( AllOf(RidIs("q"), BytesSentIs( Gt(outbound_rtp_by_rid.at("q").bytes_sent.value()))), AllOf(RidIs("h"), BytesSentIs( Gt(outbound_rtp_by_rid.at("h").bytes_sent.value()))), AllOf(RidIs("f"), BytesSentIs( Gt(outbound_rtp_by_rid.at("f").bytes_sent.value()))))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); } // The code path that disables layers based on resolution size should NOT run // when `scale_resolution_down_to` is specified. (It shouldn't run in any case // but that is an existing legacy code and non-compliance problem that we don't // have to repeat here.) TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest, LowResolutionSimulcastWithScaleTo) { scoped_refptr local_pc_wrapper = CreatePc(); if (SkipTestDueToAv1Missing(local_pc_wrapper)) { return; } scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"q", "h", "f"}, /*active=*/true); // Configure {20p,40p,80p} with 2:1 aspect ratio. RtpTransceiverInit init; RtpEncodingParameters encoding; encoding.scalability_mode = "L1T3"; encoding.rid = "q"; encoding.scale_resolution_down_to = {.width = 40, .height = 20}; init.send_encodings.push_back(encoding); encoding.rid = "h"; encoding.scale_resolution_down_to = {.width = 80, .height = 40}; init.send_encodings.push_back(encoding); encoding.rid = "f"; encoding.scale_resolution_down_to = {.width = 160, .height = 80}; init.send_encodings.push_back(encoding); scoped_refptr stream = local_pc_wrapper->GetUserMedia( /*audio=*/false, {}, /*video=*/true, {.width = 160, .height = 80}); scoped_refptr track = stream->GetVideoTracks()[0]; auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track, init); ASSERT_TRUE(transceiver_or_error.ok()); scoped_refptr transceiver = transceiver_or_error.value(); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_); transceiver->SetCodecPreferences(codecs); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Wait for media to flow on all layers. ASSERT_THAT(GetStatsUntil(local_pc_wrapper, HasOutboundRtpBytesSent(3u)), IsRtcOk()); // q=20p, h=40p, f=80p. EXPECT_THAT(GetStatsUntil(local_pc_wrapper, OutboundRtpStatsAre(UnorderedElementsAre( AllOf(RidIs("q"), ResolutionIs(40, 20)), AllOf(RidIs("h"), ResolutionIs(80, 40)), AllOf(RidIs("f"), ResolutionIs(160, 80)))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); } TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest, SimulcastEncodingStopWhenRtpEncodingChangeToInactive) { scoped_refptr local_pc_wrapper = CreatePc(); if (SkipTestDueToAv1Missing(local_pc_wrapper)) { return; } scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"q", "h", "f"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, codec_name_); transceiver->SetCodecPreferences(codecs); scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); ASSERT_EQ(parameters.encodings[0].rid, "q"); parameters.encodings[0].scalability_mode = "L1T3"; parameters.encodings[0].scale_resolution_down_by = 4; ASSERT_EQ(parameters.encodings[1].rid, "h"); parameters.encodings[1].scalability_mode = "L1T3"; parameters.encodings[1].scale_resolution_down_by = 2; ASSERT_EQ(parameters.encodings[2].rid, "f"); parameters.encodings[2].scalability_mode = "L1T3"; parameters.encodings[2].scale_resolution_down_by = 1; sender->SetParameters(parameters); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); ASSERT_THAT(GetStatsUntil(local_pc_wrapper, OutboundRtpStatsAre(Contains( AllOf(RidIs("f"), FramesEncodedIs(Gt(0))))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); // Switch higest layer to Inactive. parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(3)); parameters.encodings[2].active = false; sender->SetParameters(parameters); auto error_or_stats = GetStatsUntil( local_pc_wrapper, OutboundRtpStatsAre(Contains(AllOf(RidIs("f"), Not(Active())))), {.timeout = kLongTimeoutForRampingUp}); ASSERT_THAT(error_or_stats, IsRtcOk()); auto outbound_rtp_by_rid = GetOutboundRtpStreamStatsByRid(error_or_stats.value()); int encoded_frames_f = outbound_rtp_by_rid.at("f").frames_encoded.value(); int encoded_frames_h = outbound_rtp_by_rid.at("h").frames_encoded.value(); int encoded_frames_q = outbound_rtp_by_rid.at("q").frames_encoded.value(); // Wait until the encoder has encoded another 10 frames on lower layers. ASSERT_THAT( GetStatsUntil( local_pc_wrapper, OutboundRtpStatsAre(UnorderedElementsAre( AllOf(RidIs("q"), FramesEncodedIs(Gt(encoded_frames_q + 10))), AllOf(RidIs("h"), FramesEncodedIs(Gt(encoded_frames_h + 10))), AllOf(RidIs("f"), FramesEncodedIs(Le(encoded_frames_f + 2))))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); } TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest, ScaleToDownscaleAndThenUpscale) { scoped_refptr local_pc_wrapper = CreatePc(); if (SkipTestDueToAv1Missing(local_pc_wrapper)) { return; } scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f"}, /*active=*/true); // This transceiver receives a 1280x720 source. scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_); transceiver->SetCodecPreferences(codecs); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Request 640x360, which is the same as scaling down by 2. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(1)); parameters.encodings[0].scalability_mode = "L1T3"; parameters.encodings[0].scale_resolution_down_to = {.width = 640, .height = 360}; sender->SetParameters(parameters); // Confirm 640x360 is sent. ASSERT_THAT( GetStatsUntil(local_pc_wrapper, OutboundRtpStatsAre(ElementsAre(ResolutionIs(640, 360))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); // Test coverage for https://crbug.com/webrtc/361477261: // Due initial frame dropping, OnFrameDroppedDueToSize() should have created // some resolution restrictions by now. With 720p input frame, restriction is // 540p which is not observable when sending 360p, but it prevents us from // immediately sending 720p. Restrictions will be lifted after a few seconds // (when good QP is reported by QualityScaler) and 720p should be sent. The // bug was not reconfiguring the encoder when restrictions were updated so the // restrictions at the time of the SetParameter() call were made indefinite. // Request the full 1280x720 resolution. parameters = sender->GetParameters(); parameters.encodings[0].scale_resolution_down_to = {.width = 1280, .height = 720}; sender->SetParameters(parameters); // Confirm 1280x720 is sent. EXPECT_THAT( GetStatsUntil(local_pc_wrapper, OutboundRtpStatsAre(ElementsAre(ResolutionIs(1280, 720))), {.timeout = kLongTimeoutForRampingUp}), IsRtcOk()); } TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest, ScaleToIsOrientationAgnostic) { scoped_refptr local_pc_wrapper = CreatePc(); if (SkipTestDueToAv1Missing(local_pc_wrapper)) { return; } scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f"}, /*active=*/true); // This transceiver receives a 1280x720 source. scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_); transceiver->SetCodecPreferences(codecs); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // 360x640 is the same as 640x360 due to orientation agnosticism. // The orientation is determined by the frame (1280x720): landscape. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(1)); parameters.encodings[0].scale_resolution_down_to = {.width = 360, .height = 640}; sender->SetParameters(parameters); // Confirm 640x360 is sent. EXPECT_THAT( GetStatsUntil(local_pc_wrapper, OutboundRtpStatsAre(ElementsAre(ResolutionIs(640, 360)))), IsRtcOk()); } TEST_P(PeerConnectionEncodingsIntegrationParameterizedTest, ScaleToMaintainsAspectRatio) { scoped_refptr local_pc_wrapper = CreatePc(); if (SkipTestDueToAv1Missing(local_pc_wrapper)) { return; } scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f"}, /*active=*/true); // This transceiver receives a 1280x720 source. scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector codecs = GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_); transceiver->SetCodecPreferences(codecs); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Restrict height more than width, the scaling factor needed on height should // also be applied on the width in order to maintain the frame aspect ratio. scoped_refptr sender = transceiver->sender(); RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(1)); parameters.encodings[0].scale_resolution_down_to = {.width = 1280, .height = 360}; sender->SetParameters(parameters); // Confirm 640x360 is sent. EXPECT_THAT( GetStatsUntil(local_pc_wrapper, OutboundRtpStatsAre(ElementsAre(ResolutionIs(640, 360)))), IsRtcOk()); } INSTANTIATE_TEST_SUITE_P(StandardPath, PeerConnectionEncodingsIntegrationParameterizedTest, ::testing::Values("VP8", "VP9", #if defined(WEBRTC_USE_H264) "H264", #endif // defined(WEBRTC_USE_H264) "AV1"), StringParamToString()); // These tests use fake encoders and decoders, allowing testing of codec // preferences, SDP negotiation and get/setParamaters(). But because the codecs // implementations are fake, these tests do not encode or decode any frames. class PeerConnectionEncodingsFakeCodecsIntegrationTest : public PeerConnectionEncodingsIntegrationTest { public: #ifdef RTC_ENABLE_H265 scoped_refptr CreatePcWithFakeH265( std::unique_ptr field_trials = nullptr) { std::unique_ptr video_encoder_factory = std::make_unique(); video_encoder_factory->AddSupportedVideoCodec( SdpVideoFormat("H265", {{"profile-id", "1"}, {"tier-flag", "0"}, {"level-id", "156"}, {"tx-mode", "SRST"}}, {ScalabilityMode::kL1T1})); std::unique_ptr video_decoder_factory = std::make_unique(); video_decoder_factory->AddSupportedVideoCodecType("H265"); auto pc_wrapper = make_ref_counted( "pc", env_, &pss_, background_thread_.get(), background_thread_.get()); pc_wrapper->CreatePc( {}, CreateBuiltinAudioEncoderFactory(), CreateBuiltinAudioDecoderFactory(), std::move(video_encoder_factory), std::move(video_decoder_factory), std::move(field_trials)); return pc_wrapper; } #endif // RTC_ENABLE_H265 // Creates a PC where we have H264 with one sendonly, one recvonly and one // sendrecv "profile-level-id". The sendrecv one is constrained baseline. scoped_refptr CreatePcWithUnidirectionalH264( std::unique_ptr field_trials = nullptr) { std::unique_ptr video_encoder_factory = std::make_unique(); SdpVideoFormat h264_constrained_baseline = SdpVideoFormat("H264", {{"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, {"profile-level-id", "42f00b"}}, // sendrecv {ScalabilityMode::kL1T1}); video_encoder_factory->AddSupportedVideoCodec(h264_constrained_baseline); video_encoder_factory->AddSupportedVideoCodec( SdpVideoFormat("H264", {{"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, {"profile-level-id", "640034"}}, // sendonly {ScalabilityMode::kL1T1})); std::unique_ptr video_decoder_factory = std::make_unique(); video_decoder_factory->AddSupportedVideoCodec(h264_constrained_baseline); video_decoder_factory->AddSupportedVideoCodec( SdpVideoFormat("H264", {{"level-asymmetry-allowed", "1"}, {"packetization-mode", "1"}, {"profile-level-id", "f4001f"}}, // recvonly {ScalabilityMode::kL1T1})); auto pc_wrapper = make_ref_counted( "pc", env_, &pss_, background_thread_.get(), background_thread_.get()); pc_wrapper->CreatePc( {}, CreateBuiltinAudioEncoderFactory(), CreateBuiltinAudioDecoderFactory(), std::move(video_encoder_factory), std::move(video_decoder_factory), std::move(field_trials)); return pc_wrapper; } std::string LocalDescriptionStr(PeerConnectionTestWrapper* pc_wrapper) { const SessionDescriptionInterface* local_description = pc_wrapper->pc()->local_description(); if (!local_description) { return ""; } std::string str; if (!local_description->ToString(&str)) { return ""; } return str; } }; #ifdef RTC_ENABLE_H265 TEST_F(PeerConnectionEncodingsFakeCodecsIntegrationTest, H265Singlecast) { scoped_refptr local_pc_wrapper = CreatePcWithFakeH265(); scoped_refptr remote_pc_wrapper = CreatePcWithFakeH265(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); scoped_refptr transceiver = local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO).MoveValue(); std::vector preferred_codecs = GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "H265"); transceiver->SetCodecPreferences(preferred_codecs); Negotiate(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Verify codec. scoped_refptr report = GetStats(local_pc_wrapper); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(1u)); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]), StrCaseEq("video/H265")); } TEST_F(PeerConnectionEncodingsFakeCodecsIntegrationTest, H265Simulcast) { scoped_refptr local_pc_wrapper = CreatePcWithFakeH265(); scoped_refptr remote_pc_wrapper = CreatePcWithFakeH265(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"q", "h", "f"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector preferred_codecs = GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "H265"); transceiver->SetCodecPreferences(preferred_codecs); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // Wait until all outbound RTPs exist. EXPECT_THAT( GetStatsUntil(local_pc_wrapper, OutboundRtpStatsAre(UnorderedElementsAre( AllOf(RidIs("q")), AllOf(RidIs("h")), AllOf(RidIs("f"))))), IsRtcOk()); // Verify codec. scoped_refptr report = GetStats(local_pc_wrapper); std::vector outbound_rtps = report->GetStatsOfType(); ASSERT_THAT(outbound_rtps, SizeIs(3u)); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]), StrCaseEq("video/H265")); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[1]), StrCaseEq("video/H265")); EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[2]), StrCaseEq("video/H265")); } TEST_F(PeerConnectionEncodingsFakeCodecsIntegrationTest, H265SetParametersIgnoresLevelId) { scoped_refptr local_pc_wrapper = CreatePcWithFakeH265(); scoped_refptr remote_pc_wrapper = CreatePcWithFakeH265(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); std::vector preferred_codecs = GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "H265"); transceiver->SetCodecPreferences(preferred_codecs); scoped_refptr sender = transceiver->sender(); NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); local_pc_wrapper->WaitForConnection(); remote_pc_wrapper->WaitForConnection(); // This includes non-codecs like rtx, red and flexfec too so we need to find // H265. std::vector sender_codecs = local_pc_wrapper->pc_factory() ->GetRtpSenderCapabilities(MediaType::VIDEO) .codecs; auto it = std::find_if(sender_codecs.begin(), sender_codecs.end(), [](const RtpCodecCapability codec_capability) { return codec_capability.name == "H265"; }); ASSERT_NE(it, sender_codecs.end()); RtpCodecCapability& h265_codec = *it; // SetParameters() without changing level-id. EXPECT_EQ(h265_codec.parameters["level-id"], "156"); { RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(1)); parameters.encodings[0].codec = h265_codec; ASSERT_THAT(sender->SetParameters(parameters), IsRtcOk()); } // SetParameters() with a lower level-id. h265_codec.parameters["level-id"] = "30"; { RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(1)); parameters.encodings[0].codec = h265_codec; ASSERT_THAT(sender->SetParameters(parameters), IsRtcOk()); } // SetParameters() with a higher level-id. h265_codec.parameters["level-id"] = "180"; { RtpParameters parameters = sender->GetParameters(); ASSERT_THAT(parameters.encodings, SizeIs(1)); parameters.encodings[0].codec = h265_codec; ASSERT_THAT(sender->SetParameters(parameters), IsRtcOk()); } } #endif // RTC_ENABLE_H265 TEST_F(PeerConnectionEncodingsFakeCodecsIntegrationTest, H264UnidirectionalNegotiation) { scoped_refptr local_pc_wrapper = CreatePcWithUnidirectionalH264(); scoped_refptr remote_pc_wrapper = CreatePcWithUnidirectionalH264(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); scoped_refptr transceiver = local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO).MoveValue(); // Filter on codec name and assert that sender capabilities have codecs for // {sendrecv, sendonly} and the receiver capabilities have codecs for // {sendrecv, recvonly}. std::vector send_codecs = local_pc_wrapper->pc_factory() ->GetRtpSenderCapabilities(MediaType::VIDEO) .codecs; std::erase_if(send_codecs, [](const RtpCodecCapability& codec) { return codec.name != "H264"; }); std::vector recv_codecs = local_pc_wrapper->pc_factory() ->GetRtpReceiverCapabilities(MediaType::VIDEO) .codecs; std::erase_if(recv_codecs, [](const RtpCodecCapability& codec) { RTC_LOG(LS_ERROR) << codec.name; return codec.name != "H264"; }); ASSERT_THAT(send_codecs, SizeIs(2u)); ASSERT_THAT(recv_codecs, SizeIs(2u)); EXPECT_EQ(send_codecs[0], recv_codecs[0]); EXPECT_NE(send_codecs[1], recv_codecs[1]); RtpCodecCapability& sendrecv_codec = send_codecs[0]; RtpCodecCapability& sendonly_codec = send_codecs[1]; RtpCodecCapability& recvonly_codec = recv_codecs[1]; // Preferring sendonly + recvonly on a sendrecv transceiver is the same as // not having any preferences, meaning the sendrecv codec (not listed) is the // one being negotiated. std::vector preferred_codecs = {sendonly_codec, recvonly_codec}; EXPECT_THAT(transceiver->SetCodecPreferences(preferred_codecs), IsRtcOk()); EXPECT_THAT( transceiver->SetDirectionWithError(RtpTransceiverDirection::kSendRecv), IsRtcOk()); Negotiate(local_pc_wrapper, remote_pc_wrapper); std::string local_sdp = LocalDescriptionStr(local_pc_wrapper.get()); EXPECT_THAT(local_sdp, HasSubstr(sendrecv_codec.parameters["profile-level-id"])); EXPECT_THAT(local_sdp, Not(HasSubstr(sendonly_codec.parameters["profile-level-id"]))); EXPECT_THAT(local_sdp, Not(HasSubstr(recvonly_codec.parameters["profile-level-id"]))); // Prefer all codecs and expect that the SDP offer contains the relevant // codecs after filtering. Complete O/A each time. preferred_codecs = {sendrecv_codec, sendonly_codec, recvonly_codec}; EXPECT_THAT(transceiver->SetCodecPreferences(preferred_codecs), IsRtcOk()); // Transceiver direction: sendrecv. EXPECT_THAT( transceiver->SetDirectionWithError(RtpTransceiverDirection::kSendRecv), IsRtcOk()); Negotiate(local_pc_wrapper, remote_pc_wrapper); local_sdp = LocalDescriptionStr(local_pc_wrapper.get()); EXPECT_THAT(local_sdp, HasSubstr(sendrecv_codec.parameters["profile-level-id"])); EXPECT_THAT(local_sdp, Not(HasSubstr(sendonly_codec.parameters["profile-level-id"]))); EXPECT_THAT(local_sdp, Not(HasSubstr(recvonly_codec.parameters["profile-level-id"]))); // Transceiver direction: sendonly. EXPECT_THAT( transceiver->SetDirectionWithError(RtpTransceiverDirection::kSendOnly), IsRtcOk()); Negotiate(local_pc_wrapper, remote_pc_wrapper); local_sdp = LocalDescriptionStr(local_pc_wrapper.get()); EXPECT_THAT(local_sdp, HasSubstr(sendrecv_codec.parameters["profile-level-id"])); EXPECT_THAT(local_sdp, HasSubstr(sendonly_codec.parameters["profile-level-id"])); EXPECT_THAT(local_sdp, Not(HasSubstr(recvonly_codec.parameters["profile-level-id"]))); // Transceiver direction: recvonly. EXPECT_THAT( transceiver->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly), IsRtcOk()); Negotiate(local_pc_wrapper, remote_pc_wrapper); local_sdp = LocalDescriptionStr(local_pc_wrapper.get()); EXPECT_THAT(local_sdp, HasSubstr(sendrecv_codec.parameters["profile-level-id"])); EXPECT_THAT(local_sdp, Not(HasSubstr(sendonly_codec.parameters["profile-level-id"]))); EXPECT_THAT(local_sdp, HasSubstr(recvonly_codec.parameters["profile-level-id"])); // Test that offering a sendonly codec on a sendonly transceiver is possible. // - Note that we don't complete the negotiation this time because we're not // capable of receiving the codec. preferred_codecs = {sendonly_codec}; EXPECT_THAT(transceiver->SetCodecPreferences(preferred_codecs), IsRtcOk()); EXPECT_THAT( transceiver->SetDirectionWithError(RtpTransceiverDirection::kSendOnly), IsRtcOk()); std::unique_ptr offer = CreateOffer(local_pc_wrapper); EXPECT_TRUE(Await({SetLocalDescription(local_pc_wrapper, offer.get())})); local_sdp = LocalDescriptionStr(local_pc_wrapper.get()); EXPECT_THAT(local_sdp, Not(HasSubstr(sendrecv_codec.parameters["profile-level-id"]))); EXPECT_THAT(local_sdp, HasSubstr(sendonly_codec.parameters["profile-level-id"])); EXPECT_THAT(local_sdp, Not(HasSubstr(recvonly_codec.parameters["profile-level-id"]))); // Test that offering recvonly codec on a recvonly transceiver is possible. // - Note that we don't complete the negotiation this time because we're not // capable of sending the codec. preferred_codecs = {recvonly_codec}; EXPECT_THAT(transceiver->SetCodecPreferences(preferred_codecs), IsRtcOk()); EXPECT_THAT( transceiver->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly), IsRtcOk()); offer = CreateOffer(local_pc_wrapper); EXPECT_TRUE(Await({SetLocalDescription(local_pc_wrapper, offer.get())})); local_sdp = LocalDescriptionStr(local_pc_wrapper.get()); EXPECT_THAT(local_sdp, Not(HasSubstr(sendrecv_codec.parameters["profile-level-id"]))); EXPECT_THAT(local_sdp, Not(HasSubstr(sendonly_codec.parameters["profile-level-id"]))); EXPECT_THAT(local_sdp, HasSubstr(recvonly_codec.parameters["profile-level-id"])); } // Regression test for https://issues.chromium.org/issues/399667359 TEST_F(PeerConnectionEncodingsIntegrationTest, SimulcastNotSupportedGetParametersDoesNotCrash) { scoped_refptr local_pc_wrapper = CreatePc(); scoped_refptr remote_pc_wrapper = CreatePc(); ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); std::vector layers = CreateLayers({"f", "q"}, /*active=*/true); scoped_refptr transceiver = AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, layers); // Negotiate - receiver will reject simulcast, so the 2nd layer will be // disabled Negotiate(local_pc_wrapper, remote_pc_wrapper); // Negotiate again without simulcast. Negotiate(local_pc_wrapper, remote_pc_wrapper); RtpParameters parameters = transceiver->sender()->GetParameters(); EXPECT_TRUE(transceiver->sender()->SetParameters(parameters).ok()); } } // namespace webrtc