|  | /* | 
|  | *  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 <algorithm> | 
|  | #include <cassert> | 
|  | #include <cstddef> | 
|  | #include <cstdint> | 
|  | #include <memory> | 
|  | #include <optional> | 
|  | #include <set> | 
|  | #include <string> | 
|  | #include <string_view> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #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/field_trials.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/sdp_utils.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/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 <typename M> | 
|  | auto ScalabilityModeIs(M matcher) { | 
|  | return Field("scalability_mode", &RTCOutboundRtpStreamStats::scalability_mode, | 
|  | matcher); | 
|  | } | 
|  |  | 
|  | template <typename M> | 
|  | auto CodecIs(M matcher) { | 
|  | return Field("codec_id", &RTCOutboundRtpStreamStats::codec_id, matcher); | 
|  | } | 
|  |  | 
|  | template <typename M> | 
|  | auto RidIs(M matcher) { | 
|  | return Field("rid", &RTCOutboundRtpStreamStats::rid, matcher); | 
|  | } | 
|  |  | 
|  | template <typename WidthMatcher, typename HeightMatcher> | 
|  | 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 <typename M> | 
|  | auto HeightIs(M matcher) { | 
|  | return Field("frame_height", &RTCOutboundRtpStreamStats::frame_height, | 
|  | matcher); | 
|  | } | 
|  |  | 
|  | template <typename M> | 
|  | auto BytesSentIs(M matcher) { | 
|  | return Field("bytes_sent", &RTCOutboundRtpStreamStats::bytes_sent, matcher); | 
|  | } | 
|  |  | 
|  | template <typename M> | 
|  | auto FramesEncodedIs(M matcher) { | 
|  | return Field("frames_encoded", &RTCOutboundRtpStreamStats::frames_encoded, | 
|  | matcher); | 
|  | } | 
|  |  | 
|  | auto Active() { | 
|  | return Field("active", &RTCOutboundRtpStreamStats::active, true); | 
|  | } | 
|  |  | 
|  | Matcher<scoped_refptr<const RTCStatsReport>> OutboundRtpStatsAre( | 
|  | Matcher<std::vector<RTCOutboundRtpStreamStats>> matcher) { | 
|  | return Pointer(ResultOf( | 
|  | "outbound_rtp", | 
|  | [&](const RTCStatsReport* report) { | 
|  | std::vector<const RTCOutboundRtpStreamStats*> stats = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  |  | 
|  | // Copy to a new vector. | 
|  | std::vector<RTCOutboundRtpStreamStats> 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<std::string, RTCOutboundRtpStreamStats> GetOutboundRtpStreamStatsByRid( | 
|  | scoped_refptr<const RTCStatsReport> report) { | 
|  | flat_map<std::string, RTCOutboundRtpStreamStats> result; | 
|  | auto stats = report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<std::string>& info) { | 
|  | return info.param; | 
|  | } | 
|  | }; | 
|  |  | 
|  | std::string GetCurrentCodecMimeType( | 
|  | scoped_refptr<const RTCStatsReport> report, | 
|  | const RTCOutboundRtpStreamStats& outbound_rtp) { | 
|  | return outbound_rtp.codec_id.has_value() | 
|  | ? *report->GetAs<RTCCodecStats>(*outbound_rtp.codec_id)->mime_type | 
|  | : ""; | 
|  | } | 
|  |  | 
|  | const RTCOutboundRtpStreamStats* FindOutboundRtpByRid( | 
|  | const std::vector<const RTCOutboundRtpStreamStats*>& 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() | 
|  | : background_thread_(std::make_unique<Thread>(&pss_)) { | 
|  | RTC_CHECK(background_thread_->Start()); | 
|  | } | 
|  |  | 
|  | scoped_refptr<PeerConnectionTestWrapper> CreatePc( | 
|  | std::unique_ptr<FieldTrialsView> field_trials = nullptr) { | 
|  | auto pc_wrapper = make_ref_counted<PeerConnectionTestWrapper>( | 
|  | "pc", &pss_, background_thread_.get(), background_thread_.get()); | 
|  | pc_wrapper->CreatePc({}, CreateBuiltinAudioEncoderFactory(), | 
|  | CreateBuiltinAudioDecoderFactory(), | 
|  | std::move(field_trials)); | 
|  | return pc_wrapper; | 
|  | } | 
|  |  | 
|  | scoped_refptr<RtpTransceiverInterface> AddTransceiverWithSimulcastLayers( | 
|  | scoped_refptr<PeerConnectionTestWrapper> local, | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote, | 
|  | std::vector<SimulcastLayer> init_layers) { | 
|  | scoped_refptr<MediaStreamInterface> stream = local->GetUserMedia( | 
|  | /*audio=*/false, AudioOptions(), /*video=*/true, | 
|  | {.width = 1280, .height = 720}); | 
|  | scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0]; | 
|  |  | 
|  | RTCErrorOr<scoped_refptr<RtpTransceiverInterface>> 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<PeerConnectionTestWrapper> pc_wrapper, | 
|  | absl::string_view codec_name) { | 
|  | std::vector<RtpCodecCapability> 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<RtpCodecCapability> GetCapabilitiesAndRestrictToCodec( | 
|  | scoped_refptr<PeerConnectionTestWrapper> pc_wrapper, | 
|  | absl::string_view codec_name) { | 
|  | std::vector<RtpCodecCapability> codecs = | 
|  | pc_wrapper->pc_factory() | 
|  | ->GetRtpReceiverCapabilities(MediaType::VIDEO) | 
|  | .codecs; | 
|  | codecs.erase(std::remove_if(codecs.begin(), codecs.end(), | 
|  | [&codec_name](const RtpCodecCapability& codec) { | 
|  | return !codec.IsResiliencyCodec() && | 
|  | !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( | 
|  | scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper, | 
|  | scoped_refptr<PeerConnectionTestWrapper> 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); | 
|  | } | 
|  |  | 
|  | // Negotiate without any tweaks (does not work for simulcast loopback). | 
|  | void Negotiate(scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper, | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper) { | 
|  | std::unique_ptr<SessionDescriptionInterface> offer = | 
|  | CreateOffer(local_pc_wrapper); | 
|  | scoped_refptr<MockSetSessionDescriptionObserver> p1 = | 
|  | SetLocalDescription(local_pc_wrapper, offer.get()); | 
|  | scoped_refptr<MockSetSessionDescriptionObserver> p2 = | 
|  | SetRemoteDescription(remote_pc_wrapper, offer.get()); | 
|  | EXPECT_TRUE(Await({p1, p2})); | 
|  | std::unique_ptr<SessionDescriptionInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper, | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper) { | 
|  | // Create and set offer for `local_pc_wrapper`. | 
|  | std::unique_ptr<SessionDescriptionInterface> offer = | 
|  | CreateOffer(local_pc_wrapper); | 
|  | scoped_refptr<MockSetSessionDescriptionObserver> 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<MockSetSessionDescriptionObserver> p2 = | 
|  | SetRemoteDescription(remote_pc_wrapper, offer.get()); | 
|  | EXPECT_TRUE(Await({p1, p2})); | 
|  |  | 
|  | // Create and set answer for `remote_pc_wrapper`. | 
|  | std::unique_ptr<SessionDescriptionInterface> 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<SimulcastLayer> 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<const RTCStatsReport> GetStats( | 
|  | scoped_refptr<PeerConnectionTestWrapper> pc_wrapper) { | 
|  | auto callback = make_ref_counted<MockRTCStatsCollectorCallback>(); | 
|  | pc_wrapper->pc()->GetStats(callback.get()); | 
|  | RTC_CHECK(WaitUntil([&]() { return callback->called(); }, testing::IsTrue()) | 
|  | .ok()); | 
|  | return callback->report(); | 
|  | } | 
|  |  | 
|  | [[nodiscard]] RTCErrorOr<scoped_refptr<const RTCStatsReport>> GetStatsUntil( | 
|  | scoped_refptr<PeerConnectionTestWrapper> pc_wrapper, | 
|  | Matcher<scoped_refptr<const RTCStatsReport>> matcher, | 
|  | WaitUntilSettings settings = {}) { | 
|  | return WaitUntil([&]() { return GetStats(pc_wrapper); }, std::move(matcher), | 
|  | settings); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | std::unique_ptr<SessionDescriptionInterface> CreateOffer( | 
|  | scoped_refptr<PeerConnectionTestWrapper> pc_wrapper) { | 
|  | auto observer = make_ref_counted<MockCreateSessionDescriptionObserver>(); | 
|  | pc_wrapper->pc()->CreateOffer(observer.get(), {}); | 
|  | EXPECT_THAT(WaitUntil([&] { return observer->called(); }, IsTrue()), | 
|  | IsRtcOk()); | 
|  | return observer->MoveDescription(); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<SessionDescriptionInterface> CreateAnswer( | 
|  | scoped_refptr<PeerConnectionTestWrapper> pc_wrapper) { | 
|  | auto observer = make_ref_counted<MockCreateSessionDescriptionObserver>(); | 
|  | pc_wrapper->pc()->CreateAnswer(observer.get(), {}); | 
|  | EXPECT_THAT(WaitUntil([&] { return observer->called(); }, IsTrue()), | 
|  | IsRtcOk()); | 
|  | return observer->MoveDescription(); | 
|  | } | 
|  |  | 
|  | scoped_refptr<MockSetSessionDescriptionObserver> SetLocalDescription( | 
|  | scoped_refptr<PeerConnectionTestWrapper> pc_wrapper, | 
|  | SessionDescriptionInterface* sdp) { | 
|  | auto observer = make_ref_counted<MockSetSessionDescriptionObserver>(); | 
|  | pc_wrapper->pc()->SetLocalDescription( | 
|  | observer.get(), CloneSessionDescription(sdp).release()); | 
|  | return observer; | 
|  | } | 
|  |  | 
|  | scoped_refptr<MockSetSessionDescriptionObserver> SetRemoteDescription( | 
|  | scoped_refptr<PeerConnectionTestWrapper> pc_wrapper, | 
|  | SessionDescriptionInterface* sdp) { | 
|  | auto observer = make_ref_counted<MockSetSessionDescriptionObserver>(); | 
|  | 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<scoped_refptr<MockSetSessionDescriptionObserver>> observers) { | 
|  | for (auto& observer : observers) { | 
|  | auto result = WaitUntil([&] { return observer->called(); }, IsTrue()); | 
|  |  | 
|  | if (!result.ok() || !observer->result()) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | PhysicalSocketServer pss_; | 
|  | std::unique_ptr<Thread> background_thread_; | 
|  | }; | 
|  |  | 
|  | TEST_F(PeerConnectionEncodingsIntegrationTest, | 
|  | VP8_SingleEncodingDefaultsToL1T1) { | 
|  | scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = CreateLayers({"f"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> 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<const RTCStatsReport> report = stats_result.value(); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = CreateLayers({"f"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> 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<RtpCodecCapability> 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<RtpSenderInterface> 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<const RTCStatsReport> report = GetStats(local_pc_wrapper); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = CreateLayers({"f"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> 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<RtpCodecCapability> 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<RtpSenderInterface> 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<const RTCStatsReport> report = error_or_stats.value(); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = CreateLayers({"f"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> 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<RtpCodecCapability> 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<RtpSenderInterface> 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<const RTCStatsReport> report = GetStats(local_pc_wrapper); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"f", "h", "q"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> 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<RtpSenderInterface> sender = transceiver->sender(); | 
|  | std::vector<RtpEncodingParameters> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = CreateLayers({"f"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> codecs = | 
|  | GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); | 
|  | transceiver->SetCodecPreferences(codecs); | 
|  | // Configure SVC, a.k.a. "L3T3_KEY". | 
|  | scoped_refptr<RtpSenderInterface> 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<const RTCStatsReport> report = error_or_stats.value(); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"f", "h", "q"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> codecs = | 
|  | GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); | 
|  | transceiver->SetCodecPreferences(codecs); | 
|  | // Configure SVC, a.k.a. "L3T3_KEY". | 
|  | scoped_refptr<RtpSenderInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"f", "h", "q"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> 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<RtpSenderInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"q", "h", "f"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | constexpr absl::string_view kCodec = "VP9"; | 
|  | std::vector<RtpCodecCapability> 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<RtpSenderInterface> 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<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | GetStats(local_pc_wrapper) | 
|  | ->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | std::vector<size_t> 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<const RTCStatsReport> report = GetStats(local_pc_wrapper); | 
|  | ASSERT_TRUE(report); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<RTCOutboundRtpStreamStats>(); | 
|  | }, | 
|  | AllOf(Each(EncoderImplementationIs( | 
|  | "SimulcastEncoderAdapter (libvpx, libvpx)")), | 
|  | UnorderedElementsAre(ScalabilityModeIs("L1T3"), | 
|  | ScalabilityModeIs("L1T1"), | 
|  | ScalabilityModeIs(std::nullopt)))), | 
|  | IsRtcOk()); | 
|  | } | 
|  |  | 
|  | TEST_F(PeerConnectionEncodingsIntegrationTest, | 
|  | VP9_SimulcastMultiplLayersActive_StandardSvc) { | 
|  | scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"q", "h", "f"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> 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<RtpSenderInterface> 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<const RTCStatsReport> report = error_or_stats.value(); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"f", "h", "q"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> 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<RtpSenderInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"f", "h", "q"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> codecs = | 
|  | GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); | 
|  | transceiver->SetCodecPreferences(codecs); | 
|  |  | 
|  | // Sending L1T3 with legacy SVC mode means setting 1 layer active. | 
|  | scoped_refptr<RtpSenderInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"f", "h", "q"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> codecs = | 
|  | GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); | 
|  | transceiver->SetCodecPreferences(codecs); | 
|  |  | 
|  | // Legacy SVC mode and all layers inactive. | 
|  | scoped_refptr<RtpSenderInterface> 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<const RTCStatsReport> report = GetStats(local_pc_wrapper); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | ASSERT_THAT(outbound_rtps, SizeIs(1u)); | 
|  | EXPECT_EQ(*outbound_rtps[0]->bytes_sent, 0u); | 
|  | } | 
|  |  | 
|  | TEST_F(PeerConnectionEncodingsIntegrationTest, | 
|  | VP9_AllLayersInactive_StandardSvc) { | 
|  | scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"f", "h", "q"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> codecs = | 
|  | GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, "VP9"); | 
|  | transceiver->SetCodecPreferences(codecs); | 
|  |  | 
|  | // Standard mode and all layers inactive. | 
|  | scoped_refptr<RtpSenderInterface> 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<const RTCStatsReport> report = GetStats(local_pc_wrapper); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"f", "h", "q"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> 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<RtpSenderInterface> 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<const RTCStatsReport> report = GetStats(local_pc_wrapper); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"f", "h", "q"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> 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<RtpSenderInterface> 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<const RTCStatsReport> report = GetStats(local_pc_wrapper); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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, | 
|  | SimulcastProducesUniqueSsrcAndRtxSsrcs) { | 
|  | scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"f", "h", "q"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> 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<const RTCStatsReport> report = stats.MoveValue(); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | ASSERT_THAT(outbound_rtps, SizeIs(3u)); | 
|  |  | 
|  | std::set<uint32_t> ssrcs; | 
|  | std::set<uint32_t> 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); | 
|  | } | 
|  |  | 
|  | TEST_F(PeerConnectionEncodingsIntegrationTest, | 
|  | EncodingParameterCodecIsEmptyWhenCreatedAudio) { | 
|  | scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  |  | 
|  | auto transceiver_or_error = | 
|  | local_pc_wrapper->pc()->AddTransceiver(MediaType::AUDIO); | 
|  | scoped_refptr<RtpTransceiverInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  |  | 
|  | auto transceiver_or_error = | 
|  | local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO); | 
|  | scoped_refptr<RtpTransceiverInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | scoped_refptr<MediaStreamInterface> stream = local_pc_wrapper->GetUserMedia( | 
|  | /*audio=*/true, {}, /*video=*/false, {}); | 
|  | scoped_refptr<AudioTrackInterface> track = stream->GetAudioTracks()[0]; | 
|  |  | 
|  | std::optional<RtpCodecCapability> 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<RtpTransceiverInterface> 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<const RTCStatsReport> report = GetStats(local_pc_wrapper); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | scoped_refptr<MediaStreamInterface> stream = local_pc_wrapper->GetUserMedia( | 
|  | /*audio=*/false, {}, /*video=*/true, {.width = 1280, .height = 720}); | 
|  | scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0]; | 
|  |  | 
|  | std::optional<RtpCodecCapability> 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<RtpTransceiverInterface> 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<const RTCStatsReport> report = error_or_stats.MoveValue(); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | scoped_refptr<MediaStreamInterface> stream = local_pc_wrapper->GetUserMedia( | 
|  | /*audio=*/true, {}, /*video=*/false, {}); | 
|  | scoped_refptr<AudioTrackInterface> track = stream->GetAudioTracks()[0]; | 
|  |  | 
|  | std::optional<RtpCodecCapability> pcmu = | 
|  | local_pc_wrapper->FindFirstSendCodecWithName(MediaType::AUDIO, "pcmu"); | 
|  |  | 
|  | auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track); | 
|  | scoped_refptr<RtpTransceiverInterface> 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<const RTCStatsReport> report = GetStats(local_pc_wrapper); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | scoped_refptr<MediaStreamInterface> stream = local_pc_wrapper->GetUserMedia( | 
|  | /*audio=*/true, {}, /*video=*/false, {}); | 
|  | scoped_refptr<AudioTrackInterface> track = stream->GetAudioTracks()[0]; | 
|  |  | 
|  | std::optional<RtpCodecCapability> pcmu = | 
|  | local_pc_wrapper->FindFirstSendCodecWithName(MediaType::AUDIO, "pcmu"); | 
|  |  | 
|  | auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track); | 
|  | scoped_refptr<RtpTransceiverInterface> audio_transceiver = | 
|  | transceiver_or_error.MoveValue(); | 
|  |  | 
|  | NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); | 
|  | local_pc_wrapper->WaitForConnection(); | 
|  | remote_pc_wrapper->WaitForConnection(); | 
|  |  | 
|  | scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | scoped_refptr<MediaStreamInterface> stream = local_pc_wrapper->GetUserMedia( | 
|  | /*audio=*/false, {}, /*video=*/true, {.width = 1280, .height = 720}); | 
|  | scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0]; | 
|  |  | 
|  | std::optional<RtpCodecCapability> vp9 = | 
|  | local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp9"); | 
|  |  | 
|  | auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track); | 
|  | scoped_refptr<RtpTransceiverInterface> 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<const RTCStatsReport> report = error_or_stats.MoveValue(); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | scoped_refptr<MediaStreamInterface> stream = local_pc_wrapper->GetUserMedia( | 
|  | /*audio=*/false, {}, /*video=*/true, {.width = 1280, .height = 720}); | 
|  | scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0]; | 
|  |  | 
|  | std::optional<RtpCodecCapability> vp9 = | 
|  | local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp9"); | 
|  |  | 
|  | auto transceiver_or_error = local_pc_wrapper->pc()->AddTransceiver(track); | 
|  | scoped_refptr<RtpTransceiverInterface> video_transceiver = | 
|  | transceiver_or_error.MoveValue(); | 
|  |  | 
|  | NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper); | 
|  | local_pc_wrapper->WaitForConnection(); | 
|  | remote_pc_wrapper->WaitForConnection(); | 
|  |  | 
|  | scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> 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<PeerConnectionTestWrapper> 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<PeerConnectionTestWrapper> 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<RtpTransceiverInterface> 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<PeerConnectionTestWrapper> 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<RtpTransceiverInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::optional<RtpCodecCapability> opus = | 
|  | local_pc_wrapper->FindFirstSendCodecWithName(MediaType::AUDIO, "opus"); | 
|  | ASSERT_TRUE(opus); | 
|  |  | 
|  | std::vector<RtpCodecCapability> not_opus_codecs = | 
|  | local_pc_wrapper->pc_factory() | 
|  | ->GetRtpSenderCapabilities(MediaType::AUDIO) | 
|  | .codecs; | 
|  | not_opus_codecs.erase( | 
|  | std::remove_if(not_opus_codecs.begin(), not_opus_codecs.end(), | 
|  | [&](const auto& codec) { | 
|  | return absl::EqualsIgnoreCase(codec.name, opus->name); | 
|  | }), | 
|  | not_opus_codecs.end()); | 
|  |  | 
|  | auto transceiver_or_error = | 
|  | local_pc_wrapper->pc()->AddTransceiver(MediaType::AUDIO); | 
|  | ASSERT_TRUE(transceiver_or_error.ok()); | 
|  | scoped_refptr<RtpTransceiverInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::optional<RtpCodecCapability> opus = | 
|  | local_pc_wrapper->FindFirstSendCodecWithName(MediaType::AUDIO, "opus"); | 
|  | ASSERT_TRUE(opus); | 
|  |  | 
|  | std::vector<RtpCodecCapability> not_opus_codecs = | 
|  | local_pc_wrapper->pc_factory() | 
|  | ->GetRtpSenderCapabilities(MediaType::AUDIO) | 
|  | .codecs; | 
|  | not_opus_codecs.erase( | 
|  | std::remove_if(not_opus_codecs.begin(), not_opus_codecs.end(), | 
|  | [&](const auto& codec) { | 
|  | return absl::EqualsIgnoreCase(codec.name, opus->name); | 
|  | }), | 
|  | not_opus_codecs.end()); | 
|  |  | 
|  | auto transceiver_or_error = | 
|  | local_pc_wrapper->pc()->AddTransceiver(MediaType::AUDIO); | 
|  | ASSERT_TRUE(transceiver_or_error.ok()); | 
|  | scoped_refptr<RtpTransceiverInterface> audio_transceiver = | 
|  | transceiver_or_error.MoveValue(); | 
|  |  | 
|  | // Negotiation, create offer and apply it | 
|  | std::unique_ptr<SessionDescriptionInterface> offer = | 
|  | CreateOffer(local_pc_wrapper); | 
|  | scoped_refptr<MockSetSessionDescriptionObserver> p1 = | 
|  | SetLocalDescription(local_pc_wrapper, offer.get()); | 
|  | scoped_refptr<MockSetSessionDescriptionObserver> p2 = | 
|  | SetRemoteDescription(remote_pc_wrapper, offer.get()); | 
|  | EXPECT_TRUE(Await({p1, p2})); | 
|  |  | 
|  | // Update the remote transceiver to reject Opus | 
|  | std::vector<scoped_refptr<RtpTransceiverInterface>> remote_transceivers = | 
|  | remote_pc_wrapper->pc()->GetTransceivers(); | 
|  | ASSERT_TRUE(!remote_transceivers.empty()); | 
|  | scoped_refptr<RtpTransceiverInterface> remote_audio_transceiver = | 
|  | remote_transceivers[0]; | 
|  | ASSERT_TRUE( | 
|  | remote_audio_transceiver->SetCodecPreferences(not_opus_codecs).ok()); | 
|  |  | 
|  | // Create answer and apply it | 
|  | std::unique_ptr<SessionDescriptionInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> 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<RtpTransceiverInterface> video_transceiver = | 
|  | transceiver_or_error.MoveValue(); | 
|  |  | 
|  | std::unique_ptr<SessionDescriptionInterface> 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<Codec> 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<MockSetSessionDescriptionObserver> p1 = | 
|  | SetLocalDescription(local_pc_wrapper, offer.get()); | 
|  | scoped_refptr<MockSetSessionDescriptionObserver> p2 = | 
|  | SetRemoteDescription(remote_pc_wrapper, offer.get()); | 
|  | EXPECT_TRUE(Await({p1, p2})); | 
|  |  | 
|  | // Create answer and apply it | 
|  | std::unique_ptr<SessionDescriptionInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::optional<RtpCodecCapability> vp8 = | 
|  | local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp8"); | 
|  | ASSERT_TRUE(vp8); | 
|  |  | 
|  | std::vector<RtpCodecCapability> not_vp8_codecs = | 
|  | local_pc_wrapper->pc_factory() | 
|  | ->GetRtpSenderCapabilities(MediaType::VIDEO) | 
|  | .codecs; | 
|  | not_vp8_codecs.erase( | 
|  | std::remove_if(not_vp8_codecs.begin(), not_vp8_codecs.end(), | 
|  | [&](const auto& codec) { | 
|  | return absl::EqualsIgnoreCase(codec.name, vp8->name); | 
|  | }), | 
|  | not_vp8_codecs.end()); | 
|  |  | 
|  | auto transceiver_or_error = | 
|  | local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO); | 
|  | ASSERT_TRUE(transceiver_or_error.ok()); | 
|  | scoped_refptr<RtpTransceiverInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::optional<RtpCodecCapability> vp8 = | 
|  | local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp8"); | 
|  | ASSERT_TRUE(vp8); | 
|  |  | 
|  | std::vector<RtpCodecCapability> not_vp8_codecs = | 
|  | local_pc_wrapper->pc_factory() | 
|  | ->GetRtpSenderCapabilities(MediaType::VIDEO) | 
|  | .codecs; | 
|  | not_vp8_codecs.erase( | 
|  | std::remove_if(not_vp8_codecs.begin(), not_vp8_codecs.end(), | 
|  | [&](const auto& codec) { | 
|  | return absl::EqualsIgnoreCase(codec.name, vp8->name); | 
|  | }), | 
|  | not_vp8_codecs.end()); | 
|  |  | 
|  | auto transceiver_or_error = | 
|  | local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO); | 
|  | ASSERT_TRUE(transceiver_or_error.ok()); | 
|  | scoped_refptr<RtpTransceiverInterface> video_transceiver = | 
|  | transceiver_or_error.MoveValue(); | 
|  |  | 
|  | // Negotiation, create offer and apply it | 
|  | std::unique_ptr<SessionDescriptionInterface> offer = | 
|  | CreateOffer(local_pc_wrapper); | 
|  | scoped_refptr<MockSetSessionDescriptionObserver> p1 = | 
|  | SetLocalDescription(local_pc_wrapper, offer.get()); | 
|  | scoped_refptr<MockSetSessionDescriptionObserver> p2 = | 
|  | SetRemoteDescription(remote_pc_wrapper, offer.get()); | 
|  | EXPECT_TRUE(Await({p1, p2})); | 
|  |  | 
|  | // Update the remote transceiver to reject VP8 | 
|  | std::vector<scoped_refptr<RtpTransceiverInterface>> remote_transceivers = | 
|  | remote_pc_wrapper->pc()->GetTransceivers(); | 
|  | ASSERT_TRUE(!remote_transceivers.empty()); | 
|  | scoped_refptr<RtpTransceiverInterface> remote_video_transceiver = | 
|  | remote_transceivers[0]; | 
|  | ASSERT_TRUE( | 
|  | remote_video_transceiver->SetCodecPreferences(not_vp8_codecs).ok()); | 
|  |  | 
|  | // Create answer and apply it | 
|  | std::unique_ptr<SessionDescriptionInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::optional<RtpCodecCapability> opus = | 
|  | local_pc_wrapper->FindFirstSendCodecWithName(MediaType::AUDIO, "opus"); | 
|  | ASSERT_TRUE(opus); | 
|  |  | 
|  | std::vector<RtpCodecCapability> not_opus_codecs = | 
|  | local_pc_wrapper->pc_factory() | 
|  | ->GetRtpSenderCapabilities(MediaType::AUDIO) | 
|  | .codecs; | 
|  | not_opus_codecs.erase( | 
|  | std::remove_if(not_opus_codecs.begin(), not_opus_codecs.end(), | 
|  | [&](const auto& codec) { | 
|  | return absl::EqualsIgnoreCase(codec.name, opus->name); | 
|  | }), | 
|  | not_opus_codecs.end()); | 
|  |  | 
|  | 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<RtpTransceiverInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<RtpCodecCapability> send_codecs = | 
|  | local_pc_wrapper->pc_factory() | 
|  | ->GetRtpSenderCapabilities(MediaType::AUDIO) | 
|  | .codecs; | 
|  |  | 
|  | std::optional<RtpCodecCapability> opus = | 
|  | local_pc_wrapper->FindFirstSendCodecWithName(MediaType::AUDIO, "opus"); | 
|  | ASSERT_TRUE(opus); | 
|  |  | 
|  | std::optional<RtpCodecCapability> 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<RtpTransceiverInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  |  | 
|  | std::optional<RtpCodecCapability> 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<RtpTransceiverInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::optional<RtpCodecCapability> vp8 = | 
|  | local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp8"); | 
|  | ASSERT_TRUE(vp8); | 
|  |  | 
|  | std::vector<RtpCodecCapability> not_vp8_codecs = | 
|  | local_pc_wrapper->pc_factory() | 
|  | ->GetRtpSenderCapabilities(MediaType::VIDEO) | 
|  | .codecs; | 
|  | not_vp8_codecs.erase( | 
|  | std::remove_if(not_vp8_codecs.begin(), not_vp8_codecs.end(), | 
|  | [&](const auto& codec) { | 
|  | return absl::EqualsIgnoreCase(codec.name, vp8->name); | 
|  | }), | 
|  | not_vp8_codecs.end()); | 
|  |  | 
|  | 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<RtpTransceiverInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::optional<RtpCodecCapability> vp8 = | 
|  | local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp8"); | 
|  | ASSERT_TRUE(vp8); | 
|  | std::optional<RtpCodecCapability> 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<PeerConnectionTestWrapper> local_pc_wrapper = | 
|  | CreatePc(FieldTrials::CreateNoGlobal(field_trials)); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = | 
|  | CreatePc(FieldTrials::CreateNoGlobal(field_trials)); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::optional<RtpCodecCapability> vp8 = | 
|  | local_pc_wrapper->FindFirstSendCodecWithName(MediaType::VIDEO, "vp8"); | 
|  | ASSERT_TRUE(vp8); | 
|  | std::optional<RtpCodecCapability> 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<PeerConnectionTestWrapper> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  |  | 
|  | scoped_refptr<MediaStreamInterface> stream = local_pc_wrapper->GetUserMedia( | 
|  | /*audio=*/false, {}, /*video=*/true, {.width = 640, .height = 360}); | 
|  | scoped_refptr<VideoTrackInterface> 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<std::string> { | 
|  | 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<PeerConnectionTestWrapper> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | if (SkipTestDueToAv1Missing(local_pc_wrapper)) { | 
|  | return; | 
|  | } | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"f", "h", "q"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> codecs = | 
|  | GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_); | 
|  | transceiver->SetCodecPreferences(codecs); | 
|  |  | 
|  | // Standard mode and all layers inactive. | 
|  | scoped_refptr<RtpSenderInterface> 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<const RTCStatsReport> report = GetStats(local_pc_wrapper); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | if (SkipTestDueToAv1Missing(local_pc_wrapper)) { | 
|  | return; | 
|  | } | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"q", "h", "f"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> codecs = | 
|  | GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_); | 
|  | transceiver->SetCodecPreferences(codecs); | 
|  |  | 
|  | scoped_refptr<RtpSenderInterface> 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_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<const RTCStatsReport> 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<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | if (SkipTestDueToAv1Missing(local_pc_wrapper)) { | 
|  | return; | 
|  | } | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"q", "h", "f"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> codecs = | 
|  | GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_); | 
|  | transceiver->SetCodecPreferences(codecs); | 
|  |  | 
|  | scoped_refptr<RtpSenderInterface> 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<const RTCStatsReport> 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<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | if (SkipTestDueToAv1Missing(local_pc_wrapper)) { | 
|  | return; | 
|  | } | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"q", "h", "f"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> codecs = | 
|  | GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_); | 
|  | transceiver->SetCodecPreferences(codecs); | 
|  | scoped_refptr<RtpSenderInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | if (SkipTestDueToAv1Missing(local_pc_wrapper)) { | 
|  | return; | 
|  | } | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"q", "h", "f"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> codecs = | 
|  | GetCapabilitiesAndRestrictToCodec(remote_pc_wrapper, codec_name_); | 
|  | transceiver->SetCodecPreferences(codecs); | 
|  | scoped_refptr<RtpSenderInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | if (SkipTestDueToAv1Missing(local_pc_wrapper)) { | 
|  | return; | 
|  | } | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> 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<MediaStreamInterface> stream = local_pc_wrapper->GetUserMedia( | 
|  | /*audio=*/false, {}, /*video=*/true, {.width = 160, .height = 80}); | 
|  | scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0]; | 
|  | auto transceiver_or_error = | 
|  | local_pc_wrapper->pc()->AddTransceiver(track, init); | 
|  | ASSERT_TRUE(transceiver_or_error.ok()); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | transceiver_or_error.value(); | 
|  |  | 
|  | std::vector<RtpCodecCapability> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | if (SkipTestDueToAv1Missing(local_pc_wrapper)) { | 
|  | return; | 
|  | } | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"q", "h", "f"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> codecs = | 
|  | GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, codec_name_); | 
|  | transceiver->SetCodecPreferences(codecs); | 
|  |  | 
|  | scoped_refptr<RtpSenderInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | if (SkipTestDueToAv1Missing(local_pc_wrapper)) { | 
|  | return; | 
|  | } | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = CreateLayers({"f"}, /*active=*/true); | 
|  |  | 
|  | // This transceiver receives a 1280x720 source. | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> 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<RtpSenderInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | if (SkipTestDueToAv1Missing(local_pc_wrapper)) { | 
|  | return; | 
|  | } | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = CreateLayers({"f"}, /*active=*/true); | 
|  |  | 
|  | // This transceiver receives a 1280x720 source. | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> 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<RtpSenderInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | if (SkipTestDueToAv1Missing(local_pc_wrapper)) { | 
|  | return; | 
|  | } | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = CreateLayers({"f"}, /*active=*/true); | 
|  |  | 
|  | // This transceiver receives a 1280x720 source. | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> 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<RtpSenderInterface> 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<PeerConnectionTestWrapper> CreatePcWithFakeH265( | 
|  | std::unique_ptr<FieldTrialsView> field_trials = nullptr) { | 
|  | std::unique_ptr<FakeWebRtcVideoEncoderFactory> video_encoder_factory = | 
|  | std::make_unique<FakeWebRtcVideoEncoderFactory>(); | 
|  | video_encoder_factory->AddSupportedVideoCodec( | 
|  | SdpVideoFormat("H265", | 
|  | {{"profile-id", "1"}, | 
|  | {"tier-flag", "0"}, | 
|  | {"level-id", "156"}, | 
|  | {"tx-mode", "SRST"}}, | 
|  | {ScalabilityMode::kL1T1})); | 
|  | std::unique_ptr<FakeWebRtcVideoDecoderFactory> video_decoder_factory = | 
|  | std::make_unique<FakeWebRtcVideoDecoderFactory>(); | 
|  | video_decoder_factory->AddSupportedVideoCodecType("H265"); | 
|  | auto pc_wrapper = make_ref_counted<PeerConnectionTestWrapper>( | 
|  | "pc", &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<PeerConnectionTestWrapper> CreatePcWithUnidirectionalH264( | 
|  | std::unique_ptr<FieldTrialsView> field_trials = nullptr) { | 
|  | std::unique_ptr<FakeWebRtcVideoEncoderFactory> video_encoder_factory = | 
|  | std::make_unique<FakeWebRtcVideoEncoderFactory>(); | 
|  | 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<FakeWebRtcVideoDecoderFactory> video_decoder_factory = | 
|  | std::make_unique<FakeWebRtcVideoDecoderFactory>(); | 
|  | 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<PeerConnectionTestWrapper>( | 
|  | "pc", &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<PeerConnectionTestWrapper> local_pc_wrapper = | 
|  | CreatePcWithFakeH265(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = | 
|  | CreatePcWithFakeH265(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | local_pc_wrapper->pc()->AddTransceiver(MediaType::VIDEO).MoveValue(); | 
|  | std::vector<RtpCodecCapability> 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<const RTCStatsReport> report = GetStats(local_pc_wrapper); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | ASSERT_THAT(outbound_rtps, SizeIs(1u)); | 
|  | EXPECT_THAT(GetCurrentCodecMimeType(report, *outbound_rtps[0]), | 
|  | StrCaseEq("video/H265")); | 
|  | } | 
|  |  | 
|  | TEST_F(PeerConnectionEncodingsFakeCodecsIntegrationTest, H265Simulcast) { | 
|  | scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = | 
|  | CreatePcWithFakeH265(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = | 
|  | CreatePcWithFakeH265(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"q", "h", "f"}, /*active=*/true); | 
|  |  | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> 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<const RTCStatsReport> report = GetStats(local_pc_wrapper); | 
|  | std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps = | 
|  | report->GetStatsOfType<RTCOutboundRtpStreamStats>(); | 
|  | 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<PeerConnectionTestWrapper> local_pc_wrapper = | 
|  | CreatePcWithFakeH265(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = | 
|  | CreatePcWithFakeH265(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = CreateLayers({"f"}, /*active=*/true); | 
|  |  | 
|  | scoped_refptr<RtpTransceiverInterface> transceiver = | 
|  | AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper, | 
|  | layers); | 
|  | std::vector<RtpCodecCapability> preferred_codecs = | 
|  | GetCapabilitiesAndRestrictToCodec(local_pc_wrapper, "H265"); | 
|  | transceiver->SetCodecPreferences(preferred_codecs); | 
|  | scoped_refptr<RtpSenderInterface> 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<RtpCodecCapability> 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<PeerConnectionTestWrapper> local_pc_wrapper = | 
|  | CreatePcWithUnidirectionalH264(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = | 
|  | CreatePcWithUnidirectionalH264(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | scoped_refptr<RtpTransceiverInterface> 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<RtpCodecCapability> send_codecs = | 
|  | local_pc_wrapper->pc_factory() | 
|  | ->GetRtpSenderCapabilities(MediaType::VIDEO) | 
|  | .codecs; | 
|  | send_codecs.erase(std::remove_if(send_codecs.begin(), send_codecs.end(), | 
|  | [](const RtpCodecCapability& codec) { | 
|  | return codec.name != "H264"; | 
|  | }), | 
|  | send_codecs.end()); | 
|  | std::vector<RtpCodecCapability> recv_codecs = | 
|  | local_pc_wrapper->pc_factory() | 
|  | ->GetRtpReceiverCapabilities(MediaType::VIDEO) | 
|  | .codecs; | 
|  | recv_codecs.erase(std::remove_if(recv_codecs.begin(), recv_codecs.end(), | 
|  | [](const RtpCodecCapability& codec) { | 
|  | RTC_LOG(LS_ERROR) << codec.name; | 
|  | return codec.name != "H264"; | 
|  | }), | 
|  | recv_codecs.end()); | 
|  | 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<RtpCodecCapability> 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<SessionDescriptionInterface> 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<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc(); | 
|  | scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc(); | 
|  | ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper); | 
|  |  | 
|  | std::vector<SimulcastLayer> layers = | 
|  | CreateLayers({"f", "q"}, /*active=*/true); | 
|  | scoped_refptr<RtpTransceiverInterface> 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 |