| /* |
| * 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_CBR_LAYER_RATE_TRACKER_H_ |
| #define MODULES_VIDEO_CODING_UTILITY_CBR_LAYER_RATE_TRACKER_H_ |
| |
| #include <array> |
| #include <optional> |
| #include <span> |
| |
| #include "absl/container/inlined_vector.h" |
| #include "api/units/data_rate.h" |
| #include "api/video_codecs/video_encoder_interface.h" |
| #include "rtc_base/numerics/exp_filter.h" |
| |
| namespace webrtc { |
| |
| // The rates of all the layers of a stream, in the form encoder libraries tend |
| // to want them. Unlike `VideoBitrateAllocation` the bitrates are cumulative |
| // over the temporal layers: the entry for a temporal layer describes the stream |
| // made up of that layer and all the temporal layers below it, within the same |
| // spatial layer. They are not cumulative over the spatial layers. |
| struct CumulativeCbrAllocation { |
| static constexpr int kMaxSpatialLayers = 4; |
| static constexpr int kMaxTemporalLayers = 4; |
| |
| // The bitrate of all temporal layers of spatial layer `spatial_id` combined. |
| DataRate SpatialLayerBitrate(int spatial_id) const; |
| |
| // The bitrate of all layers combined. |
| DataRate TotalBitrate() const; |
| |
| bool operator==(const CumulativeCbrAllocation& other) const = default; |
| |
| // The number of temporal layers seen so far, or the number guessed from the |
| // start of the stream. At least one. |
| int num_temporal_layers = 1; |
| |
| // How many times lower the frame rate of the stream made up of the temporal |
| // layers up to and including a given one is compared to the frame rate of |
| // the full stream. Shared by all spatial layers. Rounded to an integer, which |
| // is exact for the dyadic temporal structures used in practice. Entries from |
| // the topmost temporal layer and up are one. |
| std::array<int, kMaxTemporalLayers> framerate_factor = {1, 1, 1, 1}; |
| |
| // `bitrate[spatial_id][temporal_id]` is the bitrate of the stream made up of |
| // the temporal layers up to and including `temporal_id` within spatial layer |
| // `spatial_id`. Entries above the topmost temporal layer repeat its value, |
| // and spatial layers never heard from are zero. |
| std::array<std::array<DataRate, kMaxTemporalLayers>, kMaxSpatialLayers> |
| bitrate = {}; |
| }; |
| |
| // Aggregates rate control decisions that are made per frame into a |
| // `CumulativeCbrAllocation`. |
| // |
| // Callers of `VideoEncoderInterface` state the bitrate of the 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 temporal |
| // 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 each |
| // temporal layer occur. |
| // |
| // Only the layers present in a temporal unit are heard from, so the bitrate of |
| // the others has to be assumed until they report. A caller that changes the |
| // allocation normally scales all of the temporal layers of a spatial layer by |
| // the same factor, so a change seen on one of them is taken to apply to those |
| // 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. Spatial layers are independent of each other in this respect. A |
| // layer without a frame in a temporal unit, be it a temporal or a spatial one, |
| // keeps the bitrate it last had. |
| // |
| // How often the frames of a temporal layer occur cannot be stated by the |
| // caller and is measured instead, and smoothed. All spatial layers are assumed |
| // to share one temporal structure, though not necessarily in phase: the cadence |
| // is measured from the first frame of each temporal unit, so a structure that |
| // shifts the spatial layers relative to each other, such as L2T2_KEY_SHIFT, is |
| // described correctly as well. To avoid a transient at the start of a stream, |
| // the state is primed on the first temporal unit after a keyframe under the |
| // assumption that a standard dyadic temporal pattern is used: in such a pattern |
| // that temporal unit 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 CbrLayerRateTracker { |
| public: |
| static constexpr int kMaxSpatialLayers = |
| CumulativeCbrAllocation::kMaxSpatialLayers; |
| static constexpr int kMaxTemporalLayers = |
| CumulativeCbrAllocation::kMaxTemporalLayers; |
| |
| CbrLayerRateTracker(); |
| |
| // Records the frames of one temporal unit, as passed to |
| // `VideoEncoderInterface::Encode`. Temporal units must be passed in encode |
| // order. Frames that do not use `Cbr` rate options are ignored. |
| void OnTemporalUnit( |
| std::span<const VideoEncoderInterface::FrameEncodeSettings> frames); |
| |
| // The allocation as of the most recent temporal unit. |
| const CumulativeCbrAllocation& allocation() const { return allocation_; } |
| |
| 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 frame intervals as if a standard dyadic pattern with |
| // `num_layers` temporal layers was in use. |
| void PrimeStandardPattern(int num_layers); |
| |
| // Guesses the bitrates of the temporal layers of spatial layer `spatial_id` |
| // in a standard dyadic pattern with `num_layers` temporal layers, where the |
| // just observed topmost layer frame reported `delta_bitrate`. |
| void PrimeLayerRates(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 FramerateFactor(int temporal_id) const; |
| DataRate CumulativeBitrate(int spatial_id, int temporal_id) const; |
| |
| // Recomputes `allocation_` from the state. |
| void UpdateAllocation(); |
| |
| int num_temporal_layers_ = 1; |
| bool last_unit_had_keyframe_ = false; |
| // 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_; |
| |
| CumulativeCbrAllocation allocation_; |
| }; |
| |
| } // namespace webrtc |
| |
| #endif // MODULES_VIDEO_CODING_UTILITY_CBR_LAYER_RATE_TRACKER_H_ |