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/modules/video_coding/timing/timing_unittest.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 '')
-rw-r--r-- | third_party/libwebrtc/modules/video_coding/timing/timing_unittest.cc | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/video_coding/timing/timing_unittest.cc b/third_party/libwebrtc/modules/video_coding/timing/timing_unittest.cc new file mode 100644 index 0000000000..4ba8c4dcd2 --- /dev/null +++ b/third_party/libwebrtc/modules/video_coding/timing/timing_unittest.cc @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2011 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/timing.h" + +#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" + +namespace webrtc { +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(VCMTimingTest, JitterDelay) { + test::ScopedKeyValueConfig field_trials; + SimulatedClock clock(0); + VCMTiming timing(&clock, field_trials); + timing.Reset(); + + uint32_t timestamp = 0; + timing.UpdateCurrentDelay(timestamp); + + timing.Reset(); + + timing.IncomingTimestamp(timestamp, clock.CurrentTime()); + TimeDelta jitter_delay = TimeDelta::Millis(20); + timing.SetJitterDelay(jitter_delay); + timing.UpdateCurrentDelay(timestamp); + timing.set_render_delay(TimeDelta::Zero()); + auto wait_time = timing.MaxWaitingTime( + timing.RenderTime(timestamp, clock.CurrentTime()), clock.CurrentTime(), + /*too_many_frames_queued=*/false); + // First update initializes the render time. Since we have no decode delay + // we get wait_time = renderTime - now - renderDelay = jitter. + EXPECT_EQ(jitter_delay, wait_time); + + jitter_delay += TimeDelta::Millis(VCMTiming::kDelayMaxChangeMsPerS + 10); + timestamp += 90000; + clock.AdvanceTimeMilliseconds(1000); + timing.SetJitterDelay(jitter_delay); + timing.UpdateCurrentDelay(timestamp); + wait_time = timing.MaxWaitingTime( + timing.RenderTime(timestamp, clock.CurrentTime()), clock.CurrentTime(), + /*too_many_frames_queued=*/false); + // Since we gradually increase the delay we only get 100 ms every second. + EXPECT_EQ(jitter_delay - TimeDelta::Millis(10), wait_time); + + timestamp += 90000; + clock.AdvanceTimeMilliseconds(1000); + timing.UpdateCurrentDelay(timestamp); + wait_time = timing.MaxWaitingTime( + timing.RenderTime(timestamp, clock.CurrentTime()), clock.CurrentTime(), + /*too_many_frames_queued=*/false); + EXPECT_EQ(jitter_delay, wait_time); + + // Insert frames without jitter, verify that this gives the exact wait time. + const int kNumFrames = 300; + for (int i = 0; i < kNumFrames; i++) { + clock.AdvanceTime(1 / k25Fps); + timestamp += k90kHz / k25Fps; + timing.IncomingTimestamp(timestamp, clock.CurrentTime()); + } + timing.UpdateCurrentDelay(timestamp); + wait_time = timing.MaxWaitingTime( + timing.RenderTime(timestamp, clock.CurrentTime()), clock.CurrentTime(), + /*too_many_frames_queued=*/false); + EXPECT_EQ(jitter_delay, wait_time); + + // Add decode time estimates for 1 second. + const TimeDelta kDecodeTime = TimeDelta::Millis(10); + for (int i = 0; i < k25Fps.hertz(); i++) { + clock.AdvanceTime(kDecodeTime); + timing.StopDecodeTimer(kDecodeTime, clock.CurrentTime()); + timestamp += k90kHz / k25Fps; + clock.AdvanceTime(1 / k25Fps - kDecodeTime); + timing.IncomingTimestamp(timestamp, clock.CurrentTime()); + } + timing.UpdateCurrentDelay(timestamp); + wait_time = timing.MaxWaitingTime( + timing.RenderTime(timestamp, clock.CurrentTime()), clock.CurrentTime(), + /*too_many_frames_queued=*/false); + EXPECT_EQ(jitter_delay, wait_time); + + const TimeDelta kMinTotalDelay = TimeDelta::Millis(200); + timing.set_min_playout_delay(kMinTotalDelay); + clock.AdvanceTimeMilliseconds(5000); + timestamp += 5 * 90000; + timing.UpdateCurrentDelay(timestamp); + const TimeDelta kRenderDelay = TimeDelta::Millis(10); + timing.set_render_delay(kRenderDelay); + wait_time = timing.MaxWaitingTime( + timing.RenderTime(timestamp, clock.CurrentTime()), clock.CurrentTime(), + /*too_many_frames_queued=*/false); + // We should at least have kMinTotalDelayMs - decodeTime (10) - renderTime + // (10) to wait. + EXPECT_EQ(kMinTotalDelay - kDecodeTime - kRenderDelay, wait_time); + // The total video delay should be equal to the min total delay. + EXPECT_EQ(kMinTotalDelay, timing.TargetVideoDelay()); + + // Reset playout delay. + timing.set_min_playout_delay(TimeDelta::Zero()); + clock.AdvanceTimeMilliseconds(5000); + timestamp += 5 * 90000; + timing.UpdateCurrentDelay(timestamp); + + EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); +} + +TEST(VCMTimingTest, TimestampWrapAround) { + constexpr auto kStartTime = Timestamp::Millis(1337); + test::ScopedKeyValueConfig field_trials; + SimulatedClock clock(kStartTime); + VCMTiming timing(&clock, field_trials); + + // Provoke a wrap-around. The fifth frame will have wrapped at 25 fps. + constexpr uint32_t kRtpTicksPerFrame = k90kHz / k25Fps; + uint32_t timestamp = 0xFFFFFFFFu - 3 * kRtpTicksPerFrame; + for (int i = 0; i < 5; ++i) { + timing.IncomingTimestamp(timestamp, clock.CurrentTime()); + clock.AdvanceTime(1 / k25Fps); + timestamp += kRtpTicksPerFrame; + EXPECT_EQ(kStartTime + 3 / k25Fps, + timing.RenderTime(0xFFFFFFFFu, clock.CurrentTime())); + // One ms later in 90 kHz. + EXPECT_EQ(kStartTime + 3 / k25Fps + TimeDelta::Millis(1), + timing.RenderTime(89u, clock.CurrentTime())); + } + + EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); +} + +TEST(VCMTimingTest, UseLowLatencyRenderer) { + test::ScopedKeyValueConfig field_trials; + SimulatedClock clock(0); + VCMTiming timing(&clock, field_trials); + timing.Reset(); + // Default is false. + EXPECT_FALSE(timing.RenderParameters().use_low_latency_rendering); + // False if min playout delay > 0. + timing.set_min_playout_delay(TimeDelta::Millis(10)); + timing.set_max_playout_delay(TimeDelta::Millis(20)); + EXPECT_FALSE(timing.RenderParameters().use_low_latency_rendering); + // True if min==0, max > 0. + timing.set_min_playout_delay(TimeDelta::Zero()); + EXPECT_TRUE(timing.RenderParameters().use_low_latency_rendering); + // True if min==max==0. + timing.set_max_playout_delay(TimeDelta::Zero()); + EXPECT_TRUE(timing.RenderParameters().use_low_latency_rendering); + // True also for max playout delay==500 ms. + timing.set_max_playout_delay(TimeDelta::Millis(500)); + EXPECT_TRUE(timing.RenderParameters().use_low_latency_rendering); + // 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(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. + constexpr TimeDelta kTimeDelta = 1 / Frequency::Hertz(60); + constexpr Timestamp kZeroRenderTime = Timestamp::Zero(); + SimulatedClock clock(kStartTimeUs); + test::ScopedKeyValueConfig field_trials; + VCMTiming timing(&clock, field_trials); + timing.Reset(); + timing.set_max_playout_delay(TimeDelta::Zero()); + for (int i = 0; i < 10; ++i) { + clock.AdvanceTime(kTimeDelta); + Timestamp now = clock.CurrentTime(); + EXPECT_LT(timing.MaxWaitingTime(kZeroRenderTime, now, + /*too_many_frames_queued=*/false), + TimeDelta::Zero()); + } + // Another frame submitted at the same time also returns a negative max + // waiting time. + Timestamp now = clock.CurrentTime(); + EXPECT_LT(timing.MaxWaitingTime(kZeroRenderTime, now, + /*too_many_frames_queued=*/false), + TimeDelta::Zero()); + // MaxWaitingTime should be less than zero even if there's a burst of frames. + EXPECT_LT(timing.MaxWaitingTime(kZeroRenderTime, now, + /*too_many_frames_queued=*/false), + TimeDelta::Zero()); + EXPECT_LT(timing.MaxWaitingTime(kZeroRenderTime, now, + /*too_many_frames_queued=*/false), + TimeDelta::Zero()); + EXPECT_LT(timing.MaxWaitingTime(kZeroRenderTime, now, + /*too_many_frames_queued=*/false), + TimeDelta::Zero()); + + EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); +} + +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); + test::ScopedKeyValueConfig field_trials( + "WebRTC-ZeroPlayoutDelay/min_pacing:3ms/"); + constexpr int64_t kStartTimeUs = 3.15e13; // About one year in us. + constexpr TimeDelta kTimeDelta = 1 / Frequency::Hertz(60); + constexpr auto kZeroRenderTime = Timestamp::Zero(); + SimulatedClock clock(kStartTimeUs); + VCMTiming timing(&clock, field_trials); + timing.Reset(); + // MaxWaitingTime() returns zero for evenly spaced video frames. + for (int i = 0; i < 10; ++i) { + clock.AdvanceTime(kTimeDelta); + Timestamp now = clock.CurrentTime(); + EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now, + /*too_many_frames_queued=*/false), + TimeDelta::Zero()); + timing.SetLastDecodeScheduledTimestamp(now); + } + // Another frame submitted at the same time is paced according to the field + // trial setting. + auto now = clock.CurrentTime(); + EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now, + /*too_many_frames_queued=*/false), + kMinPacing); + // If there's a burst of frames, the wait time is calculated based on next + // decode time. + EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now, + /*too_many_frames_queued=*/false), + kMinPacing); + EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now, + /*too_many_frames_queued=*/false), + kMinPacing); + // Allow a few ms to pass, this should be subtracted from the MaxWaitingTime. + constexpr TimeDelta kTwoMs = TimeDelta::Millis(2); + clock.AdvanceTime(kTwoMs); + now = clock.CurrentTime(); + EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now, + /*too_many_frames_queued=*/false), + kMinPacing - kTwoMs); + // A frame is decoded at the current time, the wait time should be restored to + // pacing delay. + timing.SetLastDecodeScheduledTimestamp(now); + EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now, + /*too_many_frames_queued=*/false), + kMinPacing); + + EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); +} + +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( + "WebRTC-ZeroPlayoutDelay/min_pacing:3ms/"); + constexpr int64_t kStartTimeUs = 3.15e13; // About one year in us. + const TimeDelta kTimeDelta = TimeDelta::Millis(1000.0 / 60.0); + SimulatedClock clock(kStartTimeUs); + VCMTiming timing(&clock, field_trials); + timing.Reset(); + clock.AdvanceTime(kTimeDelta); + auto now = clock.CurrentTime(); + Timestamp render_time = now + TimeDelta::Millis(30); + // Estimate the internal processing delay from the first frame. + TimeDelta estimated_processing_delay = + (render_time - now) - + timing.MaxWaitingTime(render_time, now, + /*too_many_frames_queued=*/false); + EXPECT_GT(estimated_processing_delay, TimeDelta::Zero()); + + // Any other frame submitted at the same time should be scheduled according to + // its render time. + for (int i = 0; i < 5; ++i) { + render_time += kTimeDelta; + EXPECT_EQ(timing.MaxWaitingTime(render_time, now, + /*too_many_frames_queued=*/false), + render_time - now - estimated_processing_delay); + } + + EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); +} + +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); + test::ScopedKeyValueConfig field_trials( + "WebRTC-ZeroPlayoutDelay/min_pacing:3ms/"); + constexpr int64_t kStartTimeUs = 3.15e13; // About one year in us. + const TimeDelta kTimeDelta = TimeDelta::Millis(1000.0 / 60.0); + constexpr auto kZeroRenderTime = Timestamp::Zero(); + SimulatedClock clock(kStartTimeUs); + VCMTiming timing(&clock, field_trials); + timing.Reset(); + // MaxWaitingTime() returns zero for evenly spaced video frames. + for (int i = 0; i < 10; ++i) { + clock.AdvanceTime(kTimeDelta); + auto now = clock.CurrentTime(); + EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now, + /*too_many_frames_queued=*/false), + TimeDelta::Zero()); + timing.SetLastDecodeScheduledTimestamp(now); + } + // Another frame submitted at the same time is paced according to the field + // trial setting. + auto now_ms = clock.CurrentTime(); + EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now_ms, + /*too_many_frames_queued=*/false), + kMinPacing); + // MaxWaitingTime returns 0 even if there's a burst of frames if + // too_many_frames_queued is set to true. + EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now_ms, + /*too_many_frames_queued=*/true), + TimeDelta::Zero()); + EXPECT_EQ(timing.MaxWaitingTime(kZeroRenderTime, now_ms, + /*too_many_frames_queued=*/true), + TimeDelta::Zero()); + + EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); +} + +TEST(VCMTimingTest, UpdateCurrentDelayCapsWhenOffByMicroseconds) { + test::ScopedKeyValueConfig field_trials; + SimulatedClock clock(0); + VCMTiming timing(&clock, field_trials); + timing.Reset(); + + // Set larger initial current delay. + timing.set_min_playout_delay(TimeDelta::Millis(200)); + timing.UpdateCurrentDelay(Timestamp::Millis(900), Timestamp::Millis(1000)); + + // Add a few microseconds to ensure that the delta of decode time is 0 after + // rounding, and should reset to the target delay. + timing.set_min_playout_delay(TimeDelta::Millis(50)); + Timestamp decode_time = Timestamp::Millis(1337); + Timestamp render_time = + 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 |