| /* |
| * Copyright (c) 2013 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> // max |
| #include <memory> |
| #include <vector> |
| |
| #include "absl/algorithm/container.h" |
| #include "api/task_queue/default_task_queue_factory.h" |
| #include "api/task_queue/task_queue_base.h" |
| #include "api/test/simulated_network.h" |
| #include "api/video/builtin_video_bitrate_allocator_factory.h" |
| #include "api/video/encoded_image.h" |
| #include "api/video/video_bitrate_allocation.h" |
| #include "api/video_codecs/video_encoder.h" |
| #include "call/call.h" |
| #include "call/fake_network_pipe.h" |
| #include "call/rtp_transport_controller_send.h" |
| #include "call/simulated_network.h" |
| #include "call/video_send_stream.h" |
| #include "modules/rtp_rtcp/include/rtp_header_extension_map.h" |
| #include "modules/rtp_rtcp/include/rtp_rtcp.h" |
| #include "modules/rtp_rtcp/source/rtcp_sender.h" |
| #include "modules/rtp_rtcp/source/rtp_header_extensions.h" |
| #include "modules/rtp_rtcp/source/rtp_packet.h" |
| #include "modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.h" |
| #include "modules/video_coding/codecs/vp8/include/vp8.h" |
| #include "modules/video_coding/codecs/vp9/include/vp9.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_base/critical_section.h" |
| #include "rtc_base/event.h" |
| #include "rtc_base/experiments/alr_experiment.h" |
| #include "rtc_base/logging.h" |
| #include "rtc_base/platform_thread.h" |
| #include "rtc_base/rate_limiter.h" |
| #include "rtc_base/synchronization/sequence_checker.h" |
| #include "rtc_base/task_queue_for_test.h" |
| #include "rtc_base/task_utils/to_queued_task.h" |
| #include "rtc_base/time_utils.h" |
| #include "rtc_base/unique_id_generator.h" |
| #include "system_wrappers/include/sleep.h" |
| #include "test/call_test.h" |
| #include "test/configurable_frame_size_encoder.h" |
| #include "test/fake_encoder.h" |
| #include "test/fake_texture_frame.h" |
| #include "test/field_trial.h" |
| #include "test/frame_forwarder.h" |
| #include "test/frame_generator_capturer.h" |
| #include "test/frame_utils.h" |
| #include "test/gmock.h" |
| #include "test/gtest.h" |
| #include "test/null_transport.h" |
| #include "test/rtcp_packet_parser.h" |
| #include "test/rtp_header_parser.h" |
| #include "test/testsupport/perf_test.h" |
| #include "test/video_encoder_proxy_factory.h" |
| #include "video/send_statistics_proxy.h" |
| #include "video/transport_adapter.h" |
| #include "video/video_send_stream.h" |
| |
| namespace webrtc { |
| namespace test { |
| class VideoSendStreamPeer { |
| public: |
| explicit VideoSendStreamPeer(webrtc::VideoSendStream* base_class_stream) |
| : internal_stream_( |
| static_cast<internal::VideoSendStream*>(base_class_stream)) {} |
| absl::optional<float> GetPacingFactorOverride() const { |
| return internal_stream_->GetPacingFactorOverride(); |
| } |
| |
| private: |
| internal::VideoSendStream const* const internal_stream_; |
| }; |
| } // namespace test |
| |
| namespace { |
| enum : int { // The first valid value is 1. |
| kAbsSendTimeExtensionId = 1, |
| kTimestampOffsetExtensionId, |
| kTransportSequenceNumberExtensionId, |
| kVideoContentTypeExtensionId, |
| kVideoRotationExtensionId, |
| kVideoTimingExtensionId, |
| }; |
| |
| constexpr int64_t kRtcpIntervalMs = 1000; |
| |
| enum VideoFormat { |
| kGeneric, |
| kVP8, |
| }; |
| } // namespace |
| |
| VideoFrame CreateVideoFrame(int width, int height, uint8_t data); |
| |
| class VideoSendStreamTest : public test::CallTest { |
| public: |
| VideoSendStreamTest() { |
| RegisterRtpExtension(RtpExtension(RtpExtension::kTransportSequenceNumberUri, |
| kTransportSequenceNumberExtensionId)); |
| } |
| |
| protected: |
| void TestNackRetransmission(uint32_t retransmit_ssrc, |
| uint8_t retransmit_payload_type); |
| void TestPacketFragmentationSize(VideoFormat format, bool with_fec); |
| |
| void TestVp9NonFlexMode(uint8_t num_temporal_layers, |
| uint8_t num_spatial_layers); |
| |
| void TestRequestSourceRotateVideo(bool support_orientation_ext); |
| }; |
| |
| TEST_F(VideoSendStreamTest, CanStartStartedStream) { |
| SendTask(RTC_FROM_HERE, task_queue(), [this]() { |
| CreateSenderCall(); |
| |
| test::NullTransport transport; |
| CreateSendConfig(1, 0, 0, &transport); |
| CreateVideoStreams(); |
| GetVideoSendStream()->Start(); |
| GetVideoSendStream()->Start(); |
| DestroyStreams(); |
| DestroyCalls(); |
| }); |
| } |
| |
| TEST_F(VideoSendStreamTest, CanStopStoppedStream) { |
| SendTask(RTC_FROM_HERE, task_queue(), [this]() { |
| CreateSenderCall(); |
| |
| test::NullTransport transport; |
| CreateSendConfig(1, 0, 0, &transport); |
| CreateVideoStreams(); |
| GetVideoSendStream()->Stop(); |
| GetVideoSendStream()->Stop(); |
| DestroyStreams(); |
| DestroyCalls(); |
| }); |
| } |
| |
| TEST_F(VideoSendStreamTest, SupportsCName) { |
| static std::string kCName = "PjQatC14dGfbVwGPUOA9IH7RlsFDbWl4AhXEiDsBizo="; |
| class CNameObserver : public test::SendTest { |
| public: |
| CNameObserver() : SendTest(kDefaultTimeoutMs) {} |
| |
| private: |
| Action OnSendRtcp(const uint8_t* packet, size_t length) override { |
| test::RtcpPacketParser parser; |
| EXPECT_TRUE(parser.Parse(packet, length)); |
| if (parser.sdes()->num_packets() > 0) { |
| EXPECT_EQ(1u, parser.sdes()->chunks().size()); |
| EXPECT_EQ(kCName, parser.sdes()->chunks()[0].cname); |
| |
| observation_complete_.Set(); |
| } |
| |
| return SEND_PACKET; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->rtp.c_name = kCName; |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) << "Timed out while waiting for RTCP with CNAME."; |
| } |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, SupportsAbsoluteSendTime) { |
| class AbsoluteSendTimeObserver : public test::SendTest { |
| public: |
| AbsoluteSendTimeObserver() : SendTest(kDefaultTimeoutMs) { |
| extensions_.Register<AbsoluteSendTime>(kAbsSendTimeExtensionId); |
| } |
| |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| RtpPacket rtp_packet(&extensions_); |
| EXPECT_TRUE(rtp_packet.Parse(packet, length)); |
| |
| uint32_t abs_send_time = 0; |
| EXPECT_FALSE(rtp_packet.HasExtension<TransmissionOffset>()); |
| EXPECT_TRUE(rtp_packet.GetExtension<AbsoluteSendTime>(&abs_send_time)); |
| if (abs_send_time != 0) { |
| // Wait for at least one packet with a non-zero send time. The send time |
| // is a 16-bit value derived from the system clock, and it is valid |
| // for a packet to have a zero send time. To tell that from an |
| // unpopulated value we'll wait for a packet with non-zero send time. |
| observation_complete_.Set(); |
| } else { |
| RTC_LOG(LS_WARNING) |
| << "Got a packet with zero absoluteSendTime, waiting" |
| " for another packet..."; |
| } |
| |
| return SEND_PACKET; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->rtp.extensions.clear(); |
| send_config->rtp.extensions.push_back( |
| RtpExtension(RtpExtension::kAbsSendTimeUri, kAbsSendTimeExtensionId)); |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) << "Timed out while waiting for single RTP packet."; |
| } |
| |
| private: |
| RtpHeaderExtensionMap extensions_; |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, SupportsTransmissionTimeOffset) { |
| static const int kEncodeDelayMs = 5; |
| class TransmissionTimeOffsetObserver : public test::SendTest { |
| public: |
| TransmissionTimeOffsetObserver() |
| : SendTest(kDefaultTimeoutMs), encoder_factory_([]() { |
| return std::make_unique<test::DelayedEncoder>( |
| Clock::GetRealTimeClock(), kEncodeDelayMs); |
| }) { |
| extensions_.Register<TransmissionOffset>(kTimestampOffsetExtensionId); |
| } |
| |
| private: |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| RtpPacket rtp_packet(&extensions_); |
| EXPECT_TRUE(rtp_packet.Parse(packet, length)); |
| |
| int32_t toffset = 0; |
| EXPECT_TRUE(rtp_packet.GetExtension<TransmissionOffset>(&toffset)); |
| EXPECT_FALSE(rtp_packet.HasExtension<AbsoluteSendTime>()); |
| EXPECT_GT(toffset, 0); |
| observation_complete_.Set(); |
| |
| return SEND_PACKET; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->encoder_settings.encoder_factory = &encoder_factory_; |
| send_config->rtp.extensions.clear(); |
| send_config->rtp.extensions.push_back(RtpExtension( |
| RtpExtension::kTimestampOffsetUri, kTimestampOffsetExtensionId)); |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) << "Timed out while waiting for a single RTP packet."; |
| } |
| |
| test::FunctionVideoEncoderFactory encoder_factory_; |
| RtpHeaderExtensionMap extensions_; |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, SupportsTransportWideSequenceNumbers) { |
| static const uint8_t kExtensionId = kTransportSequenceNumberExtensionId; |
| class TransportWideSequenceNumberObserver : public test::SendTest { |
| public: |
| TransportWideSequenceNumberObserver() |
| : SendTest(kDefaultTimeoutMs), encoder_factory_([]() { |
| return std::make_unique<test::FakeEncoder>( |
| Clock::GetRealTimeClock()); |
| }) { |
| extensions_.Register<TransportSequenceNumber>(kExtensionId); |
| } |
| |
| private: |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| RtpPacket rtp_packet(&extensions_); |
| EXPECT_TRUE(rtp_packet.Parse(packet, length)); |
| |
| EXPECT_TRUE(rtp_packet.HasExtension<TransportSequenceNumber>()); |
| EXPECT_FALSE(rtp_packet.HasExtension<TransmissionOffset>()); |
| EXPECT_FALSE(rtp_packet.HasExtension<AbsoluteSendTime>()); |
| |
| observation_complete_.Set(); |
| |
| return SEND_PACKET; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->encoder_settings.encoder_factory = &encoder_factory_; |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) << "Timed out while waiting for a single RTP packet."; |
| } |
| |
| test::FunctionVideoEncoderFactory encoder_factory_; |
| RtpHeaderExtensionMap extensions_; |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, SupportsVideoRotation) { |
| class VideoRotationObserver : public test::SendTest { |
| public: |
| VideoRotationObserver() : SendTest(kDefaultTimeoutMs) { |
| extensions_.Register<VideoOrientation>(kVideoRotationExtensionId); |
| } |
| |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| RtpPacket rtp_packet(&extensions_); |
| EXPECT_TRUE(rtp_packet.Parse(packet, length)); |
| // Only the last packet of the frame is required to have the extension. |
| if (!rtp_packet.Marker()) |
| return SEND_PACKET; |
| EXPECT_EQ(rtp_packet.GetExtension<VideoOrientation>(), kVideoRotation_90); |
| observation_complete_.Set(); |
| return SEND_PACKET; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->rtp.extensions.clear(); |
| send_config->rtp.extensions.push_back(RtpExtension( |
| RtpExtension::kVideoRotationUri, kVideoRotationExtensionId)); |
| } |
| |
| void OnFrameGeneratorCapturerCreated( |
| test::FrameGeneratorCapturer* frame_generator_capturer) override { |
| frame_generator_capturer->SetFakeRotation(kVideoRotation_90); |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) << "Timed out while waiting for single RTP packet."; |
| } |
| |
| private: |
| RtpHeaderExtensionMap extensions_; |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, SupportsVideoContentType) { |
| class VideoContentTypeObserver : public test::SendTest { |
| public: |
| VideoContentTypeObserver() |
| : SendTest(kDefaultTimeoutMs), first_frame_sent_(false) { |
| extensions_.Register<VideoContentTypeExtension>( |
| kVideoContentTypeExtensionId); |
| } |
| |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| RtpPacket rtp_packet(&extensions_); |
| EXPECT_TRUE(rtp_packet.Parse(packet, length)); |
| // Only the last packet of the key-frame must have extension. |
| if (!rtp_packet.Marker() || first_frame_sent_) |
| return SEND_PACKET; |
| // First marker bit seen means that the first frame is sent. |
| first_frame_sent_ = true; |
| VideoContentType type; |
| EXPECT_TRUE(rtp_packet.GetExtension<VideoContentTypeExtension>(&type)); |
| EXPECT_TRUE(videocontenttypehelpers::IsScreenshare(type)); |
| observation_complete_.Set(); |
| return SEND_PACKET; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->rtp.extensions.clear(); |
| send_config->rtp.extensions.push_back(RtpExtension( |
| RtpExtension::kVideoContentTypeUri, kVideoContentTypeExtensionId)); |
| encoder_config->content_type = VideoEncoderConfig::ContentType::kScreen; |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) << "Timed out while waiting for single RTP packet."; |
| } |
| |
| private: |
| bool first_frame_sent_; |
| RtpHeaderExtensionMap extensions_; |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, SupportsVideoTimingFrames) { |
| class VideoTimingObserver : public test::SendTest { |
| public: |
| VideoTimingObserver() |
| : SendTest(kDefaultTimeoutMs), first_frame_sent_(false) { |
| extensions_.Register<VideoTimingExtension>(kVideoTimingExtensionId); |
| } |
| |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| RtpPacket rtp_packet(&extensions_); |
| EXPECT_TRUE(rtp_packet.Parse(packet, length)); |
| // Only the last packet of the frame must have extension. |
| // Also don't check packets of the second frame if they happen to get |
| // through before the test terminates. |
| if (!rtp_packet.Marker() || first_frame_sent_) |
| return SEND_PACKET; |
| EXPECT_TRUE(rtp_packet.HasExtension<VideoTimingExtension>()); |
| observation_complete_.Set(); |
| first_frame_sent_ = true; |
| return SEND_PACKET; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->rtp.extensions.clear(); |
| send_config->rtp.extensions.push_back( |
| RtpExtension(RtpExtension::kVideoTimingUri, kVideoTimingExtensionId)); |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) << "Timed out while waiting for timing frames."; |
| } |
| |
| private: |
| RtpHeaderExtensionMap extensions_; |
| bool first_frame_sent_; |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| class FakeReceiveStatistics : public ReceiveStatisticsProvider { |
| public: |
| FakeReceiveStatistics(uint32_t send_ssrc, |
| uint32_t last_sequence_number, |
| uint32_t cumulative_lost, |
| uint8_t fraction_lost) { |
| stat_.SetMediaSsrc(send_ssrc); |
| stat_.SetExtHighestSeqNum(last_sequence_number); |
| stat_.SetCumulativeLost(cumulative_lost); |
| stat_.SetFractionLost(fraction_lost); |
| } |
| |
| std::vector<rtcp::ReportBlock> RtcpReportBlocks(size_t max_blocks) override { |
| EXPECT_GE(max_blocks, 1u); |
| return {stat_}; |
| } |
| |
| private: |
| rtcp::ReportBlock stat_; |
| }; |
| |
| class UlpfecObserver : public test::EndToEndTest { |
| public: |
| // Some of the test cases are expected to time out. |
| // Use a shorter timeout window than the default one for those. |
| static constexpr int kReducedTimeoutMs = 10000; |
| |
| UlpfecObserver(bool header_extensions_enabled, |
| bool use_nack, |
| bool expect_red, |
| bool expect_ulpfec, |
| const std::string& codec, |
| VideoEncoderFactory* encoder_factory) |
| : EndToEndTest(expect_ulpfec ? VideoSendStreamTest::kDefaultTimeoutMs |
| : kReducedTimeoutMs), |
| encoder_factory_(encoder_factory), |
| payload_name_(codec), |
| use_nack_(use_nack), |
| expect_red_(expect_red), |
| expect_ulpfec_(expect_ulpfec), |
| sent_media_(false), |
| sent_ulpfec_(false), |
| header_extensions_enabled_(header_extensions_enabled) { |
| extensions_.Register<AbsoluteSendTime>(kAbsSendTimeExtensionId); |
| extensions_.Register<TransportSequenceNumber>( |
| kTransportSequenceNumberExtensionId); |
| } |
| |
| private: |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| RtpPacket rtp_packet(&extensions_); |
| EXPECT_TRUE(rtp_packet.Parse(packet, length)); |
| |
| int encapsulated_payload_type = -1; |
| if (rtp_packet.PayloadType() == VideoSendStreamTest::kRedPayloadType) { |
| EXPECT_TRUE(expect_red_); |
| encapsulated_payload_type = rtp_packet.payload()[0]; |
| if (encapsulated_payload_type != |
| VideoSendStreamTest::kFakeVideoSendPayloadType) { |
| EXPECT_EQ(VideoSendStreamTest::kUlpfecPayloadType, |
| encapsulated_payload_type); |
| } |
| } else { |
| EXPECT_EQ(VideoSendStreamTest::kFakeVideoSendPayloadType, |
| rtp_packet.PayloadType()); |
| if (rtp_packet.payload_size() > 0) { |
| // Not padding-only, media received outside of RED. |
| EXPECT_FALSE(expect_red_); |
| sent_media_ = true; |
| } |
| } |
| |
| if (header_extensions_enabled_) { |
| uint32_t abs_send_time; |
| EXPECT_TRUE(rtp_packet.GetExtension<AbsoluteSendTime>(&abs_send_time)); |
| uint16_t transport_seq_num; |
| EXPECT_TRUE( |
| rtp_packet.GetExtension<TransportSequenceNumber>(&transport_seq_num)); |
| if (!first_packet_) { |
| uint32_t kHalf24BitsSpace = 0xFFFFFF / 2; |
| if (abs_send_time <= kHalf24BitsSpace && |
| prev_abs_send_time_ > kHalf24BitsSpace) { |
| // 24 bits wrap. |
| EXPECT_GT(prev_abs_send_time_, abs_send_time); |
| } else { |
| EXPECT_GE(abs_send_time, prev_abs_send_time_); |
| } |
| |
| uint16_t seq_num_diff = transport_seq_num - prev_transport_seq_num_; |
| EXPECT_EQ(1, seq_num_diff); |
| } |
| first_packet_ = false; |
| prev_abs_send_time_ = abs_send_time; |
| prev_transport_seq_num_ = transport_seq_num; |
| } |
| |
| if (encapsulated_payload_type != -1) { |
| if (encapsulated_payload_type == |
| VideoSendStreamTest::kUlpfecPayloadType) { |
| EXPECT_TRUE(expect_ulpfec_); |
| sent_ulpfec_ = true; |
| } else { |
| sent_media_ = true; |
| } |
| } |
| |
| if (sent_media_ && sent_ulpfec_) { |
| observation_complete_.Set(); |
| } |
| |
| return SEND_PACKET; |
| } |
| |
| std::unique_ptr<test::PacketTransport> CreateSendTransport( |
| TaskQueueBase* task_queue, |
| Call* sender_call) override { |
| // At low RTT (< kLowRttNackMs) -> NACK only, no FEC. |
| // Configure some network delay. |
| const int kNetworkDelayMs = 100; |
| BuiltInNetworkBehaviorConfig config; |
| config.loss_percent = 5; |
| config.queue_delay_ms = kNetworkDelayMs; |
| return std::make_unique<test::PacketTransport>( |
| task_queue, sender_call, this, test::PacketTransport::kSender, |
| VideoSendStreamTest::payload_type_map_, |
| std::make_unique<FakeNetworkPipe>( |
| Clock::GetRealTimeClock(), |
| std::make_unique<SimulatedNetwork>(config))); |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| if (use_nack_) { |
| send_config->rtp.nack.rtp_history_ms = |
| (*receive_configs)[0].rtp.nack.rtp_history_ms = |
| VideoSendStreamTest::kNackRtpHistoryMs; |
| } |
| send_config->encoder_settings.encoder_factory = encoder_factory_; |
| send_config->rtp.payload_name = payload_name_; |
| send_config->rtp.ulpfec.red_payload_type = |
| VideoSendStreamTest::kRedPayloadType; |
| send_config->rtp.ulpfec.ulpfec_payload_type = |
| VideoSendStreamTest::kUlpfecPayloadType; |
| if (!header_extensions_enabled_) { |
| send_config->rtp.extensions.clear(); |
| } else { |
| send_config->rtp.extensions.push_back( |
| RtpExtension(RtpExtension::kAbsSendTimeUri, kAbsSendTimeExtensionId)); |
| } |
| (*receive_configs)[0].rtp.extensions = send_config->rtp.extensions; |
| encoder_config->codec_type = PayloadStringToCodecType(payload_name_); |
| (*receive_configs)[0].rtp.red_payload_type = |
| send_config->rtp.ulpfec.red_payload_type; |
| (*receive_configs)[0].rtp.ulpfec_payload_type = |
| send_config->rtp.ulpfec.ulpfec_payload_type; |
| } |
| |
| void PerformTest() override { |
| EXPECT_EQ(expect_ulpfec_, Wait()) |
| << "Timed out waiting for ULPFEC and/or media packets."; |
| } |
| |
| VideoEncoderFactory* encoder_factory_; |
| RtpHeaderExtensionMap extensions_; |
| std::string payload_name_; |
| const bool use_nack_; |
| const bool expect_red_; |
| const bool expect_ulpfec_; |
| bool sent_media_; |
| bool sent_ulpfec_; |
| const bool header_extensions_enabled_; |
| bool first_packet_ = true; |
| uint32_t prev_abs_send_time_ = 0; |
| uint16_t prev_transport_seq_num_ = 0; |
| }; |
| |
| TEST_F(VideoSendStreamTest, SupportsUlpfecWithExtensions) { |
| test::FunctionVideoEncoderFactory encoder_factory( |
| []() { return VP8Encoder::Create(); }); |
| UlpfecObserver test(true, false, true, true, "VP8", &encoder_factory); |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, SupportsUlpfecWithoutExtensions) { |
| test::FunctionVideoEncoderFactory encoder_factory( |
| []() { return VP8Encoder::Create(); }); |
| UlpfecObserver test(false, false, true, true, "VP8", &encoder_factory); |
| RunBaseTest(&test); |
| } |
| |
| class VideoSendStreamWithoutUlpfecTest : public test::CallTest { |
| protected: |
| VideoSendStreamWithoutUlpfecTest() |
| : field_trial_("WebRTC-DisableUlpFecExperiment/Enabled/") {} |
| |
| test::ScopedFieldTrials field_trial_; |
| }; |
| |
| TEST_F(VideoSendStreamWithoutUlpfecTest, NoUlpfecIfDisabledThroughFieldTrial) { |
| test::FunctionVideoEncoderFactory encoder_factory( |
| []() { return VP8Encoder::Create(); }); |
| UlpfecObserver test(false, false, false, false, "VP8", &encoder_factory); |
| RunBaseTest(&test); |
| } |
| |
| // The FEC scheme used is not efficient for H264, so we should not use RED/FEC |
| // since we'll still have to re-request FEC packets, effectively wasting |
| // bandwidth since the receiver has to wait for FEC retransmissions to determine |
| // that the received state is actually decodable. |
| TEST_F(VideoSendStreamTest, DoesNotUtilizeUlpfecForH264WithNackEnabled) { |
| test::FunctionVideoEncoderFactory encoder_factory([]() { |
| return std::make_unique<test::FakeH264Encoder>(Clock::GetRealTimeClock()); |
| }); |
| UlpfecObserver test(false, true, false, false, "H264", &encoder_factory); |
| RunBaseTest(&test); |
| } |
| |
| // Without retransmissions FEC for H264 is fine. |
| TEST_F(VideoSendStreamTest, DoesUtilizeUlpfecForH264WithoutNackEnabled) { |
| test::FunctionVideoEncoderFactory encoder_factory([]() { |
| return std::make_unique<test::FakeH264Encoder>(Clock::GetRealTimeClock()); |
| }); |
| UlpfecObserver test(false, false, true, true, "H264", &encoder_factory); |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, DoesUtilizeUlpfecForVp8WithNackEnabled) { |
| test::FunctionVideoEncoderFactory encoder_factory( |
| []() { return VP8Encoder::Create(); }); |
| UlpfecObserver test(false, true, true, true, "VP8", &encoder_factory); |
| RunBaseTest(&test); |
| } |
| |
| #if defined(RTC_ENABLE_VP9) |
| TEST_F(VideoSendStreamTest, DoesUtilizeUlpfecForVp9WithNackEnabled) { |
| test::FunctionVideoEncoderFactory encoder_factory( |
| []() { return VP9Encoder::Create(); }); |
| UlpfecObserver test(false, true, true, true, "VP9", &encoder_factory); |
| RunBaseTest(&test); |
| } |
| #endif // defined(RTC_ENABLE_VP9) |
| |
| TEST_F(VideoSendStreamTest, SupportsUlpfecWithMultithreadedH264) { |
| std::unique_ptr<TaskQueueFactory> task_queue_factory = |
| CreateDefaultTaskQueueFactory(); |
| test::FunctionVideoEncoderFactory encoder_factory([&]() { |
| return std::make_unique<test::MultithreadedFakeH264Encoder>( |
| Clock::GetRealTimeClock(), task_queue_factory.get()); |
| }); |
| UlpfecObserver test(false, false, true, true, "H264", &encoder_factory); |
| RunBaseTest(&test); |
| } |
| |
| // TODO(brandtr): Move these FlexFEC tests when we have created |
| // FlexfecSendStream. |
| class FlexfecObserver : public test::EndToEndTest { |
| public: |
| FlexfecObserver(bool header_extensions_enabled, |
| bool use_nack, |
| const std::string& codec, |
| VideoEncoderFactory* encoder_factory, |
| size_t num_video_streams) |
| : EndToEndTest(VideoSendStreamTest::kDefaultTimeoutMs), |
| encoder_factory_(encoder_factory), |
| payload_name_(codec), |
| use_nack_(use_nack), |
| sent_media_(false), |
| sent_flexfec_(false), |
| header_extensions_enabled_(header_extensions_enabled), |
| num_video_streams_(num_video_streams) { |
| extensions_.Register<AbsoluteSendTime>(kAbsSendTimeExtensionId); |
| extensions_.Register<TransmissionOffset>(kTimestampOffsetExtensionId); |
| extensions_.Register<TransportSequenceNumber>( |
| kTransportSequenceNumberExtensionId); |
| } |
| |
| size_t GetNumFlexfecStreams() const override { return 1; } |
| size_t GetNumVideoStreams() const override { return num_video_streams_; } |
| |
| private: |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| RtpPacket rtp_packet(&extensions_); |
| EXPECT_TRUE(rtp_packet.Parse(packet, length)); |
| |
| if (rtp_packet.PayloadType() == VideoSendStreamTest::kFlexfecPayloadType) { |
| EXPECT_EQ(VideoSendStreamTest::kFlexfecSendSsrc, rtp_packet.Ssrc()); |
| sent_flexfec_ = true; |
| } else { |
| EXPECT_EQ(VideoSendStreamTest::kFakeVideoSendPayloadType, |
| rtp_packet.PayloadType()); |
| EXPECT_THAT(::testing::make_tuple(VideoSendStreamTest::kVideoSendSsrcs, |
| num_video_streams_), |
| ::testing::Contains(rtp_packet.Ssrc())); |
| sent_media_ = true; |
| } |
| |
| if (header_extensions_enabled_) { |
| EXPECT_TRUE(rtp_packet.HasExtension<AbsoluteSendTime>()); |
| EXPECT_TRUE(rtp_packet.HasExtension<TransmissionOffset>()); |
| EXPECT_TRUE(rtp_packet.HasExtension<TransportSequenceNumber>()); |
| } |
| |
| if (sent_media_ && sent_flexfec_) { |
| observation_complete_.Set(); |
| } |
| |
| return SEND_PACKET; |
| } |
| |
| std::unique_ptr<test::PacketTransport> CreateSendTransport( |
| TaskQueueBase* task_queue, |
| Call* sender_call) override { |
| // At low RTT (< kLowRttNackMs) -> NACK only, no FEC. |
| // Therefore we need some network delay. |
| const int kNetworkDelayMs = 100; |
| BuiltInNetworkBehaviorConfig config; |
| config.loss_percent = 5; |
| config.queue_delay_ms = kNetworkDelayMs; |
| return std::make_unique<test::PacketTransport>( |
| task_queue, sender_call, this, test::PacketTransport::kSender, |
| VideoSendStreamTest::payload_type_map_, |
| std::make_unique<FakeNetworkPipe>( |
| Clock::GetRealTimeClock(), |
| std::make_unique<SimulatedNetwork>(config))); |
| } |
| |
| std::unique_ptr<test::PacketTransport> CreateReceiveTransport( |
| TaskQueueBase* task_queue) override { |
| // We need the RTT to be >200 ms to send FEC and the network delay for the |
| // send transport is 100 ms, so add 100 ms (but no loss) on the return link. |
| BuiltInNetworkBehaviorConfig config; |
| config.loss_percent = 0; |
| config.queue_delay_ms = 100; |
| return std::make_unique<test::PacketTransport>( |
| task_queue, nullptr, this, test::PacketTransport::kReceiver, |
| VideoSendStreamTest::payload_type_map_, |
| std::make_unique<FakeNetworkPipe>( |
| Clock::GetRealTimeClock(), |
| std::make_unique<SimulatedNetwork>(config))); |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| if (use_nack_) { |
| send_config->rtp.nack.rtp_history_ms = |
| (*receive_configs)[0].rtp.nack.rtp_history_ms = |
| VideoSendStreamTest::kNackRtpHistoryMs; |
| } |
| send_config->encoder_settings.encoder_factory = encoder_factory_; |
| send_config->rtp.payload_name = payload_name_; |
| if (header_extensions_enabled_) { |
| send_config->rtp.extensions.push_back( |
| RtpExtension(RtpExtension::kAbsSendTimeUri, kAbsSendTimeExtensionId)); |
| send_config->rtp.extensions.push_back(RtpExtension( |
| RtpExtension::kTimestampOffsetUri, kTimestampOffsetExtensionId)); |
| } else { |
| send_config->rtp.extensions.clear(); |
| } |
| (*receive_configs)[0].rtp.extensions = send_config->rtp.extensions; |
| encoder_config->codec_type = PayloadStringToCodecType(payload_name_); |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) |
| << "Timed out waiting for FlexFEC and/or media packets."; |
| } |
| |
| VideoEncoderFactory* encoder_factory_; |
| RtpHeaderExtensionMap extensions_; |
| std::string payload_name_; |
| const bool use_nack_; |
| bool sent_media_; |
| bool sent_flexfec_; |
| const bool header_extensions_enabled_; |
| const size_t num_video_streams_; |
| }; |
| |
| TEST_F(VideoSendStreamTest, SupportsFlexfecVp8) { |
| test::FunctionVideoEncoderFactory encoder_factory( |
| []() { return VP8Encoder::Create(); }); |
| FlexfecObserver test(false, false, "VP8", &encoder_factory, 1); |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, SupportsFlexfecSimulcastVp8) { |
| test::FunctionVideoEncoderFactory encoder_factory( |
| []() { return VP8Encoder::Create(); }); |
| FlexfecObserver test(false, false, "VP8", &encoder_factory, 2); |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, SupportsFlexfecWithNackVp8) { |
| test::FunctionVideoEncoderFactory encoder_factory( |
| []() { return VP8Encoder::Create(); }); |
| FlexfecObserver test(false, true, "VP8", &encoder_factory, 1); |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, SupportsFlexfecWithRtpExtensionsVp8) { |
| test::FunctionVideoEncoderFactory encoder_factory( |
| []() { return VP8Encoder::Create(); }); |
| FlexfecObserver test(true, false, "VP8", &encoder_factory, 1); |
| RunBaseTest(&test); |
| } |
| |
| #if defined(RTC_ENABLE_VP9) |
| TEST_F(VideoSendStreamTest, SupportsFlexfecVp9) { |
| test::FunctionVideoEncoderFactory encoder_factory( |
| []() { return VP9Encoder::Create(); }); |
| FlexfecObserver test(false, false, "VP9", &encoder_factory, 1); |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, SupportsFlexfecWithNackVp9) { |
| test::FunctionVideoEncoderFactory encoder_factory( |
| []() { return VP9Encoder::Create(); }); |
| FlexfecObserver test(false, true, "VP9", &encoder_factory, 1); |
| RunBaseTest(&test); |
| } |
| #endif // defined(RTC_ENABLE_VP9) |
| |
| TEST_F(VideoSendStreamTest, SupportsFlexfecH264) { |
| test::FunctionVideoEncoderFactory encoder_factory([]() { |
| return std::make_unique<test::FakeH264Encoder>(Clock::GetRealTimeClock()); |
| }); |
| FlexfecObserver test(false, false, "H264", &encoder_factory, 1); |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, SupportsFlexfecWithNackH264) { |
| test::FunctionVideoEncoderFactory encoder_factory([]() { |
| return std::make_unique<test::FakeH264Encoder>(Clock::GetRealTimeClock()); |
| }); |
| FlexfecObserver test(false, true, "H264", &encoder_factory, 1); |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, SupportsFlexfecWithMultithreadedH264) { |
| std::unique_ptr<TaskQueueFactory> task_queue_factory = |
| CreateDefaultTaskQueueFactory(); |
| test::FunctionVideoEncoderFactory encoder_factory([&]() { |
| return std::make_unique<test::MultithreadedFakeH264Encoder>( |
| Clock::GetRealTimeClock(), task_queue_factory.get()); |
| }); |
| |
| FlexfecObserver test(false, false, "H264", &encoder_factory, 1); |
| RunBaseTest(&test); |
| } |
| |
| void VideoSendStreamTest::TestNackRetransmission( |
| uint32_t retransmit_ssrc, |
| uint8_t retransmit_payload_type) { |
| class NackObserver : public test::SendTest { |
| public: |
| explicit NackObserver(uint32_t retransmit_ssrc, |
| uint8_t retransmit_payload_type) |
| : SendTest(kDefaultTimeoutMs), |
| send_count_(0), |
| retransmit_count_(0), |
| retransmit_ssrc_(retransmit_ssrc), |
| retransmit_payload_type_(retransmit_payload_type) {} |
| |
| private: |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| RtpPacket rtp_packet; |
| EXPECT_TRUE(rtp_packet.Parse(packet, length)); |
| |
| // NACK packets two times at some arbitrary points. |
| const int kNackedPacketsAtOnceCount = 3; |
| const int kRetransmitTarget = kNackedPacketsAtOnceCount * 2; |
| |
| // Skip padding packets because they will never be retransmitted. |
| if (rtp_packet.payload_size() == 0) { |
| return SEND_PACKET; |
| } |
| |
| ++send_count_; |
| |
| // NACK packets at arbitrary points. |
| if (send_count_ == 5 || send_count_ == 25) { |
| nacked_sequence_numbers_.insert( |
| nacked_sequence_numbers_.end(), |
| non_padding_sequence_numbers_.end() - kNackedPacketsAtOnceCount, |
| non_padding_sequence_numbers_.end()); |
| |
| RtpRtcp::Configuration config; |
| config.clock = Clock::GetRealTimeClock(); |
| config.outgoing_transport = transport_adapter_.get(); |
| config.rtcp_report_interval_ms = kRtcpIntervalMs; |
| config.local_media_ssrc = kReceiverLocalVideoSsrc; |
| RTCPSender rtcp_sender(config); |
| |
| rtcp_sender.SetRTCPStatus(RtcpMode::kReducedSize); |
| rtcp_sender.SetRemoteSSRC(kVideoSendSsrcs[0]); |
| |
| RTCPSender::FeedbackState feedback_state; |
| |
| EXPECT_EQ(0, rtcp_sender.SendRTCP( |
| feedback_state, kRtcpNack, |
| static_cast<int>(nacked_sequence_numbers_.size()), |
| &nacked_sequence_numbers_.front())); |
| } |
| |
| uint16_t sequence_number = rtp_packet.SequenceNumber(); |
| if (rtp_packet.Ssrc() == retransmit_ssrc_ && |
| retransmit_ssrc_ != kVideoSendSsrcs[0]) { |
| // Not kVideoSendSsrcs[0], assume correct RTX packet. Extract sequence |
| // number. |
| const uint8_t* rtx_header = rtp_packet.payload().data(); |
| sequence_number = (rtx_header[0] << 8) + rtx_header[1]; |
| } |
| |
| auto found = absl::c_find(nacked_sequence_numbers_, sequence_number); |
| if (found != nacked_sequence_numbers_.end()) { |
| nacked_sequence_numbers_.erase(found); |
| |
| if (++retransmit_count_ == kRetransmitTarget) { |
| EXPECT_EQ(retransmit_ssrc_, rtp_packet.Ssrc()); |
| EXPECT_EQ(retransmit_payload_type_, rtp_packet.PayloadType()); |
| observation_complete_.Set(); |
| } |
| } else { |
| non_padding_sequence_numbers_.push_back(sequence_number); |
| } |
| |
| return SEND_PACKET; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| transport_adapter_.reset( |
| new internal::TransportAdapter(send_config->send_transport)); |
| transport_adapter_->Enable(); |
| send_config->rtp.nack.rtp_history_ms = kNackRtpHistoryMs; |
| send_config->rtp.rtx.payload_type = retransmit_payload_type_; |
| if (retransmit_ssrc_ != kVideoSendSsrcs[0]) |
| send_config->rtp.rtx.ssrcs.push_back(retransmit_ssrc_); |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) << "Timed out while waiting for NACK retransmission."; |
| } |
| |
| std::unique_ptr<internal::TransportAdapter> transport_adapter_; |
| int send_count_; |
| int retransmit_count_; |
| uint32_t retransmit_ssrc_; |
| uint8_t retransmit_payload_type_; |
| std::vector<uint16_t> nacked_sequence_numbers_; |
| std::vector<uint16_t> non_padding_sequence_numbers_; |
| } test(retransmit_ssrc, retransmit_payload_type); |
| |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, RetransmitsNack) { |
| // Normal NACKs should use the send SSRC. |
| TestNackRetransmission(kVideoSendSsrcs[0], kFakeVideoSendPayloadType); |
| } |
| |
| TEST_F(VideoSendStreamTest, RetransmitsNackOverRtx) { |
| // NACKs over RTX should use a separate SSRC. |
| TestNackRetransmission(kSendRtxSsrcs[0], kSendRtxPayloadType); |
| } |
| |
| void VideoSendStreamTest::TestPacketFragmentationSize(VideoFormat format, |
| bool with_fec) { |
| // Use a fake encoder to output a frame of every size in the range [90, 290], |
| // for each size making sure that the exact number of payload bytes received |
| // is correct and that packets are fragmented to respect max packet size. |
| static const size_t kMaxPacketSize = 128; |
| static const size_t start = 90; |
| static const size_t stop = 290; |
| |
| // Observer that verifies that the expected number of packets and bytes |
| // arrive for each frame size, from start_size to stop_size. |
| class FrameFragmentationTest : public test::SendTest { |
| public: |
| FrameFragmentationTest(size_t max_packet_size, |
| size_t start_size, |
| size_t stop_size, |
| bool test_generic_packetization, |
| bool use_fec) |
| : SendTest(kLongTimeoutMs), |
| encoder_(stop), |
| encoder_factory_(&encoder_), |
| max_packet_size_(max_packet_size), |
| stop_size_(stop_size), |
| test_generic_packetization_(test_generic_packetization), |
| use_fec_(use_fec), |
| packet_count_(0), |
| packets_lost_(0), |
| last_packet_count_(0), |
| last_packets_lost_(0), |
| accumulated_size_(0), |
| accumulated_payload_(0), |
| fec_packet_received_(false), |
| current_size_rtp_(start_size), |
| current_size_frame_(static_cast<int>(start_size)) { |
| // Fragmentation required, this test doesn't make sense without it. |
| encoder_.SetFrameSize(start_size); |
| RTC_DCHECK_GT(stop_size, max_packet_size); |
| if (!test_generic_packetization_) |
| encoder_.SetCodecType(kVideoCodecVP8); |
| } |
| |
| private: |
| Action OnSendRtp(const uint8_t* packet, size_t size) override { |
| size_t length = size; |
| RtpPacket rtp_packet; |
| EXPECT_TRUE(rtp_packet.Parse(packet, length)); |
| |
| EXPECT_LE(length, max_packet_size_); |
| |
| if (use_fec_ && rtp_packet.payload_size() > 0) { |
| uint8_t payload_type = rtp_packet.payload()[0]; |
| bool is_fec = rtp_packet.PayloadType() == kRedPayloadType && |
| payload_type == kUlpfecPayloadType; |
| if (is_fec) { |
| fec_packet_received_ = true; |
| return SEND_PACKET; |
| } |
| } |
| |
| accumulated_size_ += length; |
| |
| if (use_fec_) |
| TriggerLossReport(rtp_packet); |
| |
| if (test_generic_packetization_) { |
| size_t overhead = rtp_packet.headers_size() + rtp_packet.padding_size(); |
| // Only remove payload header and RED header if the packet actually |
| // contains payload. |
| if (length > overhead) { |
| overhead += (1 /* Generic header */); |
| if (use_fec_) |
| overhead += 1; // RED for FEC header. |
| } |
| EXPECT_GE(length, overhead); |
| accumulated_payload_ += length - overhead; |
| } |
| |
| // Marker bit set indicates last packet of a frame. |
| if (rtp_packet.Marker()) { |
| if (use_fec_ && accumulated_payload_ == current_size_rtp_ - 1) { |
| // With FEC enabled, frame size is incremented asynchronously, so |
| // "old" frames one byte too small may arrive. Accept, but don't |
| // increase expected frame size. |
| accumulated_size_ = 0; |
| accumulated_payload_ = 0; |
| return SEND_PACKET; |
| } |
| |
| EXPECT_GE(accumulated_size_, current_size_rtp_); |
| if (test_generic_packetization_) { |
| EXPECT_EQ(current_size_rtp_, accumulated_payload_); |
| } |
| |
| // Last packet of frame; reset counters. |
| accumulated_size_ = 0; |
| accumulated_payload_ = 0; |
| if (current_size_rtp_ == stop_size_) { |
| // Done! (Don't increase size again, might arrive more @ stop_size). |
| observation_complete_.Set(); |
| } else { |
| // Increase next expected frame size. If testing with FEC, make sure |
| // a FEC packet has been received for this frame size before |
| // proceeding, to make sure that redundancy packets don't exceed |
| // size limit. |
| if (!use_fec_) { |
| ++current_size_rtp_; |
| } else if (fec_packet_received_) { |
| fec_packet_received_ = false; |
| ++current_size_rtp_; |
| |
| rtc::CritScope lock(&mutex_); |
| ++current_size_frame_; |
| } |
| } |
| } |
| |
| return SEND_PACKET; |
| } |
| |
| void TriggerLossReport(const RtpPacket& rtp_packet) { |
| // Send lossy receive reports to trigger FEC enabling. |
| const int kLossPercent = 5; |
| if (++packet_count_ % (100 / kLossPercent) == 0) { |
| packets_lost_++; |
| int loss_delta = packets_lost_ - last_packets_lost_; |
| int packets_delta = packet_count_ - last_packet_count_; |
| last_packet_count_ = packet_count_; |
| last_packets_lost_ = packets_lost_; |
| uint8_t loss_ratio = |
| static_cast<uint8_t>(loss_delta * 255 / packets_delta); |
| FakeReceiveStatistics lossy_receive_stats( |
| kVideoSendSsrcs[0], rtp_packet.SequenceNumber(), |
| packets_lost_, // Cumulative lost. |
| loss_ratio); // Loss percent. |
| RtpRtcp::Configuration config; |
| config.clock = Clock::GetRealTimeClock(); |
| config.receive_statistics = &lossy_receive_stats; |
| config.outgoing_transport = transport_adapter_.get(); |
| config.rtcp_report_interval_ms = kRtcpIntervalMs; |
| config.local_media_ssrc = kVideoSendSsrcs[0]; |
| RTCPSender rtcp_sender(config); |
| |
| rtcp_sender.SetRTCPStatus(RtcpMode::kReducedSize); |
| rtcp_sender.SetRemoteSSRC(kVideoSendSsrcs[0]); |
| |
| RTCPSender::FeedbackState feedback_state; |
| |
| EXPECT_EQ(0, rtcp_sender.SendRTCP(feedback_state, kRtcpRr)); |
| } |
| } |
| |
| void UpdateConfiguration() { |
| rtc::CritScope lock(&mutex_); |
| // Increase frame size for next encoded frame, in the context of the |
| // encoder thread. |
| if (!use_fec_ && current_size_frame_ < static_cast<int32_t>(stop_size_)) { |
| ++current_size_frame_; |
| } |
| encoder_.SetFrameSize(static_cast<size_t>(current_size_frame_)); |
| } |
| void ModifySenderBitrateConfig( |
| BitrateConstraints* bitrate_config) override { |
| const int kMinBitrateBps = 300000; |
| bitrate_config->min_bitrate_bps = kMinBitrateBps; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| transport_adapter_.reset( |
| new internal::TransportAdapter(send_config->send_transport)); |
| transport_adapter_->Enable(); |
| if (use_fec_) { |
| send_config->rtp.ulpfec.red_payload_type = kRedPayloadType; |
| send_config->rtp.ulpfec.ulpfec_payload_type = kUlpfecPayloadType; |
| } |
| |
| if (!test_generic_packetization_) |
| send_config->rtp.payload_name = "VP8"; |
| |
| send_config->encoder_settings.encoder_factory = &encoder_factory_; |
| send_config->rtp.max_packet_size = kMaxPacketSize; |
| encoder_.RegisterPostEncodeCallback([this]() { UpdateConfiguration(); }); |
| |
| // Make sure there is at least one extension header, to make the RTP |
| // header larger than the base length of 12 bytes. |
| EXPECT_FALSE(send_config->rtp.extensions.empty()); |
| |
| // Setup screen content disables frame dropping which makes this easier. |
| EXPECT_EQ(1u, encoder_config->simulcast_layers.size()); |
| encoder_config->simulcast_layers[0].num_temporal_layers = 2; |
| encoder_config->content_type = VideoEncoderConfig::ContentType::kScreen; |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) << "Timed out while observing incoming RTP packets."; |
| } |
| |
| std::unique_ptr<internal::TransportAdapter> transport_adapter_; |
| test::ConfigurableFrameSizeEncoder encoder_; |
| test::VideoEncoderProxyFactory encoder_factory_; |
| |
| const size_t max_packet_size_; |
| const size_t stop_size_; |
| const bool test_generic_packetization_; |
| const bool use_fec_; |
| |
| uint32_t packet_count_; |
| uint32_t packets_lost_; |
| uint32_t last_packet_count_; |
| uint32_t last_packets_lost_; |
| size_t accumulated_size_; |
| size_t accumulated_payload_; |
| bool fec_packet_received_; |
| |
| size_t current_size_rtp_; |
| rtc::CriticalSection mutex_; |
| int current_size_frame_ RTC_GUARDED_BY(mutex_); |
| }; |
| |
| // Don't auto increment if FEC is used; continue sending frame size until |
| // a FEC packet has been received. |
| FrameFragmentationTest test(kMaxPacketSize, start, stop, format == kGeneric, |
| with_fec); |
| |
| RunBaseTest(&test); |
| } |
| |
| // TODO(sprang): Is there any way of speeding up these tests? |
| TEST_F(VideoSendStreamTest, FragmentsGenericAccordingToMaxPacketSize) { |
| TestPacketFragmentationSize(kGeneric, false); |
| } |
| |
| TEST_F(VideoSendStreamTest, FragmentsGenericAccordingToMaxPacketSizeWithFec) { |
| TestPacketFragmentationSize(kGeneric, true); |
| } |
| |
| TEST_F(VideoSendStreamTest, FragmentsVp8AccordingToMaxPacketSize) { |
| TestPacketFragmentationSize(kVP8, false); |
| } |
| |
| TEST_F(VideoSendStreamTest, FragmentsVp8AccordingToMaxPacketSizeWithFec) { |
| TestPacketFragmentationSize(kVP8, true); |
| } |
| |
| // The test will go through a number of phases. |
| // 1. Start sending packets. |
| // 2. As soon as the RTP stream has been detected, signal a low REMB value to |
| // suspend the stream. |
| // 3. Wait until |kSuspendTimeFrames| have been captured without seeing any RTP |
| // packets. |
| // 4. Signal a high REMB and then wait for the RTP stream to start again. |
| // When the stream is detected again, and the stats show that the stream |
| // is no longer suspended, the test ends. |
| TEST_F(VideoSendStreamTest, SuspendBelowMinBitrate) { |
| static const int kSuspendTimeFrames = 60; // Suspend for 2 seconds @ 30 fps. |
| |
| class RembObserver : public test::SendTest { |
| public: |
| class CaptureObserver : public rtc::VideoSinkInterface<VideoFrame> { |
| public: |
| explicit CaptureObserver(RembObserver* remb_observer) |
| : remb_observer_(remb_observer) {} |
| |
| void OnFrame(const VideoFrame&) { |
| rtc::CritScope lock(&remb_observer_->crit_); |
| if (remb_observer_->test_state_ == kDuringSuspend && |
| ++remb_observer_->suspended_frame_count_ > kSuspendTimeFrames) { |
| VideoSendStream::Stats stats = remb_observer_->stream_->GetStats(); |
| EXPECT_TRUE(stats.suspended); |
| remb_observer_->SendRtcpFeedback(remb_observer_->high_remb_bps_); |
| remb_observer_->test_state_ = kWaitingForPacket; |
| } |
| } |
| |
| private: |
| RembObserver* const remb_observer_; |
| }; |
| |
| RembObserver() |
| : SendTest(kDefaultTimeoutMs), |
| clock_(Clock::GetRealTimeClock()), |
| capture_observer_(this), |
| stream_(nullptr), |
| test_state_(kBeforeSuspend), |
| rtp_count_(0), |
| last_sequence_number_(0), |
| suspended_frame_count_(0), |
| low_remb_bps_(0), |
| high_remb_bps_(0) {} |
| |
| private: |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| rtc::CritScope lock(&crit_); |
| ++rtp_count_; |
| RtpPacket rtp_packet; |
| EXPECT_TRUE(rtp_packet.Parse(packet, length)); |
| last_sequence_number_ = rtp_packet.SequenceNumber(); |
| |
| if (test_state_ == kBeforeSuspend) { |
| // The stream has started. Try to suspend it. |
| SendRtcpFeedback(low_remb_bps_); |
| test_state_ = kDuringSuspend; |
| } else if (test_state_ == kDuringSuspend) { |
| if (rtp_packet.padding_size() == 0) { |
| // Received non-padding packet during suspension period. Reset the |
| // counter. |
| suspended_frame_count_ = 0; |
| } |
| SendRtcpFeedback(0); // REMB is only sent if value is > 0. |
| } else if (test_state_ == kWaitingForPacket) { |
| if (rtp_packet.padding_size() == 0) { |
| // Non-padding packet observed. Test is almost complete. Will just |
| // have to wait for the stats to change. |
| test_state_ = kWaitingForStats; |
| } |
| SendRtcpFeedback(0); // REMB is only sent if value is > 0. |
| } else if (test_state_ == kWaitingForStats) { |
| VideoSendStream::Stats stats = stream_->GetStats(); |
| if (stats.suspended == false) { |
| // Stats flipped to false. Test is complete. |
| observation_complete_.Set(); |
| } |
| SendRtcpFeedback(0); // REMB is only sent if value is > 0. |
| } |
| |
| return SEND_PACKET; |
| } |
| |
| void set_low_remb_bps(int value) { |
| rtc::CritScope lock(&crit_); |
| low_remb_bps_ = value; |
| } |
| |
| void set_high_remb_bps(int value) { |
| rtc::CritScope lock(&crit_); |
| high_remb_bps_ = value; |
| } |
| |
| void OnVideoStreamsCreated( |
| VideoSendStream* send_stream, |
| const std::vector<VideoReceiveStream*>& receive_streams) override { |
| stream_ = send_stream; |
| } |
| |
| void OnFrameGeneratorCapturerCreated( |
| test::FrameGeneratorCapturer* frame_generator_capturer) override { |
| frame_generator_capturer->AddOrUpdateSink(&capture_observer_, |
| rtc::VideoSinkWants()); |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| RTC_DCHECK_EQ(1, encoder_config->number_of_streams); |
| transport_adapter_.reset( |
| new internal::TransportAdapter(send_config->send_transport)); |
| transport_adapter_->Enable(); |
| send_config->rtp.nack.rtp_history_ms = kNackRtpHistoryMs; |
| send_config->suspend_below_min_bitrate = true; |
| int min_bitrate_bps = |
| test::DefaultVideoStreamFactory::kDefaultMinBitratePerStream[0]; |
| set_low_remb_bps(min_bitrate_bps - 10000); |
| int threshold_window = std::max(min_bitrate_bps / 10, 20000); |
| ASSERT_GT(encoder_config->max_bitrate_bps, |
| min_bitrate_bps + threshold_window + 5000); |
| set_high_remb_bps(min_bitrate_bps + threshold_window + 5000); |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) << "Timed out during suspend-below-min-bitrate test."; |
| } |
| |
| enum TestState { |
| kBeforeSuspend, |
| kDuringSuspend, |
| kWaitingForPacket, |
| kWaitingForStats |
| }; |
| |
| virtual void SendRtcpFeedback(int remb_value) |
| RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_) { |
| FakeReceiveStatistics receive_stats(kVideoSendSsrcs[0], |
| last_sequence_number_, rtp_count_, 0); |
| RtpRtcp::Configuration config; |
| config.clock = clock_; |
| config.receive_statistics = &receive_stats; |
| config.outgoing_transport = transport_adapter_.get(); |
| config.rtcp_report_interval_ms = kRtcpIntervalMs; |
| config.local_media_ssrc = kVideoSendSsrcs[0]; |
| RTCPSender rtcp_sender(config); |
| |
| rtcp_sender.SetRTCPStatus(RtcpMode::kReducedSize); |
| rtcp_sender.SetRemoteSSRC(kVideoSendSsrcs[0]); |
| if (remb_value > 0) { |
| rtcp_sender.SetRemb(remb_value, std::vector<uint32_t>()); |
| } |
| RTCPSender::FeedbackState feedback_state; |
| EXPECT_EQ(0, rtcp_sender.SendRTCP(feedback_state, kRtcpRr)); |
| } |
| |
| std::unique_ptr<internal::TransportAdapter> transport_adapter_; |
| Clock* const clock_; |
| CaptureObserver capture_observer_; |
| VideoSendStream* stream_; |
| |
| rtc::CriticalSection crit_; |
| TestState test_state_ RTC_GUARDED_BY(crit_); |
| int rtp_count_ RTC_GUARDED_BY(crit_); |
| int last_sequence_number_ RTC_GUARDED_BY(crit_); |
| int suspended_frame_count_ RTC_GUARDED_BY(crit_); |
| int low_remb_bps_ RTC_GUARDED_BY(crit_); |
| int high_remb_bps_ RTC_GUARDED_BY(crit_); |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| // This test that padding stops being send after a while if the Camera stops |
| // producing video frames and that padding resumes if the camera restarts. |
| TEST_F(VideoSendStreamTest, NoPaddingWhenVideoIsMuted) { |
| class NoPaddingWhenVideoIsMuted : public test::SendTest { |
| public: |
| NoPaddingWhenVideoIsMuted() |
| : SendTest(kDefaultTimeoutMs), |
| clock_(Clock::GetRealTimeClock()), |
| last_packet_time_ms_(-1), |
| capturer_(nullptr) {} |
| |
| private: |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| rtc::CritScope lock(&crit_); |
| last_packet_time_ms_ = clock_->TimeInMilliseconds(); |
| |
| RtpPacket rtp_packet; |
| rtp_packet.Parse(packet, length); |
| const bool only_padding = rtp_packet.payload_size() == 0; |
| |
| if (test_state_ == kBeforeStopCapture) { |
| // Packets are flowing, stop camera. |
| capturer_->Stop(); |
| test_state_ = kWaitingForPadding; |
| } else if (test_state_ == kWaitingForPadding && only_padding) { |
| // We're still getting padding, after stopping camera. |
| test_state_ = kWaitingForNoPackets; |
| } else if (test_state_ == kWaitingForMediaAfterCameraRestart && |
| !only_padding) { |
| // Media packets are flowing again, stop camera a second time. |
| capturer_->Stop(); |
| test_state_ = kWaitingForPaddingAfterCameraStopsAgain; |
| } else if (test_state_ == kWaitingForPaddingAfterCameraStopsAgain && |
| only_padding) { |
| // Padding is still flowing, test ok. |
| observation_complete_.Set(); |
| } |
| return SEND_PACKET; |
| } |
| |
| Action OnSendRtcp(const uint8_t* packet, size_t length) override { |
| rtc::CritScope lock(&crit_); |
| const int kNoPacketsThresholdMs = 2000; |
| if (test_state_ == kWaitingForNoPackets && |
| (last_packet_time_ms_ > 0 && |
| clock_->TimeInMilliseconds() - last_packet_time_ms_ > |
| kNoPacketsThresholdMs)) { |
| // No packets seen for |kNoPacketsThresholdMs|, restart camera. |
| capturer_->Start(); |
| test_state_ = kWaitingForMediaAfterCameraRestart; |
| } |
| return SEND_PACKET; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| // Make sure padding is sent if encoder is not producing media. |
| encoder_config->min_transmit_bitrate_bps = 50000; |
| } |
| |
| void OnFrameGeneratorCapturerCreated( |
| test::FrameGeneratorCapturer* frame_generator_capturer) override { |
| rtc::CritScope lock(&crit_); |
| capturer_ = frame_generator_capturer; |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) |
| << "Timed out while waiting for RTP packets to stop being sent."; |
| } |
| |
| enum TestState { |
| kBeforeStopCapture, |
| kWaitingForPadding, |
| kWaitingForNoPackets, |
| kWaitingForMediaAfterCameraRestart, |
| kWaitingForPaddingAfterCameraStopsAgain |
| }; |
| |
| TestState test_state_ = kBeforeStopCapture; |
| Clock* const clock_; |
| std::unique_ptr<internal::TransportAdapter> transport_adapter_; |
| rtc::CriticalSection crit_; |
| int64_t last_packet_time_ms_ RTC_GUARDED_BY(crit_); |
| test::FrameGeneratorCapturer* capturer_ RTC_GUARDED_BY(crit_); |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, PaddingIsPrimarilyRetransmissions) { |
| const int kCapacityKbps = 10000; // 10 Mbps |
| class PaddingIsPrimarilyRetransmissions : public test::EndToEndTest { |
| public: |
| PaddingIsPrimarilyRetransmissions() |
| : EndToEndTest(kDefaultTimeoutMs), |
| clock_(Clock::GetRealTimeClock()), |
| padding_length_(0), |
| total_length_(0), |
| call_(nullptr) {} |
| |
| private: |
| void OnCallsCreated(Call* sender_call, Call* receiver_call) override { |
| call_ = sender_call; |
| } |
| |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| rtc::CritScope lock(&crit_); |
| |
| RtpPacket rtp_packet; |
| rtp_packet.Parse(packet, length); |
| padding_length_ += rtp_packet.padding_size(); |
| total_length_ += length; |
| return SEND_PACKET; |
| } |
| |
| std::unique_ptr<test::PacketTransport> CreateSendTransport( |
| TaskQueueBase* task_queue, |
| Call* sender_call) override { |
| const int kNetworkDelayMs = 50; |
| BuiltInNetworkBehaviorConfig config; |
| config.loss_percent = 10; |
| config.link_capacity_kbps = kCapacityKbps; |
| config.queue_delay_ms = kNetworkDelayMs; |
| return std::make_unique<test::PacketTransport>( |
| task_queue, sender_call, this, test::PacketTransport::kSender, |
| payload_type_map_, |
| std::make_unique<FakeNetworkPipe>( |
| Clock::GetRealTimeClock(), |
| std::make_unique<SimulatedNetwork>(config))); |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| // Turn on RTX. |
| send_config->rtp.rtx.payload_type = kFakeVideoSendPayloadType; |
| send_config->rtp.rtx.ssrcs.push_back(kSendRtxSsrcs[0]); |
| } |
| |
| void PerformTest() override { |
| // TODO(isheriff): Some platforms do not ramp up as expected to full |
| // capacity due to packet scheduling delays. Fix that before getting |
| // rid of this. |
| SleepMs(5000); |
| { |
| rtc::CritScope lock(&crit_); |
| // Expect padding to be a small percentage of total bytes sent. |
| EXPECT_LT(padding_length_, .1 * total_length_); |
| } |
| } |
| |
| rtc::CriticalSection crit_; |
| Clock* const clock_; |
| size_t padding_length_ RTC_GUARDED_BY(crit_); |
| size_t total_length_ RTC_GUARDED_BY(crit_); |
| Call* call_; |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| // This test first observes "high" bitrate use at which point it sends a REMB to |
| // indicate that it should be lowered significantly. The test then observes that |
| // the bitrate observed is sinking well below the min-transmit-bitrate threshold |
| // to verify that the min-transmit bitrate respects incoming REMB. |
| // |
| // Note that the test starts at "high" bitrate and does not ramp up to "higher" |
| // bitrate since no receiver block or remb is sent in the initial phase. |
| TEST_F(VideoSendStreamTest, MinTransmitBitrateRespectsRemb) { |
| static const int kMinTransmitBitrateBps = 400000; |
| static const int kHighBitrateBps = 150000; |
| static const int kRembBitrateBps = 80000; |
| static const int kRembRespectedBitrateBps = 100000; |
| class BitrateObserver : public test::SendTest { |
| public: |
| BitrateObserver() |
| : SendTest(kDefaultTimeoutMs), |
| retranmission_rate_limiter_(Clock::GetRealTimeClock(), 1000), |
| stream_(nullptr), |
| bitrate_capped_(false) {} |
| |
| private: |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| if (RtpHeaderParser::IsRtcp(packet, length)) |
| return DROP_PACKET; |
| |
| RtpPacket rtp_packet; |
| if (!rtp_packet.Parse(packet, length)) |
| return DROP_PACKET; |
| RTC_DCHECK(stream_); |
| VideoSendStream::Stats stats = stream_->GetStats(); |
| if (!stats.substreams.empty()) { |
| EXPECT_EQ(1u, stats.substreams.size()); |
| int total_bitrate_bps = |
| stats.substreams.begin()->second.total_bitrate_bps; |
| test::PrintResult("bitrate_stats_", "min_transmit_bitrate_low_remb", |
| "bitrate_bps", static_cast<size_t>(total_bitrate_bps), |
| "bps", false); |
| if (total_bitrate_bps > kHighBitrateBps) { |
| rtp_rtcp_->SetRemb(kRembBitrateBps, {rtp_packet.Ssrc()}); |
| rtp_rtcp_->Process(); |
| bitrate_capped_ = true; |
| } else if (bitrate_capped_ && |
| total_bitrate_bps < kRembRespectedBitrateBps) { |
| observation_complete_.Set(); |
| } |
| } |
| // Packets don't have to be delivered since the test is the receiver. |
| return DROP_PACKET; |
| } |
| |
| void OnVideoStreamsCreated( |
| VideoSendStream* send_stream, |
| const std::vector<VideoReceiveStream*>& receive_streams) override { |
| stream_ = send_stream; |
| RtpRtcp::Configuration config; |
| config.clock = Clock::GetRealTimeClock(); |
| config.outgoing_transport = feedback_transport_.get(); |
| config.retransmission_rate_limiter = &retranmission_rate_limiter_; |
| rtp_rtcp_ = RtpRtcp::Create(config); |
| rtp_rtcp_->SetRTCPStatus(RtcpMode::kReducedSize); |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| feedback_transport_.reset( |
| new internal::TransportAdapter(send_config->send_transport)); |
| feedback_transport_->Enable(); |
| encoder_config->min_transmit_bitrate_bps = kMinTransmitBitrateBps; |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) |
| << "Timeout while waiting for low bitrate stats after REMB."; |
| } |
| |
| std::unique_ptr<RtpRtcp> rtp_rtcp_; |
| std::unique_ptr<internal::TransportAdapter> feedback_transport_; |
| RateLimiter retranmission_rate_limiter_; |
| VideoSendStream* stream_; |
| bool bitrate_capped_; |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, ChangingNetworkRoute) { |
| static const int kStartBitrateBps = 300000; |
| static const int kNewMaxBitrateBps = 1234567; |
| static const uint8_t kExtensionId = kTransportSequenceNumberExtensionId; |
| class ChangingNetworkRouteTest : public test::EndToEndTest { |
| public: |
| explicit ChangingNetworkRouteTest(TaskQueueBase* task_queue) |
| : EndToEndTest(test::CallTest::kDefaultTimeoutMs), |
| task_queue_(task_queue), |
| call_(nullptr) { |
| module_process_thread_.Detach(); |
| task_queue_thread_.Detach(); |
| extensions_.Register<TransportSequenceNumber>(kExtensionId); |
| } |
| |
| ~ChangingNetworkRouteTest() { |
| // Block until all already posted tasks run to avoid 'use after free' |
| // when such task accesses |this|. |
| SendTask(RTC_FROM_HERE, task_queue_, [] {}); |
| } |
| |
| void OnCallsCreated(Call* sender_call, Call* receiver_call) override { |
| RTC_DCHECK_RUN_ON(&task_queue_thread_); |
| RTC_DCHECK(!call_); |
| call_ = sender_call; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| RTC_DCHECK_RUN_ON(&task_queue_thread_); |
| send_config->rtp.extensions.clear(); |
| send_config->rtp.extensions.push_back(RtpExtension( |
| RtpExtension::kTransportSequenceNumberUri, kExtensionId)); |
| (*receive_configs)[0].rtp.extensions = send_config->rtp.extensions; |
| (*receive_configs)[0].rtp.transport_cc = true; |
| } |
| |
| void ModifyAudioConfigs( |
| AudioSendStream::Config* send_config, |
| std::vector<AudioReceiveStream::Config>* receive_configs) override { |
| RTC_DCHECK_RUN_ON(&task_queue_thread_); |
| send_config->rtp.extensions.clear(); |
| send_config->rtp.extensions.push_back(RtpExtension( |
| RtpExtension::kTransportSequenceNumberUri, kExtensionId)); |
| (*receive_configs)[0].rtp.extensions.clear(); |
| (*receive_configs)[0].rtp.extensions = send_config->rtp.extensions; |
| (*receive_configs)[0].rtp.transport_cc = true; |
| } |
| |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| RTC_DCHECK_RUN_ON(&module_process_thread_); |
| task_queue_->PostTask(ToQueuedTask([this]() { |
| RTC_DCHECK_RUN_ON(&task_queue_thread_); |
| if (!call_) |
| return; |
| Call::Stats stats = call_->GetStats(); |
| if (stats.send_bandwidth_bps > kStartBitrateBps) |
| observation_complete_.Set(); |
| })); |
| return SEND_PACKET; |
| } |
| |
| void OnStreamsStopped() override { |
| RTC_DCHECK_RUN_ON(&task_queue_thread_); |
| call_ = nullptr; |
| } |
| |
| void PerformTest() override { |
| rtc::NetworkRoute new_route; |
| new_route.connected = true; |
| new_route.local_network_id = 10; |
| new_route.remote_network_id = 20; |
| BitrateConstraints bitrate_config; |
| |
| SendTask(RTC_FROM_HERE, task_queue_, |
| [this, &new_route, &bitrate_config]() { |
| RTC_DCHECK_RUN_ON(&task_queue_thread_); |
| call_->GetTransportControllerSend()->OnNetworkRouteChanged( |
| "transport", new_route); |
| bitrate_config.start_bitrate_bps = kStartBitrateBps; |
| call_->GetTransportControllerSend()->SetSdpBitrateParameters( |
| bitrate_config); |
| }); |
| |
| EXPECT_TRUE(Wait()) |
| << "Timed out while waiting for start bitrate to be exceeded."; |
| |
| SendTask( |
| RTC_FROM_HERE, task_queue_, [this, &new_route, &bitrate_config]() { |
| RTC_DCHECK_RUN_ON(&task_queue_thread_); |
| bitrate_config.start_bitrate_bps = -1; |
| bitrate_config.max_bitrate_bps = kNewMaxBitrateBps; |
| call_->GetTransportControllerSend()->SetSdpBitrateParameters( |
| bitrate_config); |
| // TODO(holmer): We should set the last sent packet id here and |
| // verify that we correctly ignore any packet loss reported prior to |
| // that id. |
| ++new_route.local_network_id; |
| call_->GetTransportControllerSend()->OnNetworkRouteChanged( |
| "transport", new_route); |
| EXPECT_GE(call_->GetStats().send_bandwidth_bps, kStartBitrateBps); |
| }); |
| } |
| |
| private: |
| webrtc::SequenceChecker module_process_thread_; |
| webrtc::SequenceChecker task_queue_thread_; |
| TaskQueueBase* const task_queue_; |
| RtpHeaderExtensionMap extensions_; |
| Call* call_ RTC_GUARDED_BY(task_queue_thread_); |
| } test(task_queue()); |
| |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, ChangingTransportOverhead) { |
| class ChangingTransportOverheadTest : public test::EndToEndTest { |
| public: |
| explicit ChangingTransportOverheadTest(TaskQueueBase* task_queue) |
| : EndToEndTest(test::CallTest::kDefaultTimeoutMs), |
| task_queue_(task_queue), |
| call_(nullptr), |
| packets_sent_(0), |
| transport_overhead_(0) {} |
| |
| void OnCallsCreated(Call* sender_call, Call* receiver_call) override { |
| call_ = sender_call; |
| } |
| |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| EXPECT_LE(length, kMaxRtpPacketSize); |
| rtc::CritScope cs(&lock_); |
| if (++packets_sent_ < 100) |
| return SEND_PACKET; |
| observation_complete_.Set(); |
| return SEND_PACKET; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->rtp.max_packet_size = kMaxRtpPacketSize; |
| } |
| |
| void PerformTest() override { |
| SendTask(RTC_FROM_HERE, task_queue_, [this]() { |
| transport_overhead_ = 100; |
| call_->GetTransportControllerSend()->OnTransportOverheadChanged( |
| transport_overhead_); |
| }); |
| |
| EXPECT_TRUE(Wait()); |
| |
| { |
| rtc::CritScope cs(&lock_); |
| packets_sent_ = 0; |
| } |
| |
| SendTask(RTC_FROM_HERE, task_queue_, [this]() { |
| transport_overhead_ = 500; |
| call_->GetTransportControllerSend()->OnTransportOverheadChanged( |
| transport_overhead_); |
| }); |
| |
| EXPECT_TRUE(Wait()); |
| } |
| |
| private: |
| TaskQueueBase* const task_queue_; |
| Call* call_; |
| rtc::CriticalSection lock_; |
| int packets_sent_ RTC_GUARDED_BY(lock_); |
| int transport_overhead_; |
| const size_t kMaxRtpPacketSize = 1000; |
| } test(task_queue()); |
| |
| RunBaseTest(&test); |
| } |
| |
| // Test class takes takes as argument a switch selecting if type switch should |
| // occur and a function pointer to reset the send stream. This is necessary |
| // since you cannot change the content type of a VideoSendStream, you need to |
| // recreate it. Stopping and recreating the stream can only be done on the main |
| // thread and in the context of VideoSendStreamTest (not BaseTest). |
| template <typename T> |
| class MaxPaddingSetTest : public test::SendTest { |
| public: |
| static const uint32_t kMinTransmitBitrateBps = 400000; |
| static const uint32_t kActualEncodeBitrateBps = 40000; |
| static const uint32_t kMinPacketsToSend = 50; |
| |
| MaxPaddingSetTest(bool test_switch_content_type, |
| T* stream_reset_fun, |
| TaskQueueBase* task_queue) |
| : SendTest(test::CallTest::kDefaultTimeoutMs), |
| running_without_padding_(test_switch_content_type), |
| stream_resetter_(stream_reset_fun), |
| task_queue_(task_queue) { |
| RTC_DCHECK(stream_resetter_); |
| module_process_thread_.Detach(); |
| task_queue_thread_.Detach(); |
| } |
| |
| ~MaxPaddingSetTest() { |
| // Block until all already posted tasks run to avoid 'use after free' |
| // when such task accesses |this|. |
| SendTask(RTC_FROM_HERE, task_queue_, [] {}); |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| RTC_DCHECK_RUN_ON(&task_queue_thread_); |
| RTC_DCHECK_EQ(1, encoder_config->number_of_streams); |
| if (running_without_padding_) { |
| encoder_config->min_transmit_bitrate_bps = 0; |
| encoder_config->content_type = |
| VideoEncoderConfig::ContentType::kRealtimeVideo; |
| } else { |
| encoder_config->min_transmit_bitrate_bps = kMinTransmitBitrateBps; |
| encoder_config->content_type = VideoEncoderConfig::ContentType::kScreen; |
| } |
| send_stream_config_ = send_config->Copy(); |
| encoder_config_ = encoder_config->Copy(); |
| } |
| |
| void OnCallsCreated(Call* sender_call, Call* receiver_call) override { |
| RTC_DCHECK_RUN_ON(&task_queue_thread_); |
| RTC_DCHECK(task_queue_->IsCurrent()); |
| RTC_DCHECK(!call_); |
| RTC_DCHECK(sender_call); |
| call_ = sender_call; |
| } |
| |
| // Called on the pacer thread. |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| RTC_DCHECK_RUN_ON(&module_process_thread_); |
| |
| // Check the stats on the correct thread and signal the 'complete' flag |
| // once we detect that we're done. |
| |
| task_queue_->PostTask(ToQueuedTask([this]() { |
| RTC_DCHECK_RUN_ON(&task_queue_thread_); |
| // In case we get a callback during teardown. |
| // When this happens, OnStreamsStopped() has been called already, |
| // |call_| is null and the streams are being torn down. |
| if (!call_) |
| return; |
| |
| ++packets_sent_; |
| |
| Call::Stats stats = call_->GetStats(); |
| if (running_without_padding_) { |
| EXPECT_EQ(0, stats.max_padding_bitrate_bps); |
| |
| // Wait until at least kMinPacketsToSend frames have been encoded, so |
| // that we have reliable data. |
| if (packets_sent_ < kMinPacketsToSend) |
| return; |
| |
| // We've sent kMinPacketsToSend packets with default configuration, |
| // switch to enabling screen content and setting min transmit bitrate. |
| // Note that we need to recreate the stream if changing content type. |
| packets_sent_ = 0; |
| |
| encoder_config_.min_transmit_bitrate_bps = kMinTransmitBitrateBps; |
| encoder_config_.content_type = VideoEncoderConfig::ContentType::kScreen; |
| |
| running_without_padding_ = false; |
| (*stream_resetter_)(send_stream_config_, encoder_config_); |
| } else { |
| // Make sure the pacer has been configured with a min transmit bitrate. |
| if (stats.max_padding_bitrate_bps > 0) { |
| observation_complete_.Set(); |
| } |
| } |
| })); |
| |
| return SEND_PACKET; |
| } |
| |
| // Called on |task_queue_| |
| void OnStreamsStopped() override { |
| RTC_DCHECK_RUN_ON(&task_queue_thread_); |
| RTC_DCHECK(task_queue_->IsCurrent()); |
| call_ = nullptr; |
| } |
| |
| void PerformTest() override { |
| ASSERT_TRUE(Wait()) << "Timed out waiting for a valid padding bitrate."; |
| } |
| |
| private: |
| webrtc::SequenceChecker task_queue_thread_; |
| Call* call_ RTC_GUARDED_BY(task_queue_thread_) = nullptr; |
| VideoSendStream::Config send_stream_config_{nullptr}; |
| VideoEncoderConfig encoder_config_; |
| webrtc::SequenceChecker module_process_thread_; |
| uint32_t packets_sent_ RTC_GUARDED_BY(task_queue_thread_) = 0; |
| bool running_without_padding_ RTC_GUARDED_BY(task_queue_thread_); |
| T* const stream_resetter_; |
| TaskQueueBase* const task_queue_; |
| }; |
| |
| TEST_F(VideoSendStreamTest, RespectsMinTransmitBitrate) { |
| auto reset_fun = [](const VideoSendStream::Config& send_stream_config, |
| const VideoEncoderConfig& encoder_config) {}; |
| MaxPaddingSetTest<decltype(reset_fun)> test(false, &reset_fun, task_queue()); |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, RespectsMinTransmitBitrateAfterContentSwitch) { |
| // Function for removing and recreating the send stream with a new config. |
| auto reset_fun = [this](const VideoSendStream::Config& send_stream_config, |
| const VideoEncoderConfig& encoder_config) { |
| RTC_DCHECK(task_queue()->IsCurrent()); |
| Stop(); |
| DestroyVideoSendStreams(); |
| SetVideoSendConfig(send_stream_config); |
| SetVideoEncoderConfig(encoder_config); |
| CreateVideoSendStreams(); |
| SetVideoDegradation(DegradationPreference::MAINTAIN_RESOLUTION); |
| Start(); |
| }; |
| MaxPaddingSetTest<decltype(reset_fun)> test(true, &reset_fun, task_queue()); |
| RunBaseTest(&test); |
| } |
| |
| // This test verifies that new frame sizes reconfigures encoders even though not |
| // (yet) sending. The purpose of this is to permit encoding as quickly as |
| // possible once we start sending. Likely the frames being input are from the |
| // same source that will be sent later, which just means that we're ready |
| // earlier. |
| TEST_F(VideoSendStreamTest, |
| EncoderReconfigureOnResolutionChangeWhenNotSending) { |
| class EncoderObserver : public test::FakeEncoder { |
| public: |
| EncoderObserver() |
| : FakeEncoder(Clock::GetRealTimeClock()), |
| number_of_initializations_(0), |
| last_initialized_frame_width_(0), |
| last_initialized_frame_height_(0) {} |
| |
| void WaitForResolution(int width, int height) { |
| { |
| rtc::CritScope lock(&crit_); |
| if (last_initialized_frame_width_ == width && |
| last_initialized_frame_height_ == height) { |
| return; |
| } |
| } |
| EXPECT_TRUE( |
| init_encode_called_.Wait(VideoSendStreamTest::kDefaultTimeoutMs)); |
| { |
| rtc::CritScope lock(&crit_); |
| EXPECT_EQ(width, last_initialized_frame_width_); |
| EXPECT_EQ(height, last_initialized_frame_height_); |
| } |
| } |
| |
| private: |
| int32_t InitEncode(const VideoCodec* config, |
| const Settings& settings) override { |
| rtc::CritScope lock(&crit_); |
| last_initialized_frame_width_ = config->width; |
| last_initialized_frame_height_ = config->height; |
| ++number_of_initializations_; |
| init_encode_called_.Set(); |
| return FakeEncoder::InitEncode(config, settings); |
| } |
| |
| int32_t Encode(const VideoFrame& input_image, |
| const std::vector<VideoFrameType>* frame_types) override { |
| ADD_FAILURE() |
| << "Unexpected Encode call since the send stream is not started"; |
| return 0; |
| } |
| |
| rtc::CriticalSection crit_; |
| rtc::Event init_encode_called_; |
| size_t number_of_initializations_ RTC_GUARDED_BY(&crit_); |
| int last_initialized_frame_width_ RTC_GUARDED_BY(&crit_); |
| int last_initialized_frame_height_ RTC_GUARDED_BY(&crit_); |
| }; |
| |
| test::NullTransport transport; |
| EncoderObserver encoder; |
| test::VideoEncoderProxyFactory encoder_factory(&encoder); |
| |
| SendTask(RTC_FROM_HERE, task_queue(), [this, &transport, &encoder_factory]() { |
| CreateSenderCall(); |
| CreateSendConfig(1, 0, 0, &transport); |
| GetVideoSendConfig()->encoder_settings.encoder_factory = &encoder_factory; |
| CreateVideoStreams(); |
| CreateFrameGeneratorCapturer(kDefaultFramerate, kDefaultWidth, |
| kDefaultHeight); |
| frame_generator_capturer_->Start(); |
| }); |
| |
| encoder.WaitForResolution(kDefaultWidth, kDefaultHeight); |
| |
| SendTask(RTC_FROM_HERE, task_queue(), [this]() { |
| frame_generator_capturer_->ChangeResolution(kDefaultWidth * 2, |
| kDefaultHeight * 2); |
| }); |
| |
| encoder.WaitForResolution(kDefaultWidth * 2, kDefaultHeight * 2); |
| |
| SendTask(RTC_FROM_HERE, task_queue(), [this]() { |
| DestroyStreams(); |
| DestroyCalls(); |
| }); |
| } |
| |
| TEST_F(VideoSendStreamTest, CanReconfigureToUseStartBitrateAbovePreviousMax) { |
| class StartBitrateObserver : public test::FakeEncoder { |
| public: |
| StartBitrateObserver() |
| : FakeEncoder(Clock::GetRealTimeClock()), start_bitrate_kbps_(0) {} |
| int32_t InitEncode(const VideoCodec* config, |
| const Settings& settings) override { |
| rtc::CritScope lock(&crit_); |
| start_bitrate_kbps_ = config->startBitrate; |
| start_bitrate_changed_.Set(); |
| return FakeEncoder::InitEncode(config, settings); |
| } |
| |
| void SetRates(const RateControlParameters& parameters) override { |
| rtc::CritScope lock(&crit_); |
| start_bitrate_kbps_ = parameters.bitrate.get_sum_kbps(); |
| start_bitrate_changed_.Set(); |
| FakeEncoder::SetRates(parameters); |
| } |
| |
| int GetStartBitrateKbps() const { |
| rtc::CritScope lock(&crit_); |
| return start_bitrate_kbps_; |
| } |
| |
| bool WaitForStartBitrate() { |
| return start_bitrate_changed_.Wait( |
| VideoSendStreamTest::kDefaultTimeoutMs); |
| } |
| |
| private: |
| rtc::CriticalSection crit_; |
| rtc::Event start_bitrate_changed_; |
| int start_bitrate_kbps_ RTC_GUARDED_BY(crit_); |
| }; |
| |
| CreateSenderCall(); |
| |
| test::NullTransport transport; |
| CreateSendConfig(1, 0, 0, &transport); |
| |
| BitrateConstraints bitrate_config; |
| bitrate_config.start_bitrate_bps = |
| 2 * GetVideoEncoderConfig()->max_bitrate_bps; |
| sender_call_->GetTransportControllerSend()->SetSdpBitrateParameters( |
| bitrate_config); |
| |
| StartBitrateObserver encoder; |
| test::VideoEncoderProxyFactory encoder_factory(&encoder); |
| // Since this test does not use a capturer, set |internal_source| = true. |
| // Encoder configuration is otherwise updated on the next video frame. |
| encoder_factory.SetHasInternalSource(true); |
| GetVideoSendConfig()->encoder_settings.encoder_factory = &encoder_factory; |
| |
| CreateVideoStreams(); |
| |
| EXPECT_TRUE(encoder.WaitForStartBitrate()); |
| EXPECT_EQ(GetVideoEncoderConfig()->max_bitrate_bps / 1000, |
| encoder.GetStartBitrateKbps()); |
| |
| GetVideoEncoderConfig()->max_bitrate_bps = |
| 2 * bitrate_config.start_bitrate_bps; |
| GetVideoSendStream()->ReconfigureVideoEncoder( |
| GetVideoEncoderConfig()->Copy()); |
| |
| // New bitrate should be reconfigured above the previous max. As there's no |
| // network connection this shouldn't be flaky, as no bitrate should've been |
| // reported in between. |
| EXPECT_TRUE(encoder.WaitForStartBitrate()); |
| EXPECT_EQ(bitrate_config.start_bitrate_bps / 1000, |
| encoder.GetStartBitrateKbps()); |
| |
| DestroyStreams(); |
| } |
| |
| class StartStopBitrateObserver : public test::FakeEncoder { |
| public: |
| StartStopBitrateObserver() : FakeEncoder(Clock::GetRealTimeClock()) {} |
| int32_t InitEncode(const VideoCodec* config, |
| const Settings& settings) override { |
| rtc::CritScope lock(&crit_); |
| encoder_init_.Set(); |
| return FakeEncoder::InitEncode(config, settings); |
| } |
| |
| void SetRates(const RateControlParameters& parameters) override { |
| rtc::CritScope lock(&crit_); |
| bitrate_kbps_ = parameters.bitrate.get_sum_kbps(); |
| bitrate_changed_.Set(); |
| FakeEncoder::SetRates(parameters); |
| } |
| |
| bool WaitForEncoderInit() { |
| return encoder_init_.Wait(VideoSendStreamTest::kDefaultTimeoutMs); |
| } |
| |
| bool WaitBitrateChanged(bool non_zero) { |
| do { |
| absl::optional<int> bitrate_kbps; |
| { |
| rtc::CritScope lock(&crit_); |
| bitrate_kbps = bitrate_kbps_; |
| } |
| if (!bitrate_kbps) |
| continue; |
| |
| if ((non_zero && *bitrate_kbps > 0) || |
| (!non_zero && *bitrate_kbps == 0)) { |
| return true; |
| } |
| } while (bitrate_changed_.Wait(VideoSendStreamTest::kDefaultTimeoutMs)); |
| return false; |
| } |
| |
| private: |
| rtc::CriticalSection crit_; |
| rtc::Event encoder_init_; |
| rtc::Event bitrate_changed_; |
| absl::optional<int> bitrate_kbps_ RTC_GUARDED_BY(crit_); |
| }; |
| |
| // This test that if the encoder use an internal source, VideoEncoder::SetRates |
| // will be called with zero bitrate during initialization and that |
| // VideoSendStream::Stop also triggers VideoEncoder::SetRates Start to be called |
| // with zero bitrate. |
| TEST_F(VideoSendStreamTest, VideoSendStreamStopSetEncoderRateToZero) { |
| test::NullTransport transport; |
| StartStopBitrateObserver encoder; |
| test::VideoEncoderProxyFactory encoder_factory(&encoder); |
| encoder_factory.SetHasInternalSource(true); |
| test::FrameForwarder forwarder; |
| |
| SendTask(RTC_FROM_HERE, task_queue(), |
| [this, &transport, &encoder_factory, &forwarder]() { |
| CreateSenderCall(); |
| CreateSendConfig(1, 0, 0, &transport); |
| |
| sender_call_->SignalChannelNetworkState(MediaType::VIDEO, |
| kNetworkUp); |
| GetVideoSendConfig()->encoder_settings.encoder_factory = |
| &encoder_factory; |
| |
| CreateVideoStreams(); |
| // Inject a frame, to force encoder creation. |
| GetVideoSendStream()->Start(); |
| GetVideoSendStream()->SetSource(&forwarder, |
| DegradationPreference::DISABLED); |
| forwarder.IncomingCapturedFrame(CreateVideoFrame(640, 480, 4)); |
| }); |
| |
| EXPECT_TRUE(encoder.WaitForEncoderInit()); |
| |
| SendTask(RTC_FROM_HERE, task_queue(), |
| [this]() { GetVideoSendStream()->Start(); }); |
| EXPECT_TRUE(encoder.WaitBitrateChanged(true)); |
| |
| SendTask(RTC_FROM_HERE, task_queue(), |
| [this]() { GetVideoSendStream()->Stop(); }); |
| EXPECT_TRUE(encoder.WaitBitrateChanged(false)); |
| |
| SendTask(RTC_FROM_HERE, task_queue(), |
| [this]() { GetVideoSendStream()->Start(); }); |
| EXPECT_TRUE(encoder.WaitBitrateChanged(true)); |
| |
| SendTask(RTC_FROM_HERE, task_queue(), [this]() { |
| DestroyStreams(); |
| DestroyCalls(); |
| }); |
| } |
| |
| // Tests that when the encoder uses an internal source, the VideoEncoder will |
| // be updated with a new bitrate when turning the VideoSendStream on/off with |
| // VideoSendStream::UpdateActiveSimulcastLayers, and when the VideoStreamEncoder |
| // is reconfigured with new active layers. |
| TEST_F(VideoSendStreamTest, VideoSendStreamUpdateActiveSimulcastLayers) { |
| test::NullTransport transport; |
| StartStopBitrateObserver encoder; |
| test::VideoEncoderProxyFactory encoder_factory(&encoder); |
| encoder_factory.SetHasInternalSource(true); |
| test::FrameForwarder forwarder; |
| |
| SendTask(RTC_FROM_HERE, task_queue(), |
| [this, &transport, &encoder_factory, &forwarder]() { |
| CreateSenderCall(); |
| // Create two simulcast streams. |
| CreateSendConfig(2, 0, 0, &transport); |
| |
| sender_call_->SignalChannelNetworkState(MediaType::VIDEO, |
| kNetworkUp); |
| GetVideoSendConfig()->encoder_settings.encoder_factory = |
| &encoder_factory; |
| |
| CreateVideoStreams(); |
| |
| // Inject a frame, to force encoder creation. |
| GetVideoSendStream()->Start(); |
| GetVideoSendStream()->SetSource(&forwarder, |
| DegradationPreference::DISABLED); |
| forwarder.IncomingCapturedFrame(CreateVideoFrame(640, 480, 4)); |
| }); |
| |
| EXPECT_TRUE(encoder.WaitForEncoderInit()); |
| |
| // When we turn on the simulcast layers it will update the BitrateAllocator, |
| // which in turn updates the VideoEncoder's bitrate. |
| SendTask(RTC_FROM_HERE, task_queue(), [this]() { |
| GetVideoSendStream()->UpdateActiveSimulcastLayers({true, true}); |
| }); |
| EXPECT_TRUE(encoder.WaitBitrateChanged(true)); |
| |
| GetVideoEncoderConfig()->simulcast_layers[0].active = true; |
| GetVideoEncoderConfig()->simulcast_layers[1].active = false; |
| SendTask(RTC_FROM_HERE, task_queue(), [this]() { |
| GetVideoSendStream()->ReconfigureVideoEncoder( |
| GetVideoEncoderConfig()->Copy()); |
| }); |
| // TODO(bugs.webrtc.org/8807): Currently we require a hard reconfiguration to |
| // update the VideoBitrateAllocator and BitrateAllocator of which layers are |
| // active. Once the change is made for a "soft" reconfiguration we can remove |
| // the expecation for an encoder init. We can also test that bitrate changes |
| // when just updating individual active layers, which should change the |
| // bitrate set to the video encoder. |
| EXPECT_TRUE(encoder.WaitForEncoderInit()); |
| EXPECT_TRUE(encoder.WaitBitrateChanged(true)); |
| |
| // Turning off both simulcast layers should trigger a bitrate change of 0. |
| GetVideoEncoderConfig()->simulcast_layers[0].active = false; |
| GetVideoEncoderConfig()->simulcast_layers[1].active = false; |
| SendTask(RTC_FROM_HERE, task_queue(), [this]() { |
| GetVideoSendStream()->UpdateActiveSimulcastLayers({false, false}); |
| }); |
| EXPECT_TRUE(encoder.WaitBitrateChanged(false)); |
| |
| SendTask(RTC_FROM_HERE, task_queue(), [this]() { |
| DestroyStreams(); |
| DestroyCalls(); |
| }); |
| } |
| |
| VideoFrame CreateVideoFrame(int width, int height, uint8_t data) { |
| const int kSizeY = width * height * 2; |
| std::unique_ptr<uint8_t[]> buffer(new uint8_t[kSizeY]); |
| memset(buffer.get(), data, kSizeY); |
| VideoFrame frame = |
| webrtc::VideoFrame::Builder() |
| .set_video_frame_buffer(I420Buffer::Create(width, height)) |
| .set_rotation(webrtc::kVideoRotation_0) |
| .set_timestamp_us(data) |
| .build(); |
| frame.set_timestamp(data); |
| // Use data as a ms timestamp. |
| frame.set_timestamp_us(data * rtc::kNumMicrosecsPerMillisec); |
| return frame; |
| } |
| |
| TEST_F(VideoSendStreamTest, EncoderIsProperlyInitializedAndDestroyed) { |
| class EncoderStateObserver : public test::SendTest, public VideoEncoder { |
| public: |
| explicit EncoderStateObserver(TaskQueueBase* task_queue) |
| : SendTest(kDefaultTimeoutMs), |
| task_queue_(task_queue), |
| stream_(nullptr), |
| initialized_(false), |
| callback_registered_(false), |
| num_releases_(0), |
| released_(false), |
| encoder_factory_(this) {} |
| |
| bool IsReleased() { |
| rtc::CritScope lock(&crit_); |
| return released_; |
| } |
| |
| bool IsReadyForEncode() { |
| rtc::CritScope lock(&crit_); |
| return initialized_ && callback_registered_; |
| } |
| |
| size_t num_releases() { |
| rtc::CritScope lock(&crit_); |
| return num_releases_; |
| } |
| |
| private: |
| void SetFecControllerOverride( |
| FecControllerOverride* fec_controller_override) override { |
| // Ignored. |
| } |
| |
| int32_t InitEncode(const VideoCodec* codecSettings, |
| const Settings& settings) override { |
| rtc::CritScope lock(&crit_); |
| EXPECT_FALSE(initialized_); |
| initialized_ = true; |
| released_ = false; |
| return 0; |
| } |
| |
| int32_t Encode(const VideoFrame& inputImage, |
| const std::vector<VideoFrameType>* frame_types) override { |
| EXPECT_TRUE(IsReadyForEncode()); |
| |
| observation_complete_.Set(); |
| return 0; |
| } |
| |
| int32_t RegisterEncodeCompleteCallback( |
| EncodedImageCallback* callback) override { |
| rtc::CritScope lock(&crit_); |
| EXPECT_TRUE(initialized_); |
| callback_registered_ = true; |
| return 0; |
| } |
| |
| int32_t Release() override { |
| rtc::CritScope lock(&crit_); |
| EXPECT_TRUE(IsReadyForEncode()); |
| EXPECT_FALSE(released_); |
| initialized_ = false; |
| callback_registered_ = false; |
| released_ = true; |
| ++num_releases_; |
| return 0; |
| } |
| |
| void SetRates(const RateControlParameters& parameters) override { |
| EXPECT_TRUE(IsReadyForEncode()); |
| } |
| |
| void OnVideoStreamsCreated( |
| VideoSendStream* send_stream, |
| const std::vector<VideoReceiveStream*>& receive_streams) override { |
| stream_ = send_stream; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->encoder_settings.encoder_factory = &encoder_factory_; |
| encoder_config_ = encoder_config->Copy(); |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) << "Timed out while waiting for Encode."; |
| |
| SendTask(RTC_FROM_HERE, task_queue_, [this]() { |
| EXPECT_EQ(0u, num_releases()); |
| stream_->ReconfigureVideoEncoder(std::move(encoder_config_)); |
| EXPECT_EQ(0u, num_releases()); |
| stream_->Stop(); |
| // Encoder should not be released before destroying the |
| // VideoSendStream. |
| EXPECT_FALSE(IsReleased()); |
| EXPECT_TRUE(IsReadyForEncode()); |
| stream_->Start(); |
| }); |
| |
| // Sanity check, make sure we still encode frames with this encoder. |
| EXPECT_TRUE(Wait()) << "Timed out while waiting for Encode."; |
| } |
| |
| TaskQueueBase* const task_queue_; |
| rtc::CriticalSection crit_; |
| VideoSendStream* stream_; |
| bool initialized_ RTC_GUARDED_BY(crit_); |
| bool callback_registered_ RTC_GUARDED_BY(crit_); |
| size_t num_releases_ RTC_GUARDED_BY(crit_); |
| bool released_ RTC_GUARDED_BY(crit_); |
| test::VideoEncoderProxyFactory encoder_factory_; |
| VideoEncoderConfig encoder_config_; |
| } test_encoder(task_queue()); |
| |
| RunBaseTest(&test_encoder); |
| |
| EXPECT_TRUE(test_encoder.IsReleased()); |
| EXPECT_EQ(1u, test_encoder.num_releases()); |
| } |
| |
| static const size_t kVideoCodecConfigObserverNumberOfTemporalLayers = 3; |
| template <typename T> |
| class VideoCodecConfigObserver : public test::SendTest, |
| public test::FakeEncoder { |
| public: |
| VideoCodecConfigObserver(VideoCodecType video_codec_type, |
| const char* codec_name) |
| : SendTest(VideoSendStreamTest::kDefaultTimeoutMs), |
| FakeEncoder(Clock::GetRealTimeClock()), |
| video_codec_type_(video_codec_type), |
| codec_name_(codec_name), |
| num_initializations_(0), |
| stream_(nullptr), |
| encoder_factory_(this) { |
| InitCodecSpecifics(); |
| } |
| |
| private: |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->encoder_settings.encoder_factory = &encoder_factory_; |
| send_config->rtp.payload_name = codec_name_; |
| |
| encoder_config->codec_type = video_codec_type_; |
| encoder_config->encoder_specific_settings = GetEncoderSpecificSettings(); |
| EXPECT_EQ(1u, encoder_config->simulcast_layers.size()); |
| encoder_config->simulcast_layers[0].num_temporal_layers = |
| kVideoCodecConfigObserverNumberOfTemporalLayers; |
| encoder_config_ = encoder_config->Copy(); |
| } |
| |
| void OnVideoStreamsCreated( |
| VideoSendStream* send_stream, |
| const std::vector<VideoReceiveStream*>& receive_streams) override { |
| stream_ = send_stream; |
| } |
| |
| int32_t InitEncode(const VideoCodec* config, |
| const Settings& settings) override { |
| EXPECT_EQ(video_codec_type_, config->codecType); |
| VerifyCodecSpecifics(*config); |
| ++num_initializations_; |
| init_encode_event_.Set(); |
| return FakeEncoder::InitEncode(config, settings); |
| } |
| |
| void InitCodecSpecifics(); |
| void VerifyCodecSpecifics(const VideoCodec& config) const; |
| rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings> |
| GetEncoderSpecificSettings() const; |
| |
| void PerformTest() override { |
| EXPECT_TRUE( |
| init_encode_event_.Wait(VideoSendStreamTest::kDefaultTimeoutMs)); |
| ASSERT_EQ(1u, num_initializations_) << "VideoEncoder not initialized."; |
| |
| // Change encoder settings to actually trigger reconfiguration. |
| encoder_settings_.frameDroppingOn = !encoder_settings_.frameDroppingOn; |
| encoder_config_.encoder_specific_settings = GetEncoderSpecificSettings(); |
| stream_->ReconfigureVideoEncoder(std::move(encoder_config_)); |
| ASSERT_TRUE( |
| init_encode_event_.Wait(VideoSendStreamTest::kDefaultTimeoutMs)); |
| EXPECT_EQ(2u, num_initializations_) |
| << "ReconfigureVideoEncoder did not reinitialize the encoder with " |
| "new encoder settings."; |
| } |
| |
| int32_t Encode(const VideoFrame& input_image, |
| const std::vector<VideoFrameType>* frame_types) override { |
| // Silently skip the encode, FakeEncoder::Encode doesn't produce VP8. |
| return 0; |
| } |
| |
| T encoder_settings_; |
| const VideoCodecType video_codec_type_; |
| const char* const codec_name_; |
| rtc::Event init_encode_event_; |
| size_t num_initializations_; |
| VideoSendStream* stream_; |
| test::VideoEncoderProxyFactory encoder_factory_; |
| VideoEncoderConfig encoder_config_; |
| }; |
| |
| template <> |
| void VideoCodecConfigObserver<VideoCodecH264>::InitCodecSpecifics() { |
| encoder_settings_ = VideoEncoder::GetDefaultH264Settings(); |
| } |
| |
| template <> |
| void VideoCodecConfigObserver<VideoCodecH264>::VerifyCodecSpecifics( |
| const VideoCodec& config) const { |
| // Check that the number of temporal layers has propagated properly to |
| // VideoCodec. |
| EXPECT_EQ(kVideoCodecConfigObserverNumberOfTemporalLayers, |
| config.H264().numberOfTemporalLayers); |
| |
| for (unsigned char i = 0; i < config.numberOfSimulcastStreams; ++i) { |
| EXPECT_EQ(kVideoCodecConfigObserverNumberOfTemporalLayers, |
| config.simulcastStream[i].numberOfTemporalLayers); |
| } |
| |
| // Set expected temporal layers as they should have been set when |
| // reconfiguring the encoder and not match the set config. |
| VideoCodecH264 encoder_settings = encoder_settings_; |
| encoder_settings.numberOfTemporalLayers = |
| kVideoCodecConfigObserverNumberOfTemporalLayers; |
| EXPECT_EQ( |
| 0, memcmp(&config.H264(), &encoder_settings, sizeof(encoder_settings_))); |
| } |
| |
| template <> |
| rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings> |
| VideoCodecConfigObserver<VideoCodecH264>::GetEncoderSpecificSettings() const { |
| return new rtc::RefCountedObject< |
| VideoEncoderConfig::H264EncoderSpecificSettings>(encoder_settings_); |
| } |
| |
| template <> |
| void VideoCodecConfigObserver<VideoCodecVP8>::InitCodecSpecifics() { |
| encoder_settings_ = VideoEncoder::GetDefaultVp8Settings(); |
| } |
| |
| template <> |
| void VideoCodecConfigObserver<VideoCodecVP8>::VerifyCodecSpecifics( |
| const VideoCodec& config) const { |
| // Check that the number of temporal layers has propagated properly to |
| // VideoCodec. |
| EXPECT_EQ(kVideoCodecConfigObserverNumberOfTemporalLayers, |
| config.VP8().numberOfTemporalLayers); |
| |
| for (unsigned char i = 0; i < config.numberOfSimulcastStreams; ++i) { |
| EXPECT_EQ(kVideoCodecConfigObserverNumberOfTemporalLayers, |
| config.simulcastStream[i].numberOfTemporalLayers); |
| } |
| |
| // Set expected temporal layers as they should have been set when |
| // reconfiguring the encoder and not match the set config. |
| VideoCodecVP8 encoder_settings = encoder_settings_; |
| encoder_settings.numberOfTemporalLayers = |
| kVideoCodecConfigObserverNumberOfTemporalLayers; |
| EXPECT_EQ( |
| 0, memcmp(&config.VP8(), &encoder_settings, sizeof(encoder_settings_))); |
| } |
| |
| template <> |
| rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings> |
| VideoCodecConfigObserver<VideoCodecVP8>::GetEncoderSpecificSettings() const { |
| return new rtc::RefCountedObject< |
| VideoEncoderConfig::Vp8EncoderSpecificSettings>(encoder_settings_); |
| } |
| |
| template <> |
| void VideoCodecConfigObserver<VideoCodecVP9>::InitCodecSpecifics() { |
| encoder_settings_ = VideoEncoder::GetDefaultVp9Settings(); |
| } |
| |
| template <> |
| void VideoCodecConfigObserver<VideoCodecVP9>::VerifyCodecSpecifics( |
| const VideoCodec& config) const { |
| // Check that the number of temporal layers has propagated properly to |
| // VideoCodec. |
| EXPECT_EQ(kVideoCodecConfigObserverNumberOfTemporalLayers, |
| config.VP9().numberOfTemporalLayers); |
| |
| for (unsigned char i = 0; i < config.numberOfSimulcastStreams; ++i) { |
| EXPECT_EQ(kVideoCodecConfigObserverNumberOfTemporalLayers, |
| config.simulcastStream[i].numberOfTemporalLayers); |
| } |
| |
| // Set expected temporal layers as they should have been set when |
| // reconfiguring the encoder and not match the set config. |
| VideoCodecVP9 encoder_settings = encoder_settings_; |
| encoder_settings.numberOfTemporalLayers = |
| kVideoCodecConfigObserverNumberOfTemporalLayers; |
| EXPECT_EQ( |
| 0, memcmp(&(config.VP9()), &encoder_settings, sizeof(encoder_settings_))); |
| } |
| |
| template <> |
| rtc::scoped_refptr<VideoEncoderConfig::EncoderSpecificSettings> |
| VideoCodecConfigObserver<VideoCodecVP9>::GetEncoderSpecificSettings() const { |
| return new rtc::RefCountedObject< |
| VideoEncoderConfig::Vp9EncoderSpecificSettings>(encoder_settings_); |
| } |
| |
| TEST_F(VideoSendStreamTest, EncoderSetupPropagatesVp8Config) { |
| VideoCodecConfigObserver<VideoCodecVP8> test(kVideoCodecVP8, "VP8"); |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, EncoderSetupPropagatesVp9Config) { |
| VideoCodecConfigObserver<VideoCodecVP9> test(kVideoCodecVP9, "VP9"); |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, EncoderSetupPropagatesH264Config) { |
| VideoCodecConfigObserver<VideoCodecH264> test(kVideoCodecH264, "H264"); |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, RtcpSenderReportContainsMediaBytesSent) { |
| class RtcpSenderReportTest : public test::SendTest { |
| public: |
| RtcpSenderReportTest() |
| : SendTest(kDefaultTimeoutMs), |
| rtp_packets_sent_(0), |
| media_bytes_sent_(0) {} |
| |
| private: |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| rtc::CritScope lock(&crit_); |
| RtpPacket rtp_packet; |
| EXPECT_TRUE(rtp_packet.Parse(packet, length)); |
| ++rtp_packets_sent_; |
| media_bytes_sent_ += rtp_packet.payload_size(); |
| return SEND_PACKET; |
| } |
| |
| Action OnSendRtcp(const uint8_t* packet, size_t length) override { |
| rtc::CritScope lock(&crit_); |
| test::RtcpPacketParser parser; |
| EXPECT_TRUE(parser.Parse(packet, length)); |
| |
| if (parser.sender_report()->num_packets() > 0) { |
| // Only compare sent media bytes if SenderPacketCount matches the |
| // number of sent rtp packets (a new rtp packet could be sent before |
| // the rtcp packet). |
| if (parser.sender_report()->sender_octet_count() > 0 && |
| parser.sender_report()->sender_packet_count() == |
| rtp_packets_sent_) { |
| EXPECT_EQ(media_bytes_sent_, |
| parser.sender_report()->sender_octet_count()); |
| observation_complete_.Set(); |
| } |
| } |
| |
| return SEND_PACKET; |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) << "Timed out while waiting for RTCP sender report."; |
| } |
| |
| rtc::CriticalSection crit_; |
| size_t rtp_packets_sent_ RTC_GUARDED_BY(&crit_); |
| size_t media_bytes_sent_ RTC_GUARDED_BY(&crit_); |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, TranslatesTwoLayerScreencastToTargetBitrate) { |
| static const int kScreencastMaxTargetBitrateDeltaKbps = 1; |
| |
| class VideoStreamFactory |
| : public VideoEncoderConfig::VideoStreamFactoryInterface { |
| public: |
| VideoStreamFactory() {} |
| |
| private: |
| std::vector<VideoStream> CreateEncoderStreams( |
| int width, |
| int height, |
| const VideoEncoderConfig& encoder_config) override { |
| std::vector<VideoStream> streams = |
| test::CreateVideoStreams(width, height, encoder_config); |
| RTC_CHECK_GT(streams[0].max_bitrate_bps, |
| kScreencastMaxTargetBitrateDeltaKbps); |
| streams[0].target_bitrate_bps = |
| streams[0].max_bitrate_bps - |
| kScreencastMaxTargetBitrateDeltaKbps * 1000; |
| return streams; |
| } |
| }; |
| |
| class ScreencastTargetBitrateTest : public test::SendTest, |
| public test::FakeEncoder { |
| public: |
| ScreencastTargetBitrateTest() |
| : SendTest(kDefaultTimeoutMs), |
| test::FakeEncoder(Clock::GetRealTimeClock()), |
| encoder_factory_(this) {} |
| |
| private: |
| int32_t InitEncode(const VideoCodec* config, |
| const Settings& settings) override { |
| EXPECT_EQ(config->numberOfSimulcastStreams, 1); |
| EXPECT_EQ(static_cast<unsigned int>(kScreencastMaxTargetBitrateDeltaKbps), |
| config->simulcastStream[0].maxBitrate - |
| config->simulcastStream[0].targetBitrate); |
| observation_complete_.Set(); |
| return test::FakeEncoder::InitEncode(config, settings); |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->encoder_settings.encoder_factory = &encoder_factory_; |
| EXPECT_EQ(1u, encoder_config->number_of_streams); |
| encoder_config->video_stream_factory = |
| new rtc::RefCountedObject<VideoStreamFactory>(); |
| EXPECT_EQ(1u, encoder_config->simulcast_layers.size()); |
| encoder_config->simulcast_layers[0].num_temporal_layers = 2; |
| encoder_config->content_type = VideoEncoderConfig::ContentType::kScreen; |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) |
| << "Timed out while waiting for the encoder to be initialized."; |
| } |
| test::VideoEncoderProxyFactory encoder_factory_; |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, ReconfigureBitratesSetsEncoderBitratesCorrectly) { |
| // These are chosen to be "kind of odd" to not be accidentally checked against |
| // default values. |
| static const int kMinBitrateKbps = 137; |
| static const int kStartBitrateKbps = 345; |
| static const int kLowerMaxBitrateKbps = 312; |
| static const int kMaxBitrateKbps = 413; |
| static const int kIncreasedStartBitrateKbps = 451; |
| static const int kIncreasedMaxBitrateKbps = 597; |
| // If these fields trial are on, we get lower bitrates than expected by this |
| // test, due to the packetization overhead and encoder pushback. |
| webrtc::test::ScopedFieldTrials field_trials( |
| std::string(field_trial::GetFieldTrialString()) + |
| "WebRTC-SubtractPacketizationOverhead/Disabled/" |
| "WebRTC-VideoRateControl/bitrate_adjuster:false/"); |
| |
| class EncoderBitrateThresholdObserver : public test::SendTest, |
| public VideoBitrateAllocatorFactory, |
| public test::FakeEncoder { |
| public: |
| explicit EncoderBitrateThresholdObserver(TaskQueueBase* task_queue) |
| : SendTest(kDefaultTimeoutMs), |
| FakeEncoder(Clock::GetRealTimeClock()), |
| task_queue_(task_queue), |
| target_bitrate_(0), |
| num_rate_allocator_creations_(0), |
| num_encoder_initializations_(0), |
| call_(nullptr), |
| send_stream_(nullptr), |
| encoder_factory_(this), |
| bitrate_allocator_factory_( |
| CreateBuiltinVideoBitrateAllocatorFactory()) {} |
| |
| private: |
| std::unique_ptr<VideoBitrateAllocator> CreateVideoBitrateAllocator( |
| const VideoCodec& codec) override { |
| EXPECT_GE(codec.startBitrate, codec.minBitrate); |
| EXPECT_LE(codec.startBitrate, codec.maxBitrate); |
| if (num_rate_allocator_creations_ == 0) { |
| EXPECT_EQ(static_cast<unsigned int>(kMinBitrateKbps), codec.minBitrate); |
| EXPECT_EQ(static_cast<unsigned int>(kStartBitrateKbps), |
| codec.startBitrate); |
| EXPECT_EQ(static_cast<unsigned int>(kMaxBitrateKbps), codec.maxBitrate); |
| } else if (num_rate_allocator_creations_ == 1) { |
| EXPECT_EQ(static_cast<unsigned int>(kLowerMaxBitrateKbps), |
| codec.maxBitrate); |
| // The start bitrate should be kept (-1) and capped to the max bitrate. |
| // Since this is not an end-to-end call no receiver should have been |
| // returning a REMB that could lower this estimate. |
| EXPECT_EQ(codec.startBitrate, codec.maxBitrate); |
| } else if (num_rate_allocator_creations_ == 2) { |
| EXPECT_EQ(static_cast<unsigned int>(kIncreasedMaxBitrateKbps), |
| codec.maxBitrate); |
| // The start bitrate will be whatever the rate BitRateController |
| // has currently configured but in the span of the set max and min |
| // bitrate. |
| } |
| ++num_rate_allocator_creations_; |
| create_rate_allocator_event_.Set(); |
| |
| return bitrate_allocator_factory_->CreateVideoBitrateAllocator(codec); |
| } |
| |
| int32_t InitEncode(const VideoCodec* codecSettings, |
| const Settings& settings) override { |
| EXPECT_EQ(0, num_encoder_initializations_); |
| EXPECT_EQ(static_cast<unsigned int>(kMinBitrateKbps), |
| codecSettings->minBitrate); |
| EXPECT_EQ(static_cast<unsigned int>(kStartBitrateKbps), |
| codecSettings->startBitrate); |
| EXPECT_EQ(static_cast<unsigned int>(kMaxBitrateKbps), |
| codecSettings->maxBitrate); |
| |
| ++num_encoder_initializations_; |
| |
| observation_complete_.Set(); |
| init_encode_event_.Set(); |
| |
| return FakeEncoder::InitEncode(codecSettings, settings); |
| } |
| |
| void SetRates(const RateControlParameters& parameters) override { |
| { |
| rtc::CritScope lock(&crit_); |
| if (target_bitrate_ == parameters.bitrate.get_sum_kbps()) { |
| FakeEncoder::SetRates(parameters); |
| return; |
| } |
| target_bitrate_ = parameters.bitrate.get_sum_kbps(); |
| } |
| bitrate_changed_event_.Set(); |
| FakeEncoder::SetRates(parameters); |
| } |
| |
| void WaitForSetRates(uint32_t expected_bitrate) { |
| // Wait for the expected rate to be set. In some cases there can be |
| // more than one update pending, in which case we keep waiting |
| // until the correct value has been observed. |
| const int64_t start_time = rtc::TimeMillis(); |
| do { |
| rtc::CritScope lock(&crit_); |
| if (target_bitrate_ == expected_bitrate) { |
| return; |
| } |
| } while (bitrate_changed_event_.Wait( |
| std::max(int64_t{1}, VideoSendStreamTest::kDefaultTimeoutMs - |
| (rtc::TimeMillis() - start_time)))); |
| rtc::CritScope lock(&crit_); |
| EXPECT_EQ(target_bitrate_, expected_bitrate) |
| << "Timed out while waiting encoder rate to be set."; |
| } |
| |
| void ModifySenderBitrateConfig( |
| BitrateConstraints* bitrate_config) override { |
| bitrate_config->min_bitrate_bps = kMinBitrateKbps * 1000; |
| bitrate_config->start_bitrate_bps = kStartBitrateKbps * 1000; |
| bitrate_config->max_bitrate_bps = kMaxBitrateKbps * 1000; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->encoder_settings.encoder_factory = &encoder_factory_; |
| send_config->encoder_settings.bitrate_allocator_factory = this; |
| // Set bitrates lower/higher than min/max to make sure they are properly |
| // capped. |
| encoder_config->max_bitrate_bps = kMaxBitrateKbps * 1000; |
| EXPECT_EQ(1u, encoder_config->simulcast_layers.size()); |
| encoder_config->simulcast_layers[0].min_bitrate_bps = |
| kMinBitrateKbps * 1000; |
| encoder_config_ = encoder_config->Copy(); |
| } |
| |
| void OnCallsCreated(Call* sender_call, Call* receiver_call) override { |
| call_ = sender_call; |
| } |
| |
| void OnVideoStreamsCreated( |
| VideoSendStream* send_stream, |
| const std::vector<VideoReceiveStream*>& receive_streams) override { |
| send_stream_ = send_stream; |
| } |
| |
| void PerformTest() override { |
| ASSERT_TRUE(create_rate_allocator_event_.Wait( |
| VideoSendStreamTest::kDefaultTimeoutMs)) |
| << "Timed out while waiting for rate allocator to be created."; |
| ASSERT_TRUE( |
| init_encode_event_.Wait(VideoSendStreamTest::kDefaultTimeoutMs)) |
| << "Timed out while waiting for encoder to be configured."; |
| WaitForSetRates(kStartBitrateKbps); |
| BitrateConstraints bitrate_config; |
| bitrate_config.start_bitrate_bps = kIncreasedStartBitrateKbps * 1000; |
| bitrate_config.max_bitrate_bps = kIncreasedMaxBitrateKbps * 1000; |
| SendTask(RTC_FROM_HERE, task_queue_, [this, &bitrate_config]() { |
| call_->GetTransportControllerSend()->SetSdpBitrateParameters( |
| bitrate_config); |
| }); |
| // Encoder rate is capped by EncoderConfig max_bitrate_bps. |
| WaitForSetRates(kMaxBitrateKbps); |
| encoder_config_.max_bitrate_bps = kLowerMaxBitrateKbps * 1000; |
| send_stream_->ReconfigureVideoEncoder(encoder_config_.Copy()); |
| ASSERT_TRUE(create_rate_allocator_event_.Wait( |
| VideoSendStreamTest::kDefaultTimeoutMs)); |
| EXPECT_EQ(2, num_rate_allocator_creations_) |
| << "Rate allocator should have been recreated."; |
| |
| WaitForSetRates(kLowerMaxBitrateKbps); |
| EXPECT_EQ(1, num_encoder_initializations_); |
| |
| encoder_config_.max_bitrate_bps = kIncreasedMaxBitrateKbps * 1000; |
| send_stream_->ReconfigureVideoEncoder(encoder_config_.Copy()); |
| ASSERT_TRUE(create_rate_allocator_event_.Wait( |
| VideoSendStreamTest::kDefaultTimeoutMs)); |
| EXPECT_EQ(3, num_rate_allocator_creations_) |
| << "Rate allocator should have been recreated."; |
| |
| // Expected target bitrate is the start bitrate set in the call to |
| // call_->GetTransportControllerSend()->SetSdpBitrateParameters. |
| WaitForSetRates(kIncreasedStartBitrateKbps); |
| EXPECT_EQ(1, num_encoder_initializations_); |
| } |
| |
| TaskQueueBase* const task_queue_; |
| rtc::Event create_rate_allocator_event_; |
| rtc::Event init_encode_event_; |
| rtc::Event bitrate_changed_event_; |
| rtc::CriticalSection crit_; |
| uint32_t target_bitrate_ RTC_GUARDED_BY(&crit_); |
| |
| int num_rate_allocator_creations_; |
| int num_encoder_initializations_; |
| webrtc::Call* call_; |
| webrtc::VideoSendStream* send_stream_; |
| test::VideoEncoderProxyFactory encoder_factory_; |
| std::unique_ptr<VideoBitrateAllocatorFactory> bitrate_allocator_factory_; |
| webrtc::VideoEncoderConfig encoder_config_; |
| } test(task_queue()); |
| |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, ReportsSentResolution) { |
| static const size_t kNumStreams = 3; |
| // Unusual resolutions to make sure that they are the ones being reported. |
| static const struct { |
| int width; |
| int height; |
| } kEncodedResolution[kNumStreams] = {{241, 181}, {300, 121}, {121, 221}}; |
| class ScreencastTargetBitrateTest : public test::SendTest, |
| public test::FakeEncoder { |
| public: |
| ScreencastTargetBitrateTest() |
| : SendTest(kDefaultTimeoutMs), |
| test::FakeEncoder(Clock::GetRealTimeClock()), |
| send_stream_(nullptr), |
| encoder_factory_(this) {} |
| |
| private: |
| int32_t Encode(const VideoFrame& input_image, |
| const std::vector<VideoFrameType>* frame_types) override { |
| CodecSpecificInfo specifics; |
| specifics.codecType = kVideoCodecGeneric; |
| |
| uint8_t buffer[16] = {0}; |
| EncodedImage encoded(buffer, sizeof(buffer), sizeof(buffer)); |
| encoded.SetTimestamp(input_image.timestamp()); |
| encoded.capture_time_ms_ = input_image.render_time_ms(); |
| |
| for (size_t i = 0; i < kNumStreams; ++i) { |
| encoded._frameType = (*frame_types)[i]; |
| encoded._encodedWidth = kEncodedResolution[i].width; |
| encoded._encodedHeight = kEncodedResolution[i].height; |
| encoded.SetSpatialIndex(i); |
| EncodedImageCallback* callback; |
| { |
| rtc::CritScope cs(&crit_sect_); |
| callback = callback_; |
| } |
| RTC_DCHECK(callback); |
| if (callback->OnEncodedImage(encoded, &specifics, nullptr).error != |
| EncodedImageCallback::Result::OK) { |
| return -1; |
| } |
| } |
| |
| observation_complete_.Set(); |
| return 0; |
| } |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->encoder_settings.encoder_factory = &encoder_factory_; |
| EXPECT_EQ(kNumStreams, encoder_config->number_of_streams); |
| } |
| |
| size_t GetNumVideoStreams() const override { return kNumStreams; } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) |
| << "Timed out while waiting for the encoder to send one frame."; |
| VideoSendStream::Stats stats = send_stream_->GetStats(); |
| |
| for (size_t i = 0; i < kNumStreams; ++i) { |
| ASSERT_TRUE(stats.substreams.find(kVideoSendSsrcs[i]) != |
| stats.substreams.end()) |
| << "No stats for SSRC: " << kVideoSendSsrcs[i] |
| << ", stats should exist as soon as frames have been encoded."; |
| VideoSendStream::StreamStats ssrc_stats = |
| stats.substreams[kVideoSendSsrcs[i]]; |
| EXPECT_EQ(kEncodedResolution[i].width, ssrc_stats.width); |
| EXPECT_EQ(kEncodedResolution[i].height, ssrc_stats.height); |
| } |
| } |
| |
| void OnVideoStreamsCreated( |
| VideoSendStream* send_stream, |
| const std::vector<VideoReceiveStream*>& receive_streams) override { |
| send_stream_ = send_stream; |
| } |
| |
| VideoSendStream* send_stream_; |
| test::VideoEncoderProxyFactory encoder_factory_; |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| #if defined(RTC_ENABLE_VP9) |
| class Vp9HeaderObserver : public test::SendTest { |
| public: |
| Vp9HeaderObserver() |
| : SendTest(VideoSendStreamTest::kLongTimeoutMs), |
| encoder_factory_([]() { return VP9Encoder::Create(); }), |
| vp9_settings_(VideoEncoder::GetDefaultVp9Settings()), |
| packets_sent_(0), |
| frames_sent_(0), |
| expected_width_(0), |
| expected_height_(0) {} |
| |
| virtual void ModifyVideoConfigsHook( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) {} |
| |
| virtual void InspectHeader(const RTPVideoHeaderVP9& vp9) = 0; |
| |
| private: |
| const int kVp9PayloadType = test::CallTest::kVideoSendPayloadType; |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->encoder_settings.encoder_factory = &encoder_factory_; |
| send_config->rtp.payload_name = "VP9"; |
| send_config->rtp.payload_type = kVp9PayloadType; |
| ModifyVideoConfigsHook(send_config, receive_configs, encoder_config); |
| encoder_config->encoder_specific_settings = new rtc::RefCountedObject< |
| VideoEncoderConfig::Vp9EncoderSpecificSettings>(vp9_settings_); |
| EXPECT_EQ(1u, encoder_config->number_of_streams); |
| EXPECT_EQ(1u, encoder_config->simulcast_layers.size()); |
| encoder_config->simulcast_layers[0].num_temporal_layers = |
| vp9_settings_.numberOfTemporalLayers; |
| encoder_config_ = encoder_config->Copy(); |
| } |
| |
| void ModifyVideoCaptureStartResolution(int* width, |
| int* height, |
| int* frame_rate) override { |
| expected_width_ = *width; |
| expected_height_ = *height; |
| } |
| |
| void PerformTest() override { |
| bool wait = Wait(); |
| { |
| // In case of time out, OnSendRtp might still access frames_sent_; |
| rtc::CritScope lock(&crit_); |
| EXPECT_TRUE(wait) << "Test timed out waiting for VP9 packet, num frames " |
| << frames_sent_; |
| } |
| } |
| |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| RtpPacket rtp_packet; |
| EXPECT_TRUE(rtp_packet.Parse(packet, length)); |
| |
| EXPECT_EQ(kVp9PayloadType, rtp_packet.PayloadType()); |
| rtc::ArrayView<const uint8_t> rtp_payload = rtp_packet.payload(); |
| |
| bool new_packet = packets_sent_ == 0 || |
| IsNewerSequenceNumber(rtp_packet.SequenceNumber(), |
| last_packet_sequence_number_); |
| if (!rtp_payload.empty() && new_packet) { |
| RTPVideoHeader video_header; |
| EXPECT_NE( |
| VideoRtpDepacketizerVp9::ParseRtpPayload(rtp_payload, &video_header), |
| 0); |
| EXPECT_EQ(VideoCodecType::kVideoCodecVP9, video_header.codec); |
| // Verify common fields for all configurations. |
| const auto& vp9_header = |
| absl::get<RTPVideoHeaderVP9>(video_header.video_type_header); |
| VerifyCommonHeader(vp9_header); |
| CompareConsecutiveFrames(rtp_packet, video_header); |
| // Verify configuration specific settings. |
| InspectHeader(vp9_header); |
| |
| ++packets_sent_; |
| if (rtp_packet.Marker()) { |
| rtc::CritScope lock(&crit_); |
| ++frames_sent_; |
| } |
| last_packet_marker_ = rtp_packet.Marker(); |
| last_packet_sequence_number_ = rtp_packet.SequenceNumber(); |
| last_packet_timestamp_ = rtp_packet.Timestamp(); |
| last_vp9_ = vp9_header; |
| } |
| return SEND_PACKET; |
| } |
| |
| protected: |
| bool ContinuousPictureId(const RTPVideoHeaderVP9& vp9) const { |
| if (last_vp9_.picture_id > vp9.picture_id) { |
| return vp9.picture_id == 0; // Wrap. |
| } else { |
| return vp9.picture_id == last_vp9_.picture_id + 1; |
| } |
| } |
| |
| void VerifySpatialIdxWithinFrame(const RTPVideoHeaderVP9& vp9) const { |
| bool new_layer = vp9.spatial_idx != last_vp9_.spatial_idx; |
| EXPECT_EQ(new_layer, vp9.beginning_of_frame); |
| EXPECT_EQ(new_layer, last_vp9_.end_of_frame); |
| EXPECT_EQ(new_layer ? last_vp9_.spatial_idx + 1 : last_vp9_.spatial_idx, |
| vp9.spatial_idx); |
| } |
| |
| void VerifyFixedTemporalLayerStructure(const RTPVideoHeaderVP9& vp9, |
| uint8_t num_layers) const { |
| switch (num_layers) { |
| case 0: |
| VerifyTemporalLayerStructure0(vp9); |
| break; |
| case 1: |
| VerifyTemporalLayerStructure1(vp9); |
| break; |
| case 2: |
| VerifyTemporalLayerStructure2(vp9); |
| break; |
| case 3: |
| VerifyTemporalLayerStructure3(vp9); |
| break; |
| default: |
| RTC_NOTREACHED(); |
| } |
| } |
| |
| void VerifyTemporalLayerStructure0(const RTPVideoHeaderVP9& vp9) const { |
| EXPECT_EQ(kNoTl0PicIdx, vp9.tl0_pic_idx); |
| EXPECT_EQ(kNoTemporalIdx, vp9.temporal_idx); // no tid |
| EXPECT_FALSE(vp9.temporal_up_switch); |
| } |
| |
| void VerifyTemporalLayerStructure1(const RTPVideoHeaderVP9& vp9) const { |
| EXPECT_NE(kNoTl0PicIdx, vp9.tl0_pic_idx); |
| EXPECT_EQ(0, vp9.temporal_idx); // 0,0,0,... |
| EXPECT_FALSE(vp9.temporal_up_switch); |
| } |
| |
| void VerifyTemporalLayerStructure2(const RTPVideoHeaderVP9& vp9) const { |
| EXPECT_NE(kNoTl0PicIdx, vp9.tl0_pic_idx); |
| EXPECT_GE(vp9.temporal_idx, 0); // 0,1,0,1,... (tid reset on I-frames). |
| EXPECT_LE(vp9.temporal_idx, 1); |
| EXPECT_EQ(vp9.temporal_idx > 0, vp9.temporal_up_switch); |
| if (IsNewPictureId(vp9)) { |
| uint8_t expected_tid = |
| (!vp9.inter_pic_predicted || last_vp9_.temporal_idx == 1) ? 0 : 1; |
| EXPECT_EQ(expected_tid, vp9.temporal_idx); |
| } |
| } |
| |
| void VerifyTemporalLayerStructure3(const RTPVideoHeaderVP9& vp9) const { |
| EXPECT_NE(kNoTl0PicIdx, vp9.tl0_pic_idx); |
| EXPECT_GE(vp9.temporal_idx, 0); // 0,2,1,2,... (tid reset on I-frames). |
| EXPECT_LE(vp9.temporal_idx, 2); |
| if (IsNewPictureId(vp9) && vp9.inter_pic_predicted) { |
| EXPECT_NE(vp9.temporal_idx, last_vp9_.temporal_idx); |
| switch (vp9.temporal_idx) { |
| case 0: |
| EXPECT_EQ(2, last_vp9_.temporal_idx); |
| EXPECT_FALSE(vp9.temporal_up_switch); |
| break; |
| case 1: |
| EXPECT_EQ(2, last_vp9_.temporal_idx); |
| EXPECT_TRUE(vp9.temporal_up_switch); |
| break; |
| case 2: |
| EXPECT_LT(last_vp9_.temporal_idx, 2); |
| EXPECT_TRUE(vp9.temporal_up_switch); |
| break; |
| } |
| } |
| } |
| |
| void VerifyTl0Idx(const RTPVideoHeaderVP9& vp9) const { |
| if (vp9.tl0_pic_idx == kNoTl0PicIdx) |
| return; |
| |
| uint8_t expected_tl0_idx = last_vp9_.tl0_pic_idx; |
| if (vp9.temporal_idx == 0) |
| ++expected_tl0_idx; |
| EXPECT_EQ(expected_tl0_idx, vp9.tl0_pic_idx); |
| } |
| |
| bool IsNewPictureId(const RTPVideoHeaderVP9& vp9) const { |
| return frames_sent_ > 0 && (vp9.picture_id != last_vp9_.picture_id); |
| } |
| |
| // Flexible mode (F=1): Non-flexible mode (F=0): |
| // |
| // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ |
| // |I|P|L|F|B|E|V|-| |I|P|L|F|B|E|V|-| |
| // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ |
| // I: |M| PICTURE ID | I: |M| PICTURE ID | |
| // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ |
| // M: | EXTENDED PID | M: | EXTENDED PID | |
| // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ |
| // L: | T |U| S |D| L: | T |U| S |D| |
| // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ |
| // P,F: | P_DIFF |X|N| | TL0PICIDX | |
| // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ |
| // X: |EXTENDED P_DIFF| V: | SS .. | |
| // +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ |
| // V: | SS .. | |
| // +-+-+-+-+-+-+-+-+ |
| void VerifyCommonHeader(const RTPVideoHeaderVP9& vp9) const { |
| EXPECT_EQ(kMaxTwoBytePictureId, vp9.max_picture_id); // M:1 |
| EXPECT_NE(kNoPictureId, vp9.picture_id); // I:1 |
| EXPECT_EQ(vp9_settings_.flexibleMode, vp9.flexible_mode); // F |
| |
| if (vp9_settings_.numberOfSpatialLayers > 1) { |
| EXPECT_LT(vp9.spatial_idx, vp9_settings_.numberOfSpatialLayers); |
| } else if (vp9_settings_.numberOfTemporalLayers > 1) { |
| EXPECT_EQ(vp9.spatial_idx, 0); |
| } else { |
| EXPECT_EQ(vp9.spatial_idx, kNoSpatialIdx); |
| } |
| |
| if (vp9_settings_.numberOfTemporalLayers > 1) { |
| EXPECT_LT(vp9.temporal_idx, vp9_settings_.numberOfTemporalLayers); |
| } else if (vp9_settings_.numberOfSpatialLayers > 1) { |
| EXPECT_EQ(vp9.temporal_idx, 0); |
| } else { |
| EXPECT_EQ(vp9.temporal_idx, kNoTemporalIdx); |
| } |
| |
| if (vp9.ss_data_available) // V |
| VerifySsData(vp9); |
| |
| if (frames_sent_ == 0) |
| EXPECT_FALSE(vp9.inter_pic_predicted); // P |
| |
| if (!vp9.inter_pic_predicted) { |
| EXPECT_TRUE(vp9.temporal_idx == 0 || vp9.temporal_idx == kNoTemporalIdx); |
| EXPECT_FALSE(vp9.temporal_up_switch); |
| } |
| } |
| |
| // Scalability structure (SS). |
| // |
| // +-+-+-+-+-+-+-+-+ |
| // V: | N_S |Y|G|-|-|-| |
| // +-+-+-+-+-+-+-+-+ |
| // Y: | WIDTH | N_S + 1 times |
| // +-+-+-+-+-+-+-+-+ |
| // | HEIGHT | |
| // +-+-+-+-+-+-+-+-+ |
| // G: | N_G | |
| // +-+-+-+-+-+-+-+-+ |
| // N_G: | T |U| R |-|-| N_G times |
| // +-+-+-+-+-+-+-+-+ |
| // | P_DIFF | R times |
| // +-+-+-+-+-+-+-+-+ |
| void VerifySsData(const RTPVideoHeaderVP9& vp9) const { |
| EXPECT_TRUE(vp9.ss_data_available); // V |
| EXPECT_EQ(vp9_settings_.numberOfSpatialLayers, // N_S + 1 |
| vp9.num_spatial_layers); |
| EXPECT_TRUE(vp9.spatial_layer_resolution_present); // Y:1 |
| int expected_width = expected_width_; |
| int expected_height = expected_height_; |
| for (int i = static_cast<int>(vp9.num_spatial_layers) - 1; i >= 0; --i) { |
| EXPECT_EQ(expected_width, vp9.width[i]); // WIDTH |
| EXPECT_EQ(expected_height, vp9.height[i]); // HEIGHT |
| expected_width /= 2; |
| expected_height /= 2; |
| } |
| } |
| |
| void CompareConsecutiveFrames(const RtpPacket& rtp_packet, |
| const RTPVideoHeader& video) const { |
| const auto& vp9_header = |
| absl::get<RTPVideoHeaderVP9>(video.video_type_header); |
| |
| bool new_frame = |
| packets_sent_ == 0 || |
| IsNewerTimestamp(rtp_packet.Timestamp(), last_packet_timestamp_); |
| EXPECT_EQ(new_frame, video.is_first_packet_in_frame); |
| if (!new_frame) { |
| EXPECT_FALSE(last_packet_marker_); |
| EXPECT_EQ(last_packet_timestamp_, rtp_packet.Timestamp()); |
| EXPECT_EQ(last_vp9_.picture_id, vp9_header.picture_id); |
| EXPECT_EQ(last_vp9_.temporal_idx, vp9_header.temporal_idx); |
| EXPECT_EQ(last_vp9_.tl0_pic_idx, vp9_header.tl0_pic_idx); |
| VerifySpatialIdxWithinFrame(vp9_header); |
| return; |
| } |
| // New frame. |
| EXPECT_TRUE(vp9_header.beginning_of_frame); |
| |
| // Compare with last packet in previous frame. |
| if (frames_sent_ == 0) |
| return; |
| EXPECT_TRUE(last_vp9_.end_of_frame); |
| EXPECT_TRUE(last_packet_marker_); |
| EXPECT_TRUE(ContinuousPictureId(vp9_header)); |
| VerifyTl0Idx(vp9_header); |
| } |
| |
| test::FunctionVideoEncoderFactory encoder_factory_; |
| VideoCodecVP9 vp9_settings_; |
| webrtc::VideoEncoderConfig encoder_config_; |
| bool last_packet_marker_ = false; |
| uint16_t last_packet_sequence_number_ = 0; |
| uint32_t last_packet_timestamp_ = 0; |
| RTPVideoHeaderVP9 last_vp9_; |
| size_t packets_sent_; |
| rtc::CriticalSection crit_; |
| size_t frames_sent_; |
| int expected_width_; |
| int expected_height_; |
| }; |
| |
| TEST_F(VideoSendStreamTest, Vp9NonFlexMode_1Tl1SLayers) { |
| const uint8_t kNumTemporalLayers = 1; |
| const uint8_t kNumSpatialLayers = 1; |
| TestVp9NonFlexMode(kNumTemporalLayers, kNumSpatialLayers); |
| } |
| |
| TEST_F(VideoSendStreamTest, Vp9NonFlexMode_2Tl1SLayers) { |
| const uint8_t kNumTemporalLayers = 2; |
| const uint8_t kNumSpatialLayers = 1; |
| TestVp9NonFlexMode(kNumTemporalLayers, kNumSpatialLayers); |
| } |
| |
| TEST_F(VideoSendStreamTest, Vp9NonFlexMode_3Tl1SLayers) { |
| const uint8_t kNumTemporalLayers = 3; |
| const uint8_t kNumSpatialLayers = 1; |
| TestVp9NonFlexMode(kNumTemporalLayers, kNumSpatialLayers); |
| } |
| |
| TEST_F(VideoSendStreamTest, Vp9NonFlexMode_1Tl2SLayers) { |
| const uint8_t kNumTemporalLayers = 1; |
| const uint8_t kNumSpatialLayers = 2; |
| TestVp9NonFlexMode(kNumTemporalLayers, kNumSpatialLayers); |
| } |
| |
| TEST_F(VideoSendStreamTest, Vp9NonFlexMode_2Tl2SLayers) { |
| const uint8_t kNumTemporalLayers = 2; |
| const uint8_t kNumSpatialLayers = 2; |
| TestVp9NonFlexMode(kNumTemporalLayers, kNumSpatialLayers); |
| } |
| |
| TEST_F(VideoSendStreamTest, Vp9NonFlexMode_3Tl2SLayers) { |
| const uint8_t kNumTemporalLayers = 3; |
| const uint8_t kNumSpatialLayers = 2; |
| TestVp9NonFlexMode(kNumTemporalLayers, kNumSpatialLayers); |
| } |
| |
| void VideoSendStreamTest::TestVp9NonFlexMode(uint8_t num_temporal_layers, |
| uint8_t num_spatial_layers) { |
| static const size_t kNumFramesToSend = 100; |
| // Set to < kNumFramesToSend and coprime to length of temporal layer |
| // structures to verify temporal id reset on key frame. |
| static const int kKeyFrameInterval = 31; |
| |
| static const int kWidth = kMinVp9SpatialLayerWidth; |
| static const int kHeight = kMinVp9SpatialLayerHeight; |
| static const float kGoodBitsPerPixel = 0.1f; |
| class NonFlexibleMode : public Vp9HeaderObserver { |
| public: |
| NonFlexibleMode(uint8_t num_temporal_layers, uint8_t num_spatial_layers) |
| : num_temporal_layers_(num_temporal_layers), |
| num_spatial_layers_(num_spatial_layers), |
| l_field_(num_temporal_layers > 1 || num_spatial_layers > 1) {} |
| |
| void ModifyVideoConfigsHook( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| encoder_config->codec_type = kVideoCodecVP9; |
| int bitrate_bps = 0; |
| for (int sl_idx = 0; sl_idx < num_spatial_layers_; ++sl_idx) { |
| const int width = kWidth << sl_idx; |
| const int height = kHeight << sl_idx; |
| const float bpp = kGoodBitsPerPixel / (1 << sl_idx); |
| bitrate_bps += static_cast<int>(width * height * bpp * 30); |
| } |
| encoder_config->max_bitrate_bps = bitrate_bps * 2; |
| |
| vp9_settings_.flexibleMode = false; |
| vp9_settings_.frameDroppingOn = false; |
| vp9_settings_.keyFrameInterval = kKeyFrameInterval; |
| vp9_settings_.numberOfTemporalLayers = num_temporal_layers_; |
| vp9_settings_.numberOfSpatialLayers = num_spatial_layers_; |
| } |
| |
| void ModifyVideoCaptureStartResolution(int* width, |
| int* height, |
| int* frame_rate) override { |
| expected_width_ = kWidth << (num_spatial_layers_ - 1); |
| expected_height_ = kHeight << (num_spatial_layers_ - 1); |
| *width = expected_width_; |
| *height = expected_height_; |
| } |
| |
| void InspectHeader(const RTPVideoHeaderVP9& vp9) override { |
| bool ss_data_expected = |
| !vp9.inter_pic_predicted && vp9.beginning_of_frame && |
| (vp9.spatial_idx == 0 || vp9.spatial_idx == kNoSpatialIdx); |
| EXPECT_EQ(ss_data_expected, vp9.ss_data_available); |
| if (num_spatial_layers_ > 1) { |
| EXPECT_EQ(vp9.spatial_idx > 0, vp9.inter_layer_predicted); |
| } else { |
| EXPECT_FALSE(vp9.inter_layer_predicted); |
| } |
| |
| EXPECT_EQ(!vp9.inter_pic_predicted, |
| frames_sent_ % kKeyFrameInterval == 0); |
| |
| if (IsNewPictureId(vp9)) { |
| if (num_temporal_layers_ == 1 && num_spatial_layers_ == 1) { |
| EXPECT_EQ(kNoSpatialIdx, vp9.spatial_idx); |
| } else { |
| EXPECT_EQ(0, vp9.spatial_idx); |
| } |
| if (num_spatial_layers_ > 1) |
| EXPECT_EQ(num_spatial_layers_ - 1, last_vp9_.spatial_idx); |
| } |
| |
| VerifyFixedTemporalLayerStructure(vp9, |
| l_field_ ? num_temporal_layers_ : 0); |
| |
| if (frames_sent_ > kNumFramesToSend) |
| observation_complete_.Set(); |
| } |
| const uint8_t num_temporal_layers_; |
| const uint8_t num_spatial_layers_; |
| const bool l_field_; |
| |
| private: |
| void ModifySenderBitrateConfig( |
| BitrateConstraints* bitrate_config) override { |
| const int kMinBitrateBps = 300000; |
| bitrate_config->min_bitrate_bps = kMinBitrateBps; |
| } |
| } test(num_temporal_layers, num_spatial_layers); |
| |
| RunBaseTest(&test); |
| } |
| |
| TEST_F(VideoSendStreamTest, Vp9NonFlexModeSmallResolution) { |
| static const size_t kNumFramesToSend = 50; |
| static const int kWidth = 4; |
| static const int kHeight = 4; |
| class NonFlexibleModeResolution : public Vp9HeaderObserver { |
| void ModifyVideoConfigsHook( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| encoder_config->codec_type = kVideoCodecVP9; |
| vp9_settings_.flexibleMode = false; |
| vp9_settings_.numberOfTemporalLayers = 1; |
| vp9_settings_.numberOfSpatialLayers = 1; |
| |
| EXPECT_EQ(1u, encoder_config->number_of_streams); |
| } |
| |
| void InspectHeader(const RTPVideoHeaderVP9& vp9_header) override { |
| if (frames_sent_ > kNumFramesToSend) |
| observation_complete_.Set(); |
| } |
| |
| void ModifyVideoCaptureStartResolution(int* width, |
| int* height, |
| int* frame_rate) override { |
| expected_width_ = kWidth; |
| expected_height_ = kHeight; |
| *width = kWidth; |
| *height = kHeight; |
| } |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| #if defined(WEBRTC_ANDROID) |
| // Crashes on Android; bugs.webrtc.org/7401 |
| #define MAYBE_Vp9FlexModeRefCount DISABLED_Vp9FlexModeRefCount |
| #else |
| // TODO(webrtc:9270): Support of flexible mode is temporarily disabled. Enable |
| // the test after webrtc:9270 is implemented. |
| #define MAYBE_Vp9FlexModeRefCount DISABLED_Vp9FlexModeRefCount |
| // #define MAYBE_Vp9FlexModeRefCount Vp9FlexModeRefCount |
| #endif |
| TEST_F(VideoSendStreamTest, MAYBE_Vp9FlexModeRefCount) { |
| class FlexibleMode : public Vp9HeaderObserver { |
| void ModifyVideoConfigsHook( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| encoder_config->codec_type = kVideoCodecVP9; |
| encoder_config->content_type = VideoEncoderConfig::ContentType::kScreen; |
| vp9_settings_.flexibleMode = true; |
| vp9_settings_.numberOfTemporalLayers = 1; |
| vp9_settings_.numberOfSpatialLayers = 2; |
| } |
| |
| void InspectHeader(const RTPVideoHeaderVP9& vp9_header) override { |
| EXPECT_TRUE(vp9_header.flexible_mode); |
| EXPECT_EQ(kNoTl0PicIdx, vp9_header.tl0_pic_idx); |
| if (vp9_header.inter_pic_predicted) { |
| EXPECT_GT(vp9_header.num_ref_pics, 0u); |
| observation_complete_.Set(); |
| } |
| } |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| #endif // defined(RTC_ENABLE_VP9) |
| |
| void VideoSendStreamTest::TestRequestSourceRotateVideo( |
| bool support_orientation_ext) { |
| CreateSenderCall(); |
| |
| test::NullTransport transport; |
| CreateSendConfig(1, 0, 0, &transport); |
| GetVideoSendConfig()->rtp.extensions.clear(); |
| if (support_orientation_ext) { |
| GetVideoSendConfig()->rtp.extensions.push_back( |
| RtpExtension(RtpExtension::kVideoRotationUri, 1)); |
| } |
| |
| CreateVideoStreams(); |
| test::FrameForwarder forwarder; |
| GetVideoSendStream()->SetSource(&forwarder, |
| DegradationPreference::MAINTAIN_FRAMERATE); |
| |
| EXPECT_TRUE(forwarder.sink_wants().rotation_applied != |
| support_orientation_ext); |
| |
| DestroyStreams(); |
| } |
| |
| TEST_F(VideoSendStreamTest, |
| RequestSourceRotateIfVideoOrientationExtensionNotSupported) { |
| TestRequestSourceRotateVideo(false); |
| } |
| |
| TEST_F(VideoSendStreamTest, |
| DoNotRequestsRotationIfVideoOrientationExtensionSupported) { |
| TestRequestSourceRotateVideo(true); |
| } |
| |
| TEST_F(VideoSendStreamTest, EncoderConfigMaxFramerateReportedToSource) { |
| static const int kMaxFps = 22; |
| class FpsObserver : public test::SendTest, |
| public test::FrameGeneratorCapturer::SinkWantsObserver { |
| public: |
| FpsObserver() : SendTest(kDefaultTimeoutMs) {} |
| |
| void OnFrameGeneratorCapturerCreated( |
| test::FrameGeneratorCapturer* frame_generator_capturer) override { |
| frame_generator_capturer->SetSinkWantsObserver(this); |
| } |
| |
| void OnSinkWantsChanged(rtc::VideoSinkInterface<VideoFrame>* sink, |
| const rtc::VideoSinkWants& wants) override { |
| if (wants.max_framerate_fps == kMaxFps) |
| observation_complete_.Set(); |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| encoder_config->simulcast_layers[0].max_framerate = kMaxFps; |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) << "Timed out while waiting for fps to be reported."; |
| } |
| } test; |
| |
| RunBaseTest(&test); |
| } |
| |
| // This test verifies that overhead is removed from the bandwidth estimate by |
| // testing that the maximum possible target payload rate is smaller than the |
| // maximum bandwidth estimate by the overhead rate. |
| TEST_F(VideoSendStreamTest, RemoveOverheadFromBandwidth) { |
| test::ScopedFieldTrials override_field_trials( |
| "WebRTC-SendSideBwe-WithOverhead/Enabled/"); |
| class RemoveOverheadFromBandwidthTest : public test::EndToEndTest, |
| public test::FakeEncoder { |
| public: |
| explicit RemoveOverheadFromBandwidthTest(TaskQueueBase* task_queue) |
| : EndToEndTest(test::CallTest::kDefaultTimeoutMs), |
| FakeEncoder(Clock::GetRealTimeClock()), |
| task_queue_(task_queue), |
| encoder_factory_(this), |
| call_(nullptr), |
| max_bitrate_bps_(0), |
| first_packet_sent_(false) {} |
| |
| void SetRates(const RateControlParameters& parameters) override { |
| rtc::CritScope lock(&crit_); |
| // Wait for the first sent packet so that videosendstream knows |
| // rtp_overhead. |
| if (first_packet_sent_) { |
| max_bitrate_bps_ = parameters.bitrate.get_sum_bps(); |
| bitrate_changed_event_.Set(); |
| } |
| return FakeEncoder::SetRates(parameters); |
| } |
| |
| void OnCallsCreated(Call* sender_call, Call* receiver_call) override { |
| call_ = sender_call; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| send_config->rtp.max_packet_size = 1200; |
| send_config->encoder_settings.encoder_factory = &encoder_factory_; |
| EXPECT_FALSE(send_config->rtp.extensions.empty()); |
| } |
| |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| rtc::CritScope lock(&crit_); |
| first_packet_sent_ = true; |
| return SEND_PACKET; |
| } |
| |
| void PerformTest() override { |
| BitrateConstraints bitrate_config; |
| constexpr int kStartBitrateBps = 60000; |
| constexpr int kMaxBitrateBps = 60000; |
| constexpr int kMinBitrateBps = 10000; |
| bitrate_config.start_bitrate_bps = kStartBitrateBps; |
| bitrate_config.max_bitrate_bps = kMaxBitrateBps; |
| bitrate_config.min_bitrate_bps = kMinBitrateBps; |
| SendTask(RTC_FROM_HERE, task_queue_, [this, &bitrate_config]() { |
| call_->GetTransportControllerSend()->SetSdpBitrateParameters( |
| bitrate_config); |
| call_->GetTransportControllerSend()->OnTransportOverheadChanged(40); |
| }); |
| |
| // At a bitrate of 60kbps with a packet size of 1200B video and an |
| // overhead of 40B per packet video produces 2240bps overhead. |
| // So the encoder BW should be set to 57760bps. |
| EXPECT_TRUE( |
| bitrate_changed_event_.Wait(VideoSendStreamTest::kDefaultTimeoutMs)); |
| { |
| rtc::CritScope lock(&crit_); |
| EXPECT_LE(max_bitrate_bps_, 57760u); |
| } |
| } |
| |
| private: |
| TaskQueueBase* const task_queue_; |
| test::VideoEncoderProxyFactory encoder_factory_; |
| Call* call_; |
| rtc::CriticalSection crit_; |
| uint32_t max_bitrate_bps_ RTC_GUARDED_BY(&crit_); |
| bool first_packet_sent_ RTC_GUARDED_BY(&crit_); |
| rtc::Event bitrate_changed_event_; |
| } test(task_queue()); |
| RunBaseTest(&test); |
| } |
| |
| class PacingFactorObserver : public test::SendTest { |
| public: |
| PacingFactorObserver(bool configure_send_side, |
| absl::optional<float> expected_pacing_factor) |
| : test::SendTest(VideoSendStreamTest::kDefaultTimeoutMs), |
| configure_send_side_(configure_send_side), |
| expected_pacing_factor_(expected_pacing_factor) {} |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| // Check if send-side bwe extension is already present, and remove it if |
| // it is not desired. |
| bool has_send_side = false; |
| for (auto it = send_config->rtp.extensions.begin(); |
| it != send_config->rtp.extensions.end(); ++it) { |
| if (it->uri == RtpExtension::kTransportSequenceNumberUri) { |
| if (configure_send_side_) { |
| has_send_side = true; |
| } else { |
| send_config->rtp.extensions.erase(it); |
| } |
| break; |
| } |
| } |
| |
| if (configure_send_side_ && !has_send_side) { |
| rtc::UniqueNumberGenerator<int> unique_id_generator; |
| unique_id_generator.AddKnownId(0); // First valid RTP extension ID is 1. |
| for (const RtpExtension& extension : send_config->rtp.extensions) { |
| unique_id_generator.AddKnownId(extension.id); |
| } |
| // Want send side, not present by default, so add it. |
| send_config->rtp.extensions.emplace_back( |
| RtpExtension::kTransportSequenceNumberUri, unique_id_generator()); |
| } |
| |
| // ALR only enabled for screenshare. |
| encoder_config->content_type = VideoEncoderConfig::ContentType::kScreen; |
| } |
| |
| void OnVideoStreamsCreated( |
| VideoSendStream* send_stream, |
| const std::vector<VideoReceiveStream*>& receive_streams) override { |
| auto internal_send_peer = test::VideoSendStreamPeer(send_stream); |
| // Video streams created, check that pacing factor is correctly configured. |
| EXPECT_EQ(expected_pacing_factor_, |
| internal_send_peer.GetPacingFactorOverride()); |
| observation_complete_.Set(); |
| } |
| |
| void PerformTest() override { |
| EXPECT_TRUE(Wait()) << "Timed out while waiting for stream creation."; |
| } |
| |
| private: |
| const bool configure_send_side_; |
| const absl::optional<float> expected_pacing_factor_; |
| }; |
| |
| std::string GetAlrProbingExperimentString() { |
| return std::string( |
| AlrExperimentSettings::kScreenshareProbingBweExperimentName) + |
| "/1.0,2875,80,40,-60,3/"; |
| } |
| const float kAlrProbingExperimentPaceMultiplier = 1.0f; |
| |
| TEST_F(VideoSendStreamTest, AlrConfiguredWhenSendSideOn) { |
| test::ScopedFieldTrials alr_experiment(GetAlrProbingExperimentString()); |
| // Send-side bwe on, use pacing factor from |kAlrProbingExperiment| above. |
| PacingFactorObserver test_with_send_side(true, |
| kAlrProbingExperimentPaceMultiplier); |
| RunBaseTest(&test_with_send_side); |
| } |
| |
| TEST_F(VideoSendStreamTest, AlrNotConfiguredWhenSendSideOff) { |
| test::ScopedFieldTrials alr_experiment(GetAlrProbingExperimentString()); |
| // Send-side bwe off, use configuration should not be overridden. |
| PacingFactorObserver test_without_send_side(false, absl::nullopt); |
| RunBaseTest(&test_without_send_side); |
| } |
| |
| // Test class takes as argument a function pointer to reset the send |
| // stream and call OnVideoStreamsCreated. This is necessary since you cannot |
| // change the content type of a VideoSendStream, you need to recreate it. |
| // Stopping and recreating the stream can only be done on the main thread and in |
| // the context of VideoSendStreamTest (not BaseTest). The test switches from |
| // realtime to screenshare and back. |
| template <typename T> |
| class ContentSwitchTest : public test::SendTest { |
| public: |
| enum class StreamState { |
| kBeforeSwitch = 0, |
| kInScreenshare = 1, |
| kAfterSwitchBack = 2, |
| }; |
| static const uint32_t kMinPacketsToSend = 50; |
| |
| explicit ContentSwitchTest(T* stream_reset_fun) |
| : SendTest(test::CallTest::kDefaultTimeoutMs), |
| call_(nullptr), |
| state_(StreamState::kBeforeSwitch), |
| send_stream_(nullptr), |
| send_stream_config_(nullptr), |
| packets_sent_(0), |
| stream_resetter_(stream_reset_fun) { |
| RTC_DCHECK(stream_resetter_); |
| } |
| |
| void OnVideoStreamsCreated( |
| VideoSendStream* send_stream, |
| const std::vector<VideoReceiveStream*>& receive_streams) override { |
| rtc::CritScope lock(&crit_); |
| send_stream_ = send_stream; |
| } |
| |
| void ModifyVideoConfigs( |
| VideoSendStream::Config* send_config, |
| std::vector<VideoReceiveStream::Config>* receive_configs, |
| VideoEncoderConfig* encoder_config) override { |
| RTC_DCHECK_EQ(1, encoder_config->number_of_streams); |
| encoder_config->min_transmit_bitrate_bps = 0; |
| encoder_config->content_type = |
| VideoEncoderConfig::ContentType::kRealtimeVideo; |
| send_stream_config_ = send_config->Copy(); |
| encoder_config_ = encoder_config->Copy(); |
| } |
| |
| void OnCallsCreated(Call* sender_call, Call* receiver_call) override { |
| call_ = sender_call; |
| } |
| |
| Action OnSendRtp(const uint8_t* packet, size_t length) override { |
| rtc::CritScope lock(&crit_); |
| |
| auto internal_send_peer = test::VideoSendStreamPeer(send_stream_); |
| float pacing_factor = |
| internal_send_peer.GetPacingFactorOverride().value_or(0.0f); |
| float expected_pacing_factor = PacedSender::kDefaultPaceMultiplier; |
| if (send_stream_->GetStats().content_type == |
| webrtc::VideoContentType::SCREENSHARE) { |
| expected_pacing_factor = 1.0f; // Currently used pacing factor in ALR. |
| } |
| |
| EXPECT_NEAR(expected_pacing_factor, pacing_factor, 1e-6); |
| |
| // Wait until at least kMinPacketsToSend packets to be sent, so that |
| // some frames would be encoded. |
| if (++packets_sent_ < kMinPacketsToSend) |
| return SEND_PACKET; |
| |
| if (state_ != StreamState::kAfterSwitchBack) { |
| // We've sent kMinPacketsToSend packets, switch the content type and move |
| // move to the next state. |
| // Note that we need to recreate the stream if changing content type. |
| packets_sent_ = 0; |
| if (encoder_config_.content_type == |
| VideoEncoderConfig::ContentType::kRealtimeVideo) { |
| encoder_config_.content_type = VideoEncoderConfig::ContentType::kScreen; |
| } else { |
| encoder_config_.content_type = |
| VideoEncoderConfig::ContentType::kRealtimeVideo; |
| } |
| switch (state_) { |
| case StreamState::kBeforeSwitch: |
| state_ = StreamState::kInScreenshare; |
| break; |
| case StreamState::kInScreenshare: |
| state_ = StreamState::kAfterSwitchBack; |
| break; |
| case StreamState::kAfterSwitchBack: |
| RTC_NOTREACHED(); |
| break; |
| } |
| content_switch_event_.Set(); |
| return SEND_PACKET; |
| } |
| |
| observation_complete_.Set(); |
| return SEND_PACKET; |
| } |
| |
| void PerformTest() override { |
| while (GetStreamState() != StreamState::kAfterSwitchBack) { |
| ASSERT_TRUE( |
| content_switch_event_.Wait(test::CallTest::kDefaultTimeoutMs)); |
| (*stream_resetter_)(send_stream_config_, encoder_config_, this); |
| } |
| |
| ASSERT_TRUE(Wait()) |
| << "Timed out waiting for a frame sent after switch back"; |
| } |
| |
| private: |
| StreamState GetStreamState() { |
| rtc::CritScope lock(&crit_); |
| return state_; |
| } |
| |
| rtc::CriticalSection crit_; |
| rtc::Event content_switch_event_; |
| Call* call_; |
| StreamState state_ RTC_GUARDED_BY(crit_); |
| VideoSendStream* send_stream_ RTC_GUARDED_BY(crit_); |
| VideoSendStream::Config send_stream_config_; |
| VideoEncoderConfig encoder_config_; |
| uint32_t packets_sent_ RTC_GUARDED_BY(crit_); |
| T* stream_resetter_; |
| }; |
| |
| TEST_F(VideoSendStreamTest, SwitchesToScreenshareAndBack) { |
| auto reset_fun = [this](const VideoSendStream::Config& send_stream_config, |
| const VideoEncoderConfig& encoder_config, |
| test::BaseTest* test) { |
| SendTask(RTC_FROM_HERE, task_queue(), |
| [this, &send_stream_config, &encoder_config, &test]() { |
| Stop(); |
| DestroyVideoSendStreams(); |
| SetVideoSendConfig(send_stream_config); |
| SetVideoEncoderConfig(encoder_config); |
| CreateVideoSendStreams(); |
| SetVideoDegradation(DegradationPreference::MAINTAIN_RESOLUTION); |
| test->OnVideoStreamsCreated(GetVideoSendStream(), |
| video_receive_streams_); |
| Start(); |
| }); |
| }; |
| ContentSwitchTest<decltype(reset_fun)> test(&reset_fun); |
| RunBaseTest(&test); |
| } |
| |
| } // namespace webrtc |