summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/video/video_stream_buffer_controller_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/video/video_stream_buffer_controller_unittest.cc')
-rw-r--r--third_party/libwebrtc/video/video_stream_buffer_controller_unittest.cc922
1 files changed, 922 insertions, 0 deletions
diff --git a/third_party/libwebrtc/video/video_stream_buffer_controller_unittest.cc b/third_party/libwebrtc/video/video_stream_buffer_controller_unittest.cc
new file mode 100644
index 0000000000..e7235a2ff1
--- /dev/null
+++ b/third_party/libwebrtc/video/video_stream_buffer_controller_unittest.cc
@@ -0,0 +1,922 @@
+/*
+ * 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 <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "absl/types/optional.h"
+#include "absl/types/variant.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/video_content_type.h"
+#include "api/video/video_timing.h"
+#include "rtc_base/checks.h"
+#include "test/fake_encoded_frame.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/scoped_key_value_config.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);
+const VideoPlayoutDelay kZeroPlayoutDelay = {0, 0};
+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->Timestamp() == 0) {
+ frame->SetReceivedTime(kClockStart.ms());
+ } else {
+ frame->SetReceivedTime(
+ TimeDelta::Seconds(frame->Timestamp() / 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 VCMReceiveStatisticsCallbackMock : public VCMReceiveStatisticsCallback {
+ 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,
+ OnFrameBufferTimingsUpdated,
+ (int max_decode_ms,
+ int current_delay_ms,
+ int target_delay_ms,
+ int jitter_buffer_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_(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 =
+ absl::variant<std::unique_ptr<EncodedFrame>, TimeDelta /*wait_time*/>;
+
+ absl::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 rtc::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_;
+ test::ScopedKeyValueConfig field_trials_;
+ GlobalSimulatedTimeController time_controller_;
+ Clock* const clock_;
+ test::FakeMetronome fake_metronome_;
+ DecodeSynchronizer decode_sync_;
+
+ ::testing::NiceMock<VCMTimingTest> timing_;
+ ::testing::NiceMock<VCMReceiveStatisticsCallbackMock> stats_callback_;
+ std::unique_ptr<VideoStreamBufferController> buffer_;
+
+ private:
+ void SetWaitResult(WaitResult result) {
+ RTC_DCHECK(!wait_result_);
+ if (absl::holds_alternative<std::unique_ptr<EncodedFrame>>(result)) {
+ RTC_DCHECK(absl::get<std::unique_ptr<EncodedFrame>>(result));
+ }
+ wait_result_.emplace(std::move(result));
+ }
+
+ uint32_t dropped_frames_ = 0;
+ absl::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(absl::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(absl::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(absl::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(absl::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_, 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(absl::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_min_playout_delay(TimeDelta::Zero());
+ timing_.set_max_playout_delay(TimeDelta::Millis(10));
+ auto frame = test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build();
+ // Playout delay of 0 implies low-latency rendering.
+ frame->SetPlayoutDelay({0, 10});
+ 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).AsLast().Build();
+ frame->SetPlayoutDelay({0, 10});
+ 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(absl::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_min_playout_delay(TimeDelta::Zero());
+ timing_.set_max_playout_delay(TimeDelta::Millis(10));
+ auto frame = test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build();
+ // Playout delay of 0 implies low-latency rendering.
+ frame->SetPlayoutDelay({0, 10});
+ 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).AsLast().Build();
+ frame->SetPlayoutDelay({0, 10});
+ 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_min_playout_delay(TimeDelta::Zero());
+ timing_.set_max_playout_delay(TimeDelta::Zero());
+ auto frame = test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build();
+ // Playout delay of 0 implies low-latency rendering.
+ frame->SetPlayoutDelay({0, 0});
+ 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).AsLast().Build();
+ frame->SetPlayoutDelay({0, 0});
+ 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