blob: 20c4fe0faadcbdb08855b5c98a0a04bdf333ff6c [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 <optional>
#include <utility>
#include "absl/base/nullability.h"
#include "api/environment/environment.h"
#include "api/sequence_checker.h"
#include "api/task_queue/pending_task_safety_flag.h"
#include "api/task_queue/task_queue_base.h"
#include "api/units/time_delta.h"
#include "api/video/encoded_frame.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_frame.h"
#include "modules/video_coding/timing/timing.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "video/render/incoming_video_stream.h"
#include "video/task_queue_frame_decode_scheduler.h"
#include "video/timing/simulator/assembler.h"
namespace webrtc::video_timing_simulator {
namespace {
// TODO: b/423646186 - Consider adding some variability to the decode time, and
// update VCMTiming accordingly.
VideoFrame SimulateDecode(const EncodedFrame& encoded_frame) {
return VideoFrame::Builder()
.set_video_frame_buffer(I420Buffer::Create(/*width=*/1, /*height=*/1))
.set_timestamp_us(encoded_frame.RenderTimestamp()->us())
.set_timestamp_rtp(encoded_frame.RtpTimestamp())
// The `id` needs to be unwrapped by the consumer.
.set_id(static_cast<uint16_t>(encoded_frame.Id()))
.set_packet_infos(encoded_frame.PacketInfos())
.build();
}
} // namespace
RenderingTracker::RenderingTracker(const Environment& env,
const Config& config,
std::unique_ptr<VCMTiming> video_timing,
RenderingTrackerEvents* absl_nonnull
observer)
: env_(env),
config_(config),
simulator_queue_(TaskQueueBase::Current()),
video_timing_(std::move(video_timing)),
video_stream_buffer_controller_(
&env.clock(),
simulator_queue_,
video_timing_.get(),
/*stats_proxy=*/this,
/*receiver=*/this,
config.max_wait_for_keyframe,
config.max_wait_for_frame,
std::make_unique<TaskQueueFrameDecodeScheduler>(&env_.clock(),
simulator_queue_),
env_.field_trials()),
incoming_video_stream_(
std::make_unique<IncomingVideoStream>(env_,
config_.render_delay.ms(),
/*callback=*/this)),
observer_(*observer),
decoded_frame_id_cb_(nullptr) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
// Validation.
RTC_DCHECK_NE(config.ssrc, 0u);
RTC_DCHECK(config.max_wait_for_keyframe.IsFinite());
RTC_DCHECK(config.max_wait_for_frame.IsFinite());
RTC_DCHECK(config.render_delay.IsFinite());
// Setup.
ResetVideoStreamBufferControllerObserverStats();
video_timing_->set_render_delay(config_.render_delay);
video_stream_buffer_controller_.StartNextDecode(/*keyframe_required=*/true);
}
RenderingTracker::~RenderingTracker() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
video_stream_buffer_controller_.Stop();
}
void RenderingTracker::SetDecodedFrameIdCallback(
DecodedFrameIdCallback* absl_nonnull decoded_frame_id_cb) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
decoded_frame_id_cb_ = decoded_frame_id_cb;
}
void RenderingTracker::OnAssembledFrame(
std::unique_ptr<EncodedFrame> assembled_frame) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
int64_t frame_id = assembled_frame->Id();
bool is_keyframe = assembled_frame->is_keyframe();
std::optional<int64_t> last_continuous_frame_id =
video_stream_buffer_controller_.InsertFrame(std::move(assembled_frame));
if (!last_continuous_frame_id.has_value()) {
RTC_LOG(LS_INFO) << "Inserted ssrc=" << config_.ssrc
<< ", frame_id=" << frame_id
<< ", is_keyframe=" << is_keyframe
<< " into VideoStreamBufferController but stream was"
<< " still not continuous";
}
}
void RenderingTracker::OnEncodedFrame(
std::unique_ptr<EncodedFrame> encoded_frame) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
RTC_CHECK(decoded_frame_id_cb_) << "Callback must be set before running";
VideoFrame decoded_frame = SimulateDecode(*encoded_frame);
// Verify expected callback order from the VideoStreamBufferController.
// This check is currently true by construction, but it could change in
// the future.
RTC_DCHECK(vsbc_decodable_stats_.has_value());
VideoStreamBufferControllerObserverDecodableStats vsbc_decodable_stats =
vsbc_decodable_stats_.value_or({});
observer_.OnDecodedFrame(*encoded_frame, vsbc_frames_dropped_.value_or(0),
vsbc_decodable_stats.jitter_buffer_minimum_delay,
vsbc_decodable_stats.jitter_buffer_target_delay,
vsbc_decodable_stats.jitter_buffer_delay);
decoded_frame_id_cb_->OnDecodedFrameId(encoded_frame->Id());
encoded_frame.reset(); // Just to be explicit.
// We need to "stop the decode timer", in order for `video_timing_` to know
// that a frame was "decoded".
// TODO: b/423646186 - Consider introducing a decode time delay model.
// See `SimulateDecode()` below.
video_timing_->StopDecodeTimer(/*decode_time=*/TimeDelta::Zero(),
env_.clock().CurrentTime());
// Send the "decoded" video frame for "rendering".
// TODO: b/423646186 - Consider making this step configurable, since Chromium
// disables "prerender smoothing".
incoming_video_stream_->OnFrame(decoded_frame);
// Get ready for the next decode.
ResetVideoStreamBufferControllerObserverStats();
video_stream_buffer_controller_.StartNextDecode(
/*keyframe_required=*/false);
}
void RenderingTracker::OnDecodableFrameTimeout(TimeDelta wait_time) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
RTC_LOG(LS_WARNING) << "Stream ssrc=" << config_.ssrc
<< " timed out (wait_ms=" << wait_time.ms()
<< ", ts_ms=" << env_.clock().TimeInMilliseconds() << ")";
// TODO: b/423646186 - Consider adding this as a callback event.
video_stream_buffer_controller_.StartNextDecode(/*keyframe_required=*/true);
}
void RenderingTracker::OnDroppedFrames(uint32_t frames_dropped) {
vsbc_frames_dropped_ = frames_dropped;
}
void RenderingTracker::OnDecodableFrame(TimeDelta jitter_buffer_delay,
TimeDelta jitter_buffer_target_delay,
TimeDelta jitter_buffer_minimum_delay) {
vsbc_decodable_stats_ = VideoStreamBufferControllerObserverDecodableStats{
.jitter_buffer_delay = jitter_buffer_delay,
.jitter_buffer_target_delay = jitter_buffer_target_delay,
.jitter_buffer_minimum_delay = jitter_buffer_minimum_delay};
}
void RenderingTracker::OnFrame(const VideoFrame& decoded_frame) {
// `IncomingVideoStream` will call back on its own TaskQueue, so we copy
// `decoded_frame` and post over to the `simulator_queue_` here...
if (TaskQueueBase::Current() != simulator_queue_) {
simulator_queue_->PostTask(SafeTask(
safety_.flag(),
[this, decoded_frame]() { observer_.OnRenderedFrame(decoded_frame); }));
return;
}
// ...and in case that ever changes, we still call back here.
RTC_DCHECK_RUN_ON(&sequence_checker_);
observer_.OnRenderedFrame(decoded_frame);
}
void RenderingTracker::ResetVideoStreamBufferControllerObserverStats() {
vsbc_frames_dropped_.reset();
vsbc_decodable_stats_.reset();
}
} // namespace webrtc::video_timing_simulator