blob: 8d012e1979869898fe23d988958d7a24b439da60 [file] [log] [blame]
/*
* Copyright (c) 2025 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/timing/simulator/rendering_tracker.h"
#include <cstdint>
#include <memory>
#include <utility>
#include "api/sequence_checker.h"
#include "api/units/time_delta.h"
#include "api/video/encoded_frame.h"
#include "api/video/video_frame.h"
#include "modules/video_coding/timing/timing.h"
#include "rtc_base/thread_annotations.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "video/timing/simulator/assembler.h"
#include "video/timing/simulator/test/encoded_frame_generators.h"
#include "video/timing/simulator/test/matchers.h"
#include "video/timing/simulator/test/simulated_time_test_fixture.h"
namespace webrtc::video_timing_simulator {
namespace {
using ::testing::_;
using ::testing::Eq;
using ::testing::InSequence;
using ::testing::NiceMock;
class MockRenderingTrackerEvents : public RenderingTrackerEvents {
public:
MOCK_METHOD(void,
OnDecodedFrame,
(const EncodedFrame& decoded_frame,
int frames_dropped,
TimeDelta jitter_buffer_minimum_delay,
TimeDelta jitter_buffer_target_delay,
TimeDelta jitter_buffer_delay),
(override));
MOCK_METHOD(void,
OnRenderedFrame,
(const VideoFrame& rendered_frame),
(override));
};
class MockDecodedFrameIdCallback : public DecodedFrameIdCallback {
public:
MOCK_METHOD(void, OnDecodedFrameId, (int64_t frame_id), (override));
};
class RenderingTrackerTest : public SimulatedTimeTestFixture {
protected:
RenderingTrackerTest() : done_(false) {
SendTask([this]() {
RTC_DCHECK_RUN_ON(queue_ptr_);
rendering_tracker_ = std::make_unique<RenderingTracker>(
env_,
RenderingTracker::Config{
.ssrc = EncodedFrameBuilderGenerator::kSsrc,
.render_delay = TimeDelta::Millis(10)},
std::make_unique<VCMTiming>(&env_.clock(), env_.field_trials()),
&rendering_tracker_events_);
rendering_tracker_->SetDecodedFrameIdCallback(&decoded_frame_id_cb_);
});
}
~RenderingTrackerTest() {
SendTask([this]() {
RTC_DCHECK_RUN_ON(queue_ptr_);
rendering_tracker_.reset();
});
}
void OnAssembledFrame(std::unique_ptr<EncodedFrame> assembled_frame) {
SendTask([this, assembled_frame = std::move(assembled_frame)]() mutable {
RTC_DCHECK_RUN_ON(queue_ptr_);
rendering_tracker_->OnAssembledFrame(std::move(assembled_frame));
});
}
// Expectations.
NiceMock<MockRenderingTrackerEvents> rendering_tracker_events_;
NiceMock<MockDecodedFrameIdCallback> decoded_frame_id_cb_;
// Object under test.
std::unique_ptr<RenderingTracker> rendering_tracker_
RTC_PT_GUARDED_BY(queue_ptr_);
// Wait synchronization.
bool done_;
};
// TODO(brandtr): Check render callback as well!
TEST_F(RenderingTrackerTest, KeyframeIsRenderedImmediately) {
SingleLayerEncodedFrameGenerator generator(env_);
auto keyframe = generator.NextEncodedFrame();
EXPECT_THAT(keyframe->num_references, Eq(0));
EXPECT_CALL(decoded_frame_id_cb_, OnDecodedFrameId(0));
EXPECT_CALL(rendering_tracker_events_, OnRenderedFrame(VideoFrameWithId(0)));
OnAssembledFrame(std::move(keyframe));
// No wait -> `OnRenderedFrame` was called upon immediately.
}
TEST_F(RenderingTrackerTest, DeltaFrameIsNeverRendered) {
SingleLayerEncodedFrameGenerator generator(env_);
auto keyframe = generator.NextEncodedFrame();
time_controller_.AdvanceTime(TimeDelta::Millis(33)); // 30 fps.
auto delta_frame = generator.NextEncodedFrame();
EXPECT_THAT(delta_frame->num_references, Eq(1));
EXPECT_CALL(decoded_frame_id_cb_, OnDecodedFrameId(_)).Times(0);
EXPECT_CALL(rendering_tracker_events_, OnRenderedFrame(_)).Times(0);
OnAssembledFrame(std::move(delta_frame));
time_controller_.AdvanceTime(TimeDelta::Seconds(1));
}
TEST_F(RenderingTrackerTest, KeyframeAndDeltaFrameAreRendered) {
SingleLayerEncodedFrameGenerator generator(env_);
{
InSequence seq;
auto keyframe = generator.NextEncodedFrame();
EXPECT_CALL(decoded_frame_id_cb_, OnDecodedFrameId(0));
EXPECT_CALL(rendering_tracker_events_,
OnRenderedFrame(VideoFrameWithId(0)));
OnAssembledFrame(std::move(keyframe));
time_controller_.AdvanceTime(TimeDelta::Millis(33)); // 30 fps.
auto delta_frame = generator.NextEncodedFrame();
EXPECT_CALL(decoded_frame_id_cb_, OnDecodedFrameId(1));
EXPECT_CALL(rendering_tracker_events_, OnRenderedFrame(VideoFrameWithId(1)))
.WillOnce([this] { done_ = true; });
OnAssembledFrame(std::move(delta_frame));
time_controller_.Wait([this] { return done_; });
}
}
TEST_F(RenderingTrackerTest, ReorderedKeyframeAndDeltaFrameAreRendered) {
SingleLayerEncodedFrameGenerator generator(env_);
{
InSequence seq;
auto keyframe = generator.NextEncodedFrame();
time_controller_.AdvanceTime(TimeDelta::Millis(33)); // 30 fps.
auto delta_frame = generator.NextEncodedFrame();
OnAssembledFrame(std::move(delta_frame));
EXPECT_CALL(rendering_tracker_events_,
OnRenderedFrame(VideoFrameWithId(0)));
EXPECT_CALL(rendering_tracker_events_, OnRenderedFrame(VideoFrameWithId(1)))
.WillOnce([this] { done_ = true; });
OnAssembledFrame(std::move(keyframe));
time_controller_.Wait([this] { return done_; });
}
}
TEST_F(RenderingTrackerTest, TwoTemporalLayerGoPsAreRendered) {
TemporalLayersEncodedFrameGenerator generator(env_);
{
InSequence seq;
for (int i = 0; i < 7; ++i) {
EXPECT_CALL(rendering_tracker_events_,
OnRenderedFrame(VideoFrameWithId(i)));
OnAssembledFrame(generator.NextEncodedFrame());
time_controller_.AdvanceTime(TimeDelta::Millis(33)); // 30 fps.
}
EXPECT_CALL(rendering_tracker_events_, OnRenderedFrame(VideoFrameWithId(7)))
.WillOnce([this] { done_ = true; });
OnAssembledFrame(generator.NextEncodedFrame());
time_controller_.Wait([this] { return done_; });
}
}
TEST_F(RenderingTrackerTest, DroppedFrameIsReported) {
TemporalLayersEncodedFrameGenerator generator(env_);
{
InSequence seq;
auto keyframe = generator.NextEncodedFrame();
EXPECT_CALL(
rendering_tracker_events_,
OnDecodedFrame(EncodedFrameWithId(0), /*frames_dropped=*/0, _, _, _));
OnAssembledFrame(std::move(keyframe));
// `tl1` arrives on time.
time_controller_.AdvanceTime(TimeDelta::Millis(66)); // 30 fps.
auto tl2a = generator.NextEncodedFrame();
auto tl1 = generator.NextEncodedFrame();
EXPECT_CALL(
rendering_tracker_events_,
OnDecodedFrame(EncodedFrameWithId(2), /*frames_dropped=*/1, _, _, _));
OnAssembledFrame(std::move(tl1));
// `tl2a` is very delayed, meaning that it arrived too late to be decoded.
// This was reported for `tl1`, since that frame _was_ decoded.
EXPECT_CALL(rendering_tracker_events_,
OnDecodedFrame(EncodedFrameWithId(1), _, _, _, _))
.Times(0);
OnAssembledFrame(std::move(tl2a));
// `tl2b` arrives on time and is decoded.
EXPECT_CALL(
rendering_tracker_events_,
OnDecodedFrame(EncodedFrameWithId(3), /*frames_dropped=*/0, _, _, _))
.WillOnce([this] { done_ = true; });
time_controller_.AdvanceTime(TimeDelta::Millis(33)); // 30 fps.
auto tl2b = generator.NextEncodedFrame();
OnAssembledFrame(std::move(tl2b));
time_controller_.Wait([this] { return done_; });
}
}
TEST_F(RenderingTrackerTest, JitterBufferStatsAreReported) {
SingleLayerEncodedFrameGenerator generator(env_);
// Prime the internal timing components.
for (int i = 0; i < 30 * 10; ++i) {
auto frame = generator.NextEncodedFrame();
OnAssembledFrame(std::move(frame));
time_controller_.AdvanceTime(TimeDelta::Millis(33)); // 30 fps.
}
// A frame that arrives on time will spend a bit of time in the buffer.
auto on_time_frame = generator.NextEncodedFrame();
EXPECT_CALL(
rendering_tracker_events_,
OnDecodedFrame(EncodedFrameWithId(300), _,
/*jitter_buffer_minimum_delay=*/TimeDelta::Millis(11), _,
/*jitter_buffer_delay=*/TimeDelta::Millis(11)));
OnAssembledFrame(std::move(on_time_frame));
// A frame that arrives late will spend correspondingly less time in the
// buffer.
time_controller_.AdvanceTime(TimeDelta::Millis(43)); // 10ms delayed.
auto late_frame = generator.NextEncodedFrame();
EXPECT_CALL(
rendering_tracker_events_,
OnDecodedFrame(EncodedFrameWithId(301), _,
/*jitter_buffer_minimum_delay=*/TimeDelta::Millis(11), _,
/*jitter_buffer_delay=*/TimeDelta::Millis(1)))
.WillOnce([this] { done_ = true; });
OnAssembledFrame(std::move(late_frame));
time_controller_.Wait([this] { return done_; });
}
} // namespace
} // namespace webrtc::video_timing_simulator