blob: dfd7ec854b4fc1ce32bea962f60ab03b543c8ac4 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "codec_adapter_vaapi_decoder.h"
#include <lib/async/cpp/task.h>
#include <lib/fit/defer.h>
#include <lib/stdcompat/span.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <condition_variable>
#include <mutex>
#include <optional>
#include <unordered_map>
#include <fbl/algorithm.h>
#include <safemath/checked_math.h>
#include <va/va_drmcommon.h>
#include "geometry.h"
#include "h264_accelerator.h"
#include "media/gpu/h264_decoder.h"
#include "media/gpu/vp9_decoder.h"
#include "vp9_accelerator.h"
#define LOG(x, ...) fprintf(stderr, __VA_ARGS__)
// This class manages output buffers when the client selects a linear buffer output. Since the
// output is linear the client will have to deswizzle the output from the decoded picture buffer
// (DPB) meaning that we can't directly share the output with the client. The manager will be
// responsible for creating the DPB surfaces used by the decoder and reconstructing them when a mid
// stream configuration change is required. This buffer manager will also be responsible for copying
// the output from the DBPs to the CodecBuffers the client provides us.
class LinearBufferManager : public SurfaceBufferManager {
public:
LinearBufferManager(std::mutex& codec_lock) : SurfaceBufferManager(codec_lock) {}
~LinearBufferManager() override = default;
void AddBuffer(const CodecBuffer* buffer) override { output_buffer_pool_.AddBuffer(buffer); }
void RecycleBuffer(const CodecBuffer* buffer) override {
LinearOutput local_output;
{
std::lock_guard<std::mutex> guard(codec_lock_);
ZX_DEBUG_ASSERT(in_use_by_client_.find(buffer) != in_use_by_client_.end());
local_output = std::move(in_use_by_client_[buffer]);
in_use_by_client_.erase(buffer);
}
// ~ local_output, which may trigger a buffer free callback.
}
void DeconfigureBuffers() override {
{
std::map<const CodecBuffer*, LinearOutput> to_drop;
{
std::lock_guard<std::mutex> lock(codec_lock_);
std::swap(to_drop, in_use_by_client_);
}
}
// ~to_drop
ZX_DEBUG_ASSERT(!output_buffer_pool_.has_buffers_in_use());
}
scoped_refptr<VASurface> GetDPBSurface() override {
uint64_t surface_generation;
VASurfaceID surface_id;
gfx::Size pic_size;
{
std::lock_guard<std::mutex> guard(surface_lock_);
if (surfaces_.empty()) {
return {};
}
surface_id = surfaces_.back().release();
surfaces_.pop_back();
surface_generation = surface_generation_;
pic_size = surface_size_;
}
VASurface::ReleaseCB release_cb = [this, surface_generation](VASurfaceID surface_id) {
std::lock_guard lock(surface_lock_);
if (surface_generation_ == surface_generation) {
surfaces_.emplace_back(surface_id);
} else {
auto status =
vaDestroySurfaces(VADisplayWrapper::GetSingleton()->display(), &surface_id, 1);
if (status != VA_STATUS_SUCCESS) {
FX_LOGS(WARNING) << "vaDestroySurfaces failed: " << vaErrorStr(status);
}
}
};
return std::make_shared<VASurface>(surface_id, pic_size, VA_RT_FORMAT_YUV420,
std::move(release_cb));
}
std::optional<std::pair<const CodecBuffer*, uint32_t>> ProcessOutputSurface(
scoped_refptr<VASurface> va_surface) override {
const CodecBuffer* buffer = output_buffer_pool_.AllocateBuffer();
if (!buffer) {
return std::nullopt;
}
// If any errors happen, release the buffer back into the pool
auto release_buffer = fit::defer([&]() { output_buffer_pool_.FreeBuffer(buffer->base()); });
const auto surface_size = va_surface->size();
const auto aligned_stride_checked = GetAlignedStride(surface_size);
const auto& [y_plane_checked, uv_plane_checked] = GetSurfacePlaneSizes(surface_size);
const auto pic_size_checked = (y_plane_checked + uv_plane_checked).Cast<uint32_t>();
if (!pic_size_checked.IsValid()) {
FX_LOGS(WARNING) << "Output picture size overflowed";
return std::nullopt;
}
size_t pic_size_bytes = static_cast<size_t>(pic_size_checked.ValueOrDie());
ZX_ASSERT(buffer->size() >= pic_size_bytes);
zx::vmo vmo_dup;
zx_status_t zx_status = buffer->vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_dup);
if (zx_status != ZX_OK) {
FX_LOGS(WARNING) << "Failed to duplicate vmo " << zx_status_get_string(zx_status);
return std::nullopt;
}
// For the moment we use DRM_PRIME_2 to represent VMOs.
// To specify the destination VMO, we need two VASurfaceAttrib, one to set the
// VASurfaceAttribMemoryType to VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 and one for the
// VADRMPRIMESurfaceDescriptor.
VADRMPRIMESurfaceDescriptor ext_attrib{};
VASurfaceAttrib attrib[2] = {
{.type = VASurfaceAttribMemoryType,
.flags = VA_SURFACE_ATTRIB_SETTABLE,
.value = {.type = VAGenericValueTypeInteger,
.value = {.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2}}},
{.type = VASurfaceAttribExternalBufferDescriptor,
.flags = VA_SURFACE_ATTRIB_SETTABLE,
.value = {.type = VAGenericValueTypePointer, .value = {.p = &ext_attrib}}},
};
// VADRMPRIMESurfaceDescriptor
ext_attrib.width = surface_size.width();
ext_attrib.height = surface_size.height();
ext_attrib.fourcc = VA_FOURCC_NV12; // 2 plane YCbCr
ext_attrib.num_objects = 1;
ext_attrib.objects[0].fd = vmo_dup.release();
ext_attrib.objects[0].drm_format_modifier = fuchsia::sysmem::FORMAT_MODIFIER_LINEAR;
ext_attrib.objects[0].size = pic_size_checked.ValueOrDie();
ext_attrib.num_layers = 1;
ext_attrib.layers[0].drm_format = make_fourcc('N', 'V', '1', '2');
ext_attrib.layers[0].num_planes = 2;
// Y plane
ext_attrib.layers[0].object_index[0] = 0;
ext_attrib.layers[0].pitch[0] = aligned_stride_checked.ValueOrDie();
ext_attrib.layers[0].offset[0] = 0;
// UV Plane
ext_attrib.layers[0].object_index[1] = 0;
ext_attrib.layers[0].pitch[1] = aligned_stride_checked.ValueOrDie();
ext_attrib.layers[0].offset[1] = y_plane_checked.ValueOrDie();
VASurfaceID processed_surface_id;
// Create one surface backed by the destination VMO.
VAStatus status = vaCreateSurfaces(VADisplayWrapper::GetSingleton()->display(),
VA_RT_FORMAT_YUV420, surface_size.width(),
surface_size.height(), &processed_surface_id, 1, attrib, 2);
if (status != VA_STATUS_SUCCESS) {
FX_LOGS(WARNING) << "CreateSurface failed: " << vaErrorStr(status);
return std::nullopt;
}
ScopedSurfaceID processed_surface(processed_surface_id);
// Set up a VAImage for the destination VMO.
VAImage image;
status =
vaDeriveImage(VADisplayWrapper::GetSingleton()->display(), processed_surface.id(), &image);
if (status != VA_STATUS_SUCCESS) {
FX_LOGS(WARNING) << "DeriveImage failed: " << vaErrorStr(status);
return std::nullopt;
}
{
ScopedImageID scoped_image(image.image_id);
// Copy from potentially-tiled surface to output surface. Intel decoders only
// support writing to Y-tiled textures, so this copy is necessary for linear
// output.
status = vaGetImage(VADisplayWrapper::GetSingleton()->display(), va_surface->id(), 0, 0,
surface_size.width(), surface_size.height(), scoped_image.id());
if (status != VA_STATUS_SUCCESS) {
FX_LOGS(WARNING) << "GetImage failed: " << vaErrorStr(status);
return std::nullopt;
}
}
// ~processed_surface: Clean up the image; the data was already copied to the destination VMO
// above.
{
std::lock_guard<std::mutex> guard(codec_lock_);
ZX_DEBUG_ASSERT(in_use_by_client_.count(buffer) == 0);
in_use_by_client_.emplace(buffer, LinearOutput(buffer, this));
}
// ~guard
// LinearOutput has taken ownership of the buffer.
release_buffer.cancel();
return std::make_pair(buffer, pic_size_checked.ValueOrDie());
}
void Reset() override { output_buffer_pool_.Reset(true); }
void StopAllWaits() override { output_buffer_pool_.StopAllWaits(); }
protected:
void OnSurfaceGenerationUpdatedLocked(size_t num_of_surfaces, uint32_t output_stride)
FXL_REQUIRE(surface_lock_) override {
// Clear all existing DPB surfaces
surfaces_.clear();
std::vector<VASurfaceID> va_surfaces(num_of_surfaces, 0);
VAStatus va_res =
vaCreateSurfaces(VADisplayWrapper::GetSingleton()->display(), VA_RT_FORMAT_YUV420,
surface_size_.width(), surface_size_.height(), va_surfaces.data(),
static_cast<uint32_t>(va_surfaces.size()), nullptr, 0);
if (va_res != VA_STATUS_SUCCESS) {
// TODO(stefanbossbaly): Fix this
#if 0
SetCodecFailure("vaCreateSurfaces failed: %s", vaErrorStr(va_res));
#endif
return;
}
for (VASurfaceID id : va_surfaces) {
surfaces_.emplace_back(id);
}
output_stride_ = output_stride;
}
private:
safemath::internal::CheckedNumeric<uint32_t> GetAlignedStride(const gfx::Size& size) const {
ZX_DEBUG_ASSERT(output_stride_.has_value());
uint32_t output_stride = output_stride_.value();
auto aligned_stride = fbl::round_up(static_cast<uint64_t>(size.width()), output_stride);
return safemath::MakeCheckedNum(aligned_stride).Cast<uint32_t>();
}
std::pair<safemath::internal::CheckedNumeric<uint32_t>,
safemath::internal::CheckedNumeric<uint32_t>>
GetSurfacePlaneSizes(const gfx::Size& size) {
// Depending on if the output is tiled or not we have to align our planes on tile boundaries
// for both width and height
auto aligned_stride = GetAlignedStride(size);
auto aligned_y_height = static_cast<uint32_t>(size.height());
auto aligned_uv_height = static_cast<uint32_t>(size.height()) / 2u;
auto y_plane_size = safemath::CheckMul(aligned_stride, aligned_y_height);
auto uv_plane_size = safemath::CheckMul(aligned_stride, aligned_uv_height);
return std::make_pair(y_plane_size, uv_plane_size);
}
// VA-API outputs are distinct from the DPB and are stored in a regular
// BufferPool, since the hardware doesn't necessarily support decoding to a
// linear format like downstream consumers might need.
class LinearOutput {
public:
LinearOutput() = default;
LinearOutput(const CodecBuffer* buffer, LinearBufferManager* buffer_manager)
: codec_buffer_(buffer), buffer_manager_(buffer_manager) {}
~LinearOutput() {
if (buffer_manager_) {
buffer_manager_->output_buffer_pool_.FreeBuffer(codec_buffer_->base());
}
}
// Delete copying
LinearOutput(const LinearOutput&) noexcept = delete;
LinearOutput& operator=(const LinearOutput&) noexcept = delete;
// Allow moving
LinearOutput(LinearOutput&& other) noexcept {
codec_buffer_ = other.codec_buffer_;
buffer_manager_ = other.buffer_manager_;
other.buffer_manager_ = nullptr;
}
LinearOutput& operator=(LinearOutput&& other) noexcept {
codec_buffer_ = other.codec_buffer_;
buffer_manager_ = other.buffer_manager_;
other.buffer_manager_ = nullptr;
return *this;
}
private:
const CodecBuffer* codec_buffer_ = nullptr;
LinearBufferManager* buffer_manager_ = nullptr;
};
// The order of output_buffer_pool_ and in_use_by_client_ matters, so that
// destruction of in_use_by_client_ happens first, because those destructing
// will return buffers to output_buffer_pool_.
BufferPool output_buffer_pool_;
std::map<const CodecBuffer*, LinearOutput> in_use_by_client_ FXL_GUARDED_BY(codec_lock_);
// Holds the DPB surfaces
std::vector<ScopedSurfaceID> surfaces_ FXL_GUARDED_BY(surface_lock_) = {};
// Output stride
std::optional<uint32_t> output_stride_;
};
// This class manages output buffers when the client selects a tiled buffer output. Since the output
// is tiled the client will directly share the output from the decoded picture buffer (DPB). The
// manager will be responsible for creating the DPB surfaces that are backed by CodecBuffers the
// client provides us. The manager is also responsible for reconfiguring surfaces when a mid stream
// configuration change is required.
class TiledBufferManager : public SurfaceBufferManager {
public:
TiledBufferManager(std::mutex& codec_lock) : SurfaceBufferManager(codec_lock) {}
~TiledBufferManager() override = default;
void AddBuffer(const CodecBuffer* buffer) override { output_buffer_pool_.AddBuffer(buffer); }
void RecycleBuffer(const CodecBuffer* buffer) override {
scoped_refptr<VASurface> to_drop;
{
std::lock_guard<std::mutex> guard(codec_lock_);
ZX_DEBUG_ASSERT(in_use_by_client_.count(buffer) != 0);
auto map_itr = in_use_by_client_.find(buffer);
to_drop = std::move(map_itr->second);
in_use_by_client_.erase(map_itr);
}
// ~ to_drop, which may trigger a buffer free callback if the decoder is no longer referencing
// the frame
}
void DeconfigureBuffers() override {
// Drop all references to buffers referenced by the client but keep the ones referenced by the
// decoder
{
std::unordered_multimap<const CodecBuffer*, scoped_refptr<VASurface>> to_drop;
{
std::lock_guard<std::mutex> lock(codec_lock_);
std::swap(to_drop, in_use_by_client_);
}
}
// ~to_drop
ZX_DEBUG_ASSERT(!output_buffer_pool_.has_buffers_in_use());
}
// Getting a DPB requires that the surface is not in use by the client. This differs from the
// linear version where DPB were not backed by a VMO. This function will block until a buffer is
// recycled by the client or the manager is reset by the codec.
scoped_refptr<VASurface> GetDPBSurface() override {
const CodecBuffer* buffer = output_buffer_pool_.AllocateBuffer();
if (!buffer) {
return {};
}
// If any errors happen, release the buffer back into the pool
auto release_buffer = fit::defer([&]() { output_buffer_pool_.FreeBuffer(buffer->base()); });
std::lock_guard<std::mutex> guard(surface_lock_);
VASurfaceID vmo_surface_id;
// Check to see if there already is a surface allocated for this buffer
auto map_itr = allocated_free_surfaces_.find(buffer);
if (map_itr != allocated_free_surfaces_.end()) {
vmo_surface_id = map_itr->second.release();
allocated_free_surfaces_.erase(map_itr);
} else {
zx::vmo vmo_dup;
zx_status_t zx_status = buffer->vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_dup);
if (zx_status != ZX_OK) {
FX_LOGS(WARNING) << "Failed to duplicate vmo " << zx_status_get_string(zx_status);
return {};
}
const auto aligned_stride_checked = GetAlignedStride(surface_size_);
const auto& [y_plane_checked, uv_plane_checked] = GetSurfacePlaneSizes(surface_size_);
const auto pic_size_checked = (y_plane_checked + uv_plane_checked).Cast<uint32_t>();
if (!aligned_stride_checked.IsValid()) {
FX_LOGS(WARNING) << "Aligned stride overflowed";
return {};
}
if (!pic_size_checked.IsValid()) {
FX_LOGS(WARNING) << "Output picture size overflowed";
return {};
}
size_t pic_size_bytes = static_cast<size_t>(pic_size_checked.ValueOrDie());
ZX_ASSERT(buffer->size() >= pic_size_bytes);
// For the moment we use DRM_PRIME_2 to represent VMOs.
// To specify the destination VMO, we need two VASurfaceAttrib, one to set the
// VASurfaceAttribMemoryType to VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 and one for the
// VADRMPRIMESurfaceDescriptor.
VADRMPRIMESurfaceDescriptor ext_attrib{};
VASurfaceAttrib attrib[2] = {
{.type = VASurfaceAttribMemoryType,
.flags = VA_SURFACE_ATTRIB_SETTABLE,
.value = {.type = VAGenericValueTypeInteger,
.value = {.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2}}},
{.type = VASurfaceAttribExternalBufferDescriptor,
.flags = VA_SURFACE_ATTRIB_SETTABLE,
.value = {.type = VAGenericValueTypePointer, .value = {.p = &ext_attrib}}},
};
// VADRMPRIMESurfaceDescriptor
ext_attrib.width = surface_size_.width();
ext_attrib.height = surface_size_.height();
ext_attrib.fourcc = VA_FOURCC_NV12; // 2 plane YCbCr
ext_attrib.num_objects = 1;
ext_attrib.objects[0].fd = vmo_dup.release();
ext_attrib.objects[0].drm_format_modifier =
fuchsia::sysmem::FORMAT_MODIFIER_INTEL_I915_Y_TILED;
ext_attrib.objects[0].size = pic_size_checked.ValueOrDie();
ext_attrib.num_layers = 1;
ext_attrib.layers[0].drm_format = make_fourcc('N', 'V', '1', '2');
ext_attrib.layers[0].num_planes = 2;
// Y plane
ext_attrib.layers[0].object_index[0] = 0;
ext_attrib.layers[0].pitch[0] = aligned_stride_checked.ValueOrDie();
ext_attrib.layers[0].offset[0] = 0;
// UV Plane
ext_attrib.layers[0].object_index[1] = 0;
ext_attrib.layers[0].pitch[1] = aligned_stride_checked.ValueOrDie();
ext_attrib.layers[0].offset[1] = y_plane_checked.ValueOrDie();
// Create one surface backed by the destination VMO.
VAStatus status = vaCreateSurfaces(VADisplayWrapper::GetSingleton()->display(),
VA_RT_FORMAT_YUV420, surface_size_.width(),
surface_size_.height(), &vmo_surface_id, 1, attrib, 2);
if (status != VA_STATUS_SUCCESS) {
FX_LOGS(WARNING) << "CreateSurface failed: " << vaErrorStr(status);
return {};
}
}
gfx::Size pic_size = surface_size_;
uint64_t surface_generation = surface_generation_;
// Callback that is called when the ref_count of this new constructed surface hits 0, This
// occurs when the surface is no longer being used in the decoder (aka a new frame has replaced
// us) and is no longer in use by the client (surface has been removed from in_use_by_client_).
// Therefore once the VASurface release callback is called we can return this surface (and
// therefore the VMO backing the surface) back into the pool of available surfaces.
VASurface::ReleaseCB release_cb = [this, buffer, surface_generation](VASurfaceID surface_id) {
{
std::lock_guard<std::mutex> guard(surface_lock_);
ZX_ASSERT(surface_to_buffer_.erase(surface_id) == 1);
if (surface_generation_ == surface_generation) {
allocated_free_surfaces_.emplace(buffer, surface_id);
} else {
auto status =
vaDestroySurfaces(VADisplayWrapper::GetSingleton()->display(), &surface_id, 1);
if (status != VA_STATUS_SUCCESS) {
FX_LOGS(ERROR) << "vaDestroySurfaces failed: " << vaErrorStr(status);
}
}
}
// ~guard
output_buffer_pool_.FreeBuffer(buffer->base());
};
ZX_DEBUG_ASSERT(surface_to_buffer_.count(vmo_surface_id) == 0);
surface_to_buffer_.emplace(vmo_surface_id, buffer);
release_buffer.cancel();
return std::make_shared<VASurface>(vmo_surface_id, pic_size, VA_RT_FORMAT_YUV420,
std::move(release_cb));
}
std::optional<std::pair<const CodecBuffer*, uint32_t>> ProcessOutputSurface(
scoped_refptr<VASurface> va_surface) override {
const CodecBuffer* buffer = nullptr;
{
std::lock_guard<std::mutex> guard(surface_lock_);
ZX_DEBUG_ASSERT(surface_to_buffer_.count(va_surface->id()) != 0);
buffer = surface_to_buffer_[va_surface->id()];
}
if (!buffer) {
return {};
}
const auto& [y_plane_checked, uv_plane_checked] = GetSurfacePlaneSizes(va_surface->size());
const auto pic_size_checked = (y_plane_checked + uv_plane_checked).Cast<uint32_t>();
if (!pic_size_checked.IsValid()) {
FX_LOGS(WARNING) << "Output picture size overflowed";
return {};
}
// We are about to lend out the surface to the client so store the surface in in_use_by_client_
// multimap so it increments the refcount until the client recycles it
{
std::lock_guard<std::mutex> guard(codec_lock_);
in_use_by_client_.insert(std::make_pair(buffer, va_surface));
}
return std::make_pair(buffer, pic_size_checked.ValueOrDie());
}
void Reset() override { output_buffer_pool_.Reset(true); }
void StopAllWaits() override { output_buffer_pool_.StopAllWaits(); }
protected:
void OnSurfaceGenerationUpdatedLocked(size_t num_of_surfaces, uint32_t output_stride)
FXL_REQUIRE(surface_lock_) override {
// This will call vaDestroySurface on all surfaces held by this data structure. Don't need to
// reconstruct the surfaces here. They will be reconstructed once GetDPBSurface() is called and
// the buffer has no linked surface.
allocated_free_surfaces_.clear();
}
private:
static safemath::internal::CheckedNumeric<uint32_t> GetAlignedStride(const gfx::Size& size) {
auto aligned_stride = fbl::round_up(static_cast<uint64_t>(size.width()),
CodecAdapterVaApiDecoder::kTileWidthAlignment);
return safemath::MakeCheckedNum(aligned_stride).Cast<uint32_t>();
}
static std::pair<safemath::internal::CheckedNumeric<uint32_t>,
safemath::internal::CheckedNumeric<uint32_t>>
GetSurfacePlaneSizes(const gfx::Size& size) {
// Depending on if the output is tiled or not we have to align our planes on tile boundaries
// for both width and height
auto aligned_stride = GetAlignedStride(size);
auto aligned_y_height = static_cast<uint32_t>(size.height());
auto aligned_uv_height = static_cast<uint32_t>(size.height()) / 2u;
aligned_y_height =
fbl::round_up(aligned_y_height, CodecAdapterVaApiDecoder::kTileHeightAlignment);
aligned_uv_height =
fbl::round_up(aligned_uv_height, CodecAdapterVaApiDecoder::kTileHeightAlignment);
auto y_plane_size = safemath::CheckMul(aligned_stride, aligned_y_height);
auto uv_plane_size = safemath::CheckMul(aligned_stride, aligned_uv_height);
return std::make_pair(y_plane_size, uv_plane_size);
}
// Structure that maps allocated buffers shared with the client. Once the buffer is no longer in
// use by the client and the decoder it should be removed from this map and marked as free in the
// output_buffer_pool_.
std::unordered_map<VASurfaceID, const CodecBuffer*> surface_to_buffer_
FXL_GUARDED_BY(surface_lock_);
// Once a surface is allocated it is stored in this map which maps the codec buffer that backs
// the surface. If a resize event happens this structure will have to be invalidated and the
// surfaces will have to be regenerated to match the new surface_size_
std::unordered_map<const CodecBuffer*, ScopedSurfaceID> allocated_free_surfaces_
FXL_GUARDED_BY(surface_lock_);
// Maps the codec buffer to the VA surface being shared to the client. In addition to the
// mapping this data structure holds a reference to the surface being used by the client,
// preventing it from being destructed prior to it being recycled.
// This has to be a multimap because it is possible to lend out the same surface concurrently to
// the client and we don't want the destructor of the VASurface to be called when only one of the
// lent out surfaces is recycled. For example on VP9 if show_existing_frame is marked true, we can
// lend out the same surface concurrently.
std::unordered_multimap<const CodecBuffer*, scoped_refptr<VASurface>> in_use_by_client_
FXL_GUARDED_BY(codec_lock_);
};
void CodecAdapterVaApiDecoder::CoreCodecInit(
const fuchsia::media::FormatDetails& initial_input_format_details) {
if (!initial_input_format_details.has_format_details_version_ordinal()) {
SetCodecFailure("CoreCodecInit(): Initial input format details missing version ordinal.");
return;
}
// Will always be 0 for now.
input_format_details_version_ordinal_ =
initial_input_format_details.format_details_version_ordinal();
const std::string& mime_type = initial_input_format_details.mime_type();
if (mime_type == "video/h264-multi" || mime_type == "video/h264") {
media_decoder_ = std::make_unique<media::H264Decoder>(std::make_unique<H264Accelerator>(this),
media::H264PROFILE_HIGH);
is_h264_ = true;
} else if (mime_type == "video/vp9") {
media_decoder_ = std::make_unique<media::VP9Decoder>(std::make_unique<VP9Accelerator>(this),
media::VP9PROFILE_PROFILE0);
} else {
SetCodecFailure("CodecCodecInit(): Unknown mime_type %s\n", mime_type.c_str());
return;
}
if (codec_diagnostics_) {
std::string codec_name = is_h264_ ? "H264" : "VP9";
codec_instance_diagnostics_ = codec_diagnostics_->CreateComponentCodec(codec_name);
}
VAConfigAttrib attribs[1] = {{.type = VAConfigAttribRTFormat, .value = VA_RT_FORMAT_YUV420}};
VAConfigID config_id;
VAEntrypoint va_entrypoint = VAEntrypointVLD;
VAStatus va_status;
VAProfile va_profile;
if (mime_type == "video/h264-multi" || mime_type == "video/h264") {
va_profile = VAProfileH264High;
} else if (mime_type == "video/vp9") {
va_profile = VAProfileVP9Profile0;
} else {
SetCodecFailure("CodecCodecInit(): Unknown mime_type %s\n", mime_type.c_str());
return;
}
va_status = vaCreateConfig(VADisplayWrapper::GetSingleton()->display(), va_profile, va_entrypoint,
attribs, std::size(attribs), &config_id);
if (va_status != VA_STATUS_SUCCESS) {
SetCodecFailure("CodecCodecInit(): Failed to create config: %s", vaErrorStr(va_status));
return;
}
config_.emplace(config_id);
int max_config_attributes = vaMaxNumConfigAttributes(VADisplayWrapper::GetSingleton()->display());
std::vector<VAConfigAttrib> config_attributes(max_config_attributes);
int num_config_attributes;
va_status = vaQueryConfigAttributes(VADisplayWrapper::GetSingleton()->display(), config_->id(),
&va_profile, &va_entrypoint, config_attributes.data(),
&num_config_attributes);
if (va_status != VA_STATUS_SUCCESS) {
SetCodecFailure("CodecCodecInit(): Failed to query attributes: %s", vaErrorStr(va_status));
return;
}
std::optional<uint32_t> max_height = std::nullopt;
std::optional<uint32_t> max_width = std::nullopt;
for (int i = 0; i < num_config_attributes; i += 1) {
const VAConfigAttrib& attrib = config_attributes[i];
switch (attrib.type) {
case VAConfigAttribMaxPictureHeight:
max_height = attrib.value;
break;
case VAConfigAttribMaxPictureWidth:
max_width = attrib.value;
break;
default:
break;
}
}
if (!max_height) {
FX_LOGS(WARNING)
<< "Could not query hardware for max picture height supported. Setting default";
} else {
max_picture_height_ = max_height.value();
}
if (!max_width) {
FX_LOGS(WARNING) << "Could not query hardware for max picture width supported. Setting default";
} else {
max_picture_width_ = max_width.value();
}
zx_status_t result =
input_processing_loop_.StartThread("input_processing_thread_", &input_processing_thread_);
if (result != ZX_OK) {
SetCodecFailure(
"CodecCodecInit(): Failed to start input processing thread with "
"zx_status_t: %d",
result);
return;
}
}
void CodecAdapterVaApiDecoder::CoreCodecStartStream() {
// It's ok for RecycleInputPacket to make a packet free anywhere in this
// sequence. Nothing else ought to be happening during CoreCodecStartStream
// (in this or any other thread).
input_queue_.Reset();
free_output_packets_.Reset(/*keep_data=*/true);
{
std::lock_guard<std::mutex> guard(lock_);
is_stream_stopped_ = false;
}
// If the stream has initialized then reset
if (surface_buffer_manager_) {
surface_buffer_manager_->Reset();
}
LaunchInputProcessingLoop();
TRACE_INSTANT("codec_runner", "Media:Start", TRACE_SCOPE_THREAD);
}
void CodecAdapterVaApiDecoder::CoreCodecResetStreamAfterCurrentFrame() {
// Before we reset the decoder we must ensure that ProcessInputLoop() has exited and has no
// outstanding tasks
WaitForInputProcessingLoopToEnd();
media_decoder_.reset();
if (is_h264_) {
media_decoder_ = std::make_unique<media::H264Decoder>(std::make_unique<H264Accelerator>(this),
media::H264PROFILE_HIGH);
} else {
media_decoder_ = std::make_unique<media::VP9Decoder>(std::make_unique<VP9Accelerator>(this),
media::VP9PROFILE_PROFILE0);
}
input_queue_.Reset(/*keep_data=*/true);
LaunchInputProcessingLoop();
}
void CodecAdapterVaApiDecoder::DecodeAnnexBBuffer(media::DecoderBuffer buffer) {
media_decoder_->SetStream(next_stream_id_++, buffer);
while (true) {
state_ = DecoderState::kDecoding;
auto result = media_decoder_->Decode();
state_ = DecoderState::kIdle;
if (result == media::AcceleratedVideoDecoder::kConfigChange) {
{
std::lock_guard<std::mutex> guard(lock_);
mid_stream_output_buffer_reconfig_finish_ = false;
}
// Trigger a mid stream output constraints change
// TODO(fxbug.dev/102737): We always request a output reconfiguration. This may or may not be
// needed.
bool output_re_config_required = true;
events_->onCoreCodecMidStreamOutputConstraintsChange(output_re_config_required);
gfx::Size pic_size = media_decoder_->GetPicSize();
VAContextID context_id;
VAStatus va_res = vaCreateContext(VADisplayWrapper::GetSingleton()->display(), config_->id(),
pic_size.width(), pic_size.height(), VA_PROGRESSIVE,
nullptr, 0, &context_id);
if (va_res != VA_STATUS_SUCCESS) {
SetCodecFailure("vaCreateContext failed: %s", vaErrorStr(va_res));
break;
}
context_id_.emplace(context_id);
// Only wait if an output reconfiguration was required. Otherwise the buffers will be kept and
// only the new output constraints will be sent.
if (output_re_config_required) {
// Wait for the stream reconfiguration to finish before continuing to increment the surface
// generation value
std::unique_lock<std::mutex> lock(lock_);
surface_buffer_manager_cv_.wait(lock, [this]() FXL_REQUIRE(lock_) {
return mid_stream_output_buffer_reconfig_finish_ || is_stream_stopped_;
});
// If the stream is stopped, exit immediately
if (is_stream_stopped_) {
return;
}
}
// Increment surface generation so all existing surfaces will be freed
// when they're released instead of being returned to the pool.
surface_buffer_manager_->IncrementSurfaceGeneration(
pic_size, media_decoder_->GetRequiredNumOfPictures(), GetOutputStride());
continue;
} else if (result == media::AcceleratedVideoDecoder::kRanOutOfStreamData) {
// Reset decoder failures on successful decode
decoder_failures_ = 0;
break;
} else {
decoder_failures_ += 1;
if (decoder_failures_ >= kMaxDecoderFailures) {
SetCodecFailure(
"Decoder exceeded the number of allowed failures. media_decoder::Decode result: "
"%d",
result);
} else {
// We allow the decoder to fail a set amount of times, reset the decoder after the current
// frame. We need to stop the input_queue_ from processing any further items before the
// stream reset. The stream control thread is responsible starting the stream once is has
// been successfully reset.
input_queue_.StopAllWaits();
events_->onCoreCodecResetStreamAfterCurrentFrame();
}
break;
}
}
} // ~buffer
const char* CodecAdapterVaApiDecoder::DecoderStateName(DecoderState state) {
switch (state) {
case DecoderState::kIdle:
return "Idle";
case DecoderState::kDecoding:
return "Decoding";
case DecoderState::kError:
return "Error";
default:
return "UNKNOWN";
}
}
template <class... Args>
void CodecAdapterVaApiDecoder::SetCodecFailure(const char* format, Args&&... args) {
state_ = DecoderState::kError;
events_->onCoreCodecFailCodec(format, std::forward<Args>(args)...);
}
void CodecAdapterVaApiDecoder::ProcessInputLoop() {
std::optional<CodecInputItem> maybe_input_item;
while ((maybe_input_item = input_queue_.WaitForElement())) {
CodecInputItem input_item = std::move(maybe_input_item.value());
if (input_item.is_format_details()) {
const std::string& mime_type = input_item.format_details().mime_type();
if ((!is_h264_ && (mime_type == "video/h264-multi" || mime_type == "video/h264")) ||
(is_h264_ && mime_type == "video/vp9")) {
SetCodecFailure(
"CodecCodecInit(): Can not switch codec type after setting it in CoreCodecInit(). "
"Attempting to switch it to %s\n",
mime_type.c_str());
return;
}
if (mime_type == "video/h264-multi" || mime_type == "video/h264") {
avcc_processor_.ProcessOobBytes(input_item.format_details());
}
} else if (input_item.is_end_of_stream()) {
// TODO(stefanbossbaly): Encapsulate in abstraction
if (is_h264_) {
constexpr uint8_t kEndOfStreamNalUnitType = 11;
// Force frames to be processed.
std::vector<uint8_t> end_of_stream_delimiter{0, 0, 1, kEndOfStreamNalUnitType};
media::DecoderBuffer buffer(end_of_stream_delimiter);
media_decoder_->SetStream(next_stream_id_++, buffer);
state_ = DecoderState::kDecoding;
auto result = media_decoder_->Decode();
state_ = DecoderState::kIdle;
if (result != media::AcceleratedVideoDecoder::kRanOutOfStreamData) {
SetCodecFailure("Unexpected media_decoder::Decode result for end of stream: %d", result);
return;
}
}
bool res = media_decoder_->Flush();
if (!res) {
FX_LOGS(WARNING) << "media decoder flush failed";
}
events_->onCoreCodecOutputEndOfStream(/*error_detected_before=*/!res);
} else if (input_item.is_packet()) {
auto* packet = input_item.packet();
ZX_DEBUG_ASSERT(packet->has_start_offset());
if (packet->has_timestamp_ish()) {
stream_to_pts_map_.emplace_back(next_stream_id_, packet->timestamp_ish());
constexpr size_t kMaxPtsMapSize = 64;
if (stream_to_pts_map_.size() > kMaxPtsMapSize)
stream_to_pts_map_.pop_front();
}
const uint8_t* buffer_start = packet->buffer()->base() + packet->start_offset();
size_t buffer_size = packet->valid_length_bytes();
bool returned_buffer = false;
auto return_input_packet =
fit::defer_callback(fit::closure([this, &input_item, &returned_buffer] {
events_->onCoreCodecInputPacketDone(input_item.packet());
returned_buffer = true;
}));
if (is_h264_ && avcc_processor_.is_avcc()) {
// TODO(fxbug.dev/94139): Remove this copy.
auto output_avcc_vec = avcc_processor_.ParseVideoAvcc(buffer_start, buffer_size);
media::DecoderBuffer buffer(output_avcc_vec, packet->buffer(), packet->start_offset(),
std::move(return_input_packet));
DecodeAnnexBBuffer(std::move(buffer));
} else {
media::DecoderBuffer buffer({buffer_start, buffer_size}, packet->buffer(),
packet->start_offset(), std::move(return_input_packet));
DecodeAnnexBBuffer(std::move(buffer));
}
// Ensure that the decode buffer has been destroyed and the input packet has been returned
ZX_ASSERT(returned_buffer);
// TODO(stefanbossbaly): Encapsulate in abstraction
if (is_h264_) {
constexpr uint8_t kAccessUnitDelimiterNalUnitType = 9;
constexpr uint8_t kPrimaryPicType = 1 << (7 - 3);
// Force frames to be processed. TODO(jbauman): Key on known_end_access_unit.
std::vector<uint8_t> access_unit_delimiter{0, 0, 1, kAccessUnitDelimiterNalUnitType,
kPrimaryPicType};
media::DecoderBuffer buffer(access_unit_delimiter);
media_decoder_->SetStream(next_stream_id_++, buffer);
state_ = DecoderState::kDecoding;
auto result = media_decoder_->Decode();
state_ = DecoderState::kIdle;
if (result != media::AcceleratedVideoDecoder::kRanOutOfStreamData) {
SetCodecFailure("Unexpected media_decoder::Decode result for delimiter: %d", result);
return;
}
}
}
}
}
void CodecAdapterVaApiDecoder::CleanUpAfterStream() {
{
// TODO(stefanbossbaly): Encapsulate in abstraction
if (is_h264_) {
// Force frames to be processed.
std::vector<uint8_t> end_of_stream_delimiter{0, 0, 1, 11};
media::DecoderBuffer buffer(end_of_stream_delimiter);
media_decoder_->SetStream(next_stream_id_++, buffer);
auto result = media_decoder_->Decode();
if (result != media::AcceleratedVideoDecoder::kRanOutOfStreamData) {
SetCodecFailure("Unexpected media_decoder::Decode result for end of stream: %d", result);
return;
}
}
}
bool res = media_decoder_->Flush();
if (!res) {
FX_LOGS(WARNING) << "media decoder flush failed";
}
}
void CodecAdapterVaApiDecoder::CoreCodecMidStreamOutputBufferReConfigFinish() {
surface_buffer_manager_.reset();
if (IsOutputTiled()) {
surface_buffer_manager_ = std::make_unique<TiledBufferManager>(lock_);
} else {
surface_buffer_manager_ = std::make_unique<LinearBufferManager>(lock_);
}
LoadStagedOutputBuffers();
// Signal that we are done with the mid stream output buffer configuration to other threads
{
std::lock_guard<std::mutex> guard(lock_);
mid_stream_output_buffer_reconfig_finish_ = true;
}
surface_buffer_manager_cv_.notify_all();
}
bool CodecAdapterVaApiDecoder::ProcessOutput(scoped_refptr<VASurface> va_surface,
int bitstream_id) {
auto maybe_processed_surface = surface_buffer_manager_->ProcessOutputSurface(va_surface);
if (!maybe_processed_surface) {
return true;
}
auto& [codec_buffer, pic_size_bytes] = maybe_processed_surface.value();
auto release_buffer = fit::defer([this, codec_buffer = codec_buffer]() {
surface_buffer_manager_->RecycleBuffer(codec_buffer);
});
std::optional<CodecPacket*> maybe_output_packet = free_output_packets_.WaitForElement();
if (!maybe_output_packet) {
// Wait will succeed unless we're dropping all remaining frames of a stream.
return true;
}
auto output_packet = maybe_output_packet.value();
output_packet->SetBuffer(codec_buffer);
output_packet->SetStartOffset(0);
output_packet->SetValidLengthBytes(pic_size_bytes);
{
auto pts_it =
std::find_if(stream_to_pts_map_.begin(), stream_to_pts_map_.end(),
[bitstream_id](const auto& pair) { return pair.first == bitstream_id; });
if (pts_it != stream_to_pts_map_.end()) {
output_packet->SetTimstampIsh(pts_it->second);
} else {
output_packet->ClearTimestampIsh();
}
}
release_buffer.cancel();
events_->onCoreCodecOutputPacket(output_packet,
/*error_detected_before=*/false,
/*error_detected_during=*/false);
return true;
}
scoped_refptr<VASurface> CodecAdapterVaApiDecoder::GetVASurface() {
return surface_buffer_manager_->GetDPBSurface();
}