Add unit tests and smaller updates for DecodeTimePercentileFilter. Bug: b/493549134 Change-Id: I06e2ab28f31b952cbeb2e938a5f7f3d49026ceb4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/462841 Commit-Queue: Åsa Persson <asapersson@webrtc.org> Reviewed-by: Rasmus Brandt <brandtr@webrtc.org> Cr-Commit-Position: refs/heads/main@{#47401}
diff --git a/modules/video_coding/timing/BUILD.gn b/modules/video_coding/timing/BUILD.gn index 75de611..a9e40f4 100644 --- a/modules/video_coding/timing/BUILD.gn +++ b/modules/video_coding/timing/BUILD.gn
@@ -120,6 +120,7 @@ rtc_library("timing_unittests") { testonly = true sources = [ + "decode_time_percentile_filter_unittest.cc", "frame_delay_variation_kalman_filter_unittest.cc", "inter_frame_delay_variation_calculator_unittest.cc", "jitter_estimator_unittest.cc", @@ -128,6 +129,7 @@ "timing_unittest.cc", ] deps = [ + ":decode_time_percentile_filter", ":frame_delay_variation_kalman_filter", ":inter_frame_delay_variation_calculator", ":jitter_estimator",
diff --git a/modules/video_coding/timing/decode_time_percentile_filter.cc b/modules/video_coding/timing/decode_time_percentile_filter.cc index e40ab5c..821cf0c 100644 --- a/modules/video_coding/timing/decode_time_percentile_filter.cc +++ b/modules/video_coding/timing/decode_time_percentile_filter.cc
@@ -10,36 +10,37 @@ #include "modules/video_coding/timing/decode_time_percentile_filter.h" +#include <algorithm> #include <cstdint> namespace webrtc { - namespace { -// The first kIgnoredSampleCount samples will be ignored. -const int kIgnoredSampleCount = 5; -// Return the `kPercentile` value in RequiredDecodeTimeMs(). -const float kPercentile = 0.95f; +// The number of initial samples to ignore. +constexpr int kIgnoredSampleCount = 5; +// The percentile value used by the filter. +constexpr float kPercentile = 0.95f; // The window size in ms. -const int64_t kTimeLimitMs = 10000; +constexpr int64_t kTimeLimitMs = 10000; -} // anonymous namespace +} // namespace DecodeTimePercentileFilter::DecodeTimePercentileFilter() - : ignored_sample_count_(0), filter_(kPercentile) {} + : filter_(kPercentile) {} DecodeTimePercentileFilter::~DecodeTimePercentileFilter() = default; -void DecodeTimePercentileFilter::AddTiming(int64_t decode_time_ms, +void DecodeTimePercentileFilter::AddSample(int64_t decode_time_ms, int64_t now_ms) { - // Ignore the first `kIgnoredSampleCount` samples. + // Ignore the first samples. if (ignored_sample_count_ < kIgnoredSampleCount) { ++ignored_sample_count_; return; } // Insert new decode time value. - filter_.Insert(decode_time_ms); - history_.emplace(decode_time_ms, now_ms); + const int64_t capped_decode_ms = std::max(int64_t{0}, decode_time_ms); + filter_.Insert(capped_decode_ms); + history_.emplace(capped_decode_ms, now_ms); // Pop old decode time values. while (!history_.empty() && @@ -49,8 +50,7 @@ } } -// Get the 95th percentile observed decode time within a time window. -int64_t DecodeTimePercentileFilter::RequiredDecodeTimeMs() const { +int64_t DecodeTimePercentileFilter::GetPercentileMs() const { return filter_.GetPercentileValue(); }
diff --git a/modules/video_coding/timing/decode_time_percentile_filter.h b/modules/video_coding/timing/decode_time_percentile_filter.h index c2c653d..848b3cc 100644 --- a/modules/video_coding/timing/decode_time_percentile_filter.h +++ b/modules/video_coding/timing/decode_time_percentile_filter.h
@@ -18,8 +18,7 @@ namespace webrtc { -// The `DecodeTimePercentileFilter` filters the actual per-frame decode times -// and provides an estimate for the 95th percentile of those decode times. This +// This class estimates the 95th percentile of per-frame decode times. This // estimate can be used to determine how large the "decode delay term" should be // when determining the render timestamp for a frame. class DecodeTimePercentileFilter { @@ -27,12 +26,12 @@ DecodeTimePercentileFilter(); ~DecodeTimePercentileFilter(); - // Add a new decode time to the filter. - void AddTiming(int64_t new_decode_time_ms, int64_t now_ms); + // Adds a new decode time to the filter. + void AddSample(int64_t decode_time_ms, int64_t now_ms); - // Get the required decode time in ms. It is the 95th percentile observed - // decode time within a time window. - int64_t RequiredDecodeTimeMs() const; + // Returns the 95th percentile of observed decode times within a time window, + // in milliseconds. + int64_t GetPercentileMs() const; private: struct Sample { @@ -42,7 +41,7 @@ }; // The number of samples ignored so far. - int ignored_sample_count_; + int ignored_sample_count_ = 0; // Queue with history of latest decode time values. std::queue<Sample> history_; // `filter_` contains the same values as `history_`, but in a data structure
diff --git a/modules/video_coding/timing/decode_time_percentile_filter_unittest.cc b/modules/video_coding/timing/decode_time_percentile_filter_unittest.cc new file mode 100644 index 0000000..12ef007 --- /dev/null +++ b/modules/video_coding/timing/decode_time_percentile_filter_unittest.cc
@@ -0,0 +1,86 @@ +/* + * Copyright (c) 2026 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 "modules/video_coding/timing/decode_time_percentile_filter.h" + +#include <cstdint> + +#include "test/gtest.h" + +namespace webrtc { +namespace { + +constexpr int64_t kDecodeTimeMs = 44; +constexpr int64_t kNowMs = 1234; +// From decode_time_percentile_filter.cc. +constexpr int kIgnoredSampleCount = 5; +constexpr int64_t kTimeLimitMs = 10000; + +void AddSamples(DecodeTimePercentileFilter& filter, + int count, + int64_t decode_time_ms, + int64_t now_ms) { + for (int i = 0; i < count; ++i) { + filter.AddSample(decode_time_ms, now_ms); + } +} + +TEST(DecodeTimePercentileFilterTest, InitiallyReturnsZero) { + DecodeTimePercentileFilter filter; + EXPECT_EQ(filter.GetPercentileMs(), 0); +} + +TEST(DecodeTimePercentileFilterTest, IgnoresFirstSamples) { + DecodeTimePercentileFilter filter; + AddSamples(filter, kIgnoredSampleCount, kDecodeTimeMs, kNowMs); + EXPECT_EQ(filter.GetPercentileMs(), 0); +} + +TEST(DecodeTimePercentileFilterTest, IncludesSampleAfterIgnored) { + DecodeTimePercentileFilter filter; + AddSamples(filter, kIgnoredSampleCount + 1, kDecodeTimeMs, kNowMs); + EXPECT_EQ(filter.GetPercentileMs(), kDecodeTimeMs); +} + +TEST(DecodeTimePercentileFilterTest, CapsNegativeSamplesToZero) { + DecodeTimePercentileFilter filter; + AddSamples(filter, kIgnoredSampleCount + 1, /*decode_time_ms=*/-1, kNowMs); + EXPECT_EQ(filter.GetPercentileMs(), 0); +} + +TEST(DecodeTimePercentileFilterTest, Returns95thPercentile) { + DecodeTimePercentileFilter filter; + AddSamples(filter, kIgnoredSampleCount, kDecodeTimeMs, kNowMs); + for (int i = 1; i <= 20; ++i) { + filter.AddSample(/*decode_time_ms=*/i, kNowMs); + } + EXPECT_EQ(filter.GetPercentileMs(), 19); +} + +TEST(DecodeTimePercentileFilterTest, KeepsSamplesWithinTimeWindow) { + DecodeTimePercentileFilter filter; + AddSamples(filter, kIgnoredSampleCount + 10, kDecodeTimeMs, kNowMs); + EXPECT_EQ(filter.GetPercentileMs(), kDecodeTimeMs); + + filter.AddSample(/*decode_time_ms=*/3, kNowMs + kTimeLimitMs); + EXPECT_EQ(filter.GetPercentileMs(), kDecodeTimeMs); +} + +TEST(DecodeTimePercentileFilterTest, DiscardsSamplesOutsideTimeWindow) { + DecodeTimePercentileFilter filter; + AddSamples(filter, kIgnoredSampleCount + 10, kDecodeTimeMs, kNowMs); + EXPECT_EQ(filter.GetPercentileMs(), kDecodeTimeMs); + + filter.AddSample(/*decode_time_ms=*/3, kNowMs + kTimeLimitMs + 1); + EXPECT_EQ(filter.GetPercentileMs(), 3); +} + +} // namespace +} // namespace webrtc
diff --git a/modules/video_coding/timing/timing.cc b/modules/video_coding/timing/timing.cc index e9d0ef2..5db6208 100644 --- a/modules/video_coding/timing/timing.cc +++ b/modules/video_coding/timing/timing.cc
@@ -156,13 +156,11 @@ void VCMTiming::StopDecodeTimer(TimeDelta decode_time, Timestamp now) { MutexLock lock(&mutex_); - decode_time_filter_->AddTiming(decode_time.ms(), now.ms()); RTC_DCHECK_GE(decode_time, TimeDelta::Zero()); + decode_time_filter_->AddSample(decode_time.ms(), now.ms()); ++timings_.num_decoded_frames; - - int max_decode_ms = decode_time_filter_->RequiredDecodeTimeMs(); - RTC_DCHECK_GE(max_decode_ms, 0); - timings_.estimated_max_decode_time = TimeDelta::Millis(max_decode_ms); + timings_.estimated_max_decode_time = + TimeDelta::Millis(decode_time_filter_->GetPercentileMs()); } void VCMTiming::IncomingTimestamp(uint32_t rtp_timestamp, Timestamp now) { @@ -210,7 +208,7 @@ // `render_time` == 0 indicates that the frame should be decoded and // rendered as soon as possible. However, the decoder can be choked if too // many frames are sent at once. Therefore, limit the interframe delay to - // |zero_playout_delay_min_pacing_| unless too many frames are queued in + // `zero_playout_delay_min_pacing_` unless too many frames are queued in // which case the frames are sent to the decoder at once. if (too_many_frames_queued) { return TimeDelta::Zero(); @@ -231,12 +229,6 @@ return timings_.TargetDelay(); } -VideoFrame::RenderParameters VCMTiming::RenderParameters() const { - MutexLock lock(&mutex_); - return {.use_low_latency_rendering = timings_.UseLowLatencyRendering(), - .max_composition_delay_in_frames = max_composition_delay_in_frames_}; -} - VCMTiming::VideoDelayTimings VCMTiming::GetTimings() const { MutexLock lock(&mutex_); VideoDelayTimings timings = timings_; @@ -244,6 +236,12 @@ return timings; } +VideoFrame::RenderParameters VCMTiming::RenderParameters() const { + MutexLock lock(&mutex_); + return {.use_low_latency_rendering = timings_.UseLowLatencyRendering(), + .max_composition_delay_in_frames = max_composition_delay_in_frames_}; +} + void VCMTiming::SetMaxCompositionDelayInFrames( std::optional<int> max_composition_delay_in_frames) { MutexLock lock(&mutex_);