blob: 3295fac16abb0bde4a22eeb881dbe8a355cfa034 [file] [log] [blame]
/*
* 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/frame_buffer_proxy.h"
#include <stdint.h>
#include <limits>
#include <memory>
#include <string>
#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/run_loop.h"
#include "test/scoped_key_value_config.h"
#include "test/time_controller/simulated_time_controller.h"
#include "video/decode_synchronizer.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 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 FrameBufferProxyFixture
: public ::testing::WithParamInterface<std::string>,
public FrameSchedulingReceiver {
public:
FrameBufferProxyFixture()
: field_trials_(GetParam()),
time_controller_(kClockStart),
clock_(time_controller_.GetClock()),
decode_queue_(time_controller_.GetTaskQueueFactory()->CreateTaskQueue(
"decode_queue",
TaskQueueFactory::Priority::NORMAL)),
fake_metronome_(time_controller_.GetTaskQueueFactory(),
TimeDelta::Millis(16)),
decode_sync_(clock_, &fake_metronome_, run_loop_.task_queue()),
timing_(clock_, field_trials_),
proxy_(FrameBufferProxy::CreateFromFieldTrial(clock_,
run_loop_.task_queue(),
&timing_,
&stats_callback_,
decode_queue_.Get(),
this,
kMaxWaitForKeyframe,
kMaxWaitForFrame,
&decode_sync_,
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; });
}
~FrameBufferProxyFixture() override {
if (proxy_) {
proxy_->StopOnWorker();
}
fake_metronome_.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_);
}
run_loop_.PostTask([&] { time_controller_.AdvanceTime(wait); });
run_loop_.PostTask([&] {
if (wait_result_)
return;
// If run loop posted to a task queue, flush that if there is no result.
time_controller_.AdvanceTime(TimeDelta::Zero());
if (wait_result_)
return;
run_loop_.PostTask([&] {
time_controller_.AdvanceTime(TimeDelta::Zero());
// Quit if there is no result set.
if (!wait_result_)
run_loop_.Quit();
});
});
run_loop_.Run();
return std::move(wait_result_);
}
void StartNextDecode() {
ResetLastResult();
proxy_->StartNextDecode(false);
time_controller_.AdvanceTime(TimeDelta::Zero());
}
void StartNextDecodeForceKeyframe() {
ResetLastResult();
proxy_->StartNextDecode(true);
time_controller_.AdvanceTime(TimeDelta::Zero());
}
void ResetLastResult() { wait_result_.reset(); }
int dropped_frames() const { return dropped_frames_; }
protected:
test::ScopedKeyValueConfig field_trials_;
GlobalSimulatedTimeController time_controller_;
Clock* const clock_;
test::RunLoop run_loop_;
rtc::TaskQueue decode_queue_;
test::FakeMetronome fake_metronome_;
DecodeSynchronizer decode_sync_;
VCMTiming timing_;
::testing::NiceMock<VCMReceiveStatisticsCallbackMock> stats_callback_;
std::unique_ptr<FrameBufferProxy> proxy_;
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));
run_loop_.Quit();
}
uint32_t dropped_frames_ = 0;
absl::optional<WaitResult> wait_result_;
};
class FrameBufferProxyTest : public ::testing::Test,
public FrameBufferProxyFixture {};
TEST_P(FrameBufferProxyTest, 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(FrameBufferProxyTest, KeyFramesAreScheduled) {
StartNextDecodeForceKeyframe();
time_controller_.AdvanceTime(TimeDelta::Millis(50));
auto frame = test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build();
proxy_->InsertFrame(std::move(frame));
EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0)));
}
TEST_P(FrameBufferProxyTest, DeltaFrameTimeoutAfterKeyframeExtracted) {
StartNextDecodeForceKeyframe();
time_controller_.AdvanceTime(TimeDelta::Millis(50));
auto frame = test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build();
proxy_->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(FrameBufferProxyTest, DependantFramesAreScheduled) {
StartNextDecodeForceKeyframe();
proxy_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build());
EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0)));
StartNextDecode();
time_controller_.AdvanceTime(kFps30Delay);
proxy_->InsertFrame(test::FakeFrameBuilder()
.Id(1)
.Time(kFps30Rtp)
.AsLast()
.Refs({0})
.Build());
EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(1)));
}
TEST_P(FrameBufferProxyTest, SpatialLayersAreScheduled) {
StartNextDecodeForceKeyframe();
proxy_->InsertFrame(WithReceiveTimeFromRtpTimestamp(
test::FakeFrameBuilder().Id(0).SpatialLayer(0).Time(0).Build()));
proxy_->InsertFrame(WithReceiveTimeFromRtpTimestamp(
test::FakeFrameBuilder().Id(1).SpatialLayer(1).Time(0).Build()));
proxy_->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))));
proxy_->InsertFrame(WithReceiveTimeFromRtpTimestamp(
test::FakeFrameBuilder().Id(3).Time(kFps30Rtp).SpatialLayer(0).Build()));
proxy_->InsertFrame(WithReceiveTimeFromRtpTimestamp(
test::FakeFrameBuilder().Id(4).Time(kFps30Rtp).SpatialLayer(1).Build()));
proxy_->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(FrameBufferProxyTest, OutstandingFrameTasksAreCancelledAfterDeletion) {
StartNextDecodeForceKeyframe();
proxy_->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();
proxy_->InsertFrame(WithReceiveTimeFromRtpTimestamp(test::FakeFrameBuilder()
.Id(1)
.Time(kFps30Rtp)
.AsLast()
.Refs({0})
.Build()));
proxy_->StopOnWorker();
// 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(FrameBufferProxyTest, FramesWaitForDecoderToComplete) {
StartNextDecodeForceKeyframe();
// Start with a keyframe.
proxy_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build());
EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0)));
ResetLastResult();
// Insert a delta frame.
proxy_->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(FrameBufferProxyTest, LateFrameDropped) {
StartNextDecodeForceKeyframe();
// F1
// /
// F0 --> F2
proxy_->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);
proxy_->InsertFrame(test::FakeFrameBuilder()
.Id(2)
.Time(2 * kFps30Rtp)
.AsLast()
.Refs({0})
.Build());
EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(2)));
StartNextDecode();
proxy_->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(FrameBufferProxyTest, FramesFastForwardOnSystemHalt) {
StartNextDecodeForceKeyframe();
// F1
// /
// F0 --> F2
proxy_->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);
proxy_->InsertFrame(test::FakeFrameBuilder()
.Id(1)
.Time(kFps30Rtp)
.AsLast()
.Refs({0})
.Build());
time_controller_.AdvanceTime(kFps30Delay);
proxy_->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(FrameBufferProxyTest, ForceKeyFrame) {
StartNextDecodeForceKeyframe();
// Initial keyframe.
proxy_->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.
proxy_->InsertFrame(test::FakeFrameBuilder()
.Id(1)
.Time(kFps30Rtp)
.AsLast()
.Refs({0})
.Build());
proxy_->InsertFrame(
test::FakeFrameBuilder().Id(2).Time(kFps30Rtp * 2).AsLast().Build());
EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay * 3), Frame(test::WithId(2)));
}
TEST_P(FrameBufferProxyTest, 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
proxy_->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);
proxy_->InsertFrame(test::FakeFrameBuilder()
.Id(1)
.Time(1 * kFps30Rtp)
.Refs({0})
.AsLast()
.Build());
time_controller_.AdvanceTime(kFps30Delay);
proxy_->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);
proxy_->InsertFrame(test::FakeFrameBuilder()
.Id(3)
.Time(3 * kFps30Rtp)
.Refs({1, 2})
.AsLast()
.Build());
time_controller_.AdvanceTime(kFps30Delay / 2);
proxy_->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)));
proxy_->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(FrameBufferProxyTest, NewFrameInsertedWhileWaitingToReleaseFrame) {
StartNextDecodeForceKeyframe();
// Initial keyframe.
proxy_->InsertFrame(WithReceiveTimeFromRtpTimestamp(
test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build()));
EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Zero()), Frame(test::WithId(0)));
time_controller_.AdvanceTime(kFps30Delay / 2);
proxy_->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.
proxy_->InsertFrame(WithReceiveTimeFromRtpTimestamp(test::FakeFrameBuilder()
.Id(2)
.Time(kFps30Rtp * 2)
.Refs({0})
.AsLast()
.Build()));
EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(1)));
}
TEST_P(FrameBufferProxyTest, 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.
proxy_->InsertFrame(WithReceiveTimeFromRtpTimestamp(
test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build()));
EXPECT_THAT(WaitForFrameOrTimeout(TimeDelta::Millis(15)),
Frame(test::WithId(0)));
StartNextDecode();
// Warmup VCMTiming for 30fps.
for (int i = 1; i <= 30; ++i) {
proxy_->InsertFrame(WithReceiveTimeFromRtpTimestamp(
test::FakeFrameBuilder().Id(i).Time(i * kFps30Rtp).AsLast().Build()));
EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(i)));
StartNextDecode();
}
// F2 arrives and is scheduled.
proxy_->InsertFrame(WithReceiveTimeFromRtpTimestamp(
test::FakeFrameBuilder().Id(32).Time(32 * kFps30Rtp).AsLast().Build()));
// F3 arrives before F2 is extracted.
time_controller_.AdvanceTime(kFps30Delay);
proxy_->InsertFrame(WithReceiveTimeFromRtpTimestamp(
test::FakeFrameBuilder().Id(33).Time(33 * 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);
proxy_->InsertFrame(WithReceiveTimeFromRtpTimestamp(
test::FakeFrameBuilder().Id(31).Time(31 * kFps30Rtp).AsLast().Build()));
EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(32)));
StartNextDecode();
EXPECT_THAT(WaitForFrameOrTimeout(kFps30Delay), Frame(test::WithId(33)));
StartNextDecode();
EXPECT_THAT(WaitForFrameOrTimeout(kMaxWaitForFrame), TimedOut());
EXPECT_EQ(dropped_frames(), 1);
}
TEST_P(FrameBufferProxyTest, 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();
proxy_->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(FrameBufferProxyTest, FrameCompleteCalledOnceForDuplicateFrame) {
EXPECT_CALL(stats_callback_,
OnCompleteFrame(true, kFrameSize, VideoContentType::UNSPECIFIED))
.Times(1);
StartNextDecodeForceKeyframe();
proxy_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build());
proxy_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).AsLast().Build());
// Flush stats posted on the decode queue.
time_controller_.AdvanceTime(TimeDelta::Zero());
}
TEST_P(FrameBufferProxyTest, 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);
proxy_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).Build());
proxy_->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);
proxy_->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(FrameBufferProxyTest, 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);
proxy_->InsertFrame(test::FakeFrameBuilder().Id(0).Time(0).Build());
proxy_->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);
proxy_->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(FrameBufferProxyTest, 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.
proxy_->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();
proxy_->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();
proxy_->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)));
}
INSTANTIATE_TEST_SUITE_P(
FrameBufferProxy,
FrameBufferProxyTest,
::testing::Values("WebRTC-FrameBuffer3/arm:FrameBuffer3/",
"WebRTC-FrameBuffer3/arm:SyncDecoding/"));
class LowLatencyFrameBufferProxyTest : public ::testing::Test,
public FrameBufferProxyFixture {};
TEST_P(LowLatencyFrameBufferProxyTest,
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});
proxy_->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});
proxy_->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(LowLatencyFrameBufferProxyTest, 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});
proxy_->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});
proxy_->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(LowLatencyFrameBufferProxyTest, 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});
proxy_->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});
proxy_->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(
FrameBufferProxy,
LowLatencyFrameBufferProxyTest,
::testing::Values(
"WebRTC-FrameBuffer3/arm:FrameBuffer3/"
"WebRTC-ZeroPlayoutDelay/min_pacing:16ms,max_decode_queue_size:5/",
"WebRTC-FrameBuffer3/arm:SyncDecoding/"
"WebRTC-ZeroPlayoutDelay/min_pacing:16ms,max_decode_queue_size:5/"));
} // namespace webrtc