| /* | |
| * Copyright (c) 2022 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 "video/video_stream_buffer_controller.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <limits> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <tuple> | |
| #include <utility> | |
| #include <variant> | |
| #include <vector> | |
| #include "api/field_trials.h" | |
| #include "api/metronome/test/fake_metronome.h" | |
| #include "api/units/frequency.h" | |
| #include "api/units/time_delta.h" | |
| #include "api/units/timestamp.h" | |
| #include "api/video/encoded_frame.h" | |
| #include "api/video/video_content_type.h" | |
| #include "api/video/video_timing.h" | |
| #include "modules/video_coding/timing/timing.h" | |
| #include "rtc_base/checks.h" | |
| #include "system_wrappers/include/clock.h" | |
| #include "test/create_test_field_trials.h" | |
| #include "test/fake_encoded_frame.h" | |
| #include "test/gmock.h" | |
| #include "test/gtest.h" | |
| #include "test/time_controller/simulated_time_controller.h" | |
| #include "video/decode_synchronizer.h" | |
| #include "video/task_queue_frame_decode_scheduler.h" | |
| using ::testing::_; | |
| using ::testing::AllOf; | |
| using ::testing::Contains; | |
| using ::testing::Each; | |
| using ::testing::Eq; | |
| using ::testing::IsEmpty; | |
| using ::testing::Matches; | |
| using ::testing::Ne; | |
| using ::testing::Not; | |
| using ::testing::Optional; | |
| using ::testing::Pointee; | |
| using ::testing::SizeIs; | |
| using ::testing::VariantWith; | |
| namespace webrtc { | |
| namespace { | |
| constexpr size_t kFrameSize = 10; | |
| constexpr uint32_t kFps30Rtp = 90000 / 30; | |
| constexpr TimeDelta kFps30Delay = 1 / Frequency::Hertz(30); | |
| constexpr Timestamp kClockStart = Timestamp::Millis(1000); | |
| auto TimedOut() { | |
| return Optional(VariantWith<TimeDelta>(_)); | |
| } | |
| auto Frame(testing::Matcher<EncodedFrame> m) { | |
| return Optional(VariantWith<std::unique_ptr<EncodedFrame>>(Pointee(m))); | |
| } | |
| std::unique_ptr<test::FakeEncodedFrame> WithReceiveTimeFromRtpTimestamp( | |
| std::unique_ptr<test::FakeEncodedFrame> frame) { | |
| if (frame->RtpTimestamp() == 0) { | |
| frame->SetReceivedTime(kClockStart.ms()); | |
| } else { | |
| frame->SetReceivedTime( | |
| TimeDelta::Seconds(frame->RtpTimestamp() / 90000.0).ms() + | |
| kClockStart.ms()); | |
| } | |
| return frame; | |
| } | |
| class VCMTimingTest : public VCMTiming { | |
| public: | |
| using VCMTiming::VCMTiming; | |
| void IncomingTimestamp(uint32_t rtp_timestamp, | |
| Timestamp last_packet_time) override { | |
| IncomingTimestampMocked(rtp_timestamp, last_packet_time); | |
| VCMTiming::IncomingTimestamp(rtp_timestamp, last_packet_time); | |
| } | |
| MOCK_METHOD(void, | |
| IncomingTimestampMocked, | |
| (uint32_t rtp_timestamp, Timestamp last_packet_time), | |
| ()); | |
| }; | |
| class VideoStreamBufferControllerStatsObserverMock | |
| : public VideoStreamBufferControllerStatsObserver { | |
| public: | |
| MOCK_METHOD(void, | |
| OnCompleteFrame, | |
| (bool is_keyframe, | |
| size_t size_bytes, | |
| VideoContentType content_type), | |
| (override)); | |
| MOCK_METHOD(void, OnDroppedFrames, (uint32_t num_dropped), (override)); | |
| MOCK_METHOD(void, | |
| OnDecodableFrame, | |
| (TimeDelta jitter_buffer_delay, | |
| TimeDelta target_delay, | |
| TimeDelta minimum_delay), | |
| (override)); | |
| MOCK_METHOD(void, | |
| OnFrameBufferTimingsUpdated, | |
| (int estimated_max_decode_time_ms, | |
| int current_delay_ms, | |
| int target_delay_ms, | |
| int jitter_delay_ms, | |
| int min_playout_delay_ms, | |
| int render_delay_ms), | |
| (override)); | |
| MOCK_METHOD(void, | |
| OnTimingFrameInfoUpdated, | |
| (const TimingFrameInfo& info), | |
| (override)); | |
| }; | |
| } // namespace | |
| constexpr auto kMaxWaitForKeyframe = TimeDelta::Millis(500); | |
| constexpr auto kMaxWaitForFrame = TimeDelta::Millis(1500); | |
| class VideoStreamBufferControllerFixture | |
| : public ::testing::WithParamInterface<std::tuple<bool, std::string>>, | |
| public FrameSchedulingReceiver { | |
| public: | |
| VideoStreamBufferControllerFixture() | |
| : sync_decoding_(std::get<0>(GetParam())), | |
| field_trials_(CreateTestFieldTrials(std::get<1>(GetParam()))), | |
| time_controller_(kClockStart), | |
| clock_(time_controller_.GetClock()), | |
| fake_metronome_(TimeDelta::Millis(16)), | |
| decode_sync_(clock_, | |
| &fake_metronome_, | |
| time_controller_.GetMainThread()), | |
| timing_(clock_, field_trials_), | |
| buffer_(std::make_unique<VideoStreamBufferController>( | |
| clock_, | |
| time_controller_.GetMainThread(), | |
| &timing_, | |
| &stats_callback_, | |
| this, | |
| kMaxWaitForKeyframe, | |
| kMaxWaitForFrame, | |
| sync_decoding_ ? decode_sync_.CreateSynchronizedFrameScheduler() | |
| : std::make_unique<TaskQueueFrameDecodeScheduler>( | |
| clock_, | |
| time_controller_.GetMainThread()), | |
| field_trials_)) { | |
| // Avoid starting with negative render times. | |
| timing_.set_min_playout_delay(TimeDelta::Millis(10)); | |
| ON_CALL(stats_callback_, OnDroppedFrames) | |
| .WillByDefault( | |
| [this](auto num_dropped) { dropped_frames_ += num_dropped; }); | |
| } | |
| ~VideoStreamBufferControllerFixture() override { | |
| if (buffer_) { | |
| buffer_->Stop(); | |
| } | |
| time_controller_.AdvanceTime(TimeDelta::Zero()); | |
| } | |
| void OnEncodedFrame(std::unique_ptr<EncodedFrame> frame) override { | |
| RTC_DCHECK(frame); | |
| SetWaitResult(std::move(frame)); | |
| } | |
| void OnDecodableFrameTimeout(TimeDelta wait_time) override { | |
| SetWaitResult(wait_time); | |
| } | |
| using WaitResult = | |
| std::variant<std::unique_ptr<EncodedFrame>, TimeDelta /*wait_time*/>; | |
| std::optional<WaitResult> WaitForFrameOrTimeout(TimeDelta wait) { | |
| if (wait_result_) { | |
| return std::move(wait_result_); | |
| } | |
| time_controller_.AdvanceTime(TimeDelta::Zero()); | |
| if (wait_result_) { | |
| return std::move(wait_result_); | |
| } | |
| Timestamp now = clock_->CurrentTime(); | |
| // TODO(bugs.webrtc.org/13756): Remove this when Thread uses uses | |
| // Timestamp instead of an integer milliseconds. This extra wait is needed | |
| // for some tests that use the metronome. This is due to rounding | |
| // milliseconds, affecting the precision of simulated time controller uses | |
| // when posting tasks from threads. | |
| TimeDelta potential_extra_wait = | |
| Timestamp::Millis((now + wait).ms()) - (now + wait); | |
| time_controller_.AdvanceTime(wait); | |
| if (potential_extra_wait > TimeDelta::Zero()) { | |
| time_controller_.AdvanceTime(potential_extra_wait); | |
| } | |
| return std::move(wait_result_); | |
| } | |
| void StartNextDecode() { | |
| ResetLastResult(); | |
| buffer_->StartNextDecode(false); | |
| } | |
| void StartNextDecodeForceKeyframe() { | |
| ResetLastResult(); | |
| buffer_->StartNextDecode(true); | |
| } | |
| void ResetLastResult() { wait_result_.reset(); } | |
| int dropped_frames() const { return dropped_frames_; } | |
| protected: | |
| const bool sync_decoding_; | |
| FieldTrials field_trials_; | |
| GlobalSimulatedTimeController time_controller_; | |
| Clock* const clock_; | |
| test::FakeMetronome fake_metronome_; | |
| DecodeSynchronizer decode_sync_; | |
| ::testing::NiceMock<VCMTimingTest> timing_; | |
| ::testing::NiceMock<VideoStreamBufferControllerStatsObserverMock> | |
| stats_callback_; | |
| std::unique_ptr<VideoStreamBufferController> buffer_; | |
| private: | |
| void SetWaitResult(WaitResult result) { | |
| RTC_DCHECK(!wait_result_); | |
| if (std::holds_alternative<std::unique_ptr<EncodedFrame>>(result)) { | |
| RTC_DCHECK(std::get<std::unique_ptr<EncodedFrame>>(result)); | |
| } | |
| wait_result_.emplace(std::move(result)); | |
| } | |
| uint32_t dropped_frames_ = 0; | |
| std::optional<WaitResult> wait_result_; | |
| }; | |
| class VideoStreamBufferControllerTest | |
| : public ::testing::Test, | |
| public VideoStreamBufferControllerFixture {}; | |
| TEST_P(VideoStreamBufferControllerTest, | |
| InitialTimeoutAfterKeyframeTimeoutPeriod) { | |
| StartNextDecodeForceKeyframe(); | |
| // No frame inserted. Timeout expected. | |
| EXPECT_THAT(WaitForFrameOrTimeout(kMaxWaitForKeyframe), TimedOut()); | |
| // No new timeout set since receiver has not started new decode. | |
| ResetLastResult(); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kMaxWaitForKeyframe), Eq(std::nullopt)); | |
| // Now that receiver has asked for new frame, a new timeout can occur. | |
| StartNextDecodeForceKeyframe(); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kMaxWaitForKeyframe), TimedOut()); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, KeyFramesAreScheduled) { | |
| StartNextDecodeForceKeyframe(); | |
| time_controller_.AdvanceTime(TimeDelta::Millis(50)); | |
| auto frame = test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build(); | |
| buffer_->InsertFrame(std::move(frame)); | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0))); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, | |
| DeltaFrameTimeoutAfterKeyframeExtracted) { | |
| StartNextDecodeForceKeyframe(); | |
| time_controller_.AdvanceTime(TimeDelta::Millis(50)); | |
| auto frame = test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build(); | |
| buffer_->InsertFrame(std::move(frame)); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kMaxWaitForKeyframe), | |
| Frame(test::WithId(0))); | |
| StartNextDecode(); | |
| time_controller_.AdvanceTime(TimeDelta::Millis(50)); | |
| // Timeouts should now happen at the normal frequency. | |
| const int expected_timeouts = 5; | |
| for (int i = 0; i < expected_timeouts; ++i) { | |
| EXPECT_THAT(WaitForFrameOrTimeout(kMaxWaitForFrame), TimedOut()); | |
| StartNextDecode(); | |
| } | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, DependantFramesAreScheduled) { | |
| StartNextDecodeForceKeyframe(); | |
| buffer_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build()); | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0))); | |
| StartNextDecode(); | |
| time_controller_.AdvanceTime(kFps30Delay); | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(1) | |
| .Time(kFps30Rtp) | |
| .AsLast() | |
| .Refs({0}) | |
| .Build()); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(1))); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, SpatialLayersAreScheduled) { | |
| StartNextDecodeForceKeyframe(); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(0).SpatialLayer(0).Time(0).Build())); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(1).SpatialLayer(1).Time(0).Build())); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(2).SpatialLayer(2).Time(0).AsLast().Build())); | |
| EXPECT_THAT( | |
| WaitForFrameOrTimeout(TimeDelta::Zero()), | |
| Frame(AllOf(test::WithId(0), test::FrameWithSize(3 * kFrameSize)))); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(3).Time(kFps30Rtp).SpatialLayer(0).Build())); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(4).Time(kFps30Rtp).SpatialLayer(1).Build())); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp(test::FakeFrameBuilder() | |
| .Id(5) | |
| .Time(kFps30Rtp) | |
| .SpatialLayer(2) | |
| .AsLast() | |
| .Build())); | |
| StartNextDecode(); | |
| EXPECT_THAT( | |
| WaitForFrameOrTimeout(kFps30Delay * 10), | |
| Frame(AllOf(test::WithId(3), test::FrameWithSize(3 * kFrameSize)))); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, | |
| OutstandingFrameTasksAreCancelledAfterDeletion) { | |
| StartNextDecodeForceKeyframe(); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build())); | |
| // Get keyframe. Delta frame should now be scheduled. | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0))); | |
| StartNextDecode(); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp(test::FakeFrameBuilder() | |
| .Id(1) | |
| .Time(kFps30Rtp) | |
| .AsLast() | |
| .Refs({0}) | |
| .Build())); | |
| buffer_->Stop(); | |
| // Wait for 2x max wait time. Since we stopped, this should cause no timeouts | |
| // or frame-ready callbacks. | |
| EXPECT_THAT(WaitForFrameOrTimeout(kMaxWaitForFrame * 2), Eq(std::nullopt)); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, FramesWaitForDecoderToComplete) { | |
| StartNextDecodeForceKeyframe(); | |
| // Start with a keyframe. | |
| buffer_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build()); | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0))); | |
| ResetLastResult(); | |
| // Insert a delta frame. | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(1) | |
| .Time(kFps30Rtp) | |
| .AsLast() | |
| .Refs({0}) | |
| .Build()); | |
| // Advancing time should not result in a frame since the scheduler has not | |
| // been signalled that we are ready. | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Eq(std::nullopt)); | |
| // Signal ready. | |
| StartNextDecode(); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(1))); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, LateFrameDropped) { | |
| StartNextDecodeForceKeyframe(); | |
| // F1 | |
| // / | |
| // F0 --> F2 | |
| buffer_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build()); | |
| // Start with a keyframe. | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0))); | |
| StartNextDecode(); | |
| // Simulate late F1 which arrives after F2. | |
| time_controller_.AdvanceTime(kFps30Delay * 2); | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(2) | |
| .Time(2 * kFps30Rtp) | |
| .AsLast() | |
| .Refs({0}) | |
| .Build()); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(2))); | |
| StartNextDecode(); | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(1) | |
| .Time(1 * kFps30Rtp) | |
| .AsLast() | |
| .Refs({0}) | |
| .Build()); | |
| // Confirm frame 1 is never scheduled by timing out. | |
| EXPECT_THAT(WaitForFrameOrTimeout(kMaxWaitForFrame), TimedOut()); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, FramesFastForwardOnSystemHalt) { | |
| StartNextDecodeForceKeyframe(); | |
| // F1 | |
| // / | |
| // F0 --> F2 | |
| buffer_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build()); | |
| // Start with a keyframe. | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0))); | |
| time_controller_.AdvanceTime(kFps30Delay); | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(1) | |
| .Time(kFps30Rtp) | |
| .AsLast() | |
| .Refs({0}) | |
| .Build()); | |
| time_controller_.AdvanceTime(kFps30Delay); | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(2) | |
| .Time(2 * kFps30Rtp) | |
| .AsLast() | |
| .Refs({0}) | |
| .Build()); | |
| // Halting time should result in F1 being skipped. | |
| time_controller_.AdvanceTime(kFps30Delay * 2); | |
| StartNextDecode(); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(2))); | |
| EXPECT_EQ(dropped_frames(), 1); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, ForceKeyFrame) { | |
| StartNextDecodeForceKeyframe(); | |
| // Initial keyframe. | |
| buffer_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build()); | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0))); | |
| StartNextDecodeForceKeyframe(); | |
| // F2 is the next keyframe, and should be extracted since a keyframe was | |
| // forced. | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(1) | |
| .Time(kFps30Rtp) | |
| .AsLast() | |
| .Refs({0}) | |
| .Build()); | |
| buffer_->InsertFrame( | |
| test::FakeFrameBuilder().Id(2).Time(kFps30Rtp * 2).AsLast().Build()); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay * 3), Frame(test::WithId(2))); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, SlowDecoderDropsTemporalLayers) { | |
| StartNextDecodeForceKeyframe(); | |
| // 2 temporal layers, at 15fps per layer to make 30fps total. | |
| // Decoder is slower than 30fps, so last_frame() will be skipped. | |
| // F1 --> F3 --> F5 | |
| // / / / | |
| // F0 --> F2 --> F4 | |
| buffer_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build()); | |
| // Keyframe received. | |
| // Don't start next decode until slow delay. | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0))); | |
| time_controller_.AdvanceTime(kFps30Delay); | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(1) | |
| .Time(1 * kFps30Rtp) | |
| .Refs({0}) | |
| .AsLast() | |
| .Build()); | |
| time_controller_.AdvanceTime(kFps30Delay); | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(2) | |
| .Time(2 * kFps30Rtp) | |
| .Refs({0}) | |
| .AsLast() | |
| .Build()); | |
| // Simulate decode taking 3x FPS rate. | |
| time_controller_.AdvanceTime(kFps30Delay * 1.5); | |
| StartNextDecode(); | |
| // F2 is the best frame since decoding was so slow that F1 is too old. | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay * 2), Frame(test::WithId(2))); | |
| EXPECT_EQ(dropped_frames(), 1); | |
| time_controller_.AdvanceTime(kFps30Delay / 2); | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(3) | |
| .Time(3 * kFps30Rtp) | |
| .Refs({1, 2}) | |
| .AsLast() | |
| .Build()); | |
| time_controller_.AdvanceTime(kFps30Delay / 2); | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(4) | |
| .Time(4 * kFps30Rtp) | |
| .Refs({2}) | |
| .AsLast() | |
| .Build()); | |
| time_controller_.AdvanceTime(kFps30Delay / 2); | |
| // F4 is the best frame since decoding was so slow that F1 is too old. | |
| time_controller_.AdvanceTime(kFps30Delay); | |
| StartNextDecode(); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(4))); | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(5) | |
| .Time(5 * kFps30Rtp) | |
| .Refs({3, 4}) | |
| .AsLast() | |
| .Build()); | |
| time_controller_.AdvanceTime(kFps30Delay / 2); | |
| // F5 is not decodable since F4 was decoded, so a timeout is expected. | |
| time_controller_.AdvanceTime(TimeDelta::Millis(10)); | |
| StartNextDecode(); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kMaxWaitForFrame), TimedOut()); | |
| // TODO(bugs.webrtc.org/13343): This should be 2 dropped frames since frames 1 | |
| // and 3 were dropped. However, frame_buffer2 does not mark frame 3 as dropped | |
| // which is a bug. Uncomment below when that is fixed for frame_buffer2 is | |
| // deleted. | |
| // EXPECT_EQ(dropped_frames(), 2); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, | |
| NewFrameInsertedWhileWaitingToReleaseFrame) { | |
| StartNextDecodeForceKeyframe(); | |
| // Initial keyframe. | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build())); | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0))); | |
| time_controller_.AdvanceTime(kFps30Delay / 2); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp(test::FakeFrameBuilder() | |
| .Id(1) | |
| .Time(kFps30Rtp) | |
| .Refs({0}) | |
| .AsLast() | |
| .Build())); | |
| StartNextDecode(); | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Eq(std::nullopt)); | |
| // Scheduler is waiting to deliver Frame 1 now. Insert Frame 2. Frame 1 should | |
| // be delivered still. | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp(test::FakeFrameBuilder() | |
| .Id(2) | |
| .Time(kFps30Rtp * 2) | |
| .Refs({0}) | |
| .AsLast() | |
| .Build())); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(1))); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, SameFrameNotScheduledTwice) { | |
| // A frame could be scheduled twice if last_frame() arrive out-of-order but | |
| // the older frame is old enough to be fast forwarded. | |
| // | |
| // 1. F2 arrives and is scheduled. | |
| // 2. F3 arrives, but scheduling will not change since F2 is next. | |
| // 3. F1 arrives late and scheduling is checked since it is before F2. F1 | |
| // fast-forwarded since it is older. | |
| // | |
| // F2 is the best frame, but should only be scheduled once, followed by F3. | |
| StartNextDecodeForceKeyframe(); | |
| // First keyframe. | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build())); | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Millis(15)), | |
| Frame(test::WithId(0))); | |
| StartNextDecode(); | |
| // F2 arrives and is scheduled. | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(2).Time(2 * kFps30Rtp).AsLast().Build())); | |
| // F3 arrives before F2 is extracted. | |
| time_controller_.AdvanceTime(kFps30Delay); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(3).Time(3 * kFps30Rtp).AsLast().Build())); | |
| // F1 arrives and is fast-forwarded since it is too late. | |
| // F2 is already scheduled and should not be rescheduled. | |
| time_controller_.AdvanceTime(kFps30Delay / 2); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(1).Time(1 * kFps30Rtp).AsLast().Build())); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(2))); | |
| StartNextDecode(); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(3))); | |
| StartNextDecode(); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kMaxWaitForFrame), TimedOut()); | |
| EXPECT_EQ(dropped_frames(), 1); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, TestStatsCallback) { | |
| EXPECT_CALL(stats_callback_, | |
| OnCompleteFrame(true, kFrameSize, VideoContentType::UNSPECIFIED)); | |
| EXPECT_CALL(stats_callback_, OnDecodableFrame); | |
| EXPECT_CALL(stats_callback_, OnFrameBufferTimingsUpdated); | |
| // Fake timing having received decoded frame. | |
| timing_.StopDecodeTimer(TimeDelta::Millis(1), clock_->CurrentTime()); | |
| StartNextDecodeForceKeyframe(); | |
| buffer_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build()); | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0))); | |
| // Flush stats posted on the decode queue. | |
| time_controller_.AdvanceTime(TimeDelta::Zero()); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, | |
| FrameCompleteCalledOnceForDuplicateFrame) { | |
| EXPECT_CALL(stats_callback_, | |
| OnCompleteFrame(true, kFrameSize, VideoContentType::UNSPECIFIED)) | |
| .Times(1); | |
| StartNextDecodeForceKeyframe(); | |
| buffer_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build()); | |
| buffer_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build()); | |
| // Flush stats posted on the decode queue. | |
| time_controller_.AdvanceTime(TimeDelta::Zero()); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, | |
| FrameCompleteCalledOnceForSingleTemporalUnit) { | |
| StartNextDecodeForceKeyframe(); | |
| // `OnCompleteFrame` should not be called for the first two frames since they | |
| // do not complete the temporal layer. | |
| EXPECT_CALL(stats_callback_, OnCompleteFrame(_, _, _)).Times(0); | |
| buffer_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).Build()); | |
| buffer_->InsertFrame( | |
| test::FakeFrameBuilder().Id(1).Time(0).Refs({0}).Build()); | |
| time_controller_.AdvanceTime(TimeDelta::Zero()); | |
| // Flush stats posted on the decode queue. | |
| ::testing::Mock::VerifyAndClearExpectations(&stats_callback_); | |
| // Note that this frame is not marked as a keyframe since the last spatial | |
| // layer has dependencies. | |
| EXPECT_CALL(stats_callback_, | |
| OnCompleteFrame(false, kFrameSize, VideoContentType::UNSPECIFIED)) | |
| .Times(1); | |
| buffer_->InsertFrame( | |
| test::FakeFrameBuilder().Id(2).Time(0).Refs({0, 1}).AsLast().Build()); | |
| // Flush stats posted on the decode queue. | |
| time_controller_.AdvanceTime(TimeDelta::Zero()); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, | |
| FrameCompleteCalledOnceForCompleteTemporalUnit) { | |
| // FrameBuffer2 logs the complete frame on the arrival of the last layer. | |
| StartNextDecodeForceKeyframe(); | |
| // `OnCompleteFrame` should not be called for the first two frames since they | |
| // do not complete the temporal layer. Frame 1 arrives later, at which time | |
| // this frame can finally be considered complete. | |
| EXPECT_CALL(stats_callback_, OnCompleteFrame(_, _, _)).Times(0); | |
| buffer_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).Build()); | |
| buffer_->InsertFrame( | |
| test::FakeFrameBuilder().Id(2).Time(0).Refs({0, 1}).AsLast().Build()); | |
| time_controller_.AdvanceTime(TimeDelta::Zero()); | |
| // Flush stats posted on the decode queue. | |
| ::testing::Mock::VerifyAndClearExpectations(&stats_callback_); | |
| EXPECT_CALL(stats_callback_, | |
| OnCompleteFrame(false, kFrameSize, VideoContentType::UNSPECIFIED)) | |
| .Times(1); | |
| buffer_->InsertFrame( | |
| test::FakeFrameBuilder().Id(1).Time(0).Refs({0}).Build()); | |
| // Flush stats posted on the decode queue. | |
| time_controller_.AdvanceTime(TimeDelta::Zero()); | |
| } | |
| // Note: This test takes a long time to run if the fake metronome is active. | |
| // Since the test needs to wait for the timestamp to rollover, it has a fake | |
| // delay of around 6.5 hours. Even though time is simulated, this will be | |
| // around 1,500,000 metronome tick invocations. | |
| TEST_P(VideoStreamBufferControllerTest, NextFrameWithOldTimestamp) { | |
| // Test inserting 31 frames and pause the stream for a long time before | |
| // frame 32. | |
| StartNextDecodeForceKeyframe(); | |
| constexpr uint32_t kBaseRtp = std::numeric_limits<uint32_t>::max() / 2; | |
| // First keyframe. The receive time must be explicitly set in this test since | |
| // the RTP derived time used in all tests does not work when the long pause | |
| // happens later in the test. | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(0) | |
| .Time(kBaseRtp) | |
| .ReceivedTime(clock_->CurrentTime()) | |
| .AsLast() | |
| .Build()); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(0))); | |
| // 1 more frame to warmup VCMTiming for 30fps. | |
| StartNextDecode(); | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(1) | |
| .Time(kBaseRtp + kFps30Rtp) | |
| .ReceivedTime(clock_->CurrentTime()) | |
| .AsLast() | |
| .Build()); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(1))); | |
| // Pause the stream for such a long time it incurs an RTP timestamp rollover | |
| // by over half. | |
| constexpr uint32_t kLastRtp = kBaseRtp + kFps30Rtp; | |
| constexpr uint32_t kRolloverRtp = | |
| kLastRtp + std::numeric_limits<uint32_t>::max() / 2 + 1; | |
| constexpr Frequency kRtpHz = Frequency::KiloHertz(90); | |
| // Pause for corresponding delay such that RTP timestamp would increase this | |
| // much at 30fps. | |
| constexpr TimeDelta kRolloverDelay = | |
| (std::numeric_limits<uint32_t>::max() / 2 + 1) / kRtpHz; | |
| // Avoid timeout being set while waiting for the frame and before the receiver | |
| // is ready. | |
| ResetLastResult(); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kMaxWaitForFrame), Eq(std::nullopt)); | |
| time_controller_.AdvanceTime(kRolloverDelay - kMaxWaitForFrame); | |
| StartNextDecode(); | |
| buffer_->InsertFrame(test::FakeFrameBuilder() | |
| .Id(2) | |
| .Time(kRolloverRtp) | |
| .ReceivedTime(clock_->CurrentTime()) | |
| .AsLast() | |
| .Build()); | |
| // FrameBuffer2 drops the frame, while FrameBuffer3 will continue the stream. | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(2))); | |
| } | |
| TEST_P(VideoStreamBufferControllerTest, | |
| FrameNotSetForDecodedIfFrameBufferBecomesNonDecodable) { | |
| // This can happen if the frame buffer receives non-standard input. This test | |
| // will simply clear the frame buffer to replicate this. | |
| StartNextDecodeForceKeyframe(); | |
| // Initial keyframe. | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(0).Time(0).SpatialLayer(1).AsLast().Build())); | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0))); | |
| // Insert a frame that will become non-decodable. | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp(test::FakeFrameBuilder() | |
| .Id(11) | |
| .Time(kFps30Rtp) | |
| .Refs({0}) | |
| .SpatialLayer(1) | |
| .AsLast() | |
| .Build())); | |
| StartNextDecode(); | |
| // Second layer inserted after last layer for the same frame out-of-order. | |
| // This second frame requires some older frame to be decoded and so now the | |
| // super-frame is no longer decodable despite already being scheduled. | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp(test::FakeFrameBuilder() | |
| .Id(10) | |
| .Time(kFps30Rtp) | |
| .SpatialLayer(0) | |
| .Refs({2}) | |
| .Build())); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kMaxWaitForFrame), TimedOut()); | |
| // Ensure that this frame can be decoded later. | |
| StartNextDecode(); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp(test::FakeFrameBuilder() | |
| .Id(2) | |
| .Time(kFps30Rtp / 2) | |
| .SpatialLayer(0) | |
| .Refs({0}) | |
| .AsLast() | |
| .Build())); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(2))); | |
| StartNextDecode(); | |
| EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(10))); | |
| } | |
| INSTANTIATE_TEST_SUITE_P(VideoStreamBufferController, | |
| VideoStreamBufferControllerTest, | |
| ::testing::Combine(::testing::Bool(), | |
| ::testing::Values("")), | |
| [](const auto& info) { | |
| return std::get<0>(info.param) ? "SyncDecoding" | |
| : "UnsyncedDecoding"; | |
| }); | |
| class LowLatencyVideoStreamBufferControllerTest | |
| : public ::testing::Test, | |
| public VideoStreamBufferControllerFixture {}; | |
| TEST_P(LowLatencyVideoStreamBufferControllerTest, | |
| FramesDecodedInstantlyWithLowLatencyRendering) { | |
| // Initial keyframe. | |
| StartNextDecodeForceKeyframe(); | |
| timing_.set_playout_delay({TimeDelta::Zero(), TimeDelta::Millis(10)}); | |
| // Playout delay of 0 implies low-latency rendering. | |
| auto frame = test::FakeFrameBuilder() | |
| .Id(0) | |
| .Time(0) | |
| .PlayoutDelay({TimeDelta::Zero(), TimeDelta::Millis(10)}) | |
| .AsLast() | |
| .Build(); | |
| buffer_->InsertFrame(std::move(frame)); | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0))); | |
| // Delta frame would normally wait here, but should decode at the pacing rate | |
| // in low-latency mode. | |
| StartNextDecode(); | |
| frame = test::FakeFrameBuilder() | |
| .Id(1) | |
| .Time(kFps30Rtp) | |
| .PlayoutDelay({TimeDelta::Zero(), TimeDelta::Millis(10)}) | |
| .AsLast() | |
| .Build(); | |
| buffer_->InsertFrame(std::move(frame)); | |
| // Pacing is set to 16ms in the field trial so we should not decode yet. | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Eq(std::nullopt)); | |
| time_controller_.AdvanceTime(TimeDelta::Millis(16)); | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(1))); | |
| } | |
| TEST_P(LowLatencyVideoStreamBufferControllerTest, ZeroPlayoutDelayFullQueue) { | |
| // Initial keyframe. | |
| StartNextDecodeForceKeyframe(); | |
| timing_.set_playout_delay({TimeDelta::Zero(), TimeDelta::Millis(10)}); | |
| auto frame = test::FakeFrameBuilder() | |
| .Id(0) | |
| .Time(0) | |
| .PlayoutDelay({TimeDelta::Zero(), TimeDelta::Millis(10)}) | |
| .AsLast() | |
| .Build(); | |
| // Playout delay of 0 implies low-latency rendering. | |
| buffer_->InsertFrame(std::move(frame)); | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0))); | |
| // Queue up 5 frames (configured max queue size for 0-playout delay pacing). | |
| for (int id = 1; id <= 6; ++id) { | |
| frame = test::FakeFrameBuilder() | |
| .Id(id) | |
| .Time(kFps30Rtp * id) | |
| .PlayoutDelay({TimeDelta::Zero(), TimeDelta::Millis(10)}) | |
| .AsLast() | |
| .Build(); | |
| buffer_->InsertFrame(std::move(frame)); | |
| } | |
| // The queue is at its max size for zero playout delay pacing, so the pacing | |
| // should be ignored and the next frame should be decoded instantly. | |
| StartNextDecode(); | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(1))); | |
| } | |
| TEST_P(LowLatencyVideoStreamBufferControllerTest, | |
| MinMaxDelayZeroLowLatencyMode) { | |
| // Initial keyframe. | |
| StartNextDecodeForceKeyframe(); | |
| timing_.set_playout_delay({TimeDelta::Zero(), TimeDelta::Zero()}); | |
| // Playout delay of 0 implies low-latency rendering. | |
| auto frame = test::FakeFrameBuilder() | |
| .Id(0) | |
| .Time(0) | |
| .PlayoutDelay(VideoPlayoutDelay::Minimal()) | |
| .AsLast() | |
| .Build(); | |
| buffer_->InsertFrame(std::move(frame)); | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0))); | |
| // Delta frame would normally wait here, but should decode at the pacing rate | |
| // in low-latency mode. | |
| StartNextDecode(); | |
| frame = test::FakeFrameBuilder() | |
| .Id(1) | |
| .Time(kFps30Rtp) | |
| .PlayoutDelay(VideoPlayoutDelay::Minimal()) | |
| .AsLast() | |
| .Build(); | |
| buffer_->InsertFrame(std::move(frame)); | |
| // The min/max=0 version of low-latency rendering will result in a large | |
| // negative decode wait time, so the frame should be ready right away. | |
| EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(1))); | |
| } | |
| INSTANTIATE_TEST_SUITE_P( | |
| VideoStreamBufferController, | |
| LowLatencyVideoStreamBufferControllerTest, | |
| ::testing::Combine( | |
| ::testing::Bool(), | |
| ::testing::Values( | |
| "WebRTC-ZeroPlayoutDelay/min_pacing:16ms,max_decode_queue_size:5/", | |
| "WebRTC-ZeroPlayoutDelay/" | |
| "min_pacing:16ms,max_decode_queue_size:5/"))); | |
| class IncomingTimestampVideoStreamBufferControllerTest | |
| : public ::testing::Test, | |
| public VideoStreamBufferControllerFixture {}; | |
| TEST_P(IncomingTimestampVideoStreamBufferControllerTest, | |
| IncomingTimestampOnMarkerBitOnly) { | |
| StartNextDecodeForceKeyframe(); | |
| EXPECT_CALL(timing_, IncomingTimestampMocked) | |
| .Times(field_trials_.IsDisabled("WebRTC-IncomingTimestampOnMarkerBitOnly") | |
| ? 3 | |
| : 1); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(0).SpatialLayer(0).Time(0).Build())); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(1).SpatialLayer(1).Time(0).Build())); | |
| buffer_->InsertFrame(WithReceiveTimeFromRtpTimestamp( | |
| test::FakeFrameBuilder().Id(2).SpatialLayer(2).Time(0).AsLast().Build())); | |
| } | |
| INSTANTIATE_TEST_SUITE_P( | |
| VideoStreamBufferController, | |
| IncomingTimestampVideoStreamBufferControllerTest, | |
| ::testing::Combine( | |
| ::testing::Bool(), | |
| ::testing::Values( | |
| "WebRTC-IncomingTimestampOnMarkerBitOnly/Enabled/", | |
| "WebRTC-IncomingTimestampOnMarkerBitOnly/Disabled/"))); | |
| } // namespace webrtc |