diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/video/end_to_end_tests/stats_tests.cc | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/video/end_to_end_tests/stats_tests.cc')
-rw-r--r-- | third_party/libwebrtc/video/end_to_end_tests/stats_tests.cc | 759 |
1 files changed, 759 insertions, 0 deletions
diff --git a/third_party/libwebrtc/video/end_to_end_tests/stats_tests.cc b/third_party/libwebrtc/video/end_to_end_tests/stats_tests.cc new file mode 100644 index 0000000000..cc0b328b2b --- /dev/null +++ b/third_party/libwebrtc/video/end_to_end_tests/stats_tests.cc @@ -0,0 +1,759 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <memory> + +#include "absl/algorithm/container.h" +#include "absl/types/optional.h" +#include "api/task_queue/task_queue_base.h" +#include "api/test/simulated_network.h" +#include "api/test/video/function_video_encoder_factory.h" +#include "call/fake_network_pipe.h" +#include "call/simulated_network.h" +#include "modules/rtp_rtcp/source/rtp_packet.h" +#include "modules/video_coding/include/video_coding_defines.h" +#include "rtc_base/strings/string_builder.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/task_queue_for_test.h" +#include "system_wrappers/include/metrics.h" +#include "system_wrappers/include/sleep.h" +#include "test/call_test.h" +#include "test/fake_encoder.h" +#include "test/gtest.h" +#include "test/rtcp_packet_parser.h" +#include "test/video_test_constants.h" + +namespace webrtc { +namespace { +enum : int { // The first valid value is 1. + kVideoContentTypeExtensionId = 1, +}; +} // namespace + +class StatsEndToEndTest : public test::CallTest { + public: + StatsEndToEndTest() { + RegisterRtpExtension(RtpExtension(RtpExtension::kVideoContentTypeUri, + kVideoContentTypeExtensionId)); + } +}; + +TEST_F(StatsEndToEndTest, GetStats) { + static const int kStartBitrateBps = 3000000; + static const int kExpectedRenderDelayMs = 20; + + class StatsObserver : public test::EndToEndTest { + public: + StatsObserver() + : EndToEndTest(test::VideoTestConstants::kLongTimeout), + encoder_factory_([]() { + return std::make_unique<test::DelayedEncoder>( + Clock::GetRealTimeClock(), 10); + }) {} + + private: + Action OnSendRtp(rtc::ArrayView<const uint8_t> packet) override { + // Drop every 25th packet => 4% loss. + static const int kPacketLossFrac = 25; + RtpPacket header; + if (header.Parse(packet) && + expected_send_ssrcs_.find(header.Ssrc()) != + expected_send_ssrcs_.end() && + header.SequenceNumber() % kPacketLossFrac == 0) { + return DROP_PACKET; + } + check_stats_event_.Set(); + return SEND_PACKET; + } + + Action OnSendRtcp(rtc::ArrayView<const uint8_t> packet) override { + check_stats_event_.Set(); + return SEND_PACKET; + } + + Action OnReceiveRtp(rtc::ArrayView<const uint8_t> packet) override { + check_stats_event_.Set(); + return SEND_PACKET; + } + + Action OnReceiveRtcp(rtc::ArrayView<const uint8_t> packet) override { + check_stats_event_.Set(); + return SEND_PACKET; + } + + bool CheckReceiveStats() { + for (size_t i = 0; i < receive_streams_.size(); ++i) { + VideoReceiveStreamInterface::Stats stats = + receive_streams_[i]->GetStats(); + EXPECT_EQ(expected_receive_ssrcs_[i], stats.ssrc); + + // Make sure all fields have been populated. + // TODO(pbos): Use CompoundKey if/when we ever know that all stats are + // always filled for all receivers. + receive_stats_filled_["IncomingRate"] |= + stats.network_frame_rate != 0 || stats.total_bitrate_bps != 0; + + send_stats_filled_["DecoderImplementationName"] |= + stats.decoder_implementation_name == + test::FakeDecoder::kImplementationName; + receive_stats_filled_["PowerEfficientDecoder"] = + stats.power_efficient_decoder.has_value(); + receive_stats_filled_["RenderDelayAsHighAsExpected"] |= + stats.render_delay_ms >= kExpectedRenderDelayMs; + + receive_stats_filled_["FrameCallback"] |= stats.decode_frame_rate != 0; + + receive_stats_filled_["FrameRendered"] |= stats.render_frame_rate != 0; + + receive_stats_filled_["StatisticsUpdated"] |= + stats.rtp_stats.packets_lost != 0 || stats.rtp_stats.jitter != 0; + + receive_stats_filled_["DataCountersUpdated"] |= + stats.rtp_stats.packet_counter.payload_bytes != 0 || + stats.rtp_stats.packet_counter.header_bytes != 0 || + stats.rtp_stats.packet_counter.packets != 0 || + stats.rtp_stats.packet_counter.padding_bytes != 0; + + receive_stats_filled_["CodecStats"] |= stats.target_delay_ms != 0; + + receive_stats_filled_["FrameCounts"] |= + stats.frame_counts.key_frames != 0 || + stats.frame_counts.delta_frames != 0; + + receive_stats_filled_["JitterBufferDelay"] = + stats.jitter_buffer_delay > TimeDelta::Zero(); + receive_stats_filled_["JitterBufferTargetDelay"] = + stats.jitter_buffer_target_delay > TimeDelta::Zero(); + receive_stats_filled_["JitterBufferEmittedCount"] = + stats.jitter_buffer_emitted_count != 0; + receive_stats_filled_["JitterBufferMinimumDelay"] = + stats.jitter_buffer_minimum_delay > TimeDelta::Zero(); + + receive_stats_filled_["CName"] |= !stats.c_name.empty(); + + receive_stats_filled_["RtcpPacketTypeCount"] |= + stats.rtcp_packet_type_counts.fir_packets != 0 || + stats.rtcp_packet_type_counts.nack_packets != 0 || + stats.rtcp_packet_type_counts.pli_packets != 0 || + stats.rtcp_packet_type_counts.nack_requests != 0 || + stats.rtcp_packet_type_counts.unique_nack_requests != 0; + + RTC_DCHECK(stats.current_payload_type == -1 || + stats.current_payload_type == + test::VideoTestConstants::kFakeVideoSendPayloadType); + receive_stats_filled_["IncomingPayloadType"] |= + stats.current_payload_type == + test::VideoTestConstants::kFakeVideoSendPayloadType; + } + + return AllStatsFilled(receive_stats_filled_); + } + + bool CheckSendStats() { + RTC_DCHECK(send_stream_); + + VideoSendStream::Stats stats; + SendTask(task_queue_, [&]() { stats = send_stream_->GetStats(); }); + + size_t expected_num_streams = + test::VideoTestConstants::kNumSimulcastStreams + + expected_send_ssrcs_.size(); + send_stats_filled_["NumStreams"] |= + stats.substreams.size() == expected_num_streams; + + send_stats_filled_["CpuOveruseMetrics"] |= + stats.avg_encode_time_ms != 0 && stats.encode_usage_percent != 0 && + stats.total_encode_time_ms != 0; + + send_stats_filled_["EncoderImplementationName"] |= + stats.encoder_implementation_name == + test::FakeEncoder::kImplementationName; + + send_stats_filled_["PowerEfficientEncoder"] |= + stats.power_efficient_encoder == true; + + for (const auto& kv : stats.substreams) { + if (expected_send_ssrcs_.find(kv.first) == expected_send_ssrcs_.end()) + continue; // Probably RTX. + + send_stats_filled_[CompoundKey("CapturedFrameRate", kv.first)] |= + stats.input_frame_rate != 0; + + const VideoSendStream::StreamStats& stream_stats = kv.second; + + send_stats_filled_[CompoundKey("StatisticsUpdated", kv.first)] |= + stream_stats.report_block_data.has_value(); + + send_stats_filled_[CompoundKey("DataCountersUpdated", kv.first)] |= + stream_stats.rtp_stats.fec.packets != 0 || + stream_stats.rtp_stats.transmitted.padding_bytes != 0 || + stream_stats.rtp_stats.retransmitted.packets != 0 || + stream_stats.rtp_stats.transmitted.packets != 0; + + send_stats_filled_[CompoundKey("BitrateStatisticsObserver.Total", + kv.first)] |= + stream_stats.total_bitrate_bps != 0; + + send_stats_filled_[CompoundKey("BitrateStatisticsObserver.Retransmit", + kv.first)] |= + stream_stats.retransmit_bitrate_bps != 0; + + send_stats_filled_[CompoundKey("FrameCountObserver", kv.first)] |= + stream_stats.frame_counts.delta_frames != 0 || + stream_stats.frame_counts.key_frames != 0; + + send_stats_filled_[CompoundKey("OutgoingRate", kv.first)] |= + stats.encode_frame_rate != 0; + + send_stats_filled_[CompoundKey("Delay", kv.first)] |= + stream_stats.avg_delay_ms != 0 || stream_stats.max_delay_ms != 0; + + // TODO(pbos): Use CompoundKey when the test makes sure that all SSRCs + // report dropped packets. + send_stats_filled_["RtcpPacketTypeCount"] |= + stream_stats.rtcp_packet_type_counts.fir_packets != 0 || + stream_stats.rtcp_packet_type_counts.nack_packets != 0 || + stream_stats.rtcp_packet_type_counts.pli_packets != 0 || + stream_stats.rtcp_packet_type_counts.nack_requests != 0 || + stream_stats.rtcp_packet_type_counts.unique_nack_requests != 0; + } + + return AllStatsFilled(send_stats_filled_); + } + + std::string CompoundKey(const char* name, uint32_t ssrc) { + rtc::StringBuilder oss; + oss << name << "_" << ssrc; + return oss.Release(); + } + + bool AllStatsFilled(const std::map<std::string, bool>& stats_map) { + for (const auto& stat : stats_map) { + if (!stat.second) + return false; + } + return true; + } + + BuiltInNetworkBehaviorConfig GetSendTransportConfig() const override { + BuiltInNetworkBehaviorConfig network_config; + network_config.loss_percent = 5; + return network_config; + } + + void ModifySenderBitrateConfig( + BitrateConstraints* bitrate_config) override { + bitrate_config->start_bitrate_bps = kStartBitrateBps; + } + + void ModifyVideoConfigs( + VideoSendStream::Config* send_config, + std::vector<VideoReceiveStreamInterface::Config>* receive_configs, + VideoEncoderConfig* encoder_config) override { + // Set low simulcast bitrates to not have to wait for bandwidth ramp-up. + encoder_config->max_bitrate_bps = 50000; + for (auto& layer : encoder_config->simulcast_layers) { + layer.min_bitrate_bps = 10000; + layer.target_bitrate_bps = 15000; + layer.max_bitrate_bps = 20000; + } + + send_config->rtp.c_name = "SomeCName"; + send_config->rtp.nack.rtp_history_ms = + test::VideoTestConstants::kNackRtpHistoryMs; + send_config->rtp.rtx.payload_type = + test::VideoTestConstants::kSendRtxPayloadType; + + const std::vector<uint32_t>& ssrcs = send_config->rtp.ssrcs; + for (size_t i = 0; i < ssrcs.size(); ++i) { + expected_send_ssrcs_.insert(ssrcs[i]); + expected_receive_ssrcs_.push_back( + (*receive_configs)[i].rtp.remote_ssrc); + (*receive_configs)[i].render_delay_ms = kExpectedRenderDelayMs; + (*receive_configs)[i].rtp.nack.rtp_history_ms = + test::VideoTestConstants::kNackRtpHistoryMs; + + (*receive_configs)[i].rtp.rtx_ssrc = + test::VideoTestConstants::kSendRtxSsrcs[i]; + (*receive_configs)[i].rtp.rtx_associated_payload_types + [test::VideoTestConstants::kSendRtxPayloadType] = + test::VideoTestConstants::kFakeVideoSendPayloadType; + } + + for (size_t i = 0; i < test::VideoTestConstants::kNumSimulcastStreams; + ++i) + send_config->rtp.rtx.ssrcs.push_back( + test::VideoTestConstants::kSendRtxSsrcs[i]); + + // Use a delayed encoder to make sure we see CpuOveruseMetrics stats that + // are non-zero. + send_config->encoder_settings.encoder_factory = &encoder_factory_; + } + + size_t GetNumVideoStreams() const override { + return test::VideoTestConstants::kNumSimulcastStreams; + } + + void OnVideoStreamsCreated(VideoSendStream* send_stream, + const std::vector<VideoReceiveStreamInterface*>& + receive_streams) override { + send_stream_ = send_stream; + receive_streams_ = receive_streams; + task_queue_ = TaskQueueBase::Current(); + } + + void PerformTest() override { + Clock* clock = Clock::GetRealTimeClock(); + int64_t now_ms = clock->TimeInMilliseconds(); + int64_t stop_time_ms = + now_ms + test::VideoTestConstants::kLongTimeout.ms(); + bool receive_ok = false; + bool send_ok = false; + + while (now_ms < stop_time_ms) { + if (!receive_ok && task_queue_) { + SendTask(task_queue_, [&]() { receive_ok = CheckReceiveStats(); }); + } + if (!send_ok) + send_ok = CheckSendStats(); + + if (receive_ok && send_ok) + return; + + int64_t time_until_timeout_ms = stop_time_ms - now_ms; + if (time_until_timeout_ms > 0) + check_stats_event_.Wait(TimeDelta::Millis(time_until_timeout_ms)); + now_ms = clock->TimeInMilliseconds(); + } + + ADD_FAILURE() << "Timed out waiting for filled stats."; + for (const auto& kv : receive_stats_filled_) { + if (!kv.second) { + ADD_FAILURE() << "Missing receive stats: " << kv.first; + } + } + for (const auto& kv : send_stats_filled_) { + if (!kv.second) { + ADD_FAILURE() << "Missing send stats: " << kv.first; + } + } + } + + test::FunctionVideoEncoderFactory encoder_factory_; + std::vector<VideoReceiveStreamInterface*> receive_streams_; + std::map<std::string, bool> receive_stats_filled_; + + VideoSendStream* send_stream_ = nullptr; + std::map<std::string, bool> send_stats_filled_; + + std::vector<uint32_t> expected_receive_ssrcs_; + std::set<uint32_t> expected_send_ssrcs_; + + rtc::Event check_stats_event_; + TaskQueueBase* task_queue_ = nullptr; + } test; + + RunBaseTest(&test); +} + +TEST_F(StatsEndToEndTest, TimingFramesAreReported) { + static const int kExtensionId = 5; + RegisterRtpExtension( + RtpExtension(RtpExtension::kVideoTimingUri, kExtensionId)); + + class StatsObserver : public test::EndToEndTest { + public: + StatsObserver() : EndToEndTest(test::VideoTestConstants::kLongTimeout) {} + + private: + void ModifyVideoConfigs( + VideoSendStream::Config* send_config, + std::vector<VideoReceiveStreamInterface::Config>* receive_configs, + VideoEncoderConfig* encoder_config) override { + send_config->rtp.extensions.clear(); + send_config->rtp.extensions.push_back( + RtpExtension(RtpExtension::kVideoTimingUri, kExtensionId)); + } + + void OnVideoStreamsCreated(VideoSendStream* send_stream, + const std::vector<VideoReceiveStreamInterface*>& + receive_streams) override { + receive_streams_ = receive_streams; + task_queue_ = TaskQueueBase::Current(); + } + + void PerformTest() override { + // No frames reported initially. + SendTask(task_queue_, [&]() { + for (const auto& receive_stream : receive_streams_) { + EXPECT_FALSE(receive_stream->GetStats().timing_frame_info); + } + }); + // Wait for at least one timing frame to be sent with 100ms grace period. + SleepMs(kDefaultTimingFramesDelayMs + 100); + // Check that timing frames are reported for each stream. + SendTask(task_queue_, [&]() { + for (const auto& receive_stream : receive_streams_) { + EXPECT_TRUE(receive_stream->GetStats().timing_frame_info); + } + }); + } + + std::vector<VideoReceiveStreamInterface*> receive_streams_; + TaskQueueBase* task_queue_ = nullptr; + } test; + + RunBaseTest(&test); +} + +TEST_F(StatsEndToEndTest, TestReceivedRtpPacketStats) { + static const size_t kNumRtpPacketsToSend = 5; + class ReceivedRtpStatsObserver : public test::EndToEndTest { + public: + explicit ReceivedRtpStatsObserver(TaskQueueBase* task_queue) + : EndToEndTest(test::VideoTestConstants::kDefaultTimeout), + task_queue_(task_queue) {} + + private: + void OnVideoStreamsCreated(VideoSendStream* send_stream, + const std::vector<VideoReceiveStreamInterface*>& + receive_streams) override { + receive_stream_ = receive_streams[0]; + } + + void OnStreamsStopped() override { task_safety_flag_->SetNotAlive(); } + + Action OnSendRtp(rtc::ArrayView<const uint8_t> packet) override { + if (sent_rtp_ >= kNumRtpPacketsToSend) { + // Need to check the stats on the correct thread. + task_queue_->PostTask(SafeTask(task_safety_flag_, [this]() { + VideoReceiveStreamInterface::Stats stats = + receive_stream_->GetStats(); + if (kNumRtpPacketsToSend == stats.rtp_stats.packet_counter.packets) { + observation_complete_.Set(); + } + })); + return DROP_PACKET; + } + ++sent_rtp_; + return SEND_PACKET; + } + + void PerformTest() override { + EXPECT_TRUE(Wait()) + << "Timed out while verifying number of received RTP packets."; + } + + VideoReceiveStreamInterface* receive_stream_ = nullptr; + uint32_t sent_rtp_ = 0; + TaskQueueBase* const task_queue_; + rtc::scoped_refptr<PendingTaskSafetyFlag> task_safety_flag_ = + PendingTaskSafetyFlag::CreateDetached(); + } test(task_queue()); + + RunBaseTest(&test); +} + +#if defined(WEBRTC_WIN) +// Disabled due to flakiness on Windows (bugs.webrtc.org/7483). +#define MAYBE_ContentTypeSwitches DISABLED_ContentTypeSwitches +#else +#define MAYBE_ContentTypeSwitches ContentTypeSwitches +#endif +TEST_F(StatsEndToEndTest, MAYBE_ContentTypeSwitches) { + class StatsObserver : public test::BaseTest, + public rtc::VideoSinkInterface<VideoFrame> { + public: + StatsObserver() + : BaseTest(test::VideoTestConstants::kLongTimeout), + num_frames_received_(0) {} + + bool ShouldCreateReceivers() const override { return true; } + + void OnFrame(const VideoFrame& video_frame) override { + // The RTT is needed to estimate `ntp_time_ms` which is used by + // end-to-end delay stats. Therefore, start counting received frames once + // `ntp_time_ms` is valid. + if (video_frame.ntp_time_ms() > 0 && + Clock::GetRealTimeClock()->CurrentNtpInMilliseconds() >= + video_frame.ntp_time_ms()) { + MutexLock lock(&mutex_); + ++num_frames_received_; + } + } + + Action OnSendRtp(rtc::ArrayView<const uint8_t> packet) override { + if (MinNumberOfFramesReceived()) + observation_complete_.Set(); + return SEND_PACKET; + } + + bool MinNumberOfFramesReceived() const { + // Have some room for frames with wrong content type during switch. + const int kMinRequiredHistogramSamples = 200 + 50; + MutexLock lock(&mutex_); + return num_frames_received_ > kMinRequiredHistogramSamples; + } + + // May be called several times. + void PerformTest() override { + EXPECT_TRUE(Wait()) << "Timed out waiting for enough packets."; + // Reset frame counter so next PerformTest() call will do something. + { + MutexLock lock(&mutex_); + num_frames_received_ = 0; + } + } + + mutable Mutex mutex_; + int num_frames_received_ RTC_GUARDED_BY(&mutex_); + } test; + + metrics::Reset(); + + CallConfig send_config(send_event_log_.get()); + test.ModifySenderBitrateConfig(&send_config.bitrate_config); + CallConfig recv_config(recv_event_log_.get()); + test.ModifyReceiverBitrateConfig(&recv_config.bitrate_config); + + VideoEncoderConfig encoder_config_with_screenshare; + + SendTask(task_queue(), [this, &test, &send_config, &recv_config, + &encoder_config_with_screenshare]() { + CreateSenderCall(send_config); + CreateReceiverCall(recv_config); + CreateReceiveTransport(test.GetReceiveTransportConfig(), &test); + CreateSendTransport(test.GetReceiveTransportConfig(), &test); + + receiver_call_->SignalChannelNetworkState(MediaType::VIDEO, kNetworkUp); + CreateSendConfig(1, 0, 0); + CreateMatchingReceiveConfigs(); + + // Modify send and receive configs. + GetVideoSendConfig()->rtp.nack.rtp_history_ms = + test::VideoTestConstants::kNackRtpHistoryMs; + video_receive_configs_[0].rtp.nack.rtp_history_ms = + test::VideoTestConstants::kNackRtpHistoryMs; + video_receive_configs_[0].renderer = &test; + // RTT needed for RemoteNtpTimeEstimator for the receive stream. + video_receive_configs_[0].rtp.rtcp_xr.receiver_reference_time_report = true; + // Start with realtime video. + GetVideoEncoderConfig()->content_type = + VideoEncoderConfig::ContentType::kRealtimeVideo; + // Encoder config for the second part of the test uses screenshare. + encoder_config_with_screenshare = GetVideoEncoderConfig()->Copy(); + encoder_config_with_screenshare.content_type = + VideoEncoderConfig::ContentType::kScreen; + + CreateVideoStreams(); + CreateFrameGeneratorCapturer(test::VideoTestConstants::kDefaultFramerate, + test::VideoTestConstants::kDefaultWidth, + test::VideoTestConstants::kDefaultHeight); + Start(); + }); + + test.PerformTest(); + + // Replace old send stream. + SendTask(task_queue(), [this, &encoder_config_with_screenshare]() { + DestroyVideoSendStreams(); + CreateVideoSendStream(encoder_config_with_screenshare); + SetVideoDegradation(DegradationPreference::BALANCED); + GetVideoSendStream()->Start(); + }); + + // Continue to run test but now with screenshare. + test.PerformTest(); + + SendTask(task_queue(), [this]() { + Stop(); + DestroyStreams(); + DestroyCalls(); + }); + + // Verify that stats have been updated for both screenshare and video. + EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.EndToEndDelayInMs")); + EXPECT_METRIC_EQ( + 1, metrics::NumSamples("WebRTC.Video.Screenshare.EndToEndDelayInMs")); + EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.EndToEndDelayMaxInMs")); + EXPECT_METRIC_EQ( + 1, metrics::NumSamples("WebRTC.Video.Screenshare.EndToEndDelayMaxInMs")); + EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.InterframeDelayInMs")); + EXPECT_METRIC_EQ( + 1, metrics::NumSamples("WebRTC.Video.Screenshare.InterframeDelayInMs")); + EXPECT_METRIC_EQ(1, + metrics::NumSamples("WebRTC.Video.InterframeDelayMaxInMs")); + EXPECT_METRIC_EQ(1, metrics::NumSamples( + "WebRTC.Video.Screenshare.InterframeDelayMaxInMs")); +} + +TEST_F(StatsEndToEndTest, VerifyNackStats) { + static const int kPacketNumberToDrop = 200; + class NackObserver : public test::EndToEndTest { + public: + explicit NackObserver(TaskQueueBase* task_queue) + : EndToEndTest(test::VideoTestConstants::kLongTimeout), + task_queue_(task_queue) {} + + private: + Action OnSendRtp(rtc::ArrayView<const uint8_t> packet) override { + { + MutexLock lock(&mutex_); + if (++sent_rtp_packets_ == kPacketNumberToDrop) { + RtpPacket header; + EXPECT_TRUE(header.Parse(packet)); + dropped_rtp_packet_ = header.SequenceNumber(); + return DROP_PACKET; + } + } + task_queue_->PostTask( + SafeTask(task_safety_flag_, [this]() { VerifyStats(); })); + return SEND_PACKET; + } + + Action OnReceiveRtcp(rtc::ArrayView<const uint8_t> packet) override { + MutexLock lock(&mutex_); + test::RtcpPacketParser rtcp_parser; + rtcp_parser.Parse(packet); + const std::vector<uint16_t>& nacks = rtcp_parser.nack()->packet_ids(); + if (!nacks.empty() && absl::c_linear_search(nacks, dropped_rtp_packet_)) { + dropped_rtp_packet_requested_ = true; + } + return SEND_PACKET; + } + + void VerifyStats() { + MutexLock lock(&mutex_); + if (!dropped_rtp_packet_requested_) + return; + int send_stream_nack_packets = 0; + int receive_stream_nack_packets = 0; + VideoSendStream::Stats stats = send_stream_->GetStats(); + for (const auto& kv : stats.substreams) { + const VideoSendStream::StreamStats& stream_stats = kv.second; + send_stream_nack_packets += + stream_stats.rtcp_packet_type_counts.nack_packets; + } + for (const auto& receive_stream : receive_streams_) { + VideoReceiveStreamInterface::Stats stats = receive_stream->GetStats(); + receive_stream_nack_packets += + stats.rtcp_packet_type_counts.nack_packets; + } + if (send_stream_nack_packets >= 1 && receive_stream_nack_packets >= 1) { + // NACK packet sent on receive stream and received on sent stream. + if (MinMetricRunTimePassed()) + observation_complete_.Set(); + } + } + + bool MinMetricRunTimePassed() { + int64_t now_ms = Clock::GetRealTimeClock()->TimeInMilliseconds(); + if (!start_runtime_ms_) + start_runtime_ms_ = now_ms; + + int64_t elapsed_sec = (now_ms - *start_runtime_ms_) / 1000; + return elapsed_sec > metrics::kMinRunTimeInSeconds; + } + + void ModifyVideoConfigs( + VideoSendStream::Config* send_config, + std::vector<VideoReceiveStreamInterface::Config>* receive_configs, + VideoEncoderConfig* encoder_config) override { + send_config->rtp.nack.rtp_history_ms = + test::VideoTestConstants::kNackRtpHistoryMs; + (*receive_configs)[0].rtp.nack.rtp_history_ms = + test::VideoTestConstants::kNackRtpHistoryMs; + } + + void OnVideoStreamsCreated(VideoSendStream* send_stream, + const std::vector<VideoReceiveStreamInterface*>& + receive_streams) override { + send_stream_ = send_stream; + receive_streams_ = receive_streams; + } + + void OnStreamsStopped() override { task_safety_flag_->SetNotAlive(); } + + void PerformTest() override { + EXPECT_TRUE(Wait()) << "Timed out waiting for packet to be NACKed."; + } + + Mutex mutex_; + uint64_t sent_rtp_packets_ RTC_GUARDED_BY(&mutex_) = 0; + uint16_t dropped_rtp_packet_ RTC_GUARDED_BY(&mutex_) = 0; + bool dropped_rtp_packet_requested_ RTC_GUARDED_BY(&mutex_) = false; + std::vector<VideoReceiveStreamInterface*> receive_streams_; + VideoSendStream* send_stream_ = nullptr; + absl::optional<int64_t> start_runtime_ms_; + TaskQueueBase* const task_queue_; + rtc::scoped_refptr<PendingTaskSafetyFlag> task_safety_flag_ = + PendingTaskSafetyFlag::CreateDetached(); + } test(task_queue()); + + metrics::Reset(); + RunBaseTest(&test); + + EXPECT_METRIC_EQ( + 1, metrics::NumSamples("WebRTC.Video.UniqueNackRequestsSentInPercent")); + EXPECT_METRIC_EQ(1, metrics::NumSamples( + "WebRTC.Video.UniqueNackRequestsReceivedInPercent")); + EXPECT_METRIC_GT(metrics::MinSample("WebRTC.Video.NackPacketsSentPerMinute"), + 0); +} + +TEST_F(StatsEndToEndTest, CallReportsRttForSender) { + static const int kSendDelayMs = 30; + static const int kReceiveDelayMs = 70; + + SendTask(task_queue(), [this]() { + BuiltInNetworkBehaviorConfig config; + config.queue_delay_ms = kSendDelayMs; + CreateCalls(); + CreateSendTransport(config, /*observer*/ nullptr); + + config.queue_delay_ms = kReceiveDelayMs; + CreateReceiveTransport(config, /*observer*/ nullptr); + + CreateSendConfig(1, 0, 0); + CreateMatchingReceiveConfigs(); + + CreateVideoStreams(); + CreateFrameGeneratorCapturer(test::VideoTestConstants::kDefaultFramerate, + test::VideoTestConstants::kDefaultWidth, + test::VideoTestConstants::kDefaultHeight); + receiver_call_->SignalChannelNetworkState(MediaType::VIDEO, kNetworkUp); + Start(); + }); + + int64_t start_time_ms = clock_->TimeInMilliseconds(); + while (true) { + Call::Stats stats; + SendTask(task_queue(), + [this, &stats]() { stats = sender_call_->GetStats(); }); + ASSERT_GE(start_time_ms + test::VideoTestConstants::kDefaultTimeout.ms(), + clock_->TimeInMilliseconds()) + << "No RTT stats before timeout!"; + if (stats.rtt_ms != -1) { + // To avoid failures caused by rounding or minor ntp clock adjustments, + // relax expectation by 1ms. + constexpr int kAllowedErrorMs = 1; + EXPECT_GE(stats.rtt_ms, kSendDelayMs + kReceiveDelayMs - kAllowedErrorMs); + break; + } + SleepMs(10); + } + + SendTask(task_queue(), [this]() { + Stop(); + DestroyStreams(); + DestroyCalls(); + }); +} +} // namespace webrtc |