diff --git a/call/video_receive_stream.cc b/call/video_receive_stream.cc index 0e0ad44514..3e2a51322f 100644 --- a/call/video_receive_stream.cc +++ b/call/video_receive_stream.cc @@ -80,7 +80,11 @@ std::string VideoReceiveStreamInterface::Stats::ToString( ss << "jitter_delay_ms: " << jitter_buffer_ms << ", "; ss << "totalAssemblyTime: " << total_assembly_time.seconds() << ", "; ss << "jitterBufferDelay: " << jitter_buffer_delay.seconds() << ", "; + ss << "jitterBufferTargetDelay: " + << jitter_buffer_target_delay.seconds() << ", "; ss << "jitterBufferEmittedCount: " << jitter_buffer_emitted_count << ", "; + ss << "jitterBufferMinimumDelay: " + << jitter_buffer_minimum_delay.seconds(); ss << "totalDecodeTime: " << total_decode_time.seconds() << ", "; ss << "totalProcessingDelay: " << total_processing_delay.seconds() << ", "; diff --git a/call/video_receive_stream.h b/call/video_receive_stream.h index b25a1262a9..48a1ad0758 100644 --- a/call/video_receive_stream.h +++ b/call/video_receive_stream.h @@ -98,8 +98,12 @@ class VideoReceiveStreamInterface : public MediaReceiveStreamInterface { int jitter_buffer_ms = 0; // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferdelay TimeDelta jitter_buffer_delay = TimeDelta::Zero(); + // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbuffertargetdelay + TimeDelta jitter_buffer_target_delay = TimeDelta::Zero(); // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferemittedcount uint64_t jitter_buffer_emitted_count = 0; + // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferminimumdelay + TimeDelta jitter_buffer_minimum_delay = TimeDelta::Zero(); int min_playout_delay_ms = 0; int render_delay_ms = 10; int64_t interframe_delay_max_ms = -1; diff --git a/media/base/media_channel.h b/media/base/media_channel.h index dbcb30c90a..02dc693e86 100644 --- a/media/base/media_channel.h +++ b/media/base/media_channel.h @@ -442,15 +442,11 @@ struct MediaReceiverInfo { // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferdelay double jitter_buffer_delay_seconds = 0.0; // Target delay for the jitter buffer (cumulative). - // TODO(crbug.com/webrtc/14244): This metric is only implemented for - // audio, it should be implemented for video as well. // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbuffertargetdelay - absl::optional jitter_buffer_target_delay_seconds; + double jitter_buffer_target_delay_seconds = 0.0; // Minimum obtainable delay for the jitter buffer (cumulative). - // TODO(crbug.com/webrtc/14244): This metric is only implemented for - // audio, it should be implemented for video as well. // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferminimumdelay - absl::optional jitter_buffer_minimum_delay_seconds; + double jitter_buffer_minimum_delay_seconds = 0.0; // Number of observations for cumulative jitter latency. // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferemittedcount uint64_t jitter_buffer_emitted_count = 0; diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc index dd384b2641..3828f0092d 100644 --- a/media/engine/webrtc_video_engine.cc +++ b/media/engine/webrtc_video_engine.cc @@ -3394,7 +3394,11 @@ WebRtcVideoChannel::WebRtcVideoReceiveStream::GetVideoReceiverInfo( info.jitter_buffer_ms = stats.jitter_buffer_ms; info.jitter_buffer_delay_seconds = stats.jitter_buffer_delay.seconds(); + info.jitter_buffer_target_delay_seconds = + stats.jitter_buffer_target_delay.seconds(); info.jitter_buffer_emitted_count = stats.jitter_buffer_emitted_count; + info.jitter_buffer_minimum_delay_seconds = + stats.jitter_buffer_minimum_delay.seconds(); info.min_playout_delay_ms = stats.min_playout_delay_ms; info.render_delay_ms = stats.render_delay_ms; info.frames_received = diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc index e63199db7a..1c613a0ad1 100644 --- a/media/engine/webrtc_video_engine_unittest.cc +++ b/media/engine/webrtc_video_engine_unittest.cc @@ -6557,7 +6557,9 @@ TEST_F(WebRtcVideoChannelTest, GetStatsTranslatesDecodeStatsCorrectly) { stats.target_delay_ms = 5; stats.jitter_buffer_ms = 6; stats.jitter_buffer_delay = TimeDelta::Seconds(60); + stats.jitter_buffer_target_delay = TimeDelta::Seconds(55); stats.jitter_buffer_emitted_count = 6; + stats.jitter_buffer_minimum_delay = TimeDelta::Seconds(50); stats.min_playout_delay_ms = 7; stats.render_delay_ms = 8; stats.width = 9; @@ -6591,8 +6593,12 @@ TEST_F(WebRtcVideoChannelTest, GetStatsTranslatesDecodeStatsCorrectly) { EXPECT_EQ(stats.jitter_buffer_ms, receive_info.receivers[0].jitter_buffer_ms); EXPECT_EQ(stats.jitter_buffer_delay.seconds(), receive_info.receivers[0].jitter_buffer_delay_seconds); + EXPECT_EQ(stats.jitter_buffer_target_delay.seconds(), + receive_info.receivers[0].jitter_buffer_target_delay_seconds); EXPECT_EQ(stats.jitter_buffer_emitted_count, receive_info.receivers[0].jitter_buffer_emitted_count); + EXPECT_EQ(stats.jitter_buffer_minimum_delay.seconds(), + receive_info.receivers[0].jitter_buffer_minimum_delay_seconds); EXPECT_EQ(stats.min_playout_delay_ms, receive_info.receivers[0].min_playout_delay_ms); EXPECT_EQ(stats.render_delay_ms, receive_info.receivers[0].render_delay_ms); diff --git a/modules/video_coding/timing/timing.cc b/modules/video_coding/timing/timing.cc index 1035d6f01e..735f6328d8 100644 --- a/modules/video_coding/timing/timing.cc +++ b/modules/video_coding/timing/timing.cc @@ -252,6 +252,13 @@ TimeDelta VCMTiming::TargetDelayInternal() const { jitter_delay_ + EstimatedMaxDecodeTime() + render_delay_); } +// TODO(crbug.com/webrtc/15197): Centralize delay arithmetic. +TimeDelta VCMTiming::StatsTargetDelayInternal() const { + TimeDelta stats_target_delay = + TargetDelayInternal() - (EstimatedMaxDecodeTime() + render_delay_); + return std::max(TimeDelta::Zero(), stats_target_delay); +} + VideoFrame::RenderParameters VCMTiming::RenderParameters() const { MutexLock lock(&mutex_); return {.use_low_latency_rendering = UseLowLatencyRendering(), @@ -271,12 +278,12 @@ VCMTiming::VideoDelayTimings VCMTiming::GetTimings() const { MutexLock lock(&mutex_); return VideoDelayTimings{ .num_decoded_frames = num_decoded_frames_, - .jitter_delay = jitter_delay_, + .minimum_delay = jitter_delay_, .estimated_max_decode_time = EstimatedMaxDecodeTime(), .render_delay = render_delay_, .min_playout_delay = min_playout_delay_, .max_playout_delay = max_playout_delay_, - .target_delay = TargetDelayInternal(), + .target_delay = StatsTargetDelayInternal(), .current_delay = current_delay_}; } diff --git a/modules/video_coding/timing/timing.h b/modules/video_coding/timing/timing.h index dbac40df58..9e7fb874c7 100644 --- a/modules/video_coding/timing/timing.h +++ b/modules/video_coding/timing/timing.h @@ -31,13 +31,15 @@ class VCMTiming { public: struct VideoDelayTimings { size_t num_decoded_frames; - // Delay added to smooth out frame delay variation ("jitter") caused by - // the network. - TimeDelta jitter_delay; + // Pre-decode delay added to smooth out frame delay variation ("jitter") + // caused by the network. The target delay will be no smaller than this + // delay, thus it is called `minimum_delay`. + TimeDelta minimum_delay; // Estimated time needed to decode a video frame. Obtained as the 95th // percentile decode time over a recent time window. TimeDelta estimated_max_decode_time; - // Estimated time needed to render a frame. Set to a constant. + // Post-decode delay added to smooth out frame delay variation caused by + // decoding and rendering. Set to a constant. TimeDelta render_delay; // Minimum total delay used when determining render time for a frame. // Obtained from API, `playout-delay` RTP header extension, or A/V sync. @@ -45,9 +47,9 @@ class VCMTiming { // Maximum total delay used when determining render time for a frame. // Obtained from `playout-delay` RTP header extension. TimeDelta max_playout_delay; - // Target delay. Obtained from all the elements above. + // Target total delay. Obtained from all the elements above. TimeDelta target_delay; - // Current delay. Obtained by smoothing out the target delay. + // Current total delay. Obtained by smoothening the `target_delay`. TimeDelta current_delay; }; @@ -133,6 +135,8 @@ class VCMTiming { Timestamp RenderTimeInternal(uint32_t frame_timestamp, Timestamp now) const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); TimeDelta TargetDelayInternal() const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + TimeDelta StatsTargetDelayInternal() const + RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); bool UseLowLatencyRendering() const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); mutable Mutex mutex_; diff --git a/modules/video_coding/timing/timing_unittest.cc b/modules/video_coding/timing/timing_unittest.cc index 8633c0de39..4ba8c4dcd2 100644 --- a/modules/video_coding/timing/timing_unittest.cc +++ b/modules/video_coding/timing/timing_unittest.cc @@ -13,6 +13,7 @@ #include "api/units/frequency.h" #include "api/units/time_delta.h" #include "system_wrappers/include/clock.h" +#include "test/gmock.h" #include "test/gtest.h" #include "test/scoped_key_value_config.h" @@ -22,9 +23,54 @@ namespace { constexpr Frequency k25Fps = Frequency::Hertz(25); constexpr Frequency k90kHz = Frequency::KiloHertz(90); +MATCHER(HasConsistentVideoDelayTimings, "") { + // Delays should be non-negative. + bool p1 = arg.minimum_delay >= TimeDelta::Zero(); + bool p2 = arg.estimated_max_decode_time >= TimeDelta::Zero(); + bool p3 = arg.render_delay >= TimeDelta::Zero(); + bool p4 = arg.min_playout_delay >= TimeDelta::Zero(); + bool p5 = arg.max_playout_delay >= TimeDelta::Zero(); + bool p6 = arg.target_delay >= TimeDelta::Zero(); + bool p7 = arg.current_delay >= TimeDelta::Zero(); + *result_listener << "\np: " << p1 << p2 << p3 << p4 << p5 << p6 << p7; + bool p = p1 && p2 && p3 && p4 && p5 && p6 && p7; + + // Delays should be internally consistent. + bool m1 = arg.minimum_delay <= arg.target_delay; + if (!m1) { + *result_listener << "\nminimum_delay: " << arg.minimum_delay << ", " + << "target_delay: " << arg.target_delay << "\n"; + } + bool m2 = arg.minimum_delay <= arg.current_delay; + if (!m2) { + *result_listener << "\nminimum_delay: " << arg.minimum_delay << ", " + << "current_delay: " << arg.current_delay; + } + bool m3 = arg.target_delay >= arg.min_playout_delay; + if (!m3) { + *result_listener << "\ntarget_delay: " << arg.target_delay << ", " + << "min_playout_delay: " << arg.min_playout_delay << "\n"; + } + // TODO(crbug.com/webrtc/15197): Uncomment when this is guaranteed. + // bool m4 = arg.target_delay <= arg.max_playout_delay; + bool m5 = arg.current_delay >= arg.min_playout_delay; + if (!m5) { + *result_listener << "\ncurrent_delay: " << arg.current_delay << ", " + << "min_playout_delay: " << arg.min_playout_delay << "\n"; + } + bool m6 = arg.current_delay <= arg.max_playout_delay; + if (!m6) { + *result_listener << "\ncurrent_delay: " << arg.current_delay << ", " + << "max_playout_delay: " << arg.max_playout_delay << "\n"; + } + bool m = m1 && m2 && m3 && m5 && m6; + + return p && m; +} + } // namespace -TEST(ReceiverTimingTest, JitterDelay) { +TEST(VCMTimingTest, JitterDelay) { test::ScopedKeyValueConfig field_trials; SimulatedClock clock(0); VCMTiming timing(&clock, field_trials); @@ -115,9 +161,11 @@ TEST(ReceiverTimingTest, JitterDelay) { clock.AdvanceTimeMilliseconds(5000); timestamp += 5 * 90000; timing.UpdateCurrentDelay(timestamp); + + EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); } -TEST(ReceiverTimingTest, TimestampWrapAround) { +TEST(VCMTimingTest, TimestampWrapAround) { constexpr auto kStartTime = Timestamp::Millis(1337); test::ScopedKeyValueConfig field_trials; SimulatedClock clock(kStartTime); @@ -136,9 +184,11 @@ TEST(ReceiverTimingTest, TimestampWrapAround) { EXPECT_EQ(kStartTime + 3 / k25Fps + TimeDelta::Millis(1), timing.RenderTime(89u, clock.CurrentTime())); } + + EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); } -TEST(ReceiverTimingTest, UseLowLatencyRenderer) { +TEST(VCMTimingTest, UseLowLatencyRenderer) { test::ScopedKeyValueConfig field_trials; SimulatedClock clock(0); VCMTiming timing(&clock, field_trials); @@ -161,9 +211,11 @@ TEST(ReceiverTimingTest, UseLowLatencyRenderer) { // False if max playout delay > 500 ms. timing.set_max_playout_delay(TimeDelta::Millis(501)); EXPECT_FALSE(timing.RenderParameters().use_low_latency_rendering); + + EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); } -TEST(ReceiverTimingTest, MaxWaitingTimeIsZeroForZeroRenderTime) { +TEST(VCMTimingTest, MaxWaitingTimeIsZeroForZeroRenderTime) { // This is the default path when the RTP playout delay header extension is set // to min==0 and max==0. constexpr int64_t kStartTimeUs = 3.15e13; // About one year in us. @@ -197,9 +249,11 @@ TEST(ReceiverTimingTest, MaxWaitingTimeIsZeroForZeroRenderTime) { EXPECT_LT(timing.MaxWaitingTime(kZeroRenderTime, now, /*too_many_frames_queued=*/false), TimeDelta::Zero()); + + EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); } -TEST(ReceiverTimingTest, MaxWaitingTimeZeroDelayPacingExperiment) { +TEST(VCMTimingTest, MaxWaitingTimeZeroDelayPacingExperiment) { // The minimum pacing is enabled by a field trial and active if the RTP // playout delay header extension is set to min==0. constexpr TimeDelta kMinPacing = TimeDelta::Millis(3); @@ -247,9 +301,11 @@ TEST(ReceiverTimingTest, MaxWaitingTimeZeroDelayPacingExperiment) { EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now, /*too_many_frames_queued=*/false), kMinPacing); + + EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); } -TEST(ReceiverTimingTest, DefaultMaxWaitingTimeUnaffectedByPacingExperiment) { +TEST(VCMTimingTest, DefaultMaxWaitingTimeUnaffectedByPacingExperiment) { // The minimum pacing is enabled by a field trial but should not have any // effect if render_time_ms is greater than 0; test::ScopedKeyValueConfig field_trials( @@ -277,9 +333,11 @@ TEST(ReceiverTimingTest, DefaultMaxWaitingTimeUnaffectedByPacingExperiment) { /*too_many_frames_queued=*/false), render_time - now - estimated_processing_delay); } + + EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); } -TEST(ReceiverTimingTest, MaxWaitingTimeReturnsZeroIfTooManyFramesQueuedIsTrue) { +TEST(VCMTimingTest, MaxWaitingTimeReturnsZeroIfTooManyFramesQueuedIsTrue) { // The minimum pacing is enabled by a field trial and active if the RTP // playout delay header extension is set to min==0. constexpr TimeDelta kMinPacing = TimeDelta::Millis(3); @@ -314,9 +372,11 @@ TEST(ReceiverTimingTest, MaxWaitingTimeReturnsZeroIfTooManyFramesQueuedIsTrue) { EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now_ms, /*too_many_frames_queued=*/true), TimeDelta::Zero()); + + EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); } -TEST(ReceiverTimingTest, UpdateCurrentDelayCapsWhenOffByMicroseconds) { +TEST(VCMTimingTest, UpdateCurrentDelayCapsWhenOffByMicroseconds) { test::ScopedKeyValueConfig field_trials; SimulatedClock clock(0); VCMTiming timing(&clock, field_trials); @@ -334,6 +394,52 @@ TEST(ReceiverTimingTest, UpdateCurrentDelayCapsWhenOffByMicroseconds) { decode_time + TimeDelta::Millis(10) + TimeDelta::Micros(37); timing.UpdateCurrentDelay(render_time, decode_time); EXPECT_EQ(timing.GetTimings().current_delay, timing.TargetVideoDelay()); + + // TODO(crbug.com/webrtc/15197): Fix this. + // EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); +} + +TEST(VCMTimingTest, GetTimings) { + test::ScopedKeyValueConfig field_trials; + SimulatedClock clock(33); + VCMTiming timing(&clock, field_trials); + timing.Reset(); + + // Setup. + TimeDelta render_delay = TimeDelta::Millis(11); + timing.set_render_delay(render_delay); + TimeDelta min_playout_delay = TimeDelta::Millis(50); + timing.set_min_playout_delay(min_playout_delay); + TimeDelta max_playout_delay = TimeDelta::Millis(500); + timing.set_max_playout_delay(max_playout_delay); + + // On complete. + timing.IncomingTimestamp(3000, clock.CurrentTime()); + clock.AdvanceTimeMilliseconds(1); + + // On decodable. + Timestamp render_time = + timing.RenderTime(/*next_temporal_unit_rtp=*/3000, clock.CurrentTime()); + TimeDelta minimum_delay = TimeDelta::Millis(123); + timing.SetJitterDelay(minimum_delay); + timing.UpdateCurrentDelay(render_time, clock.CurrentTime()); + clock.AdvanceTimeMilliseconds(100); + + // On decoded. + TimeDelta decode_time = TimeDelta::Millis(4); + timing.StopDecodeTimer(decode_time, clock.CurrentTime()); + + VCMTiming::VideoDelayTimings timings = timing.GetTimings(); + EXPECT_EQ(timings.num_decoded_frames, 1u); + EXPECT_EQ(timings.minimum_delay, minimum_delay); + // A single decoded frame is not enough to calculate p95. + EXPECT_EQ(timings.estimated_max_decode_time, TimeDelta::Zero()); + EXPECT_EQ(timings.render_delay, render_delay); + EXPECT_EQ(timings.min_playout_delay, min_playout_delay); + EXPECT_EQ(timings.max_playout_delay, max_playout_delay); + EXPECT_EQ(timings.target_delay, minimum_delay); + EXPECT_EQ(timings.current_delay, minimum_delay); + EXPECT_THAT(timings, HasConsistentVideoDelayTimings()); } } // namespace webrtc diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc index 97456a55de..eeee3b8e0e 100644 --- a/pc/rtc_stats_collector.cc +++ b/pc/rtc_stats_collector.cc @@ -416,14 +416,10 @@ void SetInboundRTPStreamStatsFromMediaReceiverInfo( static_cast(media_receiver_info.packets_lost); inbound_stats->jitter_buffer_delay = media_receiver_info.jitter_buffer_delay_seconds; - if (media_receiver_info.jitter_buffer_target_delay_seconds.has_value()) { - inbound_stats->jitter_buffer_target_delay = - *media_receiver_info.jitter_buffer_target_delay_seconds; - } - if (media_receiver_info.jitter_buffer_minimum_delay_seconds.has_value()) { - inbound_stats->jitter_buffer_minimum_delay = - *media_receiver_info.jitter_buffer_minimum_delay_seconds; - } + inbound_stats->jitter_buffer_target_delay = + media_receiver_info.jitter_buffer_target_delay_seconds; + inbound_stats->jitter_buffer_minimum_delay = + media_receiver_info.jitter_buffer_minimum_delay_seconds; inbound_stats->jitter_buffer_emitted_count = media_receiver_info.jitter_buffer_emitted_count; if (media_receiver_info.nacks_sent.has_value()) { diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc index fa9f2263f3..12a0063940 100644 --- a/pc/rtc_stats_integrationtest.cc +++ b/pc/rtc_stats_integrationtest.cc @@ -634,6 +634,10 @@ class RTCStatsReportVerifier { inbound_stream.jitter_buffer_delay); verifier.TestMemberIsNonNegative( inbound_stream.jitter_buffer_emitted_count); + verifier.TestMemberIsNonNegative( + inbound_stream.jitter_buffer_target_delay); + verifier.TestMemberIsNonNegative( + inbound_stream.jitter_buffer_minimum_delay); if (inbound_stream.kind.is_defined() && *inbound_stream.kind == "video") { verifier.TestMemberIsUndefined(inbound_stream.total_samples_received); verifier.TestMemberIsUndefined(inbound_stream.concealed_samples); @@ -643,9 +647,6 @@ class RTCStatsReportVerifier { inbound_stream.inserted_samples_for_deceleration); verifier.TestMemberIsUndefined( inbound_stream.removed_samples_for_acceleration); - verifier.TestMemberIsUndefined(inbound_stream.jitter_buffer_target_delay); - verifier.TestMemberIsUndefined( - inbound_stream.jitter_buffer_minimum_delay); verifier.TestMemberIsUndefined(inbound_stream.audio_level); verifier.TestMemberIsUndefined(inbound_stream.total_audio_energy); verifier.TestMemberIsUndefined(inbound_stream.total_samples_duration); diff --git a/video/end_to_end_tests/stats_tests.cc b/video/end_to_end_tests/stats_tests.cc index 3c3799fdf1..967357f954 100644 --- a/video/end_to_end_tests/stats_tests.cc +++ b/video/end_to_end_tests/stats_tests.cc @@ -129,8 +129,12 @@ TEST_F(StatsEndToEndTest, GetStats) { 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(); diff --git a/video/receive_statistics_proxy.cc b/video/receive_statistics_proxy.cc index faa0ea95eb..049f21226c 100644 --- a/video/receive_statistics_proxy.cc +++ b/video/receive_statistics_proxy.cc @@ -531,12 +531,15 @@ void ReceiveStatisticsProxy::OnDecoderInfo( })); } -void ReceiveStatisticsProxy::OnDecodableFrame(TimeDelta jitter_buffer_delay) { +void ReceiveStatisticsProxy::OnDecodableFrame(TimeDelta jitter_buffer_delay, + TimeDelta target_delay, + TimeDelta minimum_delay) { RTC_DCHECK_RUN_ON(&main_thread_); // Cumulative stats exposed through standardized GetStats. - // TODO(crbug.com/webrtc/14244): Implement targetDelay and minimumDelay here. stats_.jitter_buffer_delay += jitter_buffer_delay; + stats_.jitter_buffer_target_delay += target_delay; ++stats_.jitter_buffer_emitted_count; + stats_.jitter_buffer_minimum_delay += minimum_delay; } void ReceiveStatisticsProxy::OnFrameBufferTimingsUpdated( diff --git a/video/receive_statistics_proxy.h b/video/receive_statistics_proxy.h index 425eb1ac2c..d8da3064fd 100644 --- a/video/receive_statistics_proxy.h +++ b/video/receive_statistics_proxy.h @@ -89,7 +89,9 @@ class ReceiveStatisticsProxy : public VideoStreamBufferControllerStatsObserver, size_t size_bytes, VideoContentType content_type) override; void OnDroppedFrames(uint32_t frames_dropped) override; - void OnDecodableFrame(TimeDelta jitter_buffer_delay) override; + void OnDecodableFrame(TimeDelta jitter_buffer_delay, + TimeDelta target_delay, + TimeDelta minimum_delay) override; void OnFrameBufferTimingsUpdated(int estimated_max_decode_time_ms, int current_delay_ms, int target_delay_ms, diff --git a/video/receive_statistics_proxy_unittest.cc b/video/receive_statistics_proxy_unittest.cc index 7854c792bf..ec6dd0c420 100644 --- a/video/receive_statistics_proxy_unittest.cc +++ b/video/receive_statistics_proxy_unittest.cc @@ -560,35 +560,45 @@ TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsOnDroppedFrame) { TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsDecodeTimingStats) { const int kMaxDecodeMs = 2; const int kCurrentDelayMs = 3; - const int kTargetDelayMs = 4; + const TimeDelta kTargetDelay = TimeDelta::Millis(4); const int kJitterDelayMs = 5; const int kMinPlayoutDelayMs = 6; const int kRenderDelayMs = 7; const int64_t kRttMs = 8; - const int kJitterBufferDelayMs = 9; + const TimeDelta kJitterBufferDelay = TimeDelta::Millis(9); + const TimeDelta kMinimumDelay = TimeDelta::Millis(1); statistics_proxy_->OnRttUpdate(kRttMs); statistics_proxy_->OnFrameBufferTimingsUpdated( - kMaxDecodeMs, kCurrentDelayMs, kTargetDelayMs, kJitterDelayMs, + kMaxDecodeMs, kCurrentDelayMs, kTargetDelay.ms(), kJitterDelayMs, kMinPlayoutDelayMs, kRenderDelayMs); - statistics_proxy_->OnDecodableFrame(TimeDelta::Millis(kJitterBufferDelayMs)); + statistics_proxy_->OnDecodableFrame(kJitterBufferDelay, kTargetDelay, + kMinimumDelay); VideoReceiveStreamInterface::Stats stats = FlushAndGetStats(); EXPECT_EQ(kMaxDecodeMs, stats.max_decode_ms); EXPECT_EQ(kCurrentDelayMs, stats.current_delay_ms); - EXPECT_EQ(kTargetDelayMs, stats.target_delay_ms); + EXPECT_EQ(kTargetDelay.ms(), stats.target_delay_ms); EXPECT_EQ(kJitterDelayMs, stats.jitter_buffer_ms); EXPECT_EQ(kMinPlayoutDelayMs, stats.min_playout_delay_ms); EXPECT_EQ(kRenderDelayMs, stats.render_delay_ms); - EXPECT_EQ(kJitterBufferDelayMs, stats.jitter_buffer_delay.ms()); + EXPECT_EQ(kJitterBufferDelay, stats.jitter_buffer_delay); + EXPECT_EQ(kTargetDelay, stats.jitter_buffer_target_delay); EXPECT_EQ(1u, stats.jitter_buffer_emitted_count); + EXPECT_EQ(kMinimumDelay, stats.jitter_buffer_minimum_delay); } TEST_F(ReceiveStatisticsProxyTest, CumulativeDecodeGetStatsAccumulate) { - const int kJitterBufferDelayMs = 3; - statistics_proxy_->OnDecodableFrame(TimeDelta::Millis(kJitterBufferDelayMs)); - statistics_proxy_->OnDecodableFrame(TimeDelta::Millis(kJitterBufferDelayMs)); + const TimeDelta kJitterBufferDelay = TimeDelta::Millis(3); + const TimeDelta kTargetDelay = TimeDelta::Millis(2); + const TimeDelta kMinimumDelay = TimeDelta::Millis(1); + statistics_proxy_->OnDecodableFrame(kJitterBufferDelay, kTargetDelay, + kMinimumDelay); + statistics_proxy_->OnDecodableFrame(kJitterBufferDelay, kTargetDelay, + kMinimumDelay); VideoReceiveStreamInterface::Stats stats = FlushAndGetStats(); - EXPECT_EQ(2 * kJitterBufferDelayMs, stats.jitter_buffer_delay.ms()); + EXPECT_EQ(2 * kJitterBufferDelay, stats.jitter_buffer_delay); + EXPECT_EQ(2 * kTargetDelay, stats.jitter_buffer_target_delay); EXPECT_EQ(2u, stats.jitter_buffer_emitted_count); + EXPECT_EQ(2 * kMinimumDelay, stats.jitter_buffer_minimum_delay); } TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsRtcpPacketTypeCounts) { diff --git a/video/video_stream_buffer_controller.cc b/video/video_stream_buffer_controller.cc index 870c32d298..455f064b01 100644 --- a/video/video_stream_buffer_controller.cc +++ b/video/video_stream_buffer_controller.cc @@ -336,7 +336,7 @@ void VideoStreamBufferController::UpdateFrameBufferTimings( if (timings.num_decoded_frames) { stats_proxy_->OnFrameBufferTimingsUpdated( timings.estimated_max_decode_time.ms(), timings.current_delay.ms(), - timings.target_delay.ms(), timings.jitter_delay.ms(), + timings.target_delay.ms(), timings.minimum_delay.ms(), timings.min_playout_delay.ms(), timings.render_delay.ms()); } @@ -351,7 +351,8 @@ void VideoStreamBufferController::UpdateFrameBufferTimings( // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferdelay TimeDelta jitter_buffer_delay = std::max(TimeDelta::Zero(), now - min_receive_time); - stats_proxy_->OnDecodableFrame(jitter_buffer_delay); + stats_proxy_->OnDecodableFrame(jitter_buffer_delay, timings.target_delay, + timings.minimum_delay); } void VideoStreamBufferController::UpdateTimingFrameInfo() { diff --git a/video/video_stream_buffer_controller.h b/video/video_stream_buffer_controller.h index 96e867a2f1..bb67304d06 100644 --- a/video/video_stream_buffer_controller.h +++ b/video/video_stream_buffer_controller.h @@ -45,8 +45,12 @@ class VideoStreamBufferControllerStatsObserver { virtual void OnDroppedFrames(uint32_t frames_dropped) = 0; - // Actual delay experienced by a single frame. - virtual void OnDecodableFrame(TimeDelta jitter_buffer_delay) = 0; + // `jitter_buffer_delay` is the delay experienced by a single frame, + // whereas `target_delay` and `minimum_delay` are the current delays + // applied by the jitter buffer. + virtual void OnDecodableFrame(TimeDelta jitter_buffer_delay, + TimeDelta target_delay, + TimeDelta minimum_delay) = 0; // Various jitter buffer delays determined by VCMTiming. virtual void OnFrameBufferTimingsUpdated(int estimated_max_decode_time_ms, diff --git a/video/video_stream_buffer_controller_unittest.cc b/video/video_stream_buffer_controller_unittest.cc index 4156581b7f..be779ea9da 100644 --- a/video/video_stream_buffer_controller_unittest.cc +++ b/video/video_stream_buffer_controller_unittest.cc @@ -107,7 +107,9 @@ class VideoStreamBufferControllerStatsObserverMock MOCK_METHOD(void, OnDroppedFrames, (uint32_t num_dropped), (override)); MOCK_METHOD(void, OnDecodableFrame, - (TimeDelta jitter_buffer_delay), + (TimeDelta jitter_buffer_delay, + TimeDelta target_delay, + TimeDelta minimum_delay), (override)); MOCK_METHOD(void, OnFrameBufferTimingsUpdated,