blob: 9d029a3c105e55e8c3fca899bf359d671266bb01 [file]
/*
* Copyright (c) 2026 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 MODULES_VIDEO_CODING_UTILITY_TEMPORAL_LAYER_RATE_TRACKER_H_
#define MODULES_VIDEO_CODING_UTILITY_TEMPORAL_LAYER_RATE_TRACKER_H_
#include <array>
#include <optional>
#include "absl/container/inlined_vector.h"
#include "api/units/data_rate.h"
#include "rtc_base/numerics/exp_filter.h"
namespace webrtc {
// Aggregates rate control decisions that are made per frame into the per
// temporal layer quantities encoder libraries want.
//
// Callers of `VideoEncoderInterface` state the bitrate of the temporal layer a
// frame belongs to, one frame at a time. Encoder libraries on the other hand
// tend to want the bitrate and the frame rate of the stream formed by all the
// layers up to and including a given one, which this class derives by tracking
// the bitrate stated for each layer together with how often frames of that
// layer occur.
//
// Only one layer is heard from per frame, so the bitrate of the others has to
// be assumed until they report. A caller that changes the allocation normally
// scales all of the layers by the same factor, so a change seen on one layer is
// taken to apply to the layers that have not reported since. The assumption is
// dropped as soon as a layer states a bitrate of its own, and a layer repeating
// the bitrate it already had is not taken as evidence of anything, which is
// what keeps a change of the distribution between the layers from ping ponging
// around rather than settling. A layer that stops being used keeps the bitrate
// it last had, which is only felt if it is taken back into use while a layer
// below it has gone stale as well.
//
// How often the frames of a layer occur cannot be stated by the caller and is
// measured instead, and smoothed. To avoid a transient at the start of a
// stream, the state is primed on the first frame after a keyframe under the
// assumption that a standard dyadic L1Tx pattern is used: in such a pattern
// that frame belongs to the topmost temporal layer, which reveals the layer
// count and thereby how often the frames of every layer occur. Structures the
// priming does not predict are learned instead, which takes a few repetitions
// of the pattern.
//
// An instance only ever describes one configuration of one encoder. Replace it
// rather than trying to reuse it when the encoder is reconfigured.
class TemporalLayerRateTracker {
public:
// Rate control is tracked separately per spatial layer, since the layers
// have separate bitrates. The spatial layers are assumed to run the same
// temporal pattern, though not necessarily in phase: the cadence is measured
// from the first spatial layer of each temporal unit, so a pattern that
// shifts the layers relative to each other, such as L2T2_KEY_SHIFT, is
// described correctly as well.
static constexpr int kMaxSpatialLayers = 4;
static constexpr int kMaxTemporalLayers = 4;
TemporalLayerRateTracker();
// Records that a frame of temporal layer `temporal_id` in spatial layer
// `spatial_id` was encoded, and that the layer it belongs to was allocated
// `layer_bitrate`. Frames must be passed in encode order.
void Update(int spatial_id,
int temporal_id,
DataRate layer_bitrate,
bool is_keyframe);
// The number of temporal layers seen so far, or the number the priming
// guessed. At least one.
int num_temporal_layers() const { return num_temporal_layers_; }
// How many times lower the frame rate of the stream made up of the temporal
// layers up to and including `temporal_id` is compared to the frame rate of
// the full stream. Rounded to an integer, which is exact for the dyadic
// temporal structures used in practice. At least one.
int FramerateFactor(int temporal_id) const;
// The bitrate of the stream made up of the temporal layers up to and
// including `temporal_id` within spatial layer `spatial_id`.
DataRate CumulativeBitrate(int spatial_id, int temporal_id) const;
// The bitrate of all temporal layers of spatial layer `spatial_id` combined.
DataRate StreamBitrate(int spatial_id) const;
private:
// What is known about the bitrate of one temporal layer of one spatial
// layer.
struct LayerRate {
// The bitrate the tracker believes the layer has: what the caller stated
// for it scaled by the changes seen on the other layers since, or what the
// priming guessed.
DataRate estimate = DataRate::Zero();
// The bitrate the caller most recently stated for the layer, if any. Only
// a departure from this counts as a change of the allocation.
std::optional<DataRate> stated;
};
using SpatialLayerRates = std::array<LayerRate, kMaxTemporalLayers>;
// `ExpFilter` is not assignable, so the filters are held in a container that
// can be filled with copies of a prototype on construction.
using TemporalLayerFilters =
absl::InlinedVector<ExpFilter, kMaxTemporalLayers>;
// Records that `layer_bitrate` was allocated to temporal layer `temporal_id`
// of spatial layer `spatial_id`, and carries the change over to the layers
// that have not reported since.
void UpdateLayerRates(int spatial_id,
int temporal_id,
DataRate layer_bitrate);
// Primes the state as if a standard dyadic L1Tx pattern with `num_layers`
// temporal layers was in use, where the just observed topmost layer frame of
// spatial layer `spatial_id` reported `delta_bitrate`.
void PrimeStandardPattern(int num_layers,
int spatial_id,
DataRate delta_bitrate);
// The share of the frames of the stream that belong to `temporal_id`, or
// zero if no two frames of that layer have been seen yet.
double FrameFraction(int temporal_id) const;
int num_temporal_layers_ = 1;
bool last_frame_was_keyframe_ = false;
// The spatial id of the previous frame, used to tell the frames of a new
// temporal unit from the remaining spatial layers of the current one.
std::optional<int> last_updated_spatial_id_;
// The number of temporal units seen so far, which doubles as the index of
// the current one, and the index of the temporal unit each temporal layer
// was last seen in.
int temporal_unit_count_ = 0;
std::array<std::optional<int>, kMaxTemporalLayers> last_unit_of_layer_;
// What each temporal layer of each spatial layer is believed to hold.
std::array<SpatialLayerRates, kMaxSpatialLayers> layer_rates_;
// The number of temporal units between consecutive frames of each temporal
// layer. Filtering the interval rather than the share of the frames each
// layer holds directly means the samples are constant for a fixed temporal
// structure, so the filter can be quick without the estimate rippling.
TemporalLayerFilters frame_interval_filters_;
};
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_TEMPORAL_LAYER_RATE_TRACKER_H_