| /* | |
| * 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 <cstdint> | |
| #include <memory> | |
| #include <optional> | |
| #include <span> | |
| #include <utility> | |
| #include "api/environment/environment.h" | |
| #include "api/units/data_size.h" | |
| #include "api/units/frequency.h" | |
| #include "api/units/time_delta.h" | |
| #include "api/units/timestamp.h" | |
| #include "api/video/timing/video_jitter_timing_interface.h" | |
| #include "system_wrappers/include/clock.h" | |
| #include "test/create_test_environment.h" | |
| #include "test/gmock.h" | |
| #include "test/gtest.h" | |
| namespace webrtc { | |
| namespace { | |
| using ::testing::_; | |
| using ::testing::ElementsAre; | |
| using ::testing::Eq; | |
| using ::testing::Field; | |
| using ::testing::FieldsAre; | |
| using ::testing::Return; | |
| class MockVideoJitterTiming : public VideoJitterTimingInterface { | |
| public: | |
| MOCK_METHOD(void, Reset, (), (override)); | |
| MOCK_METHOD(void, OnCompleteFrame, (const FrameInfo& info), (override)); | |
| MOCK_METHOD(std::optional<Timestamp>, | |
| LocalTime, | |
| (uint32_t rtp_timestamp), | |
| (const, override)); | |
| MOCK_METHOD(std::optional<TimeDelta>, | |
| OnContinuousTemporalUnits, | |
| (std::span<const uint32_t> rtp_timestamps, Timestamp now), | |
| (override)); | |
| MOCK_METHOD(std::optional<TimeDelta>, | |
| OnDecodableTemporalUnit, | |
| (const TemporalUnitInfo& info), | |
| (override)); | |
| MOCK_METHOD(void, OnNetworkUpdate, (const NetworkInfo& info), (override)); | |
| }; | |
| constexpr Frequency k25Fps = Frequency::Hertz(25); | |
| constexpr Frequency k90kHz = Frequency::KiloHertz(90); | |
| constexpr TimeDelta kMinimumDelay = TimeDelta::Millis(100); | |
| constexpr TimeDelta kDecodeTime = TimeDelta::Millis(20); | |
| constexpr TimeDelta kRenderDelay = TimeDelta::Millis(15); | |
| constexpr Timestamp kUnusedTimestamp = Timestamp::MinusInfinity(); | |
| 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.stats_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.stats_target_delay; | |
| if (!m1) { | |
| *result_listener << "\nminimum_delay: " << ToString(arg.minimum_delay) | |
| << ", " << "stats_target_delay: " | |
| << ToString(arg.stats_target_delay) << "\n"; | |
| } | |
| bool m2 = arg.minimum_delay <= arg.current_delay; | |
| if (!m2) { | |
| *result_listener << "\nminimum_delay: " << ToString(arg.minimum_delay) | |
| << ", " | |
| << "current_delay: " << ToString(arg.current_delay); | |
| } | |
| bool m3 = arg.stats_target_delay >= arg.min_playout_delay; | |
| if (!m3) { | |
| *result_listener << "\nstats_target_delay: " | |
| << ToString(arg.stats_target_delay) << ", " | |
| << "min_playout_delay: " << ToString(arg.min_playout_delay) | |
| << "\n"; | |
| } | |
| // TODO(crbug.com/webrtc/15197): Uncomment when this is guaranteed. | |
| // bool m4 = arg.stats_target_delay <= arg.max_playout_delay; | |
| bool m5 = arg.current_delay >= arg.min_playout_delay; | |
| if (!m5) { | |
| *result_listener << "\ncurrent_delay: " << ToString(arg.current_delay) | |
| << ", " | |
| << "min_playout_delay: " << ToString(arg.min_playout_delay) | |
| << "\n"; | |
| } | |
| bool m6 = arg.current_delay <= arg.max_playout_delay; | |
| if (!m6) { | |
| *result_listener << "\ncurrent_delay: " << ToString(arg.current_delay) | |
| << ", " | |
| << "max_playout_delay: " << ToString(arg.max_playout_delay) | |
| << "\n"; | |
| } | |
| bool m = m1 && m2 && m3 && m5 && m6; | |
| return p && m; | |
| } | |
| void UpdateDecodeTimer(VCMTiming& timing, | |
| SimulatedClock& clock, | |
| TimeDelta decode_time) { | |
| for (int i = 0; i < k25Fps.hertz(); ++i) { | |
| clock.AdvanceTime(decode_time); | |
| timing.UpdateDecodeTimeEstimate(decode_time, clock.CurrentTime()); | |
| clock.AdvanceTime(1 / k25Fps - decode_time); | |
| } | |
| } | |
| TEST(VCMTimingTest, TimestampWrapAround) { | |
| constexpr auto kStartTime = Timestamp::Millis(1337); | |
| SimulatedClock clock(kStartTime); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| // 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.OnCompleteFrame({.rtp_timestamp = timestamp, | |
| .time = clock.CurrentTime(), | |
| .last_spatial_layer = true}); | |
| 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) { | |
| SimulatedClock clock(0); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| // Default is false. | |
| EXPECT_FALSE(timing.RenderParameters().use_low_latency_rendering); | |
| // False if min playout delay > 0. | |
| timing.set_playout_delay({TimeDelta::Millis(10), TimeDelta::Millis(20)}); | |
| EXPECT_FALSE(timing.RenderParameters().use_low_latency_rendering); | |
| // True if min==0, max > 0. | |
| timing.set_playout_delay({TimeDelta::Zero(), TimeDelta::Millis(20)}); | |
| EXPECT_TRUE(timing.RenderParameters().use_low_latency_rendering); | |
| // True if min==max==0. | |
| timing.set_playout_delay({TimeDelta::Zero(), TimeDelta::Zero()}); | |
| EXPECT_TRUE(timing.RenderParameters().use_low_latency_rendering); | |
| // True also for max playout delay==500 ms. | |
| timing.set_playout_delay({TimeDelta::Zero(), TimeDelta::Millis(500)}); | |
| EXPECT_TRUE(timing.RenderParameters().use_low_latency_rendering); | |
| // False if max playout delay > 500 ms. | |
| timing.set_playout_delay({TimeDelta::Zero(), TimeDelta::Millis(501)}); | |
| EXPECT_FALSE(timing.RenderParameters().use_low_latency_rendering); | |
| EXPECT_THAT(timing.GetTimings(), HasConsistentVideoDelayTimings()); | |
| } | |
| TEST(VCMTimingTest, UpdateCurrentDelayCapsWhenOffByMicroseconds) { | |
| SimulatedClock clock(0); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| // 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 + kRenderDelay + 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, UpdateDecodeTimeEstimateClearsOldEstimates) { | |
| SimulatedClock clock(0); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| UpdateDecodeTimer(timing, clock, kDecodeTime); | |
| EXPECT_EQ(timing.GetTimings().estimated_max_decode_time, kDecodeTime); | |
| // Advance time beyond the filter window, old estimates should be cleared. | |
| clock.AdvanceTime(TimeDelta::Seconds(30)); | |
| timing.UpdateDecodeTimeEstimate(TimeDelta::Millis(3), clock.CurrentTime()); | |
| EXPECT_EQ(timing.GetTimings().estimated_max_decode_time, | |
| TimeDelta::Millis(3)); | |
| } | |
| TEST(VCMTimingTest, GetMinPlayoutDelay) { | |
| SimulatedClock clock(0); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| timing.set_min_playout_delay(TimeDelta::Millis(123)); | |
| EXPECT_EQ(timing.min_playout_delay(), TimeDelta::Millis(123)); | |
| } | |
| TEST(VCMTimingTest, InitialVideoDelayTimings) { | |
| SimulatedClock clock(0); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| VCMTiming::VideoDelayTimings timings = timing.GetTimings(); | |
| EXPECT_EQ(timings.num_decoded_frames, 0u); | |
| EXPECT_EQ(timings.minimum_delay, TimeDelta::Zero()); | |
| EXPECT_EQ(timings.estimated_max_decode_time, TimeDelta::Zero()); | |
| EXPECT_EQ(timings.render_delay, kRenderDelay); | |
| EXPECT_EQ(timings.min_playout_delay, TimeDelta::Zero()); | |
| EXPECT_EQ(timings.stats_target_delay, TimeDelta::Zero()); | |
| EXPECT_EQ(timings.current_delay, TimeDelta::Zero()); | |
| EXPECT_THAT(timings, HasConsistentVideoDelayTimings()); | |
| } | |
| TEST(VCMTimingTest, GetTimings) { | |
| SimulatedClock clock(33); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, /*render_delay=*/TimeDelta::Millis(7)); | |
| // Setup. | |
| TimeDelta min_playout_delay = TimeDelta::Millis(50); | |
| TimeDelta max_playout_delay = TimeDelta::Millis(500); | |
| timing.set_playout_delay({min_playout_delay, max_playout_delay}); | |
| // On complete. | |
| timing.OnCompleteFrame({.rtp_timestamp = 3000, | |
| .time = clock.CurrentTime(), | |
| .last_spatial_layer = true}); | |
| clock.AdvanceTimeMilliseconds(1); | |
| // On decodable. | |
| Timestamp render_time = | |
| timing.RenderTime(/*next_temporal_unit_rtp=*/3000, clock.CurrentTime()); | |
| TimeDelta minimum_delay = TimeDelta::Millis(123); | |
| timing.SetMinimumDelay(minimum_delay); | |
| timing.UpdateCurrentDelay(render_time, clock.CurrentTime()); | |
| clock.AdvanceTimeMilliseconds(100); | |
| // On decoded. | |
| UpdateDecodeTimer(timing, clock, kDecodeTime); | |
| VCMTiming::VideoDelayTimings timings = timing.GetTimings(); | |
| EXPECT_GT(timings.num_decoded_frames, 0u); | |
| EXPECT_EQ(timings.minimum_delay, minimum_delay); | |
| EXPECT_EQ(timings.estimated_max_decode_time, kDecodeTime); | |
| EXPECT_EQ(timings.render_delay, TimeDelta::Millis(7)); | |
| EXPECT_EQ(timings.min_playout_delay, min_playout_delay); | |
| EXPECT_EQ(timings.max_playout_delay, max_playout_delay); | |
| EXPECT_EQ(timings.stats_target_delay, minimum_delay); | |
| EXPECT_EQ(timings.current_delay, minimum_delay); | |
| EXPECT_THAT(timings, HasConsistentVideoDelayTimings()); | |
| } | |
| TEST(VCMTimingTest, Reset) { | |
| SimulatedClock clock(Timestamp::Millis(33)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, /*render_delay=*/TimeDelta::Millis(7)); | |
| TimeDelta min_playout_delay = TimeDelta::Millis(50); | |
| TimeDelta max_playout_delay = TimeDelta::Millis(500); | |
| timing.set_playout_delay({min_playout_delay, max_playout_delay}); | |
| // On complete. | |
| timing.OnCompleteFrame({.rtp_timestamp = 3000, | |
| .time = clock.CurrentTime(), | |
| .last_spatial_layer = true}); | |
| // On decodable. | |
| Timestamp render_time = timing.RenderTime(3000, clock.CurrentTime()); | |
| timing.SetMinimumDelay(TimeDelta::Millis(123)); | |
| timing.UpdateCurrentDelay(render_time, clock.CurrentTime()); | |
| // On decoded. | |
| UpdateDecodeTimer(timing, clock, kDecodeTime); | |
| timing.Reset(); | |
| VCMTiming::VideoDelayTimings timings = timing.GetTimings(); | |
| EXPECT_GT(timings.num_decoded_frames, 0u); | |
| EXPECT_EQ(timings.minimum_delay, TimeDelta::Zero()); | |
| EXPECT_EQ(timings.estimated_max_decode_time, TimeDelta::Zero()); | |
| EXPECT_EQ(timings.render_delay, | |
| VCMTiming::VideoDelayTimings::kDefaultRenderDelay); | |
| EXPECT_EQ(timings.min_playout_delay, TimeDelta::Zero()); | |
| EXPECT_EQ(timings.max_playout_delay, max_playout_delay); | |
| EXPECT_EQ(timings.stats_target_delay, TimeDelta::Zero()); | |
| EXPECT_EQ(timings.current_delay, TimeDelta::Zero()); | |
| EXPECT_THAT(timings, HasConsistentVideoDelayTimings()); | |
| } | |
| TEST(VCMTimingTest, GetTimingsBeforeAndAfterValidRtpTimestamp) { | |
| SimulatedClock clock(33); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| // Setup. | |
| TimeDelta min_playout_delay = TimeDelta::Millis(50); | |
| timing.set_playout_delay({min_playout_delay, TimeDelta::Millis(500)}); | |
| // On decodable frames before valid rtp timestamp. | |
| constexpr int decodable_frame_cnt = 10; | |
| constexpr uint32_t any_time_elapsed = 17; | |
| constexpr uint32_t rtp_ts_base = 3000; | |
| constexpr uint32_t rtp_ts_delta_10fps = 9000; | |
| constexpr uint32_t frame_ts_delta_10fps = 100; | |
| uint32_t rtp_ts = rtp_ts_base; | |
| for (int i = 0; i < decodable_frame_cnt; i++) { | |
| clock.AdvanceTimeMilliseconds(any_time_elapsed); | |
| rtp_ts += rtp_ts_delta_10fps; | |
| Timestamp render_time = timing.RenderTime(rtp_ts, clock.CurrentTime()); | |
| // Render time should be CurrentTime, because timing.OnCompleteFrame | |
| // has not been called yet. | |
| EXPECT_EQ(render_time, clock.CurrentTime()); | |
| } | |
| // On frame complete, which one not 'metadata.delayed_by_retransmission' | |
| Timestamp valid_frame_ts = clock.CurrentTime(); | |
| timing.OnCompleteFrame({.rtp_timestamp = rtp_ts, | |
| .time = valid_frame_ts, | |
| .last_spatial_layer = true}); | |
| clock.AdvanceTimeMilliseconds(any_time_elapsed); | |
| rtp_ts += rtp_ts_delta_10fps; | |
| Timestamp render_time = timing.RenderTime(rtp_ts, clock.CurrentTime()); | |
| // Render time should be relative to the latest valid frame timestamp. | |
| EXPECT_EQ(render_time, valid_frame_ts + | |
| TimeDelta::Millis(frame_ts_delta_10fps) + | |
| min_playout_delay); | |
| } | |
| TEST(VCMTimingTest, OnDecodableTemporalUnitUpdatesMinimumDelay) { | |
| SimulatedClock clock(Timestamp::Millis(0)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| timing.OnDecodableTemporalUnit({.rtp_timestamp = 0, | |
| .size = DataSize::Bytes(789), | |
| .time = clock.CurrentTime(), | |
| .was_retransmitted = false}); | |
| EXPECT_GT(timing.GetTimings().minimum_delay, TimeDelta::Zero()); | |
| } | |
| TEST(VCMTimingTest, | |
| OnDecodableTemporalUnitDoesNotUpdateMinimumDelayOnRetransmission) { | |
| SimulatedClock clock(Timestamp::Millis(0)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| timing.OnDecodableTemporalUnit({.rtp_timestamp = 0, | |
| .size = DataSize::Bytes(789), | |
| .time = clock.CurrentTime(), | |
| .was_retransmitted = true}); | |
| EXPECT_EQ(timing.GetTimings().minimum_delay, TimeDelta::Zero()); | |
| } | |
| TEST(VCMTimingTest, UsesDefaultVideoJitterTimingWhenNotProvided) { | |
| SimulatedClock clock(Timestamp::Millis(0)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay, /*video_jitter_timing=*/nullptr); | |
| timing.OnDecodableTemporalUnit({.rtp_timestamp = 0, | |
| .size = DataSize::Bytes(789), | |
| .time = clock.CurrentTime(), | |
| .was_retransmitted = false}); | |
| EXPECT_GT(timing.GetTimings().minimum_delay, TimeDelta::Zero()); | |
| } | |
| TEST(VCMTimingTest, OnDecodableTemporalUnitSetsMinimumDelay) { | |
| SimulatedClock clock(Timestamp::Millis(0)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| auto mock_jitter_timing = std::make_unique<MockVideoJitterTiming>(); | |
| EXPECT_CALL(*mock_jitter_timing, OnDecodableTemporalUnit(_)) | |
| .WillOnce(Return(TimeDelta::Millis(123))); | |
| VCMTiming timing(env, kRenderDelay, std::move(mock_jitter_timing)); | |
| timing.OnDecodableTemporalUnit({.rtp_timestamp = 0, | |
| .size = DataSize::Bytes(789), | |
| .time = clock.CurrentTime(), | |
| .was_retransmitted = false}); | |
| EXPECT_EQ(timing.GetTimings().minimum_delay, TimeDelta::Millis(123)); | |
| } | |
| TEST(VCMTimingTest, OnContinuousTemporalUnitsSetsMinimumDelay) { | |
| SimulatedClock clock(Timestamp::Millis(0)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| auto mock_jitter_timing = std::make_unique<MockVideoJitterTiming>(); | |
| EXPECT_CALL( | |
| *mock_jitter_timing, | |
| OnContinuousTemporalUnits(ElementsAre(234u), Eq(clock.CurrentTime()))) | |
| .WillOnce(Return(TimeDelta::Millis(123))); | |
| VCMTiming timing(env, kRenderDelay, std::move(mock_jitter_timing)); | |
| timing.OnContinuousTemporalUnits({{234}}, clock.CurrentTime()); | |
| EXPECT_EQ(timing.GetTimings().minimum_delay, TimeDelta::Millis(123)); | |
| } | |
| TEST(VCMTimingTest, RenderTimeAccountsForCurrentDelay) { | |
| SimulatedClock clock(Timestamp::Millis(88)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| timing.set_playout_delay({TimeDelta::Millis(100), TimeDelta::Millis(200)}); | |
| timing.OnCompleteFrame({.rtp_timestamp = 0, | |
| .time = clock.CurrentTime(), | |
| .last_spatial_layer = true}); | |
| // Current delay is initialized to minimum delay. | |
| timing.SetMinimumDelay(TimeDelta::Millis(123)); | |
| EXPECT_EQ(timing.RenderTime(/*rtp_timestamp=*/0, kUnusedTimestamp), | |
| clock.CurrentTime() + TimeDelta::Millis(123)); | |
| } | |
| TEST(VCMTimingTest, RenderTimeRespectsMinPlayoutDelay) { | |
| SimulatedClock clock(Timestamp::Millis(88)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| timing.set_playout_delay({TimeDelta::Millis(100), TimeDelta::Millis(200)}); | |
| timing.OnCompleteFrame({.rtp_timestamp = 0, | |
| .time = clock.CurrentTime(), | |
| .last_spatial_layer = true}); | |
| // Current delay is initialized to minimum delay. | |
| timing.SetMinimumDelay(TimeDelta::Millis(90)); | |
| EXPECT_EQ(timing.RenderTime(/*rtp_timestamp=*/0, kUnusedTimestamp), | |
| clock.CurrentTime() + TimeDelta::Millis(100)); | |
| } | |
| TEST(VCMTimingTest, RenderTimeRespectsMaxPlayoutDelay) { | |
| SimulatedClock clock(Timestamp::Millis(88)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| timing.set_playout_delay({TimeDelta::Millis(100), TimeDelta::Millis(200)}); | |
| timing.OnCompleteFrame({.rtp_timestamp = 0, | |
| .time = clock.CurrentTime(), | |
| .last_spatial_layer = true}); | |
| // Current delay is initialized to minimum delay. | |
| timing.SetMinimumDelay(TimeDelta::Millis(210)); | |
| EXPECT_EQ(timing.RenderTime(/*rtp_timestamp=*/0, kUnusedTimestamp), | |
| clock.CurrentTime() + TimeDelta::Millis(200)); | |
| } | |
| TEST(VCMTimingTest, ResetClearsTimestampExtrapolator) { | |
| SimulatedClock clock(Timestamp::Millis(55)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| // Current delay is initialized to minimum delay. | |
| timing.SetMinimumDelay(TimeDelta::Millis(123)); | |
| timing.OnCompleteFrame({.rtp_timestamp = 0, | |
| .time = clock.CurrentTime(), | |
| .last_spatial_layer = true}); | |
| EXPECT_EQ(timing.RenderTime(/*rtp_timestamp=*/0, kUnusedTimestamp), | |
| clock.CurrentTime() + TimeDelta::Millis(123)); | |
| timing.Reset(); | |
| // Extrapolator should be reset and current time returned. | |
| timing.SetMinimumDelay(TimeDelta::Millis(123)); | |
| EXPECT_EQ(timing.RenderTime(/*rtp_timestamp=*/0, Timestamp::Millis(789)), | |
| Timestamp::Millis(789)); | |
| } | |
| TEST(VCMTimingTest, IncreasesCurrentDelayWhenFrameIsLate) { | |
| SimulatedClock clock(0); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| timing.SetMinimumDelay(kMinimumDelay); | |
| // Current delay is initialized to minimum delay. | |
| EXPECT_EQ(timing.GetTimings().current_delay, kMinimumDelay); | |
| EXPECT_EQ(timing.TargetVideoDelay(), kMinimumDelay + kRenderDelay); | |
| const TimeDelta kFrameDelay = TimeDelta::Millis(4); | |
| // Current delay should be increased to get closer to target delay. | |
| Timestamp render_time = clock.CurrentTime() + kRenderDelay; | |
| Timestamp actual_decode_time = clock.CurrentTime() + kFrameDelay; | |
| timing.UpdateCurrentDelay(render_time, actual_decode_time); | |
| EXPECT_EQ(timing.GetTimings().current_delay, kMinimumDelay + kFrameDelay); | |
| } | |
| TEST(VCMTimingTest, CapsCurrentDelayIncreaseToTarget) { | |
| SimulatedClock clock(0); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| timing.SetMinimumDelay(kMinimumDelay); | |
| // Current delay is initialized to minimum delay. | |
| EXPECT_EQ(timing.GetTimings().current_delay, kMinimumDelay); | |
| EXPECT_EQ(timing.TargetVideoDelay(), kMinimumDelay + kRenderDelay); | |
| const TimeDelta kFrameDelay = TimeDelta::Millis(588); | |
| // Current delay should be increased but not exceed target delay. | |
| Timestamp render_time = clock.CurrentTime() + kRenderDelay; | |
| Timestamp actual_decode_time = clock.CurrentTime() + kFrameDelay; | |
| timing.UpdateCurrentDelay(render_time, actual_decode_time); | |
| EXPECT_EQ(timing.GetTimings().current_delay, kMinimumDelay + kRenderDelay); | |
| } | |
| TEST(VCMTimingTest, KeepsCurrentDelayWhenFrameIsEarly) { | |
| SimulatedClock clock(0); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| timing.SetMinimumDelay(kMinimumDelay); | |
| // Current delay is initialized to minimum delay. | |
| EXPECT_EQ(timing.GetTimings().current_delay, kMinimumDelay); | |
| EXPECT_EQ(timing.TargetVideoDelay(), kMinimumDelay + kRenderDelay); | |
| // Frame is early. | |
| // Delay should remain unchanged. | |
| Timestamp render_time = clock.CurrentTime() + kRenderDelay * 2; | |
| Timestamp actual_decode_time = clock.CurrentTime(); | |
| timing.UpdateCurrentDelay(render_time, actual_decode_time); | |
| EXPECT_EQ(timing.GetTimings().current_delay, kMinimumDelay); | |
| } | |
| TEST(VCMTimingTest, IncreasesCurrentDelayWhenFrameIsLateWithDecodeTime) { | |
| SimulatedClock clock(0); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| timing.SetMinimumDelay(kMinimumDelay); | |
| UpdateDecodeTimer(timing, clock, kDecodeTime); | |
| // Current delay is initialized to minimum delay. | |
| EXPECT_EQ(timing.GetTimings().current_delay, kMinimumDelay); | |
| EXPECT_EQ(timing.TargetVideoDelay(), | |
| kMinimumDelay + kDecodeTime + kRenderDelay); | |
| const TimeDelta kFrameDelay = TimeDelta::Millis(4); | |
| // Current delay should be increased to get closer to target delay. | |
| Timestamp render_time = clock.CurrentTime() + kDecodeTime + kRenderDelay; | |
| Timestamp actual_decode_time = clock.CurrentTime() + kFrameDelay; | |
| timing.UpdateCurrentDelay(render_time, actual_decode_time); | |
| EXPECT_EQ(timing.GetTimings().current_delay, kMinimumDelay + kFrameDelay); | |
| } | |
| TEST(VCMTimingTest, DecreasesCurrentDelayToTarget) { | |
| SimulatedClock clock(0); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| timing.SetMinimumDelay(kMinimumDelay); | |
| // Current delay should be increased to target for late frame. | |
| timing.UpdateCurrentDelay(clock.CurrentTime(), | |
| clock.CurrentTime() + TimeDelta::Millis(588)); | |
| EXPECT_EQ(timing.GetTimings().current_delay, timing.TargetVideoDelay()); | |
| // Reduce minimum delay. | |
| timing.SetMinimumDelay(kMinimumDelay / 2); | |
| EXPECT_EQ(timing.TargetVideoDelay(), kMinimumDelay / 2 + kRenderDelay); | |
| // Current delay should be decreased to new target for frame on-time. | |
| timing.UpdateCurrentDelay(clock.CurrentTime() + kRenderDelay, | |
| clock.CurrentTime()); | |
| EXPECT_EQ(timing.GetTimings().current_delay, | |
| kMinimumDelay / 2 + kRenderDelay); | |
| } | |
| TEST(VCMTimingTest, MinPlayoutDelayUpdatesTargetDelay) { | |
| SimulatedClock clock(0); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| VCMTiming timing(env, kRenderDelay); | |
| timing.SetMinimumDelay(kMinimumDelay); | |
| const TimeDelta kMinPlayout = | |
| kMinimumDelay + kRenderDelay + TimeDelta::Millis(50); | |
| timing.set_min_playout_delay(kMinPlayout); | |
| EXPECT_EQ(timing.TargetVideoDelay(), kMinPlayout); | |
| } | |
| TEST(VCMTimingTest, DelegatesToVideoJitterTiming) { | |
| SimulatedClock clock(Timestamp::Millis(11)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| auto video_jitter_timing = std::make_unique<MockVideoJitterTiming>(); | |
| EXPECT_CALL(*video_jitter_timing, | |
| OnNetworkUpdate(FieldsAre(TimeDelta::Millis(101)))); | |
| EXPECT_CALL(*video_jitter_timing, Reset()); | |
| EXPECT_CALL( | |
| *video_jitter_timing, | |
| OnContinuousTemporalUnits(ElementsAre(44, 55), Eq(clock.CurrentTime()))); | |
| EXPECT_CALL(*video_jitter_timing, | |
| OnCompleteFrame(Field( | |
| &VideoJitterTimingInterface::FrameInfo::rtp_timestamp, 123))); | |
| VCMTiming timing(env, kRenderDelay, std::move(video_jitter_timing)); | |
| timing.OnNetworkUpdate({.rtt = TimeDelta::Millis(101)}); | |
| timing.Reset(); | |
| timing.OnContinuousTemporalUnits({{44, 55}}, clock.CurrentTime()); | |
| timing.OnCompleteFrame({.rtp_timestamp = 123}); | |
| } | |
| TEST(VCMTimingTest, RenderTimeReturnsEstimatedLocalTimeForRtpTimestamp) { | |
| SimulatedClock clock(Timestamp::Millis(0)); | |
| Environment env = CreateTestEnvironment({.time = &clock}); | |
| auto video_jitter_timing = std::make_unique<MockVideoJitterTiming>(); | |
| EXPECT_CALL(*video_jitter_timing, LocalTime(22)) | |
| .WillOnce(::testing::Return(Timestamp::Millis(1234))); | |
| VCMTiming timing(env, kRenderDelay, std::move(video_jitter_timing)); | |
| EXPECT_EQ(timing.RenderTime(/*rtp_timestamp=*/22, clock.CurrentTime()), | |
| Timestamp::Millis(1234)); | |
| } | |
| } // namespace | |
| } // namespace webrtc |