From da9e284308c451bc913febc23eebdc80f0f593c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Bostr=C3=B6m?= Date: Thu, 6 Apr 2023 15:27:33 +0200 Subject: [PATCH] Rename simulcast flow tests: PeerConnectionEncodingsIntegrationTest. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a pure rename+move of PeerConnectionSimulcastMediaFlowTests. The reason for renaming is to reflect that a) this is an integration test, not a unit test, and b) not all of the tests use simulcast (some use a single encoding, i.e. singlecast or SVC). Shared helper functions between PeerConnectionEncodingsIntegrationTest and PeerConnectionSimulcastTests are placed in a test-only util file. # Already pass, no need to wait for chromium bots for webrtc testonly CL NOTRY=True Bug: webrtc:15063 Change-Id: Iec90d1a7ab712be1395c7644723422c8c6179974 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/300540 Reviewed-by: Jeremy Leconte Commit-Queue: Henrik Boström Cr-Commit-Position: refs/heads/main@{#39799} --- pc/BUILD.gn | 10 +- ...er_connection_encodings_integrationtest.cc | 972 +++++++++++++++++ pc/peer_connection_simulcast_unittest.cc | 975 +----------------- pc/test/simulcast_layer_util.cc | 55 + pc/test/simulcast_layer_util.h | 39 + 5 files changed, 1082 insertions(+), 969 deletions(-) create mode 100644 pc/peer_connection_encodings_integrationtest.cc create mode 100644 pc/test/simulcast_layer_util.cc create mode 100644 pc/test/simulcast_layer_util.h diff --git a/pc/BUILD.gn b/pc/BUILD.gn index 8c07879762..740c3e838c 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn @@ -2309,6 +2309,7 @@ if (rtc_include_tests && !build_with_chromium) { "peer_connection_bundle_unittest.cc", "peer_connection_crypto_unittest.cc", "peer_connection_data_channel_unittest.cc", + "peer_connection_encodings_integrationtest.cc", "peer_connection_end_to_end_unittest.cc", "peer_connection_factory_unittest.cc", "peer_connection_field_trial_tests.cc", @@ -2750,6 +2751,8 @@ if (rtc_include_tests && !build_with_chromium) { "test/peer_connection_test_wrapper.cc", "test/peer_connection_test_wrapper.h", "test/rtc_stats_obtainer.h", + "test/simulcast_layer_util.cc", + "test/simulcast_layer_util.h", "test/test_sdp_strings.h", ] @@ -2763,6 +2766,8 @@ if (rtc_include_tests && !build_with_chromium) { ":rtp_receiver", ":rtp_sender", ":sctp_data_channel", + ":session_description", + ":simulcast_description", ":stream_collection", ":video_track_source", "../api:audio_options_api", @@ -2831,7 +2836,10 @@ if (rtc_include_tests && !build_with_chromium) { "../test:test_support", "../test:video_test_common", ] - absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] + absl_deps = [ + "//third_party/abseil-cpp/absl/algorithm:container", + "//third_party/abseil-cpp/absl/types:optional", + ] } svc_tests_resources = [ diff --git a/pc/peer_connection_encodings_integrationtest.cc b/pc/peer_connection_encodings_integrationtest.cc new file mode 100644 index 0000000000..68687122f0 --- /dev/null +++ b/pc/peer_connection_encodings_integrationtest.cc @@ -0,0 +1,972 @@ +/* + * 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 "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/audio_codecs/opus_audio_decoder_factory.h" +#include "api/audio_codecs/opus_audio_encoder_factory.h" +#include "api/rtp_parameters.h" +#include "api/stats/rtcstats_objects.h" +#include "api/video_codecs/video_decoder_factory_template.h" +#include "api/video_codecs/video_decoder_factory_template_dav1d_adapter.h" +#include "api/video_codecs/video_decoder_factory_template_libvpx_vp8_adapter.h" +#include "api/video_codecs/video_decoder_factory_template_libvpx_vp9_adapter.h" +#include "api/video_codecs/video_decoder_factory_template_open_h264_adapter.h" +#include "api/video_codecs/video_encoder_factory_template.h" +#include "api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h" +#include "api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h" +#include "api/video_codecs/video_encoder_factory_template_libvpx_vp9_adapter.h" +#include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.h" +#include "pc/sdp_utils.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/gunit.h" +#include "rtc_base/physical_socket_server.h" +#include "test/gmock.h" +#include "test/gtest.h" + +using ::testing::Eq; +using ::testing::Optional; +using ::testing::SizeIs; +using ::testing::StrCaseEq; +using ::testing::StrEq; + +namespace webrtc { + +namespace { + +constexpr TimeDelta kDefaultTimeout = TimeDelta::Seconds(5); +constexpr TimeDelta kLongTimeoutForRampingUp = TimeDelta::Seconds(30); + +// RTX, RED and FEC are reliability mechanisms used in combinations with other +// codecs, but are not themselves a specific codec. Typically you don't want to +// filter these out of the list of codec preferences. +bool IsReliabilityMechanism(const webrtc::RtpCodecCapability& codec) { + return absl::EqualsIgnoreCase(codec.name, cricket::kRtxCodecName) || + absl::EqualsIgnoreCase(codec.name, cricket::kRedCodecName) || + absl::EqualsIgnoreCase(codec.name, cricket::kUlpfecCodecName); +} + +std::string GetCurrentCodecMimeType( + rtc::scoped_refptr report, + const webrtc::RTCOutboundRtpStreamStats& outbound_rtp) { + return outbound_rtp.codec_id.is_defined() + ? *report->GetAs(*outbound_rtp.codec_id) + ->mime_type + : ""; +} + +struct RidAndResolution { + std::string rid; + uint32_t width; + uint32_t height; +}; + +const webrtc::RTCOutboundRtpStreamStats* FindOutboundRtpByRid( + const std::vector& outbound_rtps, + const absl::string_view& rid) { + for (const auto* outbound_rtp : outbound_rtps) { + if (outbound_rtp->rid.is_defined() && *outbound_rtp->rid == rid) { + return outbound_rtp; + } + } + return nullptr; +} + +} // namespace + +class PeerConnectionEncodingsIntegrationTest : public ::testing::Test { + public: + PeerConnectionEncodingsIntegrationTest() + : background_thread_(std::make_unique(&pss_)) { + RTC_CHECK(background_thread_->Start()); + } + + rtc::scoped_refptr CreatePc() { + auto pc_wrapper = rtc::make_ref_counted( + "pc", &pss_, background_thread_.get(), background_thread_.get()); + pc_wrapper->CreatePc({}, webrtc::CreateOpusAudioEncoderFactory(), + webrtc::CreateOpusAudioDecoderFactory()); + return pc_wrapper; + } + + rtc::scoped_refptr AddTransceiverWithSimulcastLayers( + rtc::scoped_refptr local, + rtc::scoped_refptr remote, + std::vector init_layers) { + rtc::scoped_refptr stream = + local->GetUserMedia( + /*audio=*/false, cricket::AudioOptions(), /*video=*/true, + {.width = 1280, .height = 720}); + rtc::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 HasSenderVideoCodecCapability( + rtc::scoped_refptr pc_wrapper, + absl::string_view codec_name) { + std::vector codecs = + pc_wrapper->pc_factory() + ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_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( + rtc::scoped_refptr pc_wrapper, + absl::string_view codec_name) { + std::vector codecs = + pc_wrapper->pc_factory() + ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO) + .codecs; + codecs.erase(std::remove_if(codecs.begin(), codecs.end(), + [&codec_name](const RtpCodecCapability& codec) { + return !IsReliabilityMechanism(codec) && + !absl::EqualsIgnoreCase(codec.name, + codec_name); + }), + codecs.end()); + 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( + rtc::scoped_refptr local_pc_wrapper, + rtc::scoped_refptr remote_pc_wrapper) { + local_pc_wrapper->SignalOnIceCandidateReady.connect( + remote_pc_wrapper.get(), &PeerConnectionTestWrapper::AddIceCandidate); + remote_pc_wrapper->SignalOnIceCandidateReady.connect( + local_pc_wrapper.get(), &PeerConnectionTestWrapper::AddIceCandidate); + } + + void NegotiateWithSimulcastTweaks( + rtc::scoped_refptr local_pc_wrapper, + rtc::scoped_refptr remote_pc_wrapper, + std::vector init_layers) { + // Create and set offer for `local_pc_wrapper`. + std::unique_ptr offer = + CreateOffer(local_pc_wrapper); + rtc::scoped_refptr p1 = + SetLocalDescription(local_pc_wrapper, offer.get()); + // Modify the offer before handoff because `remote_pc_wrapper` only supports + // receiving singlecast. + cricket::SimulcastDescription simulcast_description = + RemoveSimulcast(offer.get()); + rtc::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); + p1 = SetLocalDescription(remote_pc_wrapper, answer.get()); + // Modify the answer before handoff because `local_pc_wrapper` should still + // send simulcast. + cricket::MediaContentDescription* mcd_answer = + answer->description()->contents()[0].media_description(); + mcd_answer->mutable_streams().clear(); + std::vector simulcast_layers = + simulcast_description.send_layers().GetAllLayers(); + cricket::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})); + } + + rtc::scoped_refptr GetStats( + rtc::scoped_refptr pc_wrapper) { + auto callback = rtc::make_ref_counted(); + pc_wrapper->pc()->GetStats(callback.get()); + EXPECT_TRUE_WAIT(callback->called(), kDefaultTimeout.ms()); + return callback->report(); + } + + bool HasOutboundRtpBytesSent( + rtc::scoped_refptr pc_wrapper, + size_t num_layers) { + return HasOutboundRtpBytesSent(pc_wrapper, num_layers, num_layers); + } + + bool HasOutboundRtpBytesSent( + rtc::scoped_refptr pc_wrapper, + size_t num_layers, + size_t num_active_layers) { + rtc::scoped_refptr report = GetStats(pc_wrapper); + std::vector outbound_rtps = + report->GetStatsOfType(); + if (outbound_rtps.size() != num_layers) { + return false; + } + size_t num_sending_layers = 0; + for (const auto* outbound_rtp : outbound_rtps) { + if (outbound_rtp->bytes_sent.is_defined() && + *outbound_rtp->bytes_sent > 0u) { + ++num_sending_layers; + } + } + return num_sending_layers == num_active_layers; + } + + bool HasOutboundRtpWithRidAndScalabilityMode( + rtc::scoped_refptr pc_wrapper, + absl::string_view rid, + absl::string_view expected_scalability_mode, + uint32_t frame_height) { + rtc::scoped_refptr report = GetStats(pc_wrapper); + std::vector outbound_rtps = + report->GetStatsOfType(); + auto* outbound_rtp = FindOutboundRtpByRid(outbound_rtps, rid); + if (!outbound_rtp || !outbound_rtp->scalability_mode.is_defined() || + *outbound_rtp->scalability_mode != expected_scalability_mode) { + return false; + } + if (outbound_rtp->frame_height.is_defined()) { + RTC_LOG(LS_INFO) << "Waiting for target resolution (" << frame_height + << "p). Currently at " << *outbound_rtp->frame_height + << "p..."; + } else { + RTC_LOG(LS_INFO) + << "Waiting for target resolution. No frames encoded yet..."; + } + if (!outbound_rtp->frame_height.is_defined() || + *outbound_rtp->frame_height != frame_height) { + // Sleep to avoid log spam when this is used in ASSERT_TRUE_WAIT(). + rtc::Thread::Current()->SleepMs(1000); + return false; + } + return true; + } + + bool OutboundRtpResolutionsAreLessThanOrEqualToExpectations( + rtc::scoped_refptr pc_wrapper, + std::vector resolutions) { + rtc::scoped_refptr report = GetStats(pc_wrapper); + std::vector outbound_rtps = + report->GetStatsOfType(); + for (const RidAndResolution& resolution : resolutions) { + const RTCOutboundRtpStreamStats* outbound_rtp = nullptr; + if (!resolution.rid.empty()) { + outbound_rtp = FindOutboundRtpByRid(outbound_rtps, resolution.rid); + } else if (outbound_rtps.size() == 1u) { + outbound_rtp = outbound_rtps[0]; + } + if (!outbound_rtp || !outbound_rtp->frame_width.is_defined() || + !outbound_rtp->frame_height.is_defined()) { + // RTP not found by rid or has not encoded a frame yet. + RTC_LOG(LS_ERROR) << "rid=" << resolution.rid << " does not have " + << "resolution metrics"; + return false; + } + if (*outbound_rtp->frame_width > resolution.width || + *outbound_rtp->frame_height > resolution.height) { + RTC_LOG(LS_ERROR) << "rid=" << resolution.rid << " is " + << *outbound_rtp->frame_width << "x" + << *outbound_rtp->frame_height + << ", this is greater than the " + << "expected " << resolution.width << "x" + << resolution.height; + return false; + } + } + return true; + } + + protected: + std::unique_ptr CreateOffer( + rtc::scoped_refptr pc_wrapper) { + auto observer = + rtc::make_ref_counted(); + pc_wrapper->pc()->CreateOffer(observer.get(), {}); + EXPECT_EQ_WAIT(true, observer->called(), kDefaultTimeout.ms()); + return observer->MoveDescription(); + } + + std::unique_ptr CreateAnswer( + rtc::scoped_refptr pc_wrapper) { + auto observer = + rtc::make_ref_counted(); + pc_wrapper->pc()->CreateAnswer(observer.get(), {}); + EXPECT_EQ_WAIT(true, observer->called(), kDefaultTimeout.ms()); + return observer->MoveDescription(); + } + + rtc::scoped_refptr SetLocalDescription( + rtc::scoped_refptr pc_wrapper, + SessionDescriptionInterface* sdp) { + auto observer = rtc::make_ref_counted(); + pc_wrapper->pc()->SetLocalDescription( + observer.get(), CloneSessionDescription(sdp).release()); + return observer; + } + + rtc::scoped_refptr SetRemoteDescription( + rtc::scoped_refptr pc_wrapper, + SessionDescriptionInterface* sdp) { + auto observer = rtc::make_ref_counted(); + pc_wrapper->pc()->SetRemoteDescription( + observer.get(), CloneSessionDescription(sdp).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) { + EXPECT_EQ_WAIT(true, observer->called(), kDefaultTimeout.ms()); + if (!observer->result()) { + return false; + } + } + return true; + } + + rtc::PhysicalSocketServer pss_; + std::unique_ptr background_thread_; +}; + +TEST_F(PeerConnectionEncodingsIntegrationTest, + SendingOneEncodings_VP8_DefaultsToL1T1) { + rtc::scoped_refptr local_pc_wrapper = CreatePc(); + rtc::scoped_refptr remote_pc_wrapper = CreatePc(); + ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); + + std::vector layers = + CreateLayers({"f"}, /*active=*/true); + rtc::scoped_refptr transceiver = + AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, + layers); + std::vector codecs = + GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP8"); + transceiver->SetCodecPreferences(codecs); + + NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers); + local_pc_wrapper->WaitForConnection(); + remote_pc_wrapper->WaitForConnection(); + + // Wait until media is flowing. + ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 1u), + kDefaultTimeout.ms()); + EXPECT_TRUE(OutboundRtpResolutionsAreLessThanOrEqualToExpectations( + local_pc_wrapper, {{"", 1280, 720}})); + // Verify codec and scalability mode. + rtc::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")); +} + +TEST_F(PeerConnectionEncodingsIntegrationTest, + SendingThreeEncodings_VP8_Simulcast) { + rtc::scoped_refptr local_pc_wrapper = CreatePc(); + rtc::scoped_refptr remote_pc_wrapper = CreatePc(); + ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); + + std::vector layers = + CreateLayers({"f", "h", "q"}, /*active=*/true); + rtc::scoped_refptr transceiver = + AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, + layers); + std::vector codecs = + GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP8"); + transceiver->SetCodecPreferences(codecs); + + NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers); + 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. + ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 3u), + kLongTimeoutForRampingUp.ms()); + EXPECT_TRUE(OutboundRtpResolutionsAreLessThanOrEqualToExpectations( + local_pc_wrapper, {{"f", 320, 180}, {"h", 640, 360}, {"q", 1280, 720}})); + // Verify codec and scalability mode. + rtc::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/VP8")); + EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[1]), + StrCaseEq("video/VP8")); + EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[2]), + StrCaseEq("video/VP8")); + 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")); +} + +TEST_F(PeerConnectionEncodingsIntegrationTest, + SendingOneEncoding_VP8_RejectsSVCWhenNotPossibleAndDefaultsToL1T1) { + rtc::scoped_refptr local_pc_wrapper = CreatePc(); + rtc::scoped_refptr remote_pc_wrapper = CreatePc(); + ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); + + std::vector layers = + CreateLayers({"f"}, /*active=*/true); + rtc::scoped_refptr transceiver = + AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, + layers); + // Restricting codecs restricts what SetParameters() will accept or reject. + std::vector codecs = + GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP8"); + transceiver->SetCodecPreferences(codecs); + // Attempt SVC (L3T3_KEY). This is not possible because only VP8 is up for + // negotiation and VP8 does not support it. + rtc::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(absl::nullopt)); + + NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers); + local_pc_wrapper->WaitForConnection(); + remote_pc_wrapper->WaitForConnection(); + + // Wait until media is flowing. + ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 1u), + kDefaultTimeout.ms()); + // When `scalability_mode` is not set, VP8 defaults to L1T1. + rtc::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(absl::nullopt)); +} + +TEST_F(PeerConnectionEncodingsIntegrationTest, + SendingOneEncoding_VP8_FallbackFromSVCResultsInL1T2) { + rtc::scoped_refptr local_pc_wrapper = CreatePc(); + rtc::scoped_refptr remote_pc_wrapper = CreatePc(); + ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); + + std::vector layers = + CreateLayers({"f"}, /*active=*/true); + rtc::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(cricket::MEDIA_TYPE_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. + rtc::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, layers); + local_pc_wrapper->WaitForConnection(); + remote_pc_wrapper->WaitForConnection(); + // `scalaiblity_mode` is assigned the fallback value "L1T2" which is different + // than the default of absl::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_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 1u), + kDefaultTimeout.ms()); + // GetStats() confirms "L1T2" is used which is different than the "L1T1" + // default or the "L3T3_KEY" that was attempted. + rtc::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")); +} + +#if defined(WEBRTC_USE_H264) + +TEST_F(PeerConnectionEncodingsIntegrationTest, + SendingThreeEncodings_H264_Simulcast) { + rtc::scoped_refptr local_pc_wrapper = CreatePc(); + rtc::scoped_refptr remote_pc_wrapper = CreatePc(); + ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); + + std::vector layers = + CreateLayers({"f", "h", "q"}, /*active=*/true); + rtc::scoped_refptr transceiver = + AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, + layers); + std::vector codecs = + GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "H264"); + transceiver->SetCodecPreferences(codecs); + + NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers); + 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. + ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 3u), + kLongTimeoutForRampingUp.ms()); + EXPECT_TRUE(OutboundRtpResolutionsAreLessThanOrEqualToExpectations( + local_pc_wrapper, {{"f", 320, 180}, {"h", 640, 360}, {"q", 1280, 720}})); + // Verify codec and scalability mode. + rtc::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/H264")); + EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[1]), + StrCaseEq("video/H264")); + EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[2]), + StrCaseEq("video/H264")); + 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")); +} + +#endif // defined(WEBRTC_USE_H264) + +// 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, + SendingThreeEncodings_VP9_LegacySVC) { + rtc::scoped_refptr local_pc_wrapper = CreatePc(); + rtc::scoped_refptr remote_pc_wrapper = CreatePc(); + ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); + + std::vector layers = + CreateLayers({"f", "h", "q"}, /*active=*/true); + rtc::scoped_refptr transceiver = + AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, + layers); + std::vector codecs = + GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP9"); + transceiver->SetCodecPreferences(codecs); + + NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers); + 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_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 1u), + kDefaultTimeout.ms()); + // Wait until scalability mode is reported and expected resolution reached. + // Ramp up time may be significant. + ASSERT_TRUE_WAIT(HasOutboundRtpWithRidAndScalabilityMode( + local_pc_wrapper, "f", "L3T3_KEY", 720), + (2 * kLongTimeoutForRampingUp).ms()); + + // 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). + rtc::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, + SendingOneEncoding_VP9_StandardSVC) { + rtc::scoped_refptr local_pc_wrapper = CreatePc(); + rtc::scoped_refptr remote_pc_wrapper = CreatePc(); + ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); + + std::vector layers = + CreateLayers({"f"}, /*active=*/true); + rtc::scoped_refptr transceiver = + AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, + layers); + std::vector codecs = + GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP9"); + transceiver->SetCodecPreferences(codecs); + // Configure SVC, a.k.a. "L3T3_KEY". + rtc::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, layers); + 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_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 1u), + kDefaultTimeout.ms()); + EXPECT_TRUE(OutboundRtpResolutionsAreLessThanOrEqualToExpectations( + local_pc_wrapper, {{"", 1280, 720}})); + // Verify codec and scalability mode. + rtc::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/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, + SendingThreeEncodings_VP9_StandardSVC) { + rtc::scoped_refptr local_pc_wrapper = CreatePc(); + rtc::scoped_refptr remote_pc_wrapper = CreatePc(); + ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); + + std::vector layers = + CreateLayers({"f", "h", "q"}, /*active=*/true); + rtc::scoped_refptr transceiver = + AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, + layers); + std::vector codecs = + GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP9"); + transceiver->SetCodecPreferences(codecs); + // Configure SVC, a.k.a. "L3T3_KEY". + rtc::scoped_refptr sender = transceiver->sender(); + RtpParameters parameters = sender->GetParameters(); + ASSERT_EQ(parameters.encodings.size(), 3u); + 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, layers); + 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. + ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 3u, 1u), + kDefaultTimeout.ms()); + // Wait until scalability mode is reported and expected resolution reached. + // Ramp up time is significant. + ASSERT_TRUE_WAIT(HasOutboundRtpWithRidAndScalabilityMode( + local_pc_wrapper, "f", "L3T3_KEY", 720), + (2 * kLongTimeoutForRampingUp).ms()); + + // GetParameters() is consistent with what we asked for and got. + parameters = sender->GetParameters(); + ASSERT_EQ(parameters.encodings.size(), 3u); + EXPECT_THAT(parameters.encodings[0].scalability_mode, + Optional(std::string("L3T3_KEY"))); + EXPECT_FALSE(parameters.encodings[1].scalability_mode.has_value()); + EXPECT_FALSE(parameters.encodings[2].scalability_mode.has_value()); +} + +TEST_F(PeerConnectionEncodingsIntegrationTest, + SendingThreeEncodings_VP9_Simulcast) { + rtc::scoped_refptr local_pc_wrapper = CreatePc(); + rtc::scoped_refptr remote_pc_wrapper = CreatePc(); + ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); + + std::vector layers = + CreateLayers({"f", "h", "q"}, /*active=*/true); + rtc::scoped_refptr transceiver = + AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, + layers); + std::vector codecs = + GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP9"); + transceiver->SetCodecPreferences(codecs); + + // Opt-in to spec-compliant simulcast by explicitly setting the + // `scalability_mode` and `scale_resolution_down_by` parameters. + rtc::scoped_refptr sender = transceiver->sender(); + RtpParameters parameters = sender->GetParameters(); + ASSERT_EQ(parameters.encodings.size(), 3u); + 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, layers); + local_pc_wrapper->WaitForConnection(); + remote_pc_wrapper->WaitForConnection(); + + // GetParameters() does not report any fallback. + parameters = sender->GetParameters(); + ASSERT_EQ(parameters.encodings.size(), 3u); + 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. + ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 3u), + kLongTimeoutForRampingUp.ms()); + EXPECT_TRUE(OutboundRtpResolutionsAreLessThanOrEqualToExpectations( + local_pc_wrapper, {{"f", 320, 180}, {"h", 640, 360}, {"q", 1280, 720}})); + // Verify codec and scalability mode. + rtc::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/VP9")); + EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[1]), + StrCaseEq("video/VP9")); + EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[2]), + StrCaseEq("video/VP9")); + 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")); +} + +// Exercise common path where `scalability_mode` is not specified until after +// negotiation, requring us to recreate the stream when the number of streams +// changes from 1 (legacy SVC) to 3 (standard simulcast). +TEST_F(PeerConnectionEncodingsIntegrationTest, + SendingThreeEncodings_VP9_FromLegacyToSingleActiveWithScalability) { + rtc::scoped_refptr local_pc_wrapper = CreatePc(); + rtc::scoped_refptr remote_pc_wrapper = CreatePc(); + ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); + + std::vector layers = + CreateLayers({"f", "h", "q"}, /*active=*/true); + rtc::scoped_refptr transceiver = + AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, + layers); + std::vector codecs = + GetCapabilitiesAndRestrictToCodec(local_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, layers); + 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. + rtc::scoped_refptr sender = transceiver->sender(); + RtpParameters parameters = sender->GetParameters(); + ASSERT_EQ(parameters.encodings.size(), 3u); + 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 = absl::nullopt; + parameters.encodings[2].active = false; + parameters.encodings[2].scalability_mode = absl::nullopt; + sender->SetParameters(parameters); + + // Since the standard API is configuring simulcast we get three outbound-rtps, + // but only one is active. + ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 3u, 1u), + kDefaultTimeout.ms()); + // Wait until scalability mode is reported and expected resolution reached. + // Ramp up time may be significant. + ASSERT_TRUE_WAIT(HasOutboundRtpWithRidAndScalabilityMode( + local_pc_wrapper, "f", "L2T2_KEY", 720 / 2), + (2 * kLongTimeoutForRampingUp).ms()); + + // GetParameters() does not report any fallback. + parameters = sender->GetParameters(); + ASSERT_EQ(parameters.encodings.size(), 3u); + 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, + SendingThreeEncodings_VP9_StandardL1T3_AllLayersInactive) { + rtc::scoped_refptr local_pc_wrapper = CreatePc(); + rtc::scoped_refptr remote_pc_wrapper = CreatePc(); + ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); + + std::vector layers = + CreateLayers({"f", "h", "q"}, /*active=*/true); + rtc::scoped_refptr transceiver = + AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, + layers); + std::vector codecs = + GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP9"); + transceiver->SetCodecPreferences(codecs); + + // Standard mode and all layers inactive. + rtc::scoped_refptr sender = transceiver->sender(); + RtpParameters parameters = sender->GetParameters(); + ASSERT_EQ(parameters.encodings.size(), 3u); + 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, layers); + local_pc_wrapper->WaitForConnection(); + remote_pc_wrapper->WaitForConnection(); + + // Ensure no media is flowing (1 second should be enough). + rtc::Thread::Current()->SleepMs(1000); + rtc::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, + SendingThreeEncodings_AV1_Simulcast) { + rtc::scoped_refptr local_pc_wrapper = CreatePc(); + // TODO(https://crbug.com/webrtc/15011): Expand testing support for AV1 or + // allow compile time checks so that gates like this isn't needed at runtime. + if (!HasSenderVideoCodecCapability(local_pc_wrapper, "AV1")) { + RTC_LOG(LS_WARNING) << "\n***\nAV1 is not available, skipping test.\n***"; + return; + } + rtc::scoped_refptr remote_pc_wrapper = CreatePc(); + ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); + + std::vector layers = + CreateLayers({"f", "h", "q"}, /*active=*/true); + rtc::scoped_refptr transceiver = + AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, + layers); + std::vector codecs = + GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "AV1"); + transceiver->SetCodecPreferences(codecs); + + // Opt-in to spec-compliant simulcast by explicitly setting the + // `scalability_mode`. + rtc::scoped_refptr sender = transceiver->sender(); + RtpParameters parameters = sender->GetParameters(); + ASSERT_EQ(parameters.encodings.size(), 3u); + 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, layers); + local_pc_wrapper->WaitForConnection(); + remote_pc_wrapper->WaitForConnection(); + + // GetParameters() does not report any fallback. + parameters = sender->GetParameters(); + ASSERT_EQ(parameters.encodings.size(), 3u); + 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. + // + // This test is given 2X timeout because AV1 simulcast ramp-up time is + // terrible compared to other codecs. + // TODO(https://crbug.com/webrtc/15006): Improve the ramp-up time and stop + // giving this test extra long timeout. + ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 3u), + (2 * kLongTimeoutForRampingUp).ms()); + EXPECT_TRUE(OutboundRtpResolutionsAreLessThanOrEqualToExpectations( + local_pc_wrapper, {{"f", 320, 180}, {"h", 640, 360}, {"q", 1280, 720}})); + // Verify codec and scalability mode. + rtc::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/AV1")); + EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[1]), + StrCaseEq("video/AV1")); + EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[2]), + StrCaseEq("video/AV1")); + 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")); +} + +} // namespace webrtc diff --git a/pc/peer_connection_simulcast_unittest.cc b/pc/peer_connection_simulcast_unittest.cc index 7e7f8a3b12..11a0fb121f 100644 --- a/pc/peer_connection_simulcast_unittest.cc +++ b/pc/peer_connection_simulcast_unittest.cc @@ -35,7 +35,6 @@ #include "api/rtp_transceiver_direction.h" #include "api/rtp_transceiver_interface.h" #include "api/scoped_refptr.h" -#include "api/stats/rtcstats_objects.h" #include "api/uma_metrics.h" #include "api/video/video_codec_constants.h" #include "api/video_codecs/video_decoder_factory_template.h" @@ -60,15 +59,13 @@ #include "pc/simulcast_description.h" #include "pc/test/fake_audio_capture_module.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/gunit.h" -#include "rtc_base/physical_socket_server.h" #include "rtc_base/strings/string_builder.h" #include "rtc_base/thread.h" #include "rtc_base/unique_id_generator.h" #include "system_wrappers/include/metrics.h" -#include "test/field_trial.h" #include "test/gmock.h" #include "test/gtest.h" @@ -81,13 +78,10 @@ using ::testing::Field; using ::testing::IsEmpty; using ::testing::Le; using ::testing::Ne; -using ::testing::Optional; using ::testing::Pair; using ::testing::Property; using ::testing::SizeIs; using ::testing::StartsWith; -using ::testing::StrCaseEq; -using ::testing::StrEq; using cricket::MediaContentDescription; using cricket::RidDescription; @@ -110,21 +104,6 @@ std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) namespace { -std::vector CreateLayers(const std::vector& rids, - const std::vector& active) { - RTC_DCHECK_EQ(rids.size(), active.size()); - std::vector result; - absl::c_transform(rids, active, std::back_inserter(result), - [](const std::string& rid, bool is_active) { - return SimulcastLayer(rid, !is_active); - }); - return result; -} -std::vector CreateLayers(const std::vector& rids, - bool active) { - return CreateLayers(rids, std::vector(rids.size(), active)); -} - #if RTC_METRICS_ENABLED std::vector CreateLayers(int num_layers, bool active) { rtc::UniqueStringGenerator rid_generator; @@ -132,52 +111,14 @@ std::vector CreateLayers(int num_layers, bool active) { for (int i = 0; i < num_layers; ++i) { rids.push_back(rid_generator.GenerateString()); } - return CreateLayers(rids, active); + return webrtc::CreateLayers(rids, active); } #endif -// RTX, RED and FEC are reliability mechanisms used in combinations with other -// codecs, but are not themselves a specific codec. Typically you don't want to -// filter these out of the list of codec preferences. -bool IsReliabilityMechanism(const webrtc::RtpCodecCapability& codec) { - return absl::EqualsIgnoreCase(codec.name, cricket::kRtxCodecName) || - absl::EqualsIgnoreCase(codec.name, cricket::kRedCodecName) || - absl::EqualsIgnoreCase(codec.name, cricket::kUlpfecCodecName); -} - -std::string GetCurrentCodecMimeType( - rtc::scoped_refptr report, - const webrtc::RTCOutboundRtpStreamStats& outbound_rtp) { - return outbound_rtp.codec_id.is_defined() - ? *report->GetAs(*outbound_rtp.codec_id) - ->mime_type - : ""; -} - -struct RidAndResolution { - std::string rid; - uint32_t width; - uint32_t height; -}; - -const webrtc::RTCOutboundRtpStreamStats* FindOutboundRtpByRid( - const std::vector& outbound_rtps, - const absl::string_view& rid) { - for (const auto* outbound_rtp : outbound_rtps) { - if (outbound_rtp->rid.is_defined() && *outbound_rtp->rid == rid) { - return outbound_rtp; - } - } - return nullptr; -} - } // namespace namespace webrtc { -constexpr TimeDelta kDefaultTimeout = TimeDelta::Seconds(5); -constexpr TimeDelta kLongTimeoutForRampingUp = TimeDelta::Seconds(30); - class PeerConnectionSimulcastTests : public ::testing::Test { public: PeerConnectionSimulcastTests() @@ -238,18 +179,6 @@ class PeerConnectionSimulcastTests : public ::testing::Test { EXPECT_TRUE(local->SetRemoteDescription(std::move(answer), &err)) << err; } - RtpTransceiverInit CreateTransceiverInit( - const std::vector& layers) { - RtpTransceiverInit init; - for (const SimulcastLayer& layer : layers) { - RtpEncodingParameters encoding; - encoding.rid = layer.rid; - encoding.active = !layer.is_paused; - init.send_encodings.push_back(encoding); - } - return init; - } - rtc::scoped_refptr AddTransceiver( PeerConnectionWrapper* pc, const std::vector& layers, @@ -258,13 +187,6 @@ class PeerConnectionSimulcastTests : public ::testing::Test { return pc->AddTransceiver(media_type, init); } - SimulcastDescription RemoveSimulcast(SessionDescriptionInterface* sd) { - auto mcd = sd->description()->contents()[0].media_description(); - auto result = mcd->simulcast_description(); - mcd->set_simulcast_description(SimulcastDescription()); - return result; - } - void AddRequestToReceiveSimulcast(const std::vector& layers, SessionDescriptionInterface* sd) { auto mcd = sd->description()->contents()[0].media_description(); @@ -698,7 +620,7 @@ TEST_F(PeerConnectionSimulcastTests, SimulcastSldModificationRejected) { TEST_F(PeerConnectionSimulcastMetricsTests, NoSimulcastUsageIsLogged) { auto local = CreatePeerConnectionWrapper(); auto remote = CreatePeerConnectionWrapper(); - auto layers = CreateLayers(0, true); + auto layers = ::CreateLayers(0, true); AddTransceiver(local.get(), layers); ExchangeOfferAnswer(local.get(), remote.get(), layers); @@ -712,7 +634,7 @@ TEST_F(PeerConnectionSimulcastMetricsTests, NoSimulcastUsageIsLogged) { TEST_F(PeerConnectionSimulcastMetricsTests, SpecComplianceIsLogged) { auto local = CreatePeerConnectionWrapper(); auto remote = CreatePeerConnectionWrapper(); - auto layers = CreateLayers(3, true); + auto layers = ::CreateLayers(3, true); AddTransceiver(local.get(), layers); ExchangeOfferAnswer(local.get(), remote.get(), layers); @@ -732,7 +654,7 @@ TEST_F(PeerConnectionSimulcastMetricsTests, SpecComplianceIsLogged) { TEST_F(PeerConnectionSimulcastMetricsTests, IncomingSimulcastIsLogged) { auto local = CreatePeerConnectionWrapper(); auto remote = CreatePeerConnectionWrapper(); - auto layers = CreateLayers(3, true); + auto layers = ::CreateLayers(3, true); AddTransceiver(local.get(), layers); auto offer = local->CreateOfferAndSetAsLocal(); EXPECT_THAT(LocalDescriptionSamples(), @@ -781,7 +703,7 @@ TEST_F(PeerConnectionSimulcastMetricsTests, RejectedSimulcastIsLogged) { TEST_F(PeerConnectionSimulcastMetricsTests, LegacySimulcastIsLogged) { auto local = CreatePeerConnectionWrapper(); auto remote = CreatePeerConnectionWrapper(); - auto layers = CreateLayers(0, true); + auto layers = ::CreateLayers(0, true); AddTransceiver(local.get(), layers); auto offer = local->CreateOffer(); // Munge the SDP to set up legacy simulcast. @@ -850,7 +772,7 @@ const int kMaxLayersInMetricsTest = 8; TEST_P(PeerConnectionSimulcastMetricsTests, NumberOfSendEncodingsIsLogged) { auto local = CreatePeerConnectionWrapper(); auto num_layers = GetParam(); - auto layers = CreateLayers(num_layers, true); + auto layers = ::CreateLayers(num_layers, true); AddTransceiver(local.get(), layers); EXPECT_EQ(1, metrics::NumSamples( "WebRTC.PeerConnection.Simulcast.NumberOfSendEncodings")); @@ -864,887 +786,4 @@ INSTANTIATE_TEST_SUITE_P(NumberOfSendEncodings, ::testing::Range(0, kMaxLayersInMetricsTest)); #endif -// Inherits some helper methods from PeerConnectionSimulcastTests but -// uses real threads and PeerConnectionTestWrapper to create fake media streams -// with flowing media and establish connections. -// TODO(https://crbug.com/webrtc/14884): Move these integration tests into a -// separate file and rename them to PeerConnectionEncodingIntegrationTests. -class PeerConnectionSimulcastWithMediaFlowTests - : public PeerConnectionSimulcastTests { - public: - PeerConnectionSimulcastWithMediaFlowTests() - : background_thread_(std::make_unique(&pss_)) { - RTC_CHECK(background_thread_->Start()); - } - - rtc::scoped_refptr CreatePc() { - auto pc_wrapper = rtc::make_ref_counted( - "pc", &pss_, background_thread_.get(), background_thread_.get()); - pc_wrapper->CreatePc({}, webrtc::CreateOpusAudioEncoderFactory(), - webrtc::CreateOpusAudioDecoderFactory()); - return pc_wrapper; - } - - rtc::scoped_refptr AddTransceiverWithSimulcastLayers( - rtc::scoped_refptr local, - rtc::scoped_refptr remote, - std::vector init_layers) { - rtc::scoped_refptr stream = - local->GetUserMedia( - /*audio=*/false, cricket::AudioOptions(), /*video=*/true, - {.width = 1280, .height = 720}); - rtc::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 HasSenderVideoCodecCapability( - rtc::scoped_refptr pc_wrapper, - absl::string_view codec_name) { - std::vector codecs = - pc_wrapper->pc_factory() - ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_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( - rtc::scoped_refptr pc_wrapper, - absl::string_view codec_name) { - std::vector codecs = - pc_wrapper->pc_factory() - ->GetRtpSenderCapabilities(cricket::MEDIA_TYPE_VIDEO) - .codecs; - codecs.erase(std::remove_if(codecs.begin(), codecs.end(), - [&codec_name](const RtpCodecCapability& codec) { - return !IsReliabilityMechanism(codec) && - !absl::EqualsIgnoreCase(codec.name, - codec_name); - }), - codecs.end()); - 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( - rtc::scoped_refptr local_pc_wrapper, - rtc::scoped_refptr remote_pc_wrapper) { - local_pc_wrapper->SignalOnIceCandidateReady.connect( - remote_pc_wrapper.get(), &PeerConnectionTestWrapper::AddIceCandidate); - remote_pc_wrapper->SignalOnIceCandidateReady.connect( - local_pc_wrapper.get(), &PeerConnectionTestWrapper::AddIceCandidate); - } - - void NegotiateWithSimulcastTweaks( - rtc::scoped_refptr local_pc_wrapper, - rtc::scoped_refptr remote_pc_wrapper, - std::vector init_layers) { - // Create and set offer for `local_pc_wrapper`. - std::unique_ptr offer = - CreateOffer(local_pc_wrapper); - rtc::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()); - rtc::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); - p1 = SetLocalDescription(remote_pc_wrapper, answer.get()); - // Modify the answer before handoff because `local_pc_wrapper` should still - // send simulcast. - cricket::MediaContentDescription* mcd_answer = - answer->description()->contents()[0].media_description(); - mcd_answer->mutable_streams().clear(); - std::vector simulcast_layers = - simulcast_description.send_layers().GetAllLayers(); - cricket::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})); - } - - rtc::scoped_refptr GetStats( - rtc::scoped_refptr pc_wrapper) { - auto callback = rtc::make_ref_counted(); - pc_wrapper->pc()->GetStats(callback.get()); - EXPECT_TRUE_WAIT(callback->called(), kDefaultTimeout.ms()); - return callback->report(); - } - - bool HasOutboundRtpBytesSent( - rtc::scoped_refptr pc_wrapper, - size_t num_layers) { - return HasOutboundRtpBytesSent(pc_wrapper, num_layers, num_layers); - } - - bool HasOutboundRtpBytesSent( - rtc::scoped_refptr pc_wrapper, - size_t num_layers, - size_t num_active_layers) { - rtc::scoped_refptr report = GetStats(pc_wrapper); - std::vector outbound_rtps = - report->GetStatsOfType(); - if (outbound_rtps.size() != num_layers) { - return false; - } - size_t num_sending_layers = 0; - for (const auto* outbound_rtp : outbound_rtps) { - if (outbound_rtp->bytes_sent.is_defined() && - *outbound_rtp->bytes_sent > 0u) { - ++num_sending_layers; - } - } - return num_sending_layers == num_active_layers; - } - - bool HasOutboundRtpWithRidAndScalabilityMode( - rtc::scoped_refptr pc_wrapper, - absl::string_view rid, - absl::string_view expected_scalability_mode, - uint32_t frame_height) { - rtc::scoped_refptr report = GetStats(pc_wrapper); - std::vector outbound_rtps = - report->GetStatsOfType(); - auto* outbound_rtp = FindOutboundRtpByRid(outbound_rtps, rid); - if (!outbound_rtp || !outbound_rtp->scalability_mode.is_defined() || - *outbound_rtp->scalability_mode != expected_scalability_mode) { - return false; - } - if (outbound_rtp->frame_height.is_defined()) { - RTC_LOG(LS_INFO) << "Waiting for target resolution (" << frame_height - << "p). Currently at " << *outbound_rtp->frame_height - << "p..."; - } else { - RTC_LOG(LS_INFO) - << "Waiting for target resolution. No frames encoded yet..."; - } - if (!outbound_rtp->frame_height.is_defined() || - *outbound_rtp->frame_height != frame_height) { - // Sleep to avoid log spam when this is used in ASSERT_TRUE_WAIT(). - rtc::Thread::Current()->SleepMs(1000); - return false; - } - return true; - } - - bool OutboundRtpResolutionsAreLessThanOrEqualToExpectations( - rtc::scoped_refptr pc_wrapper, - std::vector resolutions) { - rtc::scoped_refptr report = GetStats(pc_wrapper); - std::vector outbound_rtps = - report->GetStatsOfType(); - for (const RidAndResolution& resolution : resolutions) { - const RTCOutboundRtpStreamStats* outbound_rtp = nullptr; - if (!resolution.rid.empty()) { - outbound_rtp = FindOutboundRtpByRid(outbound_rtps, resolution.rid); - } else if (outbound_rtps.size() == 1u) { - outbound_rtp = outbound_rtps[0]; - } - if (!outbound_rtp || !outbound_rtp->frame_width.is_defined() || - !outbound_rtp->frame_height.is_defined()) { - // RTP not found by rid or has not encoded a frame yet. - RTC_LOG(LS_ERROR) << "rid=" << resolution.rid << " does not have " - << "resolution metrics"; - return false; - } - if (*outbound_rtp->frame_width > resolution.width || - *outbound_rtp->frame_height > resolution.height) { - RTC_LOG(LS_ERROR) << "rid=" << resolution.rid << " is " - << *outbound_rtp->frame_width << "x" - << *outbound_rtp->frame_height - << ", this is greater than the " - << "expected " << resolution.width << "x" - << resolution.height; - return false; - } - } - return true; - } - - protected: - std::unique_ptr CreateOffer( - rtc::scoped_refptr pc_wrapper) { - auto observer = - rtc::make_ref_counted(); - pc_wrapper->pc()->CreateOffer(observer.get(), {}); - EXPECT_EQ_WAIT(true, observer->called(), kDefaultTimeout.ms()); - return observer->MoveDescription(); - } - - std::unique_ptr CreateAnswer( - rtc::scoped_refptr pc_wrapper) { - auto observer = - rtc::make_ref_counted(); - pc_wrapper->pc()->CreateAnswer(observer.get(), {}); - EXPECT_EQ_WAIT(true, observer->called(), kDefaultTimeout.ms()); - return observer->MoveDescription(); - } - - rtc::scoped_refptr SetLocalDescription( - rtc::scoped_refptr pc_wrapper, - SessionDescriptionInterface* sdp) { - auto observer = rtc::make_ref_counted(); - pc_wrapper->pc()->SetLocalDescription( - observer.get(), CloneSessionDescription(sdp).release()); - return observer; - } - - rtc::scoped_refptr SetRemoteDescription( - rtc::scoped_refptr pc_wrapper, - SessionDescriptionInterface* sdp) { - auto observer = rtc::make_ref_counted(); - pc_wrapper->pc()->SetRemoteDescription( - observer.get(), CloneSessionDescription(sdp).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) { - EXPECT_EQ_WAIT(true, observer->called(), kDefaultTimeout.ms()); - if (!observer->result()) { - return false; - } - } - return true; - } - - rtc::PhysicalSocketServer pss_; - std::unique_ptr background_thread_; -}; - -TEST_F(PeerConnectionSimulcastWithMediaFlowTests, - SendingOneEncodings_VP8_DefaultsToL1T1) { - rtc::scoped_refptr local_pc_wrapper = CreatePc(); - rtc::scoped_refptr remote_pc_wrapper = CreatePc(); - ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); - - std::vector layers = CreateLayers({"f"}, /*active=*/true); - rtc::scoped_refptr transceiver = - AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, - layers); - std::vector codecs = - GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP8"); - transceiver->SetCodecPreferences(codecs); - - NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers); - local_pc_wrapper->WaitForConnection(); - remote_pc_wrapper->WaitForConnection(); - - // Wait until media is flowing. - ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 1u), - kDefaultTimeout.ms()); - EXPECT_TRUE(OutboundRtpResolutionsAreLessThanOrEqualToExpectations( - local_pc_wrapper, {{"", 1280, 720}})); - // Verify codec and scalability mode. - rtc::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")); -} - -TEST_F(PeerConnectionSimulcastWithMediaFlowTests, - SendingThreeEncodings_VP8_Simulcast) { - rtc::scoped_refptr local_pc_wrapper = CreatePc(); - rtc::scoped_refptr remote_pc_wrapper = CreatePc(); - ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); - - std::vector layers = - CreateLayers({"f", "h", "q"}, /*active=*/true); - rtc::scoped_refptr transceiver = - AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, - layers); - std::vector codecs = - GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP8"); - transceiver->SetCodecPreferences(codecs); - - NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers); - 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. - ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 3u), - kLongTimeoutForRampingUp.ms()); - EXPECT_TRUE(OutboundRtpResolutionsAreLessThanOrEqualToExpectations( - local_pc_wrapper, {{"f", 320, 180}, {"h", 640, 360}, {"q", 1280, 720}})); - // Verify codec and scalability mode. - rtc::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/VP8")); - EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[1]), - StrCaseEq("video/VP8")); - EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[2]), - StrCaseEq("video/VP8")); - 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")); -} - -TEST_F(PeerConnectionSimulcastWithMediaFlowTests, - SendingOneEncoding_VP8_RejectsSVCWhenNotPossibleAndDefaultsToL1T1) { - rtc::scoped_refptr local_pc_wrapper = CreatePc(); - rtc::scoped_refptr remote_pc_wrapper = CreatePc(); - ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); - - std::vector layers = CreateLayers({"f"}, /*active=*/true); - rtc::scoped_refptr transceiver = - AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, - layers); - // Restricting codecs restricts what SetParameters() will accept or reject. - std::vector codecs = - GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP8"); - transceiver->SetCodecPreferences(codecs); - // Attempt SVC (L3T3_KEY). This is not possible because only VP8 is up for - // negotiation and VP8 does not support it. - rtc::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(absl::nullopt)); - - NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers); - local_pc_wrapper->WaitForConnection(); - remote_pc_wrapper->WaitForConnection(); - - // Wait until media is flowing. - ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 1u), - kDefaultTimeout.ms()); - // When `scalability_mode` is not set, VP8 defaults to L1T1. - rtc::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(absl::nullopt)); -} - -TEST_F(PeerConnectionSimulcastWithMediaFlowTests, - SendingOneEncoding_VP8_FallbackFromSVCResultsInL1T2) { - rtc::scoped_refptr local_pc_wrapper = CreatePc(); - rtc::scoped_refptr remote_pc_wrapper = CreatePc(); - ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); - - std::vector layers = CreateLayers({"f"}, /*active=*/true); - rtc::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(cricket::MEDIA_TYPE_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. - rtc::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, layers); - local_pc_wrapper->WaitForConnection(); - remote_pc_wrapper->WaitForConnection(); - // `scalaiblity_mode` is assigned the fallback value "L1T2" which is different - // than the default of absl::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_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 1u), - kDefaultTimeout.ms()); - // GetStats() confirms "L1T2" is used which is different than the "L1T1" - // default or the "L3T3_KEY" that was attempted. - rtc::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")); -} - -#if defined(WEBRTC_USE_H264) - -TEST_F(PeerConnectionSimulcastWithMediaFlowTests, - SendingThreeEncodings_H264_Simulcast) { - rtc::scoped_refptr local_pc_wrapper = CreatePc(); - rtc::scoped_refptr remote_pc_wrapper = CreatePc(); - ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); - - std::vector layers = - CreateLayers({"f", "h", "q"}, /*active=*/true); - rtc::scoped_refptr transceiver = - AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, - layers); - std::vector codecs = - GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "H264"); - transceiver->SetCodecPreferences(codecs); - - NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers); - 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. - ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 3u), - kLongTimeoutForRampingUp.ms()); - EXPECT_TRUE(OutboundRtpResolutionsAreLessThanOrEqualToExpectations( - local_pc_wrapper, {{"f", 320, 180}, {"h", 640, 360}, {"q", 1280, 720}})); - // Verify codec and scalability mode. - rtc::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/H264")); - EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[1]), - StrCaseEq("video/H264")); - EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[2]), - StrCaseEq("video/H264")); - 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")); -} - -#endif // defined(WEBRTC_USE_H264) - -// 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(PeerConnectionSimulcastWithMediaFlowTests, - SendingThreeEncodings_VP9_LegacySVC) { - rtc::scoped_refptr local_pc_wrapper = CreatePc(); - rtc::scoped_refptr remote_pc_wrapper = CreatePc(); - ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); - - std::vector layers = - CreateLayers({"f", "h", "q"}, /*active=*/true); - rtc::scoped_refptr transceiver = - AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, - layers); - std::vector codecs = - GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP9"); - transceiver->SetCodecPreferences(codecs); - - NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper, layers); - 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_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 1u), - kDefaultTimeout.ms()); - // Wait until scalability mode is reported and expected resolution reached. - // Ramp up time may be significant. - ASSERT_TRUE_WAIT(HasOutboundRtpWithRidAndScalabilityMode( - local_pc_wrapper, "f", "L3T3_KEY", 720), - (2 * kLongTimeoutForRampingUp).ms()); - - // 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). - rtc::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(PeerConnectionSimulcastWithMediaFlowTests, - SendingOneEncoding_VP9_StandardSVC) { - rtc::scoped_refptr local_pc_wrapper = CreatePc(); - rtc::scoped_refptr remote_pc_wrapper = CreatePc(); - ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); - - std::vector layers = CreateLayers({"f"}, /*active=*/true); - rtc::scoped_refptr transceiver = - AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, - layers); - std::vector codecs = - GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP9"); - transceiver->SetCodecPreferences(codecs); - // Configure SVC, a.k.a. "L3T3_KEY". - rtc::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, layers); - 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_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 1u), - kDefaultTimeout.ms()); - EXPECT_TRUE(OutboundRtpResolutionsAreLessThanOrEqualToExpectations( - local_pc_wrapper, {{"", 1280, 720}})); - // Verify codec and scalability mode. - rtc::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/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(PeerConnectionSimulcastWithMediaFlowTests, - SendingThreeEncodings_VP9_StandardSVC) { - rtc::scoped_refptr local_pc_wrapper = CreatePc(); - rtc::scoped_refptr remote_pc_wrapper = CreatePc(); - ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); - - std::vector layers = - CreateLayers({"f", "h", "q"}, /*active=*/true); - rtc::scoped_refptr transceiver = - AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, - layers); - std::vector codecs = - GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP9"); - transceiver->SetCodecPreferences(codecs); - // Configure SVC, a.k.a. "L3T3_KEY". - rtc::scoped_refptr sender = transceiver->sender(); - RtpParameters parameters = sender->GetParameters(); - ASSERT_EQ(parameters.encodings.size(), 3u); - 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, layers); - 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. - ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 3u, 1u), - kDefaultTimeout.ms()); - // Wait until scalability mode is reported and expected resolution reached. - // Ramp up time is significant. - ASSERT_TRUE_WAIT(HasOutboundRtpWithRidAndScalabilityMode( - local_pc_wrapper, "f", "L3T3_KEY", 720), - (2 * kLongTimeoutForRampingUp).ms()); - - // GetParameters() is consistent with what we asked for and got. - parameters = sender->GetParameters(); - ASSERT_EQ(parameters.encodings.size(), 3u); - EXPECT_THAT(parameters.encodings[0].scalability_mode, - Optional(std::string("L3T3_KEY"))); - EXPECT_FALSE(parameters.encodings[1].scalability_mode.has_value()); - EXPECT_FALSE(parameters.encodings[2].scalability_mode.has_value()); -} - -TEST_F(PeerConnectionSimulcastWithMediaFlowTests, - SendingThreeEncodings_VP9_Simulcast) { - rtc::scoped_refptr local_pc_wrapper = CreatePc(); - rtc::scoped_refptr remote_pc_wrapper = CreatePc(); - ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); - - std::vector layers = - CreateLayers({"f", "h", "q"}, /*active=*/true); - rtc::scoped_refptr transceiver = - AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, - layers); - std::vector codecs = - GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP9"); - transceiver->SetCodecPreferences(codecs); - - // Opt-in to spec-compliant simulcast by explicitly setting the - // `scalability_mode` and `scale_resolution_down_by` parameters. - rtc::scoped_refptr sender = transceiver->sender(); - RtpParameters parameters = sender->GetParameters(); - ASSERT_EQ(parameters.encodings.size(), 3u); - 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, layers); - local_pc_wrapper->WaitForConnection(); - remote_pc_wrapper->WaitForConnection(); - - // GetParameters() does not report any fallback. - parameters = sender->GetParameters(); - ASSERT_EQ(parameters.encodings.size(), 3u); - 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. - ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 3u), - kLongTimeoutForRampingUp.ms()); - EXPECT_TRUE(OutboundRtpResolutionsAreLessThanOrEqualToExpectations( - local_pc_wrapper, {{"f", 320, 180}, {"h", 640, 360}, {"q", 1280, 720}})); - // Verify codec and scalability mode. - rtc::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/VP9")); - EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[1]), - StrCaseEq("video/VP9")); - EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[2]), - StrCaseEq("video/VP9")); - 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")); -} - -// Exercise common path where `scalability_mode` is not specified until after -// negotiation, requring us to recreate the stream when the number of streams -// changes from 1 (legacy SVC) to 3 (standard simulcast). -TEST_F(PeerConnectionSimulcastWithMediaFlowTests, - SendingThreeEncodings_VP9_FromLegacyToSingleActiveWithScalability) { - rtc::scoped_refptr local_pc_wrapper = CreatePc(); - rtc::scoped_refptr remote_pc_wrapper = CreatePc(); - ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); - - std::vector layers = - CreateLayers({"f", "h", "q"}, /*active=*/true); - rtc::scoped_refptr transceiver = - AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, - layers); - std::vector codecs = - GetCapabilitiesAndRestrictToCodec(local_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, layers); - 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. - rtc::scoped_refptr sender = transceiver->sender(); - RtpParameters parameters = sender->GetParameters(); - ASSERT_EQ(parameters.encodings.size(), 3u); - 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 = absl::nullopt; - parameters.encodings[2].active = false; - parameters.encodings[2].scalability_mode = absl::nullopt; - sender->SetParameters(parameters); - - // Since the standard API is configuring simulcast we get three outbound-rtps, - // but only one is active. - ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 3u, 1u), - kDefaultTimeout.ms()); - // Wait until scalability mode is reported and expected resolution reached. - // Ramp up time may be significant. - ASSERT_TRUE_WAIT(HasOutboundRtpWithRidAndScalabilityMode( - local_pc_wrapper, "f", "L2T2_KEY", 720 / 2), - (2 * kLongTimeoutForRampingUp).ms()); - - // GetParameters() does not report any fallback. - parameters = sender->GetParameters(); - ASSERT_EQ(parameters.encodings.size(), 3u); - 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(PeerConnectionSimulcastWithMediaFlowTests, - SendingThreeEncodings_VP9_StandardL1T3_AllLayersInactive) { - rtc::scoped_refptr local_pc_wrapper = CreatePc(); - rtc::scoped_refptr remote_pc_wrapper = CreatePc(); - ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); - - std::vector layers = - CreateLayers({"f", "h", "q"}, /*active=*/true); - rtc::scoped_refptr transceiver = - AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, - layers); - std::vector codecs = - GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "VP9"); - transceiver->SetCodecPreferences(codecs); - - // Standard mode and all layers inactive. - rtc::scoped_refptr sender = transceiver->sender(); - RtpParameters parameters = sender->GetParameters(); - ASSERT_EQ(parameters.encodings.size(), 3u); - 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, layers); - local_pc_wrapper->WaitForConnection(); - remote_pc_wrapper->WaitForConnection(); - - // Ensure no media is flowing (1 second should be enough). - rtc::Thread::Current()->SleepMs(1000); - rtc::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(PeerConnectionSimulcastWithMediaFlowTests, - SendingThreeEncodings_AV1_Simulcast) { - rtc::scoped_refptr local_pc_wrapper = CreatePc(); - // TODO(https://crbug.com/webrtc/15011): Expand testing support for AV1 or - // allow compile time checks so that gates like this isn't needed at runtime. - if (!HasSenderVideoCodecCapability(local_pc_wrapper, "AV1")) { - RTC_LOG(LS_WARNING) << "\n***\nAV1 is not available, skipping test.\n***"; - return; - } - rtc::scoped_refptr remote_pc_wrapper = CreatePc(); - ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); - - std::vector layers = - CreateLayers({"f", "h", "q"}, /*active=*/true); - rtc::scoped_refptr transceiver = - AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, - layers); - std::vector codecs = - GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "AV1"); - transceiver->SetCodecPreferences(codecs); - - // Opt-in to spec-compliant simulcast by explicitly setting the - // `scalability_mode`. - rtc::scoped_refptr sender = transceiver->sender(); - RtpParameters parameters = sender->GetParameters(); - ASSERT_EQ(parameters.encodings.size(), 3u); - 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, layers); - local_pc_wrapper->WaitForConnection(); - remote_pc_wrapper->WaitForConnection(); - - // GetParameters() does not report any fallback. - parameters = sender->GetParameters(); - ASSERT_EQ(parameters.encodings.size(), 3u); - 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. - // - // This test is given 2X timeout because AV1 simulcast ramp-up time is - // terrible compared to other codecs. - // TODO(https://crbug.com/webrtc/15006): Improve the ramp-up time and stop - // giving this test extra long timeout. - ASSERT_TRUE_WAIT(HasOutboundRtpBytesSent(local_pc_wrapper, 3u), - (2 * kLongTimeoutForRampingUp).ms()); - EXPECT_TRUE(OutboundRtpResolutionsAreLessThanOrEqualToExpectations( - local_pc_wrapper, {{"f", 320, 180}, {"h", 640, 360}, {"q", 1280, 720}})); - // Verify codec and scalability mode. - rtc::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/AV1")); - EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[1]), - StrCaseEq("video/AV1")); - EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[2]), - StrCaseEq("video/AV1")); - 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")); -} - } // namespace webrtc diff --git a/pc/test/simulcast_layer_util.cc b/pc/test/simulcast_layer_util.cc new file mode 100644 index 0000000000..6ce09b5e9b --- /dev/null +++ b/pc/test/simulcast_layer_util.cc @@ -0,0 +1,55 @@ +/* + * 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 "pc/test/simulcast_layer_util.h" + +#include "absl/algorithm/container.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +std::vector CreateLayers( + const std::vector& rids, + const std::vector& active) { + RTC_DCHECK_EQ(rids.size(), active.size()); + std::vector result; + absl::c_transform(rids, active, std::back_inserter(result), + [](const std::string& rid, bool is_active) { + return cricket::SimulcastLayer(rid, !is_active); + }); + return result; +} + +std::vector CreateLayers( + const std::vector& rids, + bool active) { + return CreateLayers(rids, std::vector(rids.size(), active)); +} + +RtpTransceiverInit CreateTransceiverInit( + const std::vector& layers) { + RtpTransceiverInit init; + for (const cricket::SimulcastLayer& layer : layers) { + RtpEncodingParameters encoding; + encoding.rid = layer.rid; + encoding.active = !layer.is_paused; + init.send_encodings.push_back(encoding); + } + return init; +} + +cricket::SimulcastDescription RemoveSimulcast(SessionDescriptionInterface* sd) { + auto mcd = sd->description()->contents()[0].media_description(); + auto result = mcd->simulcast_description(); + mcd->set_simulcast_description(cricket::SimulcastDescription()); + return result; +} + +} // namespace webrtc diff --git a/pc/test/simulcast_layer_util.h b/pc/test/simulcast_layer_util.h new file mode 100644 index 0000000000..6822e3c9fd --- /dev/null +++ b/pc/test/simulcast_layer_util.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#ifndef PC_TEST_SIMULCAST_LAYER_UTIL_H_ +#define PC_TEST_SIMULCAST_LAYER_UTIL_H_ + +#include +#include + +#include "api/jsep.h" +#include "api/rtp_transceiver_interface.h" +#include "pc/session_description.h" +#include "pc/simulcast_description.h" + +namespace webrtc { + +std::vector CreateLayers( + const std::vector& rids, + const std::vector& active); + +std::vector CreateLayers( + const std::vector& rids, + bool active); + +RtpTransceiverInit CreateTransceiverInit( + const std::vector& layers); + +cricket::SimulcastDescription RemoveSimulcast(SessionDescriptionInterface* sd); + +} // namespace webrtc + +#endif // PC_TEST_SIMULCAST_LAYER_UTIL_H_