blob: daed9fe3a7fa9cd2f9f0f9d7e3b75e8f3cc9492a [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.
*/
#include "modules/video_coding/svc/reference_buffer_manager.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <span>
#include <vector>
#include "absl/algorithm/container.h"
#include "api/units/timestamp.h"
#include "api/video_codecs/video_encoder_interface.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
namespace webrtc {
using BufferSpaceType =
ReferenceBufferManager::PredictionConstraints::BufferSpaceType;
using FrameType = VideoEncoderInterface::FrameType;
ReferenceBufferManager::ReferenceBufferManager(
const PredictionConstraints& constraints)
: buffer_space_type_(constraints.buffer_space_type()),
num_spatial_layers_(std::max(1, constraints.max_spatial_layers())),
pools_(buffer_space_type_ == BufferSpaceType::kMultiInstance
? num_spatial_layers_
: 1,
std::vector<Slot>(constraints.num_buffers())) {
RTC_DCHECK_GT(constraints.num_buffers(), 0);
}
int ReferenceBufferManager::NumFreeBuffers(int spatial_id) const {
return absl::c_count_if(Pool(spatial_id), [](const Slot& slot) {
return !slot.reserved && !slot.metadata.has_value();
});
}
std::optional<ReferenceBufferManager::Buffer>
ReferenceBufferManager::ReserveBuffer(int spatial_id) {
std::span<Slot> pool = Pool(spatial_id);
for (size_t i = 0; i < pool.size(); ++i) {
Slot& slot = pool[i];
if (!slot.reserved && !slot.metadata.has_value()) {
slot.reserved = true;
slot.spatial_id = spatial_id;
return Buffer{.id = static_cast<int>(i), .spatial_id = spatial_id};
}
}
return std::nullopt;
}
void ReferenceBufferManager::SetBufferMetadata(Buffer buffer,
const Metadata& metadata) {
RTC_DCHECK_EQ(buffer.spatial_id, metadata.spatial_id);
Slot& slot = GetSlot(buffer);
RTC_DCHECK(slot.reserved) << "Buffer " << buffer.id << " is not reserved.";
RTC_DCHECK_EQ(slot.spatial_id, buffer.spatial_id);
if (metadata.type == FrameType::kKeyframe) {
switch (buffer_space_type_) {
case BufferSpaceType::kMultiInstance:
case BufferSpaceType::kSingleKeyframe:
// Pool is either private to this layer or invalidated as a whole.
for (Slot& other : Pool(buffer.spatial_id)) {
other.metadata.reset();
}
break;
case BufferSpaceType::kMultiKeyframe:
ReleaseIf(buffer.spatial_id, [](const Slot&) { return true; });
break;
}
}
slot.reserved = false;
slot.metadata = metadata;
slot.sequence_number = next_sequence_number_++;
}
std::optional<ReferenceBufferManager::Metadata>
ReferenceBufferManager::GetMetadata(Buffer buffer) const {
std::span<const Slot> pool = Pool(buffer.spatial_id);
RTC_DCHECK_GE(buffer.id, 0);
RTC_DCHECK_LT(buffer.id, pool.size());
const Slot& slot = pool[buffer.id];
if (slot.spatial_id != buffer.spatial_id) {
return std::nullopt;
}
return slot.metadata;
}
std::optional<ReferenceBufferManager::Buffer> ReferenceBufferManager::Get(
int spatial_id,
int temporal_id) const {
return FindLast(spatial_id, [&](const Metadata& metadata) {
return metadata.temporal_id == temporal_id;
});
}
std::optional<ReferenceBufferManager::Buffer>
ReferenceBufferManager::GetLastTl0(int spatial_id) const {
return Get(spatial_id, /*temporal_id=*/0);
}
std::vector<ReferenceBufferManager::Buffer>
ReferenceBufferManager::GetAllLowerTemporalLayers(int spatial_id,
int temporal_id) const {
std::vector<Buffer> buffers;
for (int tid = 0; tid < temporal_id; ++tid) {
if (std::optional<Buffer> buffer = Get(spatial_id, tid)) {
buffers.push_back(*buffer);
}
}
return buffers;
}
std::vector<ReferenceBufferManager::Buffer>
ReferenceBufferManager::SortByRecency(std::span<const Buffer> buffers) const {
std::vector<Buffer> sorted;
sorted.reserve(buffers.size());
for (Buffer buffer : buffers) {
if (!GetMetadata(buffer).has_value()) {
RTC_LOG(LS_WARNING) << "Ignoring unpopulated buffer " << buffer.id
<< " (spatial_id=" << buffer.spatial_id << ").";
continue;
}
sorted.push_back(buffer);
}
absl::c_stable_sort(sorted, [&](Buffer a, Buffer b) {
return Pool(a.spatial_id)[a.id].sequence_number >
Pool(b.spatial_id)[b.id].sequence_number;
});
return sorted;
}
void ReferenceBufferManager::MarkFrameAsAcknowledged(int spatial_id,
int frame_id) {
for (Slot& slot : Pool(spatial_id)) {
if (slot.spatial_id == spatial_id && slot.metadata.has_value() &&
slot.metadata->frame_id == frame_id) {
slot.metadata->acknowledged = true;
}
}
}
std::optional<ReferenceBufferManager::Buffer>
ReferenceBufferManager::GetLastAcknowledgedFrame(int spatial_id) const {
return FindLast(spatial_id, [](const Metadata& metadata) {
return metadata.acknowledged;
});
}
void ReferenceBufferManager::ReleaseBuffer(Buffer buffer) {
Slot& slot = GetSlot(buffer);
if (slot.spatial_id != buffer.spatial_id) {
// Already released and reserved by another layer.
return;
}
slot.reserved = false;
slot.metadata.reset();
}
void ReferenceBufferManager::ReleaseAllHigherTemporalLayers(int spatial_id,
int temporal_id) {
ReleaseIf(spatial_id, [&](const Slot& slot) {
return slot.metadata->temporal_id.has_value() &&
*slot.metadata->temporal_id > temporal_id;
});
}
void ReferenceBufferManager::ReleaseAllPriorTemporalUnits(int spatial_id,
Timestamp cutoff) {
ReleaseIf(spatial_id, [&](const Slot& slot) {
return slot.metadata->timestamp < cutoff;
});
}
void ReferenceBufferManager::ReleaseAllBuffers(int spatial_id) {
for (Slot& slot : Pool(spatial_id)) {
if (slot.spatial_id == spatial_id) {
slot.reserved = false;
slot.metadata.reset();
}
}
}
std::span<ReferenceBufferManager::Slot> ReferenceBufferManager::Pool(
int spatial_id) {
RTC_DCHECK_GE(spatial_id, 0);
RTC_DCHECK_LT(spatial_id, num_spatial_layers_);
return pools_[std::clamp<int>(spatial_id, 0, pools_.size() - 1)];
}
std::span<const ReferenceBufferManager::Slot> ReferenceBufferManager::Pool(
int spatial_id) const {
RTC_DCHECK_GE(spatial_id, 0);
RTC_DCHECK_LT(spatial_id, num_spatial_layers_);
return pools_[std::clamp<int>(spatial_id, 0, pools_.size() - 1)];
}
ReferenceBufferManager::Slot& ReferenceBufferManager::GetSlot(Buffer buffer) {
std::span<Slot> pool = Pool(buffer.spatial_id);
RTC_DCHECK_GE(buffer.id, 0);
RTC_DCHECK_LT(buffer.id, pool.size());
return pool[buffer.id];
}
template <typename Predicate>
std::optional<ReferenceBufferManager::Buffer> ReferenceBufferManager::FindLast(
int spatial_id,
Predicate predicate) const {
std::span<const Slot> pool = Pool(spatial_id);
std::optional<Buffer> last;
int64_t last_sequence_number = -1;
for (size_t i = 0; i < pool.size(); ++i) {
const Slot& slot = pool[i];
if (slot.spatial_id == spatial_id && slot.metadata.has_value() &&
slot.sequence_number > last_sequence_number &&
predicate(*slot.metadata)) {
last = Buffer{.id = static_cast<int>(i), .spatial_id = spatial_id};
last_sequence_number = slot.sequence_number;
}
}
return last;
}
template <typename Predicate>
void ReferenceBufferManager::ReleaseIf(int spatial_id, Predicate predicate) {
for (Slot& slot : Pool(spatial_id)) {
if (slot.spatial_id == spatial_id && slot.metadata.has_value() &&
predicate(slot)) {
slot.metadata.reset();
}
}
}
} // namespace webrtc