blob: 8e74cbf2b53097dc5dd9c5fe091cf54f788246ff [file] [log] [blame] [edit]
/*
* 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.
*/
#ifndef VIDEO_TIMING_SIMULATOR_RENDERING_SIMULATOR_H_
#define VIDEO_TIMING_SIMULATOR_RENDERING_SIMULATOR_H_
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "absl/algorithm/container.h"
#include "api/array_view.h"
#include "api/environment/environment.h"
#include "api/units/data_size.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "logging/rtc_event_log/rtc_event_log_parser.h"
#include "modules/video_coding/timing/timing.h"
#include "video/timing/simulator/frame_base.h"
#include "video/timing/simulator/stream_base.h"
namespace webrtc::video_timing_simulator {
// The `RenderingSimulator` takes an `ParsedRtcEventLog` and produces a
// sequence of metadata about decoded and rendered frames that were contained in
// the log.
class RenderingSimulator {
public:
struct Config {
using VideoTimingFactory =
std::function<std::unique_ptr<VCMTiming>(Environment)>;
std::string name = "";
std::string field_trials_string = "";
VideoTimingFactory video_timing_factory = [](Environment env) {
return std::make_unique<VCMTiming>(&env.clock(), env.field_trials());
};
// Whether or not to reset the stream state on newly logged streams with the
// same SSRC.
bool reuse_streams = true;
};
// Metadata about a single rendered frame.
struct Frame : public FrameBase<Frame> {
// -- Values --
// Frame information.
int num_packets = -1;
DataSize size = DataSize::Zero();
// RTP header information.
int payload_type = -1;
uint32_t rtp_timestamp = 0;
int64_t unwrapped_rtp_timestamp = -1;
// Dependency descriptor information.
int64_t frame_id = -1;
int spatial_id = -1;
int temporal_id = -1;
int num_references = -1;
// Packet timestamps.
Timestamp first_packet_arrival_timestamp = Timestamp::PlusInfinity();
Timestamp last_packet_arrival_timestamp = Timestamp::MinusInfinity();
// Frame timestamps.
Timestamp assembled_timestamp = Timestamp::PlusInfinity();
Timestamp render_timestamp = Timestamp::PlusInfinity();
Timestamp decoded_timestamp = Timestamp::PlusInfinity();
Timestamp rendered_timestamp = Timestamp::PlusInfinity();
// Jitter buffer state at the time of this frame.
int frames_dropped = -1;
// TODO: b/423646186 - Add `current_delay_ms`.
// The `jitter_buffer_*` metrics below are recorded by the production code,
// and should be compatible with the `webrtc-stats` definitions. One major
// difference is that they are _not_ cumulative.
// https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferminimumdelay
TimeDelta jitter_buffer_minimum_delay = TimeDelta::MinusInfinity();
// https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbuffertargetdelay
TimeDelta jitter_buffer_target_delay = TimeDelta::MinusInfinity();
// https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferdelay
TimeDelta jitter_buffer_delay = TimeDelta::MinusInfinity();
// -- Populated values --
// One-way delay relative some baseline.
TimeDelta frame_delay_variation = TimeDelta::PlusInfinity();
// -- Value accessors --
Timestamp ArrivalTimestampInternal() const { return rendered_timestamp; }
// -- Per-frame metrics --
// Time spent being assembled (waiting for all packets to arrive).
TimeDelta PacketBufferDuration() const {
return assembled_timestamp - first_packet_arrival_timestamp;
}
// Time spent waiting to be decoded, after assembly. (This includes a,
// currently, zero decode duration.) Note that this is similar to
// `jitter_buffer_delay`, except that the latter is 1) recorded by the
// production code; and, 2) is anchored on `first_packet_arrival_timestamp`
// rather than `assembled_timestamp`.
TimeDelta FrameBufferDuration() const {
return decoded_timestamp - assembled_timestamp;
}
// Time spent waiting to be rendered, after decode.
TimeDelta RenderBufferDuration() const {
return rendered_timestamp - decoded_timestamp;
}
// Margin between render timestamp (target) and
// assembled timestamp (actual):
// * A frame that is assembled early w.r.t. the target (<=> arriving
// on time from the network) has a positive margin.
// * A frame that is assembled on time w.r.t. the target (<=> arriving
// slightly late from the network) has zero margin.
// * A frame that is assembled late w.r.t. the target (<=> arriving
// very late from the network) has negative margin.
// Positive margins mean no video freezes, at the cost of receiver delay.
// A jitter buffer needs to strike a balance between video freezes and
// delay. In terms of margin, that means low positive margin values, a
// couple of frames with zero margin, and very few frames with
// negative margin.
TimeDelta RenderMargin() const {
return render_timestamp - assembled_timestamp;
}
// Split the margin along zero: "excess margin" for positive margins and
// "deficit margin" for negative margins.
// A jitter buffer would generally want to minimize the number of frames
// with a deficit margin (delayed frames/buffer underruns => video freezes),
// while also minimizing the stream-level min/p10 of the excess margin
// (early frames spending a long time in the buffer => high latency).
std::optional<TimeDelta> RenderedExcessMargin() const {
TimeDelta margin = RenderMargin();
if (margin < TimeDelta::Zero()) {
return std::nullopt;
}
return margin;
}
std::optional<TimeDelta> RenderedDeficitMargin() const {
TimeDelta margin = RenderMargin();
if (margin > TimeDelta::Zero()) {
return std::nullopt;
}
return margin;
}
};
// All frames in one stream.
struct Stream : public StreamBase<Stream> {
Timestamp creation_timestamp = Timestamp::PlusInfinity();
uint32_t ssrc = 0;
std::vector<Frame> frames;
};
// All streams.
struct Results {
std::string config_name;
std::vector<Stream> streams;
};
// Static configuration.
static constexpr TimeDelta kRenderDelay = TimeDelta::Millis(10);
explicit RenderingSimulator(Config config);
~RenderingSimulator();
RenderingSimulator(const RenderingSimulator&) = delete;
RenderingSimulator& operator=(const RenderingSimulator&) = delete;
Results Simulate(const ParsedRtcEventLog& parsed_log) const;
private:
const Config config_;
};
// -- Comparators and sorting --
inline bool RenderOrder(const RenderingSimulator::Frame& a,
const RenderingSimulator::Frame& b) {
return a.render_timestamp < b.render_timestamp;
}
inline void SortByRenderOrder(ArrayView<RenderingSimulator::Frame> frames) {
absl::c_stable_sort(frames, RenderOrder);
}
inline bool DecodedOrder(const RenderingSimulator::Frame& a,
const RenderingSimulator::Frame& b) {
return a.decoded_timestamp < b.decoded_timestamp;
}
inline void SortByDecodedOrder(ArrayView<RenderingSimulator::Frame> frames) {
absl::c_stable_sort(frames, DecodedOrder);
}
inline bool RenderedOrder(const RenderingSimulator::Frame& a,
const RenderingSimulator::Frame& b) {
return a.rendered_timestamp < b.rendered_timestamp;
}
inline void SortByRenderedOrder(ArrayView<RenderingSimulator::Frame> frames) {
absl::c_stable_sort(frames, RenderedOrder);
}
// -- Inter-frame metrics --
// Difference in render time (target) between two frames.
inline TimeDelta InterRenderTime(const RenderingSimulator::Frame& cur,
const RenderingSimulator::Frame& prev) {
return cur.render_timestamp - prev.render_timestamp;
}
// Difference in decoded time (actual) between two frames.
inline TimeDelta InterDecodedTime(const RenderingSimulator::Frame& cur,
const RenderingSimulator::Frame& prev) {
return cur.decoded_timestamp - prev.decoded_timestamp;
}
// Difference in rendered time (actual) between two frames.
inline TimeDelta InterRenderedTime(const RenderingSimulator::Frame& cur,
const RenderingSimulator::Frame& prev) {
return cur.rendered_timestamp - prev.rendered_timestamp;
}
} // namespace webrtc::video_timing_simulator
#endif // VIDEO_TIMING_SIMULATOR_RENDERING_SIMULATOR_H_