blob: 1d0e177afa9b463124f14ff5588ada058afa9e04 [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.
#ifndef SRC_MEDIA_CODEC_CODECS_VAAPI_CODEC_ADAPTER_VAAPI_DECODER_H_
#define SRC_MEDIA_CODEC_CODECS_VAAPI_CODEC_ADAPTER_VAAPI_DECODER_H_
#include <fidl/fuchsia.sysmem/cpp/wire.h>
#include <fuchsia/sysmem/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/fit/function.h>
#include <lib/fit/result.h>
#include <lib/media/codec_impl/codec_adapter.h>
#include <lib/media/codec_impl/codec_buffer.h>
#include <lib/media/codec_impl/codec_diagnostics.h>
#include <lib/media/codec_impl/codec_input_item.h>
#include <lib/media/codec_impl/codec_packet.h>
#include <lib/media/codec_impl/fourcc.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <threads.h>
#include <zircon/assert.h>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include <set>
#include <string>
#include <fbl/algorithm.h>
#include <va/va.h>
#include "buffer_pool.h"
#include "media/base/decoder_buffer.h"
#include "media/gpu/accelerated_video_decoder.h"
#include "src/lib/fxl/synchronization/thread_annotations.h"
#include "src/media/codec/codecs/vaapi/avcc_processor.h"
#include "src/media/lib/mpsc_queue/mpsc_queue.h"
#include "vaapi_utils.h"
class CodecAdapterVaApiDecoder;
// Used for friend declarations in CodecAdapterVaApiDecoder
namespace test {
class Vp9VaapiTestFixture;
} // namespace test
// Interface used to manage output buffer, DPB surfaces and their relationship to each other. The
// goal of this class is to abstract away the implementation details on how linear and tiled
// surfaces are handled differently.
class SurfaceBufferManager {
public:
using CodecFailureCallback = fit::function<void(const std::string)>;
virtual ~SurfaceBufferManager() = default;
// Adds a output CodecBuffer under the management of the class
virtual void AddBuffer(const CodecBuffer* buffer) = 0;
// Called when an output buffer that was shared with the client is no longer by that client and
// now can be used again.
virtual void RecycleBuffer(const CodecBuffer* buffer) = 0;
// Deconfigures all output buffers under the manager's control
virtual void DeconfigureBuffers() = 0;
// Get a surface that will be used as a DPB for the codec. If no current surfaces are available
// this function will block until either a DPB surfaces becomes available or Reset() is called
virtual scoped_refptr<VASurface> GetDPBSurface() = 0;
// This function returns an output CodecBuffer to be sent to the client for the given DPB surface
virtual std::optional<std::pair<const CodecBuffer*, uint32_t>> ProcessOutputSurface(
scoped_refptr<VASurface> dpb_surface) = 0;
// Resets any underlying blocking data structures after a call to StopAllWaits(). This allows the
// data structures to block again.
virtual void Reset() = 0;
// Stops all blocking calls, specially the potentially blocking call of GetDPBSurface() or
// ProcessOutputSurface(). Will cause blocking calls to immediately return with default
// constructed objects as their return values.
virtual void StopAllWaits() = 0;
// Given the new picture size, return the dimensions of the surface needed to hold the new picture
// size. This function will use historic data of the picture size, meaning that if a surface size
// can not decrease, due to Intel media driver requirements, this function will take that into
// consideration when returning a required surface size. Note that this function does not take
// into account the pixel format.
virtual gfx::Size GetRequiredSurfaceSize(const gfx::Size& picture_size) = 0;
// TODO(https://fxbug.dev/42060469): This is a temporary workaround until the new media APIs are
// adopted Returns true if the surface manager can not have outstanding reference frames when new
// buffers are required to hold the new coded picture size.
virtual bool NeedsKeyframeForBufferAllocation() const = 0;
// Updates the picture size of the current stream. If the surfaces that are currently under
// management are too small to handle the new picture size, OnSurfaceGenerationUpdatedLocked()
// will be called to generate new surfaces that will be able to hold the new picture size.
void UpdatePictureSize(const gfx::Size& new_picture_size, size_t num_of_surfaces) {
// We always update the codec picture size
coded_picture_size_ = new_picture_size;
// Ensure the new picture size does not exceed our surface
std::lock_guard<std::mutex> guard(surface_lock_);
// Ensure that the new picture size does not exceed either the width or the height of the
// |dpb_surface_size_|. This is a requirement of VA-API/media driver. In order to have reference
// frames of different dimensions, the dimensions of the newer surfaces must be either equal to
// or greater than the previous dimensions otherwise we will get VA_STATUS_ERROR_DECODING_ERROR
// when calling vaSyncSurface().
if ((new_picture_size.width() > dpb_surface_size_.width()) ||
new_picture_size.height() > dpb_surface_size_.height()) {
FX_SLOG(INFO,
"Current DPB surfaces are NOT large enough to hold new picture size. Incrementing "
"surface generation.",
FX_KV("new_picture_width", new_picture_size.width()),
FX_KV("new_picture_height", new_picture_size.height()),
FX_KV("dpb_surface_width", dpb_surface_size_.width()),
FX_KV("dpb_surface_height", dpb_surface_size_.height()));
surface_generation_ += 1;
// Signal to subclass that new surface generation has occurred. Called under lock
OnSurfaceGenerationUpdatedLocked(num_of_surfaces);
} else {
FX_SLOG(INFO, "Current DPB surfaces are large enough to hold new picture size",
FX_KV("new_picture_width", new_picture_size.width()),
FX_KV("new_picture_height", new_picture_size.height()),
FX_KV("dpb_surface_width", dpb_surface_size_.width()),
FX_KV("dpb_surface_height", dpb_surface_size_.height()));
}
}
gfx::Size GetDPBSurfaceSize() {
std::lock_guard<std::mutex> guard(surface_lock_);
return dpb_surface_size_;
}
protected:
explicit SurfaceBufferManager(std::mutex& codec_lock, CodecFailureCallback failure_callback)
: codec_lock_(codec_lock), failure_callback_(std::move(failure_callback)) {}
// Used to set any codec failures that happen during the management of the buffers
void SetCodecFailure(const std::string& codec_failure) { failure_callback_(codec_failure); }
// Event method subclass must implement. Called when the surface generation has been incremented
// and a new surface size is available. This function is guaranteed to be called with the
// surface_lock_ locked.
virtual void OnSurfaceGenerationUpdatedLocked(size_t num_of_surfaces)
FXL_REQUIRE(surface_lock_) = 0;
// The lock is owned by the VAAPI decoder and hence the decoder class will always outlive this
// class.
std::mutex& codec_lock_;
// Lock that must be used when modifying any surface data.
std::mutex surface_lock_{};
// Holds the current version of surface generation. If incremented DPB surfaces will have to be
// destroyed and recreated with the new the new surface_size_ dimensions
uint64_t surface_generation_ FXL_GUARDED_BY(surface_lock_) = {};
// Hold the current size of the DPB surfaces. Once a DBP size is assigned, either the width or the
// height of the surface can not become smaller, it can only grow or remain equal in size. The
// |dpb_surface_size_| must always be greater than or equal to |coded_picture_size_|.
gfx::Size dpb_surface_size_ FXL_GUARDED_BY(surface_lock_) = {};
// Holds the current codec picture size. There are no restrictions based on previous values of
// |coded_picture_size_|, meaning the value may grow or shrink regardless of previous values and
// is only driven by the current codec picture size of the current frame.
gfx::Size coded_picture_size_;
// 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_{};
private:
CodecFailureCallback failure_callback_;
};
class CodecAdapterVaApiDecoder : public CodecAdapter {
public:
CodecAdapterVaApiDecoder(std::mutex& lock, CodecAdapterEvents* codec_adapter_events)
: CodecAdapter(lock, codec_adapter_events),
avcc_processor_(fit::bind_member<&CodecAdapterVaApiDecoder::DecodeAnnexBBuffer>(this),
codec_adapter_events) {
ZX_DEBUG_ASSERT(events_);
}
~CodecAdapterVaApiDecoder() override {
input_processing_loop_.Shutdown();
// Tear down first to make sure the accelerator doesn't reference other variables in this
// class later.
media_decoder_.reset();
}
void SetCodecDiagnostics(CodecDiagnostics* codec_diagnostics) override {
codec_diagnostics_ = codec_diagnostics;
}
bool IsCoreCodecRequiringOutputConfigForFormatDetection() override { return false; }
bool IsCoreCodecMappedBufferUseful(CodecPort port) override { return true; }
bool IsCoreCodecHwBased(CodecPort port) override { return true; }
void CoreCodecInit(const fuchsia::media::FormatDetails& initial_input_format_details) override;
void CoreCodecAddBuffer(CodecPort port, const CodecBuffer* buffer) override {
if (port != kOutputPort) {
return;
}
staged_output_buffers_.push_back(buffer);
}
void CoreCodecConfigureBuffers(
CodecPort port, const std::vector<std::unique_ptr<CodecPacket>>& packets) override {
if (port != kOutputPort) {
return;
}
std::vector<CodecPacket*> all_packets;
for (auto& packet : packets) {
all_packets.push_back(packet.get());
}
std::shuffle(all_packets.begin(), all_packets.end(), not_for_security_prng_);
for (CodecPacket* packet : all_packets) {
free_output_packets_.Push(packet);
}
}
void CoreCodecStartStream() override;
void CoreCodecQueueInputFormatDetails(
const fuchsia::media::FormatDetails& per_stream_override_format_details) override {
// TODO(turnage): Accept midstream and interstream input format changes.
// For now these should always be 0, so assert to notice if anything
// changes.
ZX_ASSERT(per_stream_override_format_details.has_format_details_version_ordinal() &&
per_stream_override_format_details.format_details_version_ordinal() ==
input_format_details_version_ordinal_);
input_queue_.Push(CodecInputItem::FormatDetails(per_stream_override_format_details));
}
void CoreCodecQueueInputPacket(CodecPacket* packet) override {
TRACE_INSTANT("codec_runner", "Media:PacketReceived", TRACE_SCOPE_THREAD);
input_queue_.Push(CodecInputItem::Packet(packet));
}
void CoreCodecQueueInputEndOfStream() override {
input_queue_.Push(CodecInputItem::EndOfStream());
}
void CoreCodecStopStream() override {
input_queue_.StopAllWaits();
free_output_packets_.StopAllWaits();
// If we are waiting for a mid stream output buffer reconfiguration, stop.
// CoreCodecMidStreamOutputBufferReConfigFinish() will not be called.
{
std::lock_guard<std::mutex> guard(lock_);
is_stream_stopped_ = true;
}
surface_buffer_manager_cv_.notify_all();
// It is possible a stream was started by no input packets were provided which means that the
// surface buffer manager was never constructed.
if (surface_buffer_manager_) {
surface_buffer_manager_->StopAllWaits();
}
WaitForInputProcessingLoopToEnd();
CleanUpAfterStream();
auto queued_input_items = BlockingMpscQueue<CodecInputItem>::Extract(std::move(input_queue_));
while (!queued_input_items.empty()) {
CodecInputItem input_item = std::move(queued_input_items.front());
queued_input_items.pop();
if (input_item.is_packet()) {
events_->onCoreCodecInputPacketDone(input_item.packet());
}
}
TRACE_INSTANT("codec_runner", "Media:Stop", TRACE_SCOPE_THREAD);
}
void CoreCodecResetStreamAfterCurrentFrame() override;
void CoreCodecRecycleOutputPacket(CodecPacket* packet) override {
if (packet->is_new()) {
// CoreCodecConfigureBuffers() took care of initially populating
// free_output_packets_ (in shuffled order), so ignore new packets.
ZX_DEBUG_ASSERT(!packet->buffer());
packet->SetIsNew(false);
return;
}
if (packet->buffer()) {
ZX_ASSERT(surface_buffer_manager_);
surface_buffer_manager_->RecycleBuffer(packet->buffer());
}
free_output_packets_.Push(packet);
}
void CoreCodecEnsureBuffersNotConfigured(CodecPort port) override {
buffer_settings_[port] = std::nullopt;
buffer_counts_[port] = std::nullopt;
if (port != kOutputPort) {
// We don't do anything with input buffers.
return;
}
// The first time this function is called before CodecStartStream() which means that
// surface_buffer_manager_ will not be configured yet. If this is the case then by default our
// surface buffer manager is not configured and no action is needed
if (surface_buffer_manager_) {
// If we do have a surface_buffer_manager then deconfigure all buffers under its management
surface_buffer_manager_->DeconfigureBuffers();
surface_buffer_manager_->Reset();
}
// VMO handles for the old output buffers may still exist, but the SW
// decoder doesn't know about those, and buffer_lifetime_ordinal will
// prevent us calling output_buffer_pool_.FreeBuffer() for any of the old
// buffers. So forget about the old buffers here.
staged_output_buffers_.clear();
free_output_packets_.Reset();
}
void CoreCodecMidStreamOutputBufferReConfigPrepare() override {
// Nothing to do here.
}
void CoreCodecMidStreamOutputBufferReConfigFinish() override;
std::string CoreCodecGetName() override { return "VAAPI"; }
std::unique_ptr<const fuchsia::media::StreamOutputConstraints> CoreCodecBuildNewOutputConstraints(
uint64_t stream_lifetime_ordinal, uint64_t new_output_buffer_constraints_version_ordinal,
bool buffer_constraints_action_required) override {
auto config = std::make_unique<fuchsia::media::StreamOutputConstraints>();
config->set_stream_lifetime_ordinal(stream_lifetime_ordinal);
config->set_buffer_constraints_action_required(buffer_constraints_action_required);
auto* constraints = config->mutable_buffer_constraints();
constraints->set_buffer_constraints_version_ordinal(
new_output_buffer_constraints_version_ordinal);
return config;
}
fuchsia::media::StreamOutputFormat CoreCodecGetOutputFormat(
uint64_t stream_lifetime_ordinal,
uint64_t new_output_format_details_version_ordinal) override {
std::lock_guard<std::mutex> lock(lock_);
fuchsia::media::StreamOutputFormat result;
fuchsia::sysmem::ImageFormat_2 image_format;
gfx::Size pic_size = media_decoder_->GetPicSize();
gfx::Rect visible_rect = media_decoder_->GetVisibleRect();
image_format.pixel_format.type = fuchsia::sysmem::PixelFormatType::NV12;
bool is_output_tiled = IsOutputTiled();
image_format.pixel_format.has_format_modifier = is_output_tiled;
if (is_output_tiled) {
image_format.pixel_format.format_modifier.value =
fuchsia_sysmem::wire::kFormatModifierIntelI915YTiled;
}
image_format.coded_width = pic_size.width();
image_format.coded_height = pic_size.height();
image_format.bytes_per_row = GetOutputStride();
image_format.display_width = visible_rect.width();
image_format.display_height = visible_rect.height();
image_format.layers = 1;
image_format.color_space.type = fuchsia::sysmem::ColorSpaceType::REC709;
image_format.has_pixel_aspect_ratio = false;
fuchsia::media::FormatDetails format_details;
format_details.set_mime_type("video/raw");
fuchsia::media::VideoFormat video_format;
video_format.set_uncompressed(GetUncompressedFormat(image_format));
format_details.mutable_domain()->set_video(std::move(video_format));
result.set_stream_lifetime_ordinal(stream_lifetime_ordinal);
result.set_format_details(std::move(format_details));
result.mutable_format_details()->set_format_details_version_ordinal(
new_output_format_details_version_ordinal);
return result;
}
fuchsia_sysmem2::BufferCollectionConstraints CoreCodecGetBufferCollectionConstraints2(
CodecPort port, const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
const fuchsia::media::StreamBufferPartialSettings& partial_settings) override {
fuchsia_sysmem2::BufferCollectionConstraints constraints;
auto& bmc = constraints.buffer_memory_constraints().emplace();
if (port == kInputPort) {
constraints.min_buffer_count_for_camping() = 1;
bmc.cpu_domain_supported() = true;
// Must be big enough to hold an entire NAL unit, since the H264Decoder doesn't support
// split NAL units.
bmc.min_size_bytes() = 8192 * 512;
} else {
ZX_DEBUG_ASSERT(port == kOutputPort);
constraints.min_buffer_count_for_camping() =
static_cast<uint32_t>(media_decoder_->GetRequiredNumOfPictures());
// TODO(https://fxbug.dev/42176003): Add RAM domain support.
bmc.cpu_domain_supported() = true;
// Lamdba that will set the common constraint values regardless of what format modifier value
using CommonConstraintsFunction =
fit::inline_function<void(fuchsia_sysmem2::ImageFormatConstraints & constraints)>;
auto set_common_constraints =
CommonConstraintsFunction([this](fuchsia_sysmem2::ImageFormatConstraints& constraints) {
ZX_ASSERT(media_decoder_);
ZX_ASSERT(media_codec_.has_value());
bool is_h264 = (media_codec_.value() == CodecType::kH264);
// Currently only support outputting to NV12
constraints.pixel_format() = fuchsia_images2::PixelFormat::kNv12;
// Currently only support the REC709 color space.
constraints.color_spaces() = {fuchsia_images2::ColorSpace::kRec709};
// The non-"required_" fields indicate the decoder's ability to potentially
// output frames at various dimensions as coded in the stream. Aside from
// the current stream being somewhere in these bounds, these have nothing to
// do with the current stream in particular. We advertise the known min codec width
// depending on the codec. For the max, we advertise what the current hardware supports,
// not the codec max.
constraints.min_size() = {is_h264 ? kH264MinBlockSize : kVp9MinBlockSize,
is_h264 ? kH264MinBlockSize : kVp9MinBlockSize};
constraints.max_size() = {max_picture_width_, max_picture_height_};
constraints.max_width_times_height() = max_picture_width_ * max_picture_height_;
constraints.size_alignment() = {is_h264 ? kH264MinBlockSize : kVp9MinBlockSize,
is_h264 ? kH264MinBlockSize : kVp9MinBlockSize};
constraints.start_offset_divisor() = 1;
// Odd display dimensions are permitted, but these don't imply odd YV12
// dimensions - those are constrained by coded_width_divisor and
// coded_height_divisor which are both 16.
constraints.display_rect_alignment() = {1, 1};
// The decoder is producing frames and the decoder has no choice but to
// produce frames at their coded size. The decoder wants to potentially be
// able to support a stream with dynamic resolution, potentially including
// dimensions both less than and greater than the dimensions that led to the
// current need to allocate a BufferCollection. For this reason, the
// required_ fields are set to the exact current dimensions, and the
// permitted (non-required_) fields is set to the full potential range that
// the decoder could potentially output. If an initiator wants to require a
// larger range of dimensions that includes the required range indicated
// here (via a-priori knowledge of the potential stream dimensions), an
// initiator is free to do so.
//
// When sending constraints the first time, |surface_buffer_manager_| will not be
// constructed since we have not determined a format modifier yet until the first time
// CoreCodecMidStreamOutputBufferReConfigFinish() is called. Because of this we have not
// picked any surface size and we will just advertise the aligned picture_size of the
// first frame. Once a format modifier is selected, |surface_buffer_manager_| will be
// constructed along with the DPB surfaces. Once these surfaces are constructed, we can
// not allow the surface size to become smaller than the current DPB size, and therefore
// will have get the surface size using GetRequiredSurfaceSize() for the current coded
// picture size to ensure this condition is met.
gfx::Size pic_size = media_decoder_->GetPicSize();
gfx::Size required_size =
static_cast<bool>(surface_buffer_manager_)
? surface_buffer_manager_->GetRequiredSurfaceSize(pic_size)
: pic_size;
constraints.required_min_size() = {static_cast<uint32_t>(required_size.width()),
static_cast<uint32_t>(required_size.height())};
constraints.required_max_size() = {static_cast<uint32_t>(required_size.width()),
static_cast<uint32_t>(required_size.height())};
constraints.min_bytes_per_row() = required_size.width();
});
constraints.image_format_constraints().emplace();
// Linear Format
if (!output_buffer_format_modifier_ || (output_buffer_format_modifier_.value() ==
fuchsia_images2::PixelFormatModifier::kLinear)) {
auto& linear_constraints = constraints.image_format_constraints()->emplace_back();
linear_constraints.bytes_per_row_divisor() = kLinearSurfaceWidthAlignment;
linear_constraints.max_bytes_per_row() =
fbl::round_up(max_picture_width_, kLinearSurfaceWidthAlignment);
set_common_constraints(linear_constraints);
}
// Y-Tiled format
if (!output_buffer_format_modifier_ ||
(output_buffer_format_modifier_.value() ==
fuchsia_images2::PixelFormatModifier::kIntelI915YTiled)) {
auto& tiled_constraints = constraints.image_format_constraints()->emplace_back();
tiled_constraints.pixel_format_modifier() =
fuchsia_images2::PixelFormatModifier::kIntelI915YTiled;
tiled_constraints.bytes_per_row_divisor() = 0;
set_common_constraints(tiled_constraints);
}
ZX_ASSERT(!constraints.image_format_constraints()->empty());
}
return constraints;
}
void CoreCodecSetBufferCollectionInfo(
CodecPort port,
const fuchsia_sysmem2::BufferCollectionInfo& buffer_collection_info) override {
buffer_settings_[port] = buffer_collection_info.settings();
buffer_counts_[port] = buffer_collection_info.buffers()->size();
if (port == CodecPort::kOutputPort) {
ZX_ASSERT(buffer_collection_info.settings()->image_format_constraints().has_value());
ZX_ASSERT(buffer_collection_info.settings()
->image_format_constraints()
->pixel_format_modifier()
.has_value());
auto format_modifier =
*buffer_collection_info.settings()->image_format_constraints()->pixel_format_modifier();
// Should never happen but ensure we do not overwrite a format modifier that has been
// initialized with another value
ZX_ASSERT(!output_buffer_format_modifier_ ||
(output_buffer_format_modifier_.value() == format_modifier));
// The first time this value is set, log out if the participants selected a linear or tiled
// format.
if (!output_buffer_format_modifier_.has_value()) {
std::string format_modifier_str;
switch (format_modifier) {
case fuchsia_images2::PixelFormatModifier::kLinear:
format_modifier_str = "linear";
break;
case fuchsia_images2::PixelFormatModifier::kIntelI915YTiled:
format_modifier_str = "intel_i915_y_tiled";
break;
default:
format_modifier_str = "unknown";
break;
}
FX_SLOG(INFO, "Format modifier has been chosen",
FX_KV("format_modifier", fidl::ToUnderlying(format_modifier)),
FX_KV("format_modifier_str", format_modifier_str.c_str()));
}
output_buffer_format_modifier_ = format_modifier;
}
}
bool ProcessOutput(scoped_refptr<VASurface> surface, int bitstream_id);
VAContextID context_id() { return context_id_->id(); }
scoped_refptr<VASurface> GetVASurface();
// MIME Types for the various codecs this CodecAdapter supports
static constexpr std::string_view kMjpegMimeType = "video/x-motion-jpeg";
static constexpr std::string_view kVp9MimeType = "video/vp9";
static constexpr std::string_view kH264MimeType = "video/h264";
// Intel linear surface alignment
static constexpr uint32_t kLinearSurfaceWidthAlignment = 16u;
static constexpr uint32_t kLinearSurfaceHeightAlignment = 16u;
// Intel Y-Tiling Surface Alignment
static constexpr uint32_t kTileSurfaceWidthAlignment = 128u;
static constexpr uint32_t kTileSurfaceHeightAlignment = 32u;
private:
friend class VaApiOutput;
friend class test::Vp9VaapiTestFixture;
static constexpr uint32_t kH264MinBlockSize = 16u;
static constexpr uint32_t kVp9MinBlockSize = 2u;
// Used to keep track when mime_type is specified by client
enum class CodecType {
kMJPEG = 1,
kH264 = 2,
kVP9 = 3,
};
static std::string_view CodecTypeName(CodecType codec_type);
static std::optional<std::string_view> CodecMimeFromType(CodecType codec_type);
static std::optional<CodecType> CodecTypeFromMime(std::string_view mime_type);
// Used from trace events
enum DecoderState { kIdle, kDecoding, kError };
static const char* DecoderStateName(DecoderState state);
template <class... Args>
void SetCodecFailure(const char* format, Args&&... args);
void LaunchInputProcessingLoop() {
zx_status_t post_result =
async::PostTask(input_processing_loop_.dispatcher(), [this] { ProcessInputLoop(); });
ZX_ASSERT_MSG(post_result == ZX_OK,
"async::PostTask() failed to post input processing loop - result: %d\n",
post_result);
}
void WaitForInputProcessingLoopToEnd() {
ZX_DEBUG_ASSERT(thrd_current() != input_processing_thread_);
std::condition_variable stream_stopped_condition;
bool stream_stopped = false;
zx_status_t post_result = async::PostTask(input_processing_loop_.dispatcher(),
[this, &stream_stopped, &stream_stopped_condition] {
{
std::lock_guard<std::mutex> lock(lock_);
stream_stopped = true;
// Under lock since
// WaitForInputProcessingLoopToEnd()
// may otherwise return too soon deleting
// stream_stopped_condition too soon.
stream_stopped_condition.notify_all();
}
});
ZX_ASSERT_MSG(post_result == ZX_OK,
"async::PostTask() failed to post input processing loop - result: %d\n",
post_result);
std::unique_lock<std::mutex> lock(lock_);
stream_stopped_condition.wait(lock, [&stream_stopped] { return stream_stopped; });
}
// We don't give the codec any buffers in its output pool until
// configuration is finished or a stream starts. Until finishing
// configuration we stage all the buffers. Here we load all the staged
// buffers so the codec can make output.
void LoadStagedOutputBuffers() {
ZX_ASSERT(surface_buffer_manager_);
std::vector<const CodecBuffer*> to_add = std::move(staged_output_buffers_);
for (auto buffer : to_add) {
surface_buffer_manager_->AddBuffer(buffer);
}
}
bool IsOutputTiled() const {
ZX_ASSERT(buffer_settings_[kOutputPort]);
ZX_ASSERT(buffer_settings_[kOutputPort]->image_format_constraints().has_value());
auto& format_constraints = *buffer_settings_[kOutputPort]->image_format_constraints();
return format_constraints.pixel_format_modifier().has_value() &&
(*format_constraints.pixel_format_modifier() !=
fuchsia_images2::PixelFormatModifier::kLinear);
}
// Use to construct the |media_decoder_|. Can only be called iff |media_decoder_| is not
// initialized and |media_codec_| has a value.
void ConstructDecoder();
// Called directly after a reconfiguration change during a stream. If a the result is
// fit::error(std::string), there was a problem with the new constraints that can't be solved
// with a buffer reconfiguration (i.e. the requested buffers exceed the max supported size for our
// given hardware) and SetCodecFailure should be called with the error. If fit::ok(bool) then
// there were no fatal codec failures and the bool contains if the buffers should be reconfigured.
// If true the buffers *CAN NOT* be used with the new configuration and the old buffers will have
// to be discarded. If false the buffers *CAN* be used with the new configuration and only the new
// output format has to be sent to the client.
fit::result<std::string, bool> IsBufferReconfigurationNeeded() const;
// Processes input in a loop. Should only execute on input_processing_thread_.
// Loops for the lifetime of a stream.
void ProcessInputLoop();
// Releases any resources from the just-ended stream. This must be called after the |input_queue_|
// is cancelled and no further operations are pending on |input_processing_loop_| to ensure that
// no further processing on the stream is pending.
void CleanUpAfterStream();
void DecodeAnnexBBuffer(media::DecoderBuffer buffer);
uint32_t GetOutputStride() {
ZX_ASSERT(surface_buffer_manager_);
auto surface_size = surface_buffer_manager_->GetDPBSurfaceSize();
auto checked_stride = safemath::MakeCheckedNum(surface_size.width()).Cast<uint32_t>();
if (!checked_stride.IsValid()) {
FX_LOGS(FATAL) << "Stride could not be represented as a 32 bit integer";
}
return checked_stride.ValueOrDie();
}
fuchsia::media::VideoUncompressedFormat GetUncompressedFormat(
const fuchsia::sysmem::ImageFormat_2& image_format) {
ZX_DEBUG_ASSERT(image_format.pixel_format.type == fuchsia::sysmem::PixelFormatType::NV12);
fuchsia::media::VideoUncompressedFormat video_uncompressed;
// Common Settings
video_uncompressed.image_format = image_format;
video_uncompressed.fourcc = make_fourcc('N', 'V', '1', '2');
video_uncompressed.primary_width_pixels = image_format.coded_width;
video_uncompressed.primary_height_pixels = image_format.coded_height;
video_uncompressed.planar = true;
video_uncompressed.primary_line_stride_bytes = image_format.bytes_per_row;
video_uncompressed.secondary_line_stride_bytes = image_format.bytes_per_row;
video_uncompressed.primary_start_offset = 0;
video_uncompressed.primary_pixel_stride = 1;
video_uncompressed.secondary_pixel_stride = 2;
video_uncompressed.has_pixel_aspect_ratio = image_format.has_pixel_aspect_ratio;
video_uncompressed.pixel_aspect_ratio_height = image_format.pixel_aspect_ratio_height;
video_uncompressed.pixel_aspect_ratio_width = image_format.pixel_aspect_ratio_width;
video_uncompressed.primary_display_width_pixels = image_format.display_width;
video_uncompressed.primary_display_height_pixels = image_format.display_height;
video_uncompressed.secondary_width_pixels = image_format.coded_width / 2;
video_uncompressed.secondary_height_pixels = image_format.coded_height / 2;
// Tile dependant settings
if (IsOutputTiled()) {
video_uncompressed.swizzled = true;
video_uncompressed.secondary_start_offset =
image_format.bytes_per_row *
fbl::round_up(image_format.coded_height, kTileSurfaceHeightAlignment);
video_uncompressed.tertiary_start_offset = video_uncompressed.secondary_start_offset + 1;
} else {
video_uncompressed.swizzled = false;
video_uncompressed.secondary_start_offset =
image_format.bytes_per_row * image_format.coded_height;
video_uncompressed.tertiary_start_offset = video_uncompressed.secondary_start_offset + 1;
}
return video_uncompressed;
}
// Allow up to 240 frames (8 seconds @ 30 fps) between keyframes.
static constexpr uint32_t kMaxDecoderFailures = 240u;
BlockingMpscQueue<CodecInputItem> input_queue_{};
BlockingMpscQueue<CodecPacket*> free_output_packets_{};
std::optional<ScopedConfigID> config_;
// DPB surfaces.
std::mutex surfaces_lock_;
// 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_.
std::unique_ptr<SurfaceBufferManager> surface_buffer_manager_;
std::condition_variable surface_buffer_manager_cv_;
bool mid_stream_output_buffer_reconfig_finish_ FXL_GUARDED_BY(lock_) = false;
bool is_stream_stopped_ /* FXL_GUARDED_BY(lock_) */ = false;
// Buffers the client has added but that we cannot use until configuration is
// complete.
std::vector<const CodecBuffer*> staged_output_buffers_;
uint64_t input_format_details_version_ordinal_;
AvccProcessor avcc_processor_;
std::optional<fuchsia_sysmem2::SingleBufferSettings> buffer_settings_[kPortCount];
std::optional<uint32_t> buffer_counts_[kPortCount];
// Initially this value is std::nullopt meaning that there has been no format modifier set by the
// client. When no format modifier has been set, this codec will advertise all available
// format modifiers for the given codec via CoreCodecGetBufferCollectionConstraints(). Once
// the client sets a format modifier, it can not be changed. Any future calls to
// CoreCodecGetBufferCollectionConstraints() will only advertise the format modifier selected
// by the client since the format modifier can not be changed during a mid stream output
// buffer reconfiguration or at any other part in the codec's lifecycle.
std::optional<fuchsia_images2::PixelFormatModifier> output_buffer_format_modifier_{};
// Since CoreCodecInit() is called after SetDriverDiagnostics() we need to save a pointer to the
// codec diagnostics object so that we can create the codec diagnotcis when we construct the
// codec.
CodecDiagnostics* codec_diagnostics_{nullptr};
std::optional<ComponentCodecDiagnostics> codec_instance_diagnostics_;
std::optional<ScopedContextID> context_id_;
// Will be accessed from the input processing thread if that's active, or the main thread
// otherwise.
std::unique_ptr<media::AcceleratedVideoDecoder> media_decoder_;
// Set when CoreCodecInit() is called. Once set it can not be changed.
std::optional<CodecType> media_codec_;
uint32_t decoder_failures_{0}; // The amount of failures the decoder has encountered
DiagnosticStateWrapper<DecoderState> state_{
[]() {}, DecoderState::kIdle, &DecoderStateName}; // Used for trace events to show when we
// are waiting on the iGPU for data
// These are set in CoreCodecInit() by querying the underlying hardware. If the hardware query
// returns no results the current value is not overwritten.
uint32_t max_picture_height_{3840};
uint32_t max_picture_width_{3840};
std::deque<std::pair<int32_t, uint64_t>> stream_to_pts_map_;
int32_t next_stream_id_{};
async::Loop input_processing_loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
thrd_t input_processing_thread_;
};
#endif // SRC_MEDIA_CODEC_CODECS_VAAPI_CODEC_ADAPTER_VAAPI_DECODER_H_