blob: 2f4f89018bb746f87c43fe6f0ccce8c2b7ae6e89 [file] [log] [blame] [edit]
/*
* 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/decode_synchronizer.h"
#include <stddef.h>
#include <memory>
#include <utility>
#include "absl/functional/any_invocable.h"
#include "api/metronome/test/fake_metronome.h"
#include "api/units/time_delta.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/time_controller/simulated_time_controller.h"
#include "video/frame_decode_scheduler.h"
#include "video/frame_decode_timing.h"
using ::testing::_;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::MockFunction;
using ::testing::Return;
namespace webrtc {
class MockMetronome : public Metronome {
public:
MOCK_METHOD(void,
RequestCallOnNextTick,
(absl::AnyInvocable<void() &&> callback),
(override));
MOCK_METHOD(TimeDelta, TickPeriod, (), (const, override));
};
class DecodeSynchronizerTest : public ::testing::Test {
public:
static constexpr TimeDelta kTickPeriod = TimeDelta::Millis(33);
DecodeSynchronizerTest()
: time_controller_(Timestamp::Millis(1337)),
clock_(time_controller_.GetClock()),
metronome_(kTickPeriod),
decode_synchronizer_(clock_,
&metronome_,
time_controller_.GetMainThread()) {}
protected:
GlobalSimulatedTimeController time_controller_;
Clock* clock_;
test::ForcedTickMetronome metronome_;
DecodeSynchronizer decode_synchronizer_;
};
TEST_F(DecodeSynchronizerTest, AllFramesReadyBeforeNextTickDecoded) {
MockFunction<void(uint32_t, Timestamp)> mock_callback1;
auto scheduler1 = decode_synchronizer_.CreateSynchronizedFrameScheduler();
MockFunction<void(unsigned int, Timestamp)> mock_callback2;
auto scheduler2 = decode_synchronizer_.CreateSynchronizedFrameScheduler();
{
uint32_t frame_rtp = 90000;
FrameDecodeTiming::FrameSchedule frame_sched{
.latest_decode_time =
clock_->CurrentTime() + kTickPeriod - TimeDelta::Millis(3),
.render_time = clock_->CurrentTime() + TimeDelta::Millis(60)};
scheduler1->ScheduleFrame(frame_rtp, frame_sched,
mock_callback1.AsStdFunction());
EXPECT_CALL(mock_callback1,
Call(Eq(frame_rtp), Eq(frame_sched.render_time)));
}
{
uint32_t frame_rtp = 123456;
FrameDecodeTiming::FrameSchedule frame_sched{
.latest_decode_time =
clock_->CurrentTime() + kTickPeriod - TimeDelta::Millis(2),
.render_time = clock_->CurrentTime() + TimeDelta::Millis(70)};
scheduler2->ScheduleFrame(frame_rtp, frame_sched,
mock_callback2.AsStdFunction());
EXPECT_CALL(mock_callback2,
Call(Eq(frame_rtp), Eq(frame_sched.render_time)));
}
metronome_.Tick();
time_controller_.AdvanceTime(TimeDelta::Zero());
// Cleanup
scheduler1->Stop();
scheduler2->Stop();
}
TEST_F(DecodeSynchronizerTest, FramesNotDecodedIfDecodeTimeIsInNextInterval) {
MockFunction<void(unsigned int, Timestamp)> mock_callback;
auto scheduler = decode_synchronizer_.CreateSynchronizedFrameScheduler();
uint32_t frame_rtp = 90000;
FrameDecodeTiming::FrameSchedule frame_sched{
.latest_decode_time =
clock_->CurrentTime() + kTickPeriod + TimeDelta::Millis(10),
.render_time =
clock_->CurrentTime() + kTickPeriod + TimeDelta::Millis(30)};
scheduler->ScheduleFrame(frame_rtp, frame_sched,
mock_callback.AsStdFunction());
metronome_.Tick();
time_controller_.AdvanceTime(TimeDelta::Zero());
// No decodes should have happened in this tick.
::testing::Mock::VerifyAndClearExpectations(&mock_callback);
// Decode should happen on next tick.
EXPECT_CALL(mock_callback, Call(Eq(frame_rtp), Eq(frame_sched.render_time)));
time_controller_.AdvanceTime(kTickPeriod);
metronome_.Tick();
time_controller_.AdvanceTime(TimeDelta::Zero());
// Cleanup
scheduler->Stop();
}
TEST_F(DecodeSynchronizerTest, FrameDecodedOnce) {
MockFunction<void(unsigned int, Timestamp)> mock_callback;
auto scheduler = decode_synchronizer_.CreateSynchronizedFrameScheduler();
uint32_t frame_rtp = 90000;
FrameDecodeTiming::FrameSchedule frame_sched{
.latest_decode_time = clock_->CurrentTime() + TimeDelta::Millis(30),
.render_time = clock_->CurrentTime() + TimeDelta::Millis(60)};
scheduler->ScheduleFrame(frame_rtp, frame_sched,
mock_callback.AsStdFunction());
EXPECT_CALL(mock_callback, Call(_, _)).Times(1);
metronome_.Tick();
time_controller_.AdvanceTime(TimeDelta::Zero());
::testing::Mock::VerifyAndClearExpectations(&mock_callback);
// Trigger tick again. No frame should be decoded now.
time_controller_.AdvanceTime(kTickPeriod);
metronome_.Tick();
time_controller_.AdvanceTime(TimeDelta::Zero());
// Cleanup
scheduler->Stop();
}
TEST_F(DecodeSynchronizerTest, FrameWithDecodeTimeInPastDecodedImmediately) {
MockFunction<void(unsigned int, Timestamp)> mock_callback;
auto scheduler = decode_synchronizer_.CreateSynchronizedFrameScheduler();
uint32_t frame_rtp = 90000;
FrameDecodeTiming::FrameSchedule frame_sched{
.latest_decode_time = clock_->CurrentTime() - TimeDelta::Millis(5),
.render_time = clock_->CurrentTime() + TimeDelta::Millis(30)};
EXPECT_CALL(mock_callback, Call(Eq(90000u), _)).Times(1);
scheduler->ScheduleFrame(frame_rtp, frame_sched,
mock_callback.AsStdFunction());
// Verify the callback was invoked already.
::testing::Mock::VerifyAndClearExpectations(&mock_callback);
metronome_.Tick();
time_controller_.AdvanceTime(TimeDelta::Zero());
// Cleanup
scheduler->Stop();
}
TEST_F(DecodeSynchronizerTest,
FrameWithDecodeTimeFarBeforeNextTickDecodedImmediately) {
MockFunction<void(unsigned int, Timestamp)> mock_callback;
auto scheduler = decode_synchronizer_.CreateSynchronizedFrameScheduler();
// Frame which would be behind by more than kMaxAllowedFrameDelay after
// the next tick.
FrameDecodeTiming::FrameSchedule frame_sched{
.latest_decode_time = clock_->CurrentTime() + kTickPeriod -
FrameDecodeTiming::kMaxAllowedFrameDelay -
TimeDelta::Millis(1),
.render_time = clock_->CurrentTime() + TimeDelta::Millis(30)};
EXPECT_CALL(mock_callback, Call(Eq(90000u), _)).Times(1);
scheduler->ScheduleFrame(90000, frame_sched, mock_callback.AsStdFunction());
// Verify the callback was invoked already.
::testing::Mock::VerifyAndClearExpectations(&mock_callback);
time_controller_.AdvanceTime(kTickPeriod);
metronome_.Tick();
time_controller_.AdvanceTime(TimeDelta::Zero());
// A frame that would be behind by exactly kMaxAllowedFrameDelay after next
// tick should decode at the next tick.
FrameDecodeTiming::FrameSchedule queued_frame{
.latest_decode_time = clock_->CurrentTime() + kTickPeriod -
FrameDecodeTiming::kMaxAllowedFrameDelay,
.render_time = clock_->CurrentTime() + TimeDelta::Millis(30)};
scheduler->ScheduleFrame(180000, queued_frame, mock_callback.AsStdFunction());
// Verify the callback was invoked already.
::testing::Mock::VerifyAndClearExpectations(&mock_callback);
EXPECT_CALL(mock_callback, Call(Eq(180000u), _)).Times(1);
time_controller_.AdvanceTime(kTickPeriod);
metronome_.Tick();
time_controller_.AdvanceTime(TimeDelta::Zero());
// Cleanup
scheduler->Stop();
}
TEST_F(DecodeSynchronizerTest, FramesNotReleasedAfterStop) {
MockFunction<void(unsigned int, Timestamp)> mock_callback;
auto scheduler = decode_synchronizer_.CreateSynchronizedFrameScheduler();
uint32_t frame_rtp = 90000;
FrameDecodeTiming::FrameSchedule frame_sched{
.latest_decode_time = clock_->CurrentTime() + TimeDelta::Millis(30),
.render_time = clock_->CurrentTime() + TimeDelta::Millis(60)};
scheduler->ScheduleFrame(frame_rtp, frame_sched,
mock_callback.AsStdFunction());
// Cleanup
scheduler->Stop();
// No callback should occur on this tick since Stop() was called before.
metronome_.Tick();
time_controller_.AdvanceTime(TimeDelta::Zero());
}
TEST(DecodeSynchronizerStandaloneTest,
MetronomeNotListenedWhenNoStreamsAreActive) {
GlobalSimulatedTimeController time_controller(Timestamp::Millis(4711));
Clock* clock(time_controller.GetClock());
MockMetronome metronome;
ON_CALL(metronome, TickPeriod).WillByDefault(Return(TimeDelta::Seconds(1)));
DecodeSynchronizer decode_synchronizer_(clock, &metronome,
time_controller.GetMainThread());
absl::AnyInvocable<void() &&> callback;
EXPECT_CALL(metronome, RequestCallOnNextTick)
.WillOnce(Invoke([&callback](absl::AnyInvocable<void() &&> cb) {
callback = std::move(cb);
}));
auto scheduler = decode_synchronizer_.CreateSynchronizedFrameScheduler();
auto scheduler2 = decode_synchronizer_.CreateSynchronizedFrameScheduler();
scheduler->Stop();
scheduler2->Stop();
time_controller.AdvanceTime(TimeDelta::Seconds(1));
ASSERT_TRUE(callback);
(std::move)(callback)();
}
TEST(DecodeSynchronizerStandaloneTest,
RegistersCallbackOnceDuringRepeatedRegistrations) {
GlobalSimulatedTimeController time_controller(Timestamp::Millis(4711));
Clock* clock(time_controller.GetClock());
MockMetronome metronome;
ON_CALL(metronome, TickPeriod).WillByDefault(Return(TimeDelta::Seconds(1)));
DecodeSynchronizer decode_synchronizer_(clock, &metronome,
time_controller.GetMainThread());
// Expect at most 1 call to register a callback.
EXPECT_CALL(metronome, RequestCallOnNextTick);
auto scheduler1 = decode_synchronizer_.CreateSynchronizedFrameScheduler();
scheduler1->Stop();
auto scheduler2 = decode_synchronizer_.CreateSynchronizedFrameScheduler();
Mock::VerifyAndClearExpectations(&metronome);
scheduler2->Stop();
}
} // namespace webrtc