blob: 122427fb03629d2a9deb812f49064f9ff17c7b00 [file] [log] [blame]
// Copyright 2018 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_vp9.h"
#include <lib/fidl/cpp/clone.h>
#include <lib/trace/event.h>
#include <lib/zx/bti.h>
#include <zircon/threads.h>
#include <limits>
#include <optional>
#include <bind/fuchsia/amlogic/platform/sysmem/heap/cpp/bind.h>
#include <bind/fuchsia/sysmem/heap/cpp/bind.h>
#include "amlogic_codec_adapter.h"
#include "device_ctx.h"
#include "hevcdec.h"
#include "pts_manager.h"
#include "src/lib/memory_barriers/memory_barriers.h"
#include "thread_role.h"
#include "vp9_configuration.h"
#include "vp9_decoder.h"
#include "vp9_utils.h"
namespace amlogic_decoder {
// TODO(dustingreen):
// * Split InitializeStream() into two parts, one to get the format info from
// the HW and send it to the Codec client, the other part to configure
// output buffers once the client has configured Codec output config based
// on the format info. Wire up so that
// onCoreCodecMidStreamOutputConstraintsChange() gets called and so that
// CoreCodecBuildNewOutputConstraints() will pick up the correct current
// format info (whether still mid-stream, or at the start of a new stream
// that's starting before the mid-stream format change was processed for the
// old stream).
// * Allocate output video buffers contig by setting relevant buffer
// constraints to indicate contig to BufferAllocator / BufferCollection.
// * On EndOfStream at input, push all remaining data through the HW decoder
// and detect when the EndOfStream is appropriate to generate at the output.
// * Split video_->Parse() into start/complete and/or switch to feeding the
// ring buffer directly, or whatever is wanted by multi-concurrent-stream
// mode.
// * Consider if there's a way to get AmlogicVideo to re-use buffers across
// a stream switch without over-writing buffers that are still in-use
// downstream.
namespace {
// avconv -f lavfi -i color=c=black:s=42x52 -c:v vp9 -vframes 1 new_stream.ivf
//
// xxd -i new_stream.ivf
//
// We push this through the decoder as our "EndOfStream" marker, and detect it
// at the output (for now) by its unusual 42x52 resolution during
// InitializeStream() _and_ the fact that we've queued this marker. To force
// this frame to be handled by the decoder we queue kFlushThroughBytes of 0
// after this data.
//
// TODO(dustingreen): We don't currently detect the EndOfStream via its stream
// offset in PtsManager (for vp9), but that would be marginally more robust
// than detecting the special resolution. However, to detect via stream offset,
// we'd either need to avoid switching resolutions, or switch resolutions using
// the same output buffer set (including preserving the free/busy status of each
// buffer across the boundary), and delay notifying the client until we're sure
// a format change is real, not just the one immediately before a frame whose
// stream offset is >= the EndOfStream offset.
unsigned char new_stream_ivf[] = {
0x44, 0x4b, 0x49, 0x46, 0x00, 0x00, 0x20, 0x00, 0x56, 0x50, 0x39, 0x30, 0x2a, 0x00, 0x34,
0x00, 0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82,
0x49, 0x83, 0x42, 0x00, 0x02, 0x90, 0x03, 0x36, 0x00, 0x38, 0x24, 0x1c, 0x18, 0x54, 0x00,
0x00, 0x30, 0x60, 0x00, 0x00, 0x13, 0xbf, 0xff, 0xfd, 0x15, 0x62, 0x00, 0x00, 0x00};
unsigned int new_stream_ivf_len = 74;
constexpr uint32_t kHeaderSkipBytes = 32 + 12; // Skip IVF headers.
constexpr uint32_t kEndOfStreamWidth = 42;
constexpr uint32_t kEndOfStreamHeight = 52;
constexpr uint32_t kFlushThroughBytes = 16384;
template <typename T>
constexpr T AlignUpConstexpr(T value, T divisor) {
return (value + divisor - 1) / divisor * divisor;
}
// This is set based on a buffer size setting in Chromium. If Chromium can't deliver more data per
// packet than this, then there's not much point in allowing larger input buffers, or using the RAM
// to support larger input buffers. This isn't to say that I necessarily think this size is safe,
// but since we don't have any rigorous max size for a VP9 superframe, pretty much no size we pick
// will necessarily be safe for _all_ streams. This might be a little smaller than I'm comfortable
// with but it does save some RAM.
constexpr uint32_t kLittleInputPerPacketBufferBytesMin = 1920 * 1080 * 3 / 2 / 2 + 128 * 1024;
// The HW requires a full frame to be present in the stream buffer at a time, and we want to limit
// the size of the stream buffer also, so force the input buffer size.
constexpr uint32_t kLittleInputPerPacketBufferBytesMax = kLittleInputPerPacketBufferBytesMin;
constexpr uint32_t kLittleStreamBufferSize =
AlignUpConstexpr(kLittleInputPerPacketBufferBytesMax + kFlushThroughBytes + 8,
static_cast<uint32_t>(ZX_PAGE_SIZE));
// For now, force the input buffer size to be exactly 1/2 VDEC so that exactly two buffers barely
// fit in VDEC. The HW requires a VP9 superframe to be in a single buffer. At this size we can be
// fairly confident that actual superframes we'll see will fit in a single buffer, but not certain
// of it. But even if we used all of VDEC instead of 1/2 of VDEC, we could still not be certain of
// that. By using 1/2 of VDEC for each of 2 buffers, we get performance benefits over using all of
// VDEC for 1 buffer.
constexpr uint32_t kBigInputPerPacketBufferBytesMin = 1024 * 1024 * 775 / 100 / 2;
constexpr uint32_t kBigInputPerPacketBufferBytesMax = kBigInputPerPacketBufferBytesMin;
constexpr uint32_t kBigStreamBufferSize = AlignUpConstexpr(
kBigInputPerPacketBufferBytesMax + kFlushThroughBytes + 8, static_cast<uint32_t>(ZX_PAGE_SIZE));
constexpr uint32_t kInputPerPacketBufferBytesMin =
kUseLessRam ? kLittleInputPerPacketBufferBytesMin : kBigInputPerPacketBufferBytesMin;
constexpr uint32_t kInputPerPacketBufferBytesMax =
kUseLessRam ? kLittleInputPerPacketBufferBytesMax : kBigInputPerPacketBufferBytesMax;
constexpr uint32_t kStreamBufferSize = kUseLessRam ? kLittleStreamBufferSize : kBigStreamBufferSize;
static_assert(kStreamBufferSize % ZX_PAGE_SIZE == 0);
constexpr uint32_t kInputBufferCountForCodecMin = 1;
constexpr uint32_t kInputBufferCountForCodecMax = 64;
// Zero-initialized, so it shouldn't take up space on-disk.
const uint8_t kFlushThroughZeroes[kFlushThroughBytes] = {};
constexpr bool kHasSar = false;
constexpr uint32_t kSarWidth = 1;
constexpr uint32_t kSarHeight = 1;
constexpr uint32_t kVdecFifoAlign = 8;
static inline constexpr uint32_t make_fourcc(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
return (static_cast<uint32_t>(d) << 24) | (static_cast<uint32_t>(c) << 16) |
(static_cast<uint32_t>(b) << 8) | static_cast<uint32_t>(a);
}
} // namespace
CodecAdapterVp9::CodecAdapterVp9(std::mutex& lock, CodecAdapterEvents* codec_adapter_events,
DeviceCtx* device)
: AmlogicCodecAdapter(lock, codec_adapter_events),
device_(device),
video_(device_->video()),
input_processing_loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
shared_fidl_thread_closure_queue_(std::in_place,
device->driver()->shared_fidl_loop()->dispatcher(),
device->driver()->shared_fidl_thread()) {
ZX_DEBUG_ASSERT(device_);
ZX_DEBUG_ASSERT(video_);
}
CodecAdapterVp9::~CodecAdapterVp9() {
// We need to delete the shared_fidl_thread_closure_queue_ on its dispatcher thread, per the
// rules of ~ClosureQueue.
sync_completion_t shared_fidl_finished;
auto run_on_shared_fidl = [this, &shared_fidl_finished] {
shared_fidl_thread_closure_queue_.reset();
sync_completion_signal(&shared_fidl_finished);
};
if (thrd_current() == device_->driver()->shared_fidl_thread()) {
run_on_shared_fidl();
} else {
shared_fidl_thread_closure_queue_->Enqueue(run_on_shared_fidl);
}
sync_completion_wait(&shared_fidl_finished, ZX_TIME_INFINITE);
// TODO(dustingreen): Remove the printfs or switch them to VLOG.
input_processing_loop_.Quit();
input_processing_loop_.JoinThreads();
input_processing_loop_.Shutdown();
// nothing else to do here, at least not until we aren't calling PowerOff() in
// CoreCodecStopStream().
}
void CodecAdapterVp9::SetCodecDiagnostics(CodecDiagnostics* codec_diagnostics) {
codec_diagnostics_ = codec_diagnostics->CreateDriverCodec("VP9");
}
std::optional<media_metrics::StreamProcessorEvents2MigratedMetricDimensionImplementation>
CodecAdapterVp9::CoreCodecMetricsImplementation() {
return media_metrics::
StreamProcessorEvents2MigratedMetricDimensionImplementation_AmlogicDecoderVp9;
}
bool CodecAdapterVp9::IsCoreCodecRequiringOutputConfigForFormatDetection() { return false; }
bool CodecAdapterVp9::IsCoreCodecMappedBufferUseful(CodecPort port) {
// If buffers are protected, the decoder should/will call secmem TA to re-pack
// VP9 headers in the input. Else the decoder will use a CPU mapping to do
// this repack.
//
// TODO(dustingreen): Make the previous paragraph true. For now we have to
// re-pack using the CPU on REE side.
return true;
}
bool CodecAdapterVp9::IsCoreCodecHwBased(CodecPort port) {
if (port == kOutputPort) {
// Output is HW based regardless of whether output is secure or not.
return true;
}
ZX_DEBUG_ASSERT(port == kInputPort);
// Input is HW based only when secure input at least permitted.
return IsPortSecurePermitted(kInputPort);
}
zx::unowned_bti CodecAdapterVp9::CoreCodecBti() { return zx::unowned_bti(video_->bti()); }
void CodecAdapterVp9::CoreCodecInit(
const fuchsia::media::FormatDetails& initial_input_format_details) {
zx_status_t result = input_processing_loop_.StartThread(
"CodecAdapterVp9::input_processing_thread_", &input_processing_thread_);
if (result != ZX_OK) {
events_->onCoreCodecFailCodec(
"In CodecAdapterVp9::CoreCodecInit(), StartThread() failed (input)");
return;
}
device_->SetThreadProfile(zx::unowned_thread(thrd_get_zx_handle(input_processing_thread_)),
ThreadRole::kVp9InputProcessing);
initial_input_format_details_ = fidl::Clone(initial_input_format_details);
// TODO(dustingreen): We do most of the setup in CoreCodecStartStream()
// currently, but we should do more here and less there.
}
void CodecAdapterVp9::CoreCodecSetSecureMemoryMode(
CodecPort port, fuchsia::mediacodec::SecureMemoryMode secure_memory_mode) {
secure_memory_mode_[port] = secure_memory_mode;
secure_memory_mode_set_[port] = true;
if (port == kOutputPort) {
// Check output secure mode (not input), since overall secure vs. not-secure setup is based on
// output secure memory mode. In particular, when output is secure, the stream buffer is
// secure, which means we can't use the CPU to copy into the stream buffer. Using parser, input
// can be non-secure or secure.
//
// If output non-secure and input secure:
// * by design, this doesn't work for vp9 decode
// If output non-secure and input non-secure:
// * CPU copy from BufferCollection buffer to temp buffer, then parser copy from temp buffer
// to stream buffer. We could skip a copy here, but instead choose to get more efficient
// test coverage (at least for now) by keeping this more similar to how secure input works.
// If output secure and input non-secure:
// * CPU copy from BufferCollection buffer to tmp then parser copy from tmp to stream buffer.
// If output secure and input secure:
// * Parser copy from BufferCollection buffer to stream buffer.
//
// Always use_parser_, for now. This is for more efficient / consistent test coverage.
ZX_DEBUG_ASSERT(use_parser_);
// use_parser_ is already true. If output secure, it really must be true.
ZX_DEBUG_ASSERT(use_parser_ || !IsOutputSecure());
}
}
fuchsia_sysmem2::BufferCollectionConstraints
CodecAdapterVp9::CoreCodecGetBufferCollectionConstraints2(
CodecPort port, const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints,
const fuchsia::media::StreamBufferPartialSettings& partial_settings) {
fuchsia_sysmem2::BufferCollectionConstraints result;
// The CodecImpl won't hand us the sysmem token, so we shouldn't expect to
// have the token here.
ZX_DEBUG_ASSERT(!partial_settings.has_sysmem_token());
if (port == kInputPort) {
// We don't override CoreCodecBuildNewInputConstraints() for now, so use same values that were
// set by default implementation of CoreCodecBuildNewInputConstraints().
min_buffer_count_[kInputPort] = kInputBufferCountForCodecMin;
max_buffer_count_[kInputPort] = kInputBufferCountForCodecMax;
}
ZX_DEBUG_ASSERT(min_buffer_count_[port] != 0);
ZX_DEBUG_ASSERT(max_buffer_count_[port] != 0);
result.min_buffer_count_for_camping() = min_buffer_count_[port];
// Some slack is nice overall, but avoid having each participant ask for
// dedicated slack. Using sysmem the client will ask for it's own buffers for
// camping and any slack, so the codec doesn't need to ask for any extra on
// behalf of the client.
ZX_DEBUG_ASSERT(!result.min_buffer_count_for_dedicated_slack().has_value());
ZX_DEBUG_ASSERT(!result.min_buffer_count_for_shared_slack().has_value());
result.max_buffer_count() = max_buffer_count_[port];
uint32_t per_packet_buffer_bytes_min;
uint32_t per_packet_buffer_bytes_max;
if (port == kInputPort) {
per_packet_buffer_bytes_min = kInputPerPacketBufferBytesMin;
per_packet_buffer_bytes_max = kInputPerPacketBufferBytesMax;
} else {
ZX_DEBUG_ASSERT(port == kOutputPort);
// NV12, based on min stride.
per_packet_buffer_bytes_min = stride_ * coded_height_ * 3 / 2;
// At least for now, don't cap the per-packet buffer size for output. The
// HW only cares about the portion we set up for output anyway, and the
// client has no way to force output to occur into portions of the output
// buffer beyond what's implied by the max supported image dimensions.
per_packet_buffer_bytes_max = 0xFFFFFFFF;
}
auto& bmc = result.buffer_memory_constraints().emplace();
bmc.min_size_bytes() = per_packet_buffer_bytes_min;
bmc.max_size_bytes() = per_packet_buffer_bytes_max;
// Non-secure input buffers are never read directly by the hardware, so they don't need to be
// physically contiguous.
bmc.physically_contiguous_required() = (port == kOutputPort) || IsPortSecurePermitted(port);
bmc.secure_required() = IsPortSecureRequired(port);
bmc.cpu_domain_supported() = !IsPortSecureRequired(port);
bmc.ram_domain_supported() = !IsPortSecureRequired(port) && (port == kOutputPort);
if (IsPortSecurePermitted(port)) {
bmc.inaccessible_domain_supported() = true;
std::string secure_heap = (port == kInputPort)
? bind_fuchsia_amlogic_platform_sysmem_heap::HEAP_TYPE_SECURE_VDEC
: bind_fuchsia_amlogic_platform_sysmem_heap::HEAP_TYPE_SECURE;
ZX_DEBUG_ASSERT(!bmc.permitted_heaps().has_value());
bmc.permitted_heaps().emplace().emplace_back(sysmem::MakeHeap(secure_heap, 0));
}
if (!IsPortSecureRequired(port)) {
if (!bmc.permitted_heaps().has_value()) {
bmc.permitted_heaps().emplace();
}
bmc.permitted_heaps()->emplace_back(
sysmem::MakeHeap(bind_fuchsia_sysmem_heap::HEAP_TYPE_SYSTEM_RAM, 0));
}
if (port == kOutputPort) {
auto& image_constraints = result.image_format_constraints().emplace().emplace_back();
image_constraints.pixel_format() = fuchsia_images2::PixelFormat::kNv12;
image_constraints.pixel_format_modifier() = fuchsia_images2::PixelFormatModifier::kLinear;
// TODO(https://fxbug.dev/42084950): confirm that REC709 is always what we want here, or plumb
// actual YUV color space if it can ever be REC601_*. Since 2020 and 2100
// are minimum 10 bits per Y sample and we're outputting NV12, 601 is the
// only other potential possibility here.
image_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.
image_constraints.min_size() = {2, 2};
// This intentionally isn't the _height_ of a 4k frame, it's intentionally
// the _width_ of a 4k frame assigned to max_coded_height.
//
// See max_coded_width_times_coded_height. We intentionally constrain the
// max dimension in width or height to the width of a 4k frame. While the
// HW might be able to go bigger than that as long as the other dimension is
// smaller to compensate, we don't really need to enable any larger than
// 4k's width in either dimension, so we don't.
image_constraints.max_size() = {4096, 4096};
// AFAICT so far, this decoder has no way to output a stride other than
// fbl::round_up(width_, 32u), so we'll fail later if that stride isn't
// permitted.
image_constraints.min_bytes_per_row() = 2;
// no hard-coded max stride, at least for now
image_constraints.max_bytes_per_row() = 0xFFFFFFFF;
image_constraints.max_width_times_height() = 4096 * 2176;
// VP9 decoder writes NV12 frames separately from reference frames, so the
// coded_width and coded_height aren't constrained to be block aligned.
//
// The vp9_decoder code will round up the coded_width to use more of the
// also-rounded-up stride, so that coded_width can be even even if the
// HW reported an odd width.
//
// Unclear how we'd deal with odd coded_height, even if we wanted to.
image_constraints.size_alignment() = {2, 2};
image_constraints.bytes_per_row_divisor() = 32;
// Even though we only ever output at offset 0, sysmem defaults start_offset_divisor to the
// image format alignment which is 2 for NV12. Since we are a producer, we should fully specify
// here so late attach clients don't have to specify it explicitly.
image_constraints.start_offset_divisor() = 2;
// Odd display dimensions are permitted, but these don't necessarily imply
// odd NV12 coded_width or coded_height dimensions - those are constrainted
// above.
//
// FWIW, the webm VP9 conformance streams seem to think that an odd width
// implies that there _is_ a chroma sample for the right-most Y samples,
// since that's how we get the MD5 to match for
// Profile_0_8bit/frm_resize/crowd_run_1280X768_fr30_bd8_frm_resize_l31.
// FWIW, the HW VP9 decoder can decode and match the conformance MD5 for
// that stream, despite it's odd width.
image_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.
image_constraints.required_min_size() = {coded_width_, coded_height_};
image_constraints.required_max_size() = {coded_width_, coded_height_};
// Sysmem2 doesn't have required_min_bytes_per_row or
// required_max_bytes_per_row (at least for now). If those later prove to be
// worth adding to sysmem2, we'd set those to stride_ here. The way we'd
// know is IsCurrentOutputBufferCollectionUsable would complain about the
// stride not being within [min_bytes_per_row..max_bytes_per_row].
//
// The vp9 decoder is the producer. We're implicitly relying on the
// consumer(s) to not overly constrain the bytes_per_row using
// min_bytes_per_row and max_bytes_per_row, in order for decode of the
// current stream dimensions to be possible.
} else {
ZX_DEBUG_ASSERT(!result.image_format_constraints().has_value());
}
// We don't have to fill out usage - CodecImpl takes care of that.
ZX_DEBUG_ASSERT(!result.usage().has_value());
return result;
}
void CodecAdapterVp9::CoreCodecSetBufferCollectionInfo(
CodecPort port, const fuchsia_sysmem2::BufferCollectionInfo& buffer_collection_info) {
if (port == kOutputPort) {
ZX_DEBUG_ASSERT(
*buffer_collection_info.settings()->buffer_settings()->is_physically_contiguous());
ZX_DEBUG_ASSERT(buffer_collection_info.settings()->image_format_constraints().has_value());
ZX_DEBUG_ASSERT(
*buffer_collection_info.settings()->image_format_constraints()->pixel_format() ==
fuchsia_images2::PixelFormat::kNv12);
auto clone_info_result =
sysmem::V2CloneBufferCollectionInfo(buffer_collection_info, ZX_RIGHT_SAME_RIGHTS);
// failure to dup a handle is fatal to this process (analogous to OOM)
ZX_ASSERT(clone_info_result.is_ok());
output_buffer_collection_info_ = clone_info_result.take_value();
}
if (IsPortSecurePermitted(port)) {
ZX_DEBUG_ASSERT(
*buffer_collection_info.settings()->buffer_settings()->is_physically_contiguous());
}
buffer_settings_[port].emplace(*buffer_collection_info.settings());
}
void CodecAdapterVp9::OnFrameReady(std::shared_ptr<VideoFrame> frame) {
TRACE_DURATION("media", "CodecAdapterVp9::OnFrameReady", "index", frame->index);
// The Codec interface requires that emitted frames are cache clean
// at least for now. We invalidate without skipping over stride-width
// per line, at least partly because stride - width is small (possibly
// always 0) for this decoder. But we do invalidate the UV section
// separately in case uv_plane_offset happens to leave significant
// space after the Y section (regardless of whether there's actually
// ever much padding there).
//
// TODO(dustingreen): Probably there's not ever any significant
// padding between Y and UV for this decoder, so probably can make one
// invalidate call here instead of two with no downsides.
//
// TODO(dustingreen): Skip this when the buffer isn't map-able.
BarrierBeforeInvalidate();
io_buffer_cache_flush_invalidate(&frame->buffer, 0, frame->stride * frame->coded_height);
io_buffer_cache_flush_invalidate(&frame->buffer, frame->uv_plane_offset,
frame->stride * frame->coded_height / 2);
BarrierAfterFlush();
uint64_t total_size_bytes = frame->stride * frame->coded_height * 3 / 2;
const CodecBuffer* buffer = frame->codec_buffer;
ZX_DEBUG_ASSERT(buffer);
ZX_DEBUG_ASSERT(total_size_bytes <= buffer->size());
if (total_size_bytes > std::numeric_limits<uint32_t>::max()) {
OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN);
return;
}
// Also associates buffer with packet under lock_.
CodecPacket* packet = GetFreePacket(buffer);
// We know there will be a free packet thanks to SetCheckOutputReady().
ZX_DEBUG_ASSERT(packet);
packet->SetStartOffset(0);
packet->SetValidLengthBytes(static_cast<uint32_t>(total_size_bytes));
if (frame->has_pts) {
packet->SetTimstampIsh(frame->pts);
} else {
packet->ClearTimestampIsh();
}
if (frame->coded_width != output_coded_width_ || frame->coded_height != output_coded_height_ ||
frame->stride != output_stride_ || frame->display_width != output_display_width_ ||
frame->display_height != output_display_height_) {
output_coded_width_ = frame->coded_width;
output_coded_height_ = frame->coded_height;
output_stride_ = frame->stride;
output_display_width_ = frame->display_width;
output_display_height_ = frame->display_height;
DLOG(
"output_coded_width_: %u output_coded_height_: %u output_stride_: %u "
"output_display_width_: %u output_display_height_: %u",
output_coded_width_, output_coded_height_, output_stride_, output_display_width_,
output_display_height_);
events_->onCoreCodecOutputFormatChange();
}
DLOG("onCoreCodecOutputPacket()");
events_->onCoreCodecOutputPacket(packet, false, false);
}
void CodecAdapterVp9::OnError() {
LOG(ERROR, "CodecAdapterVp9::OnError()");
OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN);
}
void CodecAdapterVp9::OnEos() { OnCoreCodecEos(); }
bool CodecAdapterVp9::IsOutputReady() {
std::lock_guard<std::mutex> lock(lock_);
// We're ready if output hasn't been configured yet, or if we have free
// output packets. This way the decoder can swap in when there's no output
// config yet, but will stop trying to run when we're out of output packets.
return all_output_packets_.empty() || !free_output_packets_.empty();
}
// TODO(dustingreen): A lot of the stuff created in this method should be able
// to get re-used from stream to stream. We'll probably want to factor out
// create/init from stream init further down.
void CodecAdapterVp9::CoreCodecStartStream() {
zx_status_t status;
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
parsed_video_size_ = 0;
is_input_end_of_stream_queued_to_core_ = false;
has_input_keyframe_ = false;
is_stream_failed_ = false;
ZX_DEBUG_ASSERT(queued_frame_sizes_.empty());
} // ~lock
{ // scope lock
std::lock_guard<std::mutex> lock(*video_->video_decoder_lock());
// Must create under lock to ensure that a potential other instance that incremented power
// ref(s) first is fully done un-gating clocks.
auto decoder =
std::make_unique<Vp9Decoder>(video_, this, Vp9Decoder::InputType::kMultiStream,
std::move(decoder_internal_buffers_), false, IsOutputSecure());
// Moving a std::optional<> doesn't reset() the source.
decoder_internal_buffers_.reset();
decoder->SetFrameDataProvider(this);
if (codec_diagnostics_) {
decoder->SetCodecDiagnostics(&codec_diagnostics_.value());
}
status = decoder->InitializeBuffers();
if (status != ZX_OK) {
events_->onCoreCodecFailCodec("video_->video_decoder_->Initialize() failed");
return;
}
auto instance = std::make_unique<DecoderInstance>(std::move(decoder), video_->hevc_core());
// The video decoder can read from non-secure buffers even in secure mode.
std::optional<InternalBuffer> saved_stream_buffer = std::move(saved_stream_buffer_);
saved_stream_buffer_.reset();
status = video_->AllocateStreamBuffer(instance->stream_buffer(), kStreamBufferSize,
std::move(saved_stream_buffer),
/*use_parser=*/use_parser_,
/*is_secure=*/IsPortSecure(kInputPort));
if (status != ZX_OK) {
// Log here instead of in AllocateStreamBuffer() because video_ doesn't know which codec this
// is about.
events_->onCoreCodecLogEvent(
media_metrics::StreamProcessorEvents2MigratedMetricDimensionEvent::AllocationError);
events_->onCoreCodecFailCodec("AllocateStreamBuffer() failed");
return;
}
decoder_ = static_cast<Vp9Decoder*>(instance->decoder());
video_->AddNewDecoderInstance(std::move(instance));
// Decoder is currently swapped out, but will be swapped in when data is
// received for it.
} // ~lock
}
void CodecAdapterVp9::CoreCodecQueueInputFormatDetails(
const fuchsia::media::FormatDetails& per_stream_override_format_details) {
// TODO(dustingreen): Consider letting the client specify profile/level info
// in the FormatDetails at least optionally, and possibly sizing input
// buffer constraints and/or other buffers based on that.
QueueInputItem(CodecInputItem::FormatDetails(per_stream_override_format_details));
}
void CodecAdapterVp9::CoreCodecQueueInputPacket(CodecPacket* packet) {
QueueInputItem(CodecInputItem::Packet(packet));
}
void CodecAdapterVp9::CoreCodecQueueInputEndOfStream() {
// This queues a marker, but doesn't force the HW to necessarily decode all
// the way up to the marker, depending on whether the client closes the stream
// or switches to a different stream first - in those cases it's fine for the
// marker to never show up as output EndOfStream.
QueueInputItem(CodecInputItem::EndOfStream());
}
// TODO(dustingreen): See comment on CoreCodecStartStream() re. not deleting
// creating as much stuff for each stream.
void CodecAdapterVp9::CoreCodecStopStream() {
std::list<CodecInputItem> leftover_input_items = CoreCodecStopStreamInternal();
for (auto& input_item : leftover_input_items) {
if (input_item.is_packet()) {
events_->onCoreCodecInputPacketDone(std::move(input_item.packet()));
}
}
}
std::list<CodecInputItem> CodecAdapterVp9::CoreCodecStopStreamInternal() {
std::list<CodecInputItem> input_items_result;
{ // scope lock
std::unique_lock<std::mutex> lock(lock_);
// This helps any previously-queued ProcessInput() calls return faster.
is_cancelling_input_processing_ = true;
std::condition_variable stop_input_processing_condition;
// We know there won't be any new queuing of input, so once this posted work
// runs, we know all previously-queued ProcessInput() calls have returned.
PostToInputProcessingThread([this, &stop_input_processing_condition, &input_items_result] {
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
ZX_DEBUG_ASSERT(is_cancelling_input_processing_);
ZX_DEBUG_ASSERT(input_items_result.empty());
input_items_result.swap(input_queue_);
is_cancelling_input_processing_ = false;
} // ~lock
stop_input_processing_condition.notify_all();
});
while (is_cancelling_input_processing_) {
stop_input_processing_condition.wait(lock);
}
ZX_DEBUG_ASSERT(!is_cancelling_input_processing_);
} // ~lock
// TODO(dustingreen): Currently, we have to tear down a few pieces of video_,
// to make it possible to run all the AmlogicVideo + DecoderCore +
// VideoDecoder code that seems necessary to run to ensure that a new stream
// will be entirely separate from an old stream, without deleting/creating
// AmlogicVideo itself. Probably we can tackle this layer-by-layer, fixing up
// AmlogicVideo to be more re-usable without the stuff in this method, then
// DecoderCore, then VideoDecoder.
if (decoder_) {
Vp9Decoder* decoder_to_remove = decoder_;
// We care that decoder_ = nullptr under the lock before it becomes bad to
// call ReturnFrame() in CoreCodecRecycleOutputPacket(). The two sequential
// lock hold intervals of video_decoder_lock() don't need to be one
// interval.
{ // scope lock
std::lock_guard<std::mutex> lock(*video_->video_decoder_lock());
// If the decoder's still running this will stop it as well.
video_->RemoveDecoderWithCallbackLocked(
decoder_to_remove, [this, decoder_to_remove](DecoderInstance* instance) {
// Stash the internal buffers so next decoder instance won't need to allocate new ones
// unless the buffers are the wrong size or wrong is_secure() etc. This saves time
// during seek and can save time during dimension changes when achieved by stream
// switching (in contrast to dimension changes achieved within a single stream, which
// don't remove the decoder during the dimension change when successful).
decoder_internal_buffers_.emplace(decoder_to_remove->TakeInternalBuffers());
ZX_DEBUG_ASSERT(!saved_stream_buffer_);
saved_stream_buffer_ = std::move(instance->stream_buffer()->optional_buffer());
});
decoder_ = nullptr;
} // ~lock
}
queued_frame_sizes_.clear();
return input_items_result;
}
void CodecAdapterVp9::CoreCodecResetStreamAfterCurrentFrame() {
// Currently this takes ~20-40ms per reset. We might be able to improve the performance by having
// a stop that doesn't deallocate followed by a start that doesn't allocate, but since we'll
// fairly soon only be using this method during watchdog processing, it's not worth optimizing for
// the temporary time interval during which we might potentially use this on multiple
// non-keyframes in a row before a keyframe, only in the case of protected input.
//
// If we were to optimize in that way, it'd increase the complexity of init and de-init code. The
// current way we use that code exactly the same way for reset as for init and de-init, which is
// good from a test coverage point of view.
// This fences and quiesces the input processing thread, and the current StreamControl thread is
// the only other thread that modifies is_input_end_of_stream_queued_to_core_, so we know
// is_input_end_of_stream_queued_to_core_ won't be changing.
LOG(DEBUG, "before CoreCodecStopStreamInternal()");
std::list<CodecInputItem> input_items = CoreCodecStopStreamInternal();
auto return_any_input_items = fit::defer([this, &input_items] {
for (auto& input_item : input_items) {
if (input_item.is_packet()) {
events_->onCoreCodecInputPacketDone(std::move(input_item.packet()));
}
}
});
if (is_input_end_of_stream_queued_to_core_) {
// We don't handle this corner case of a corner case. Fail the stream instead.
events_->onCoreCodecFailStream(fuchsia::media::StreamError::EOS_PROCESSING);
return;
}
LOG(DEBUG, "after stop; before CoreCodecStartStream()");
CoreCodecStartStream();
LOG(DEBUG, "re-queueing items...");
while (!input_items.empty()) {
QueueInputItem(std::move(input_items.front()));
input_items.pop_front();
}
LOG(DEBUG, "done re-queueing items.");
}
void CodecAdapterVp9::CoreCodecAddBuffer(CodecPort port, const CodecBuffer* buffer) {
if (port != kOutputPort) {
return;
}
all_output_buffers_.push_back(buffer);
}
void CodecAdapterVp9::CoreCodecConfigureBuffers(
CodecPort port, const std::vector<std::unique_ptr<CodecPacket>>& packets) {
if (port == kOutputPort) {
ZX_DEBUG_ASSERT(all_output_packets_.empty());
ZX_DEBUG_ASSERT(free_output_packets_.empty());
ZX_DEBUG_ASSERT(!all_output_buffers_.empty());
ZX_DEBUG_ASSERT(all_output_buffers_.size() == packets.size());
for (auto& packet : packets) {
all_output_packets_.push_back(packet.get());
free_output_packets_.push_back(packet.get()->packet_index());
}
// This should prevent any inadvertent dependence by clients on the ordering
// of packet_index values in the output stream or any assumptions re. the
// relationship between packet_index and buffer_index.
std::shuffle(free_output_packets_.begin(), free_output_packets_.end(), not_for_security_prng_);
}
}
void CodecAdapterVp9::CoreCodecRecycleOutputPacket(CodecPacket* packet) {
if (packet->is_new()) {
packet->SetIsNew(false);
return;
}
ZX_DEBUG_ASSERT(!packet->is_new());
const CodecBuffer* buffer = packet->buffer();
// Getting the buffer is all we needed the packet for, so note that the packet
// is free fairly early, to side-step any issues with early returns. The
// CodecImpl already considers the packet free, but it won't actually get
// re-used until after it goes on the free list here.
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
packet->SetBuffer(nullptr);
free_output_packets_.push_back(packet->packet_index());
} // ~lock
{ // scope lock
std::lock_guard<std::mutex> lock(*video_->video_decoder_lock());
std::shared_ptr<VideoFrame> frame = buffer->video_frame().lock();
if (!frame) {
// EndOfStream seen at the output, or a new InitializeFrames(), can cause
// !frame, which is fine. In that case, any new stream will request
// allocation of new frames.
return;
}
// Recycle can happen while stopped, but this CodecAdapater has no way yet
// to return frames while stopped, or to re-use buffers/frames across a
// stream switch. Any new stream will request allocation of new frames.
if (!decoder_) {
return;
}
decoder_->ReturnFrame(frame);
video_->TryToReschedule();
} // ~lock
}
void CodecAdapterVp9::CoreCodecEnsureBuffersNotConfigured(CodecPort port) {
std::lock_guard<std::mutex> lock(lock_);
// This adapter should ensure that zero old CodecPacket* or CodecBuffer*
// remain in this adapter (or below).
if (port == kInputPort) {
// There shouldn't be any queued input at this point, but if there is any,
// fail here even in a release build.
ZX_ASSERT(input_queue_.empty());
} else {
ZX_DEBUG_ASSERT(port == kOutputPort);
// The old all_output_buffers_ are no longer valid.
all_output_buffers_.clear();
all_output_packets_.clear();
free_output_packets_.clear();
output_buffer_collection_info_ = std::nullopt;
}
buffer_settings_[port] = std::nullopt;
}
std::unique_ptr<const fuchsia::media::StreamOutputConstraints>
CodecAdapterVp9::CoreCodecBuildNewOutputConstraints(
uint64_t stream_lifetime_ordinal, uint64_t new_output_buffer_constraints_version_ordinal,
bool buffer_constraints_action_required) {
// bear.vp9 decodes into 320x192 YUV buffers, but the video display
// dimensions are 320x180. A the bottom of the buffer only .25 of the last
// 16 height macroblock row is meant to be displayed.
//
// This decoder produces NV12.
auto config = std::make_unique<fuchsia::media::StreamOutputConstraints>();
config->set_stream_lifetime_ordinal(stream_lifetime_ordinal);
auto* constraints = config->mutable_buffer_constraints();
// For the moment, there will be only one StreamOutputConstraints, and it'll
// need output buffers configured for it.
ZX_DEBUG_ASSERT(buffer_constraints_action_required);
config->set_buffer_constraints_action_required(buffer_constraints_action_required);
constraints->set_buffer_constraints_version_ordinal(
new_output_buffer_constraints_version_ordinal);
return config;
}
fuchsia::media::StreamOutputFormat CodecAdapterVp9::CoreCodecGetOutputFormat(
uint64_t stream_lifetime_ordinal, uint64_t new_output_format_details_version_ordinal) {
fuchsia::media::StreamOutputFormat result;
result.set_stream_lifetime_ordinal(stream_lifetime_ordinal);
result.mutable_format_details()->set_format_details_version_ordinal(
new_output_format_details_version_ordinal);
result.mutable_format_details()->set_mime_type("video/raw");
// For the moment, we'll memcpy to NV12 without any extra padding.
fuchsia::media::VideoUncompressedFormat video_uncompressed;
video_uncompressed.fourcc = make_fourcc('N', 'V', '1', '2');
video_uncompressed.primary_width_pixels = output_coded_width_;
video_uncompressed.primary_height_pixels = output_coded_height_;
video_uncompressed.secondary_width_pixels = output_coded_width_ / 2;
video_uncompressed.secondary_height_pixels = output_coded_height_ / 2;
// TODO(dustingreen): remove this field from the VideoUncompressedFormat or
// specify separately for primary / secondary.
video_uncompressed.planar = true;
video_uncompressed.swizzled = false;
video_uncompressed.primary_line_stride_bytes = output_stride_;
video_uncompressed.secondary_line_stride_bytes = output_stride_;
video_uncompressed.primary_start_offset = 0;
video_uncompressed.secondary_start_offset = output_stride_ * output_coded_height_;
video_uncompressed.tertiary_start_offset = output_stride_ * output_coded_height_ + 1;
video_uncompressed.primary_pixel_stride = 1;
video_uncompressed.secondary_pixel_stride = 2;
video_uncompressed.primary_display_width_pixels = output_display_width_;
video_uncompressed.primary_display_height_pixels = output_display_height_;
video_uncompressed.has_pixel_aspect_ratio = kHasSar;
video_uncompressed.pixel_aspect_ratio_width = kSarWidth;
video_uncompressed.pixel_aspect_ratio_height = kSarHeight;
video_uncompressed.image_format.pixel_format.type = fuchsia::sysmem::PixelFormatType::NV12;
video_uncompressed.image_format.coded_width = output_coded_width_;
video_uncompressed.image_format.coded_height = output_coded_height_;
DLOG("vp9 coded_width: %u coded_height: %u", output_coded_width_, output_coded_height_);
video_uncompressed.image_format.bytes_per_row = output_stride_;
video_uncompressed.image_format.display_width = output_display_width_;
video_uncompressed.image_format.display_height = output_display_height_;
video_uncompressed.image_format.layers = 1;
video_uncompressed.image_format.color_space.type = fuchsia::sysmem::ColorSpaceType::REC709;
video_uncompressed.image_format.has_pixel_aspect_ratio = kHasSar;
video_uncompressed.image_format.pixel_aspect_ratio_width = kSarWidth;
video_uncompressed.image_format.pixel_aspect_ratio_height = kSarHeight;
fuchsia::media::VideoFormat video_format;
video_format.set_uncompressed(std::move(video_uncompressed));
result.mutable_format_details()->mutable_domain()->set_video(std::move(video_format));
return result;
}
void CodecAdapterVp9::CoreCodecMidStreamOutputBufferReConfigPrepare() {
// For this adapter, the core codec just needs us to get new frame buffers
// set up, so nothing to do here.
//
// CoreCodecEnsureBuffersNotConfigured() will run soon.
}
void CodecAdapterVp9::CoreCodecMidStreamOutputBufferReConfigFinish() {
MidStreamOutputBufferConfigInternal(true);
}
void CodecAdapterVp9::MidStreamOutputBufferConfigInternal(bool did_reallocate_buffers) {
// Now that the client has configured output buffers, we need to hand those
// back to the core codec via return of InitializedFrames
std::vector<CodecFrame> frames;
uint32_t coded_width;
uint32_t coded_height;
uint32_t stride;
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
// Now we need to populate the frames_out vector.
for (uint32_t i = 0; i < all_output_buffers_.size(); i++) {
ZX_DEBUG_ASSERT(all_output_buffers_[i]->index() == i);
frames.emplace_back(*all_output_buffers_[i]);
frames.back().initial_usage_count() = 0;
}
for (auto* codec_packet : all_output_packets_) {
// The buffer() being non-null corresponds to the packet index not being in
// free_output_packets_. In other words, the non-null buffer() fields among all packets is
// all the used buffers. The buffer indexes and frames indexes are the same due to how frames
// is populated above.
if (codec_packet->buffer()) {
// This won't happen if we're doing a CoreCodecMidStreamOutputBufferReConfigFinish(). In
// that case we cleared all the packets and buffers and allocated new ones, so there won't
// be any packet with an assigned buffer, since there aren't any packets from before.
//
// On the other hand if we're telling an H264MultiDecoder about buffers that aren't new and
// may still be in flight on output, we some are not initially free ("initially" as in when
// InitializedFrames() is called).
//
// When !did_reallocate_buffers, we know that CoreCodecRecycleOutputPacket() won't be
// running for the entire MidStreamOutputBufferConfigInternal(). It's really that fact
// rather than the present lock_ interval that makes this initial_usage_count() stuff
// synchronize properly with CoreCodecRecycleOutputPacket().
ZX_DEBUG_ASSERT(!did_reallocate_buffers);
// Due to show_existing_frame, we may find that the same buffer is in use by more than one
// packet. So we count them.
++frames[codec_packet->buffer()->index()].initial_usage_count();
}
}
coded_width = coded_width_;
coded_height = coded_height_;
stride = stride_;
} // ~lock
{ // scope lock
std::lock_guard<std::mutex> lock(*video_->video_decoder_lock());
video_->video_decoder()->InitializedFrames(std::move(frames), coded_width, coded_height,
stride);
} // ~lock
}
void CodecAdapterVp9::CoreCodecSetStreamControlProfile(zx::unowned_thread stream_control_thread) {
device_->SetThreadProfile(std::move(stream_control_thread), ThreadRole::kVp9StreamControl);
}
void CodecAdapterVp9::PostSerial(async_dispatcher_t* dispatcher, fit::closure to_run) {
zx_status_t post_result = async::PostTask(dispatcher, std::move(to_run));
ZX_ASSERT_MSG(post_result == ZX_OK, "async::PostTask() failed - result: %d\n", post_result);
}
void CodecAdapterVp9::PostToInputProcessingThread(fit::closure to_run) {
PostSerial(input_processing_loop_.dispatcher(), std::move(to_run));
}
void CodecAdapterVp9::QueueInputItem(CodecInputItem input_item) {
bool is_trigger_needed = false;
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
// For now we don't worry about avoiding a trigger if we happen to queue
// when ProcessInput() has removed the last item but ProcessInput() is still
// running.
if (!is_process_input_queued_) {
is_trigger_needed = input_queue_.empty();
is_process_input_queued_ = is_trigger_needed;
}
input_queue_.emplace_back(std::move(input_item));
} // ~lock
if (is_trigger_needed) {
PostToInputProcessingThread(fit::bind_member(this, &CodecAdapterVp9::ProcessInput));
}
}
void CodecAdapterVp9::ProcessInput() {
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
is_process_input_queued_ = false;
} // ~lock
std::lock_guard<std::mutex> lock(*video_->video_decoder_lock());
auto decoder = static_cast<Vp9Decoder*>(video_->video_decoder());
if (decoder_ != decoder) {
video_->TryToReschedule();
// The reschedule will queue reading input data if this decoder was
// scheduled.
return;
}
if (decoder->needs_more_input_data()) {
ReadMoreInputData(decoder);
}
}
void CodecAdapterVp9::ReadMoreInputDataFromReschedule(Vp9Decoder* decoder) {
bool is_trigger_needed = false;
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
// For now we don't worry about avoiding a trigger if we happen to queue
// when ProcessInput() has removed the last item but ProcessInput() is still
// running.
if (!is_process_input_queued_) {
is_trigger_needed = true;
is_process_input_queued_ = true;
}
} // ~lock
// Trigger this on the input thread instead of immediately handling it to
// simplifying the locking.
if (is_trigger_needed) {
PostToInputProcessingThread(fit::bind_member(this, &CodecAdapterVp9::ProcessInput));
}
}
bool CodecAdapterVp9::HasMoreInputData() {
if (queued_frame_sizes_.size() > 0)
return true;
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
if (is_stream_failed_ || is_cancelling_input_processing_ || input_queue_.empty()) {
return false;
}
} // ~lock
return true;
}
void CodecAdapterVp9::AsyncResetStreamAfterCurrentFrame() {
LOG(ERROR, "async reset stream (after current frame) triggered");
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
// The current stream is temporarily failed, until CoreCodecResetStreamAfterCurrentFrame() soon
// on the StreamControl thread. This prevents ReadMoreInputData() from queueing any more input
// data after any currently-running iteration.
//
// While Vp9Decoder::needs_more_input_data() may already be returning false which may serve a
// similar purpose depending on how/when Vp9Decoder calls this method, it's nice to directly
// mute queing any more input in this layer.
is_stream_failed_ = true;
} // ~lock
events_->onCoreCodecResetStreamAfterCurrentFrame();
}
CodecInputItem CodecAdapterVp9::DequeueInputItem() {
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
if (is_stream_failed_ || is_cancelling_input_processing_ || input_queue_.empty()) {
return CodecInputItem::Invalid();
}
CodecInputItem to_ret = std::move(input_queue_.front());
input_queue_.pop_front();
return to_ret;
} // ~lock
}
// If paddr_size != 0, paddr_base is used to submit data to the HW directly by physical address.
// Otherwise, vaddr_base and vaddr_size are valid, and are used to submit data to the HW.
void CodecAdapterVp9::SubmitDataToStreamBuffer(zx_paddr_t paddr_base, uint32_t paddr_size,
uint8_t* vaddr_base, uint32_t vaddr_size) {
ZX_DEBUG_ASSERT(paddr_size == 0 || use_parser_);
video_->AssertVideoDecoderLockHeld();
zx_status_t status;
if (use_parser_) {
status = video_->SetProtected(VideoDecoder::Owner::ProtectableHardwareUnit::kParser,
IsPortSecure(kInputPort));
if (status != ZX_OK) {
LOG(ERROR, "video_->SetProtected(kParser) failed - status: %d", status);
OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN);
return;
}
// Pass nullptr because we'll handle syncing updates manually.
status = video_->parser()->InitializeEsParser(nullptr);
if (status != ZX_OK) {
DECODE_ERROR("InitializeEsParser failed - status: %d", status);
OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN);
return;
}
uint32_t size = paddr_size ? paddr_size : vaddr_size;
if (size + sizeof(kFlushThroughZeroes) > video_->GetStreamBufferEmptySpace()) {
// We don't want the parser to hang waiting for output buffer space, since new space will
// never be released to it since we need to manually update the read pointer.
// TODO(https://fxbug.dev/42117951): Handle copying only as much as can fit and waiting for
// kVp9InputBufferEmpty to continue copying the remainder.
DECODE_ERROR("Empty space in stream buffer %d too small for video data (%lu)",
video_->GetStreamBufferEmptySpace(), size + sizeof(kFlushThroughZeroes));
OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN);
return;
}
video_->parser()->SyncFromDecoderInstance(video_->current_instance());
if (paddr_size) {
status = video_->parser()->ParseVideoPhysical(paddr_base, paddr_size);
} else {
status = video_->parser()->ParseVideo(vaddr_base, vaddr_size);
}
if (status != ZX_OK) {
DECODE_ERROR("Parsing video failed - status: %d", status);
OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN);
return;
}
status = video_->parser()->WaitForParsingCompleted(ZX_SEC(10));
if (status != ZX_OK) {
DECODE_ERROR("Parsing video timed out - status: %d", status);
video_->parser()->CancelParsing();
OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN);
return;
}
status = video_->parser()->ParseVideo(kFlushThroughZeroes, sizeof(kFlushThroughZeroes));
if (status != ZX_OK) {
DECODE_ERROR("Parsing flush-through zeros failed - status: %d", status);
OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN);
return;
}
status = video_->parser()->WaitForParsingCompleted(ZX_SEC(10));
if (status != ZX_OK) {
DECODE_ERROR("Parsing flush-through zeros timed out - status: %d", status);
video_->parser()->CancelParsing();
OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN);
return;
}
video_->parser()->SyncToDecoderInstance(video_->current_instance());
} else {
ZX_DEBUG_ASSERT(!paddr_size);
status = video_->ProcessVideoNoParser(vaddr_base, vaddr_size);
if (status != ZX_OK) {
LOG(ERROR, "video_->ProcessVideoNoParser() (data) failed - status: %d", status);
OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN);
return;
}
status = video_->ProcessVideoNoParser(kFlushThroughZeroes, sizeof(kFlushThroughZeroes));
if (status != ZX_OK) {
LOG(ERROR, "video_->ProcessVideoNoParser() (zeroes) failed - status: %d", status);
OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN);
return;
}
}
}
// The decoder lock is held by caller during this method.
void CodecAdapterVp9::ReadMoreInputData(Vp9Decoder* decoder) {
LOG(DEBUG, "top");
// Typically we only get one frame from the FW per UpdateDecodeSize(), but if we submitted more
// than one frame of a superframe to the FW at once, we _sometimes_ get more than one frame from
// the FW before the kVp9CommandNalDecodeDone (and before the subsequent UpdateDecodeSize() for
// the next frame that we would have done).
//
// By adjusting queued_frame_sizes_ here when we get more than one frame, we avoid asking the FW
// to keep decoding if it's already delivered all the frames we're expecting. If we ask the FW to
// keep decoding after all expected frames, we just end up hitting the watchdog (the FW doesn't
// indicate kVp9CommandNalDecodeDone unless a frame was emitted subsequent to the previous
// UpdateDecodeSize(), as far as I can tell).
//
// If input data isn't hostile, we likely will never see queued_frame_sizes_.size() == 0 when
// decoder->FramesSinceUpdateDecodeSize() > 1, but it doesn't hurt to check, in case the FW can
// somehow be influenced to output more frames than the number of AMLV headers we added.
if (decoder->FramesSinceUpdateDecodeSize() > 1 && queued_frame_sizes_.size() != 0) {
// We expect 1, and we already removed that 1 from queued_frame_sizes_ previously. If more than
// 1 frame was indicated by the FW, then for each of the extra frames, we need to reduce the
// size of queued_frame_sizes_ by 1, without changing the sum of the values, unless the last
// item is being removed in which case the last item is removed and the sum becomes 0.
for (uint32_t i = 0; i < decoder->FramesSinceUpdateDecodeSize() - 1; ++i) {
DLOG(
"decoder->FramesSinceUpdateDecodeSize() > 1 -- "
"decoder->FramesSinceUpdateDecodeSize(): %u queued_frame_sizes_.front(): %u "
"queued_frame_sizes_.size(): %lu",
decoder->FramesSinceUpdateDecodeSize(), queued_frame_sizes_.front(),
queued_frame_sizes_.size());
uint32_t old_front_frame_size = queued_frame_sizes_.front();
queued_frame_sizes_.erase(queued_frame_sizes_.begin());
if (queued_frame_sizes_.size() == 0) {
// Done with all the frames we expected to see at the output, so move on to submit new data
// to the FIFO. If we instead were to attempt to deliver the last few bytes to the FW, it
// would just get stuck not interrupting and not indicating that the input data is consumed
// (dec_status 0xe never happens).
//
// Despite skipping past some DecodeSize, the parsed_video_size_ stays in sync; apparently
// it really is parsed_decode_size_, not decoder_processed_bytes_.
break;
}
// Should still UpdateDecodeSize() with all the data of the superframe, overall, to avoid the
// possibility of not actually giving the last byte of the last frame to the FW.
queued_frame_sizes_.front() += old_front_frame_size;
}
}
if (queued_frame_sizes_.size()) {
DLOG("UpdateDecodeSize() (from prev)");
decoder->UpdateDecodeSize(queued_frame_sizes_.front());
queued_frame_sizes_.erase(queued_frame_sizes_.begin());
return;
}
while (true) {
CodecInputItem item = DequeueInputItem();
if (!item.is_valid()) {
LOG(DEBUG, "!item.is_valid()");
return;
}
if (item.is_format_details()) {
// TODO(dustingreen): Be more strict about what the input format actually
// is, and less strict about it matching the initial format.
ZX_ASSERT(fidl::Equals(item.format_details(), initial_input_format_details_));
continue;
}
if (item.is_end_of_stream()) {
DLOG("SetEndOfStreamOffset() - parsed_video_size_: 0x%lx", parsed_video_size_);
video_->pts_manager()->SetEndOfStreamOffset(parsed_video_size_);
std::vector<uint8_t> split_data;
std::vector<uint32_t> frame_sizes;
SplitSuperframe(reinterpret_cast<const uint8_t*>(&new_stream_ivf[kHeaderSkipBytes]),
new_stream_ivf_len - kHeaderSkipBytes, &split_data, &frame_sizes);
ZX_DEBUG_ASSERT(frame_sizes.size() == 1);
ZX_DEBUG_ASSERT(split_data.size() <= std::numeric_limits<uint32_t>::max());
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
is_input_end_of_stream_queued_to_core_ = true;
} // ~lock
SubmitDataToStreamBuffer(/*paddr_base=*/0, /*paddr_size=*/0, split_data.data(),
static_cast<uint32_t>(split_data.size()));
// Intentionally not including kFlushThroughZeroes - this only includes
// data in AMLV frames.
DLOG("UpdateDecodeSize() (EOS)");
decoder->UpdateDecodeSize(static_cast<uint32_t>(split_data.size()));
return;
}
ZX_DEBUG_ASSERT(item.is_packet());
auto return_input_packet =
fit::defer([this, &item] { events_->onCoreCodecInputPacketDone(item.packet()); });
uint8_t* data = item.packet()->buffer()->base() + item.packet()->start_offset();
uint32_t len = item.packet()->valid_length_bytes();
zx_paddr_t paddr_base = 0;
uint32_t paddr_size = 0;
std::vector<uint8_t> split_data;
// If we're using TeeVp9AddHeaders() we don't actually populate split_data, but we still care
// what the size would have been.
uint32_t after_repack_len = 0;
std::vector<uint32_t> new_queued_frame_sizes;
if (IsPortSecure(kInputPort)) {
ZX_DEBUG_ASSERT(item.packet()->buffer()->is_pinned());
paddr_base = item.packet()->buffer()->physical_base() + item.packet()->start_offset();
// These are enforced by codec_impl.cc as a packet arrives.
ZX_DEBUG_ASSERT(len > 0);
ZX_DEBUG_ASSERT(item.packet()->start_offset() + len <= item.packet()->buffer()->size());
uint64_t max_after_size = item.packet()->buffer()->size() - item.packet()->start_offset();
ZX_DEBUG_ASSERT(max_after_size <= std::numeric_limits<uint32_t>::max());
zx_status_t status = video_->TeeVp9AddHeaders(
paddr_base, len, static_cast<uint32_t>(max_after_size), &after_repack_len);
if (status != ZX_OK) {
LOG(ERROR, "TeeVp9AddHeaders() failed - status: %d", status);
OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN);
return;
}
paddr_size = after_repack_len;
ZX_DEBUG_ASSERT(new_queued_frame_sizes.size() == 0);
} else {
// We split superframes essentially the same way TeeVp9AddHeaders() does, to share as much
// handling as we can regardless of whether IsPortSecure(kInputPort).
SplitSuperframe(data, len, &split_data, &new_queued_frame_sizes, /*like_secmem=*/true);
ZX_DEBUG_ASSERT(!new_queued_frame_sizes.empty());
ZX_DEBUG_ASSERT(split_data.size() <= std::numeric_limits<uint32_t>::max());
after_repack_len = static_cast<uint32_t>(split_data.size());
// Because like_sysmem true, the after_repack_len includes an extraneous superframe footer
// size also, just like TeeVp9AddHeaders().
ZX_DEBUG_ASSERT(after_repack_len == len + new_queued_frame_sizes.size() * kVp9AmlvHeaderSize);
}
uint8_t* vaddr_base = nullptr;
uint32_t vaddr_size = 0;
if (!paddr_base) {
ZX_DEBUG_ASSERT(split_data.size() <= std::numeric_limits<uint32_t>::max());
vaddr_base = split_data.data();
vaddr_size = static_cast<uint32_t>(split_data.size());
}
// For now, we only have known frame sizes for non-DRM streams. In future we intend to have
// known frame sizes regardless of DRM or not.
ZX_DEBUG_ASSERT(!IsPortSecure(kInputPort) == !new_queued_frame_sizes.empty());
if (!has_input_keyframe_ && !new_queued_frame_sizes.empty()) {
// for now
ZX_DEBUG_ASSERT(vaddr_base && vaddr_size && !paddr_base && !paddr_size);
while (!new_queued_frame_sizes.empty()) {
uint8_t* vp9_frame_header = vaddr_base + kVp9AmlvHeaderSize;
if (vp9_frame_header >= vaddr_base + vaddr_size) {
LOG(ERROR, "frame_type parsing failed");
OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_DATA_PARSING);
return;
}
auto is_key_frame_result = IsVp9KeyFrame(*vp9_frame_header);
if (!is_key_frame_result.is_ok()) {
OnCoreCodecFailStream(is_key_frame_result.error());
return;
}
bool skip_frame = !is_key_frame_result.value();
if (skip_frame) {
// Skip the first frame.
uint32_t amlv_frame_size = new_queued_frame_sizes.front();
ZX_DEBUG_ASSERT(vaddr_size >= amlv_frame_size);
ZX_DEBUG_ASSERT(after_repack_len >= amlv_frame_size);
vaddr_base += amlv_frame_size;
vaddr_size -= amlv_frame_size;
after_repack_len -= amlv_frame_size;
if (paddr_size) {
// This will become important later when we have both vaddr_base and paddr_base with
// valid data, with paddr_base protected and vaddr_base clear.
ZX_DEBUG_ASSERT(paddr_size >= amlv_frame_size);
paddr_base += amlv_frame_size;
paddr_size -= amlv_frame_size;
}
new_queued_frame_sizes.erase(new_queued_frame_sizes.begin());
// next frame of superframe, if any
continue;
}
// We didn't find any reason to skip the (now) first frame which is a keyframe, so note we
// have a keyframe and break out of "!new_queued_frame_sizes.empty()" loop so it can be
// submitted to HW along with any subsequent frames of its superframe.
ZX_DEBUG_ASSERT(!new_queued_frame_sizes.empty());
has_input_keyframe_ = true;
break;
}
if (new_queued_frame_sizes.empty()) {
// The vaddr_size can still be non-zero here, due to superframe header bytes, which is fine.
//
// next input item, if any
//
// ~return_input_packet, ~item
continue;
}
}
uint32_t increased_size = after_repack_len - len;
if ((after_repack_len < len) || (increased_size % 16 != 0) || (increased_size < 16)) {
LOG(ERROR, "Repack gave bad size 0x%x from 0x%x", after_repack_len, len);
OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN);
return;
}
//////////////////////////////
// No failures from here down.
//////////////////////////////
LOG(DEBUG, "InsertPts() - parsed_video_size_: 0x%lx has_timestamp_ish: %u timestamp_ish: %lu",
parsed_video_size_, item.packet()->has_timestamp_ish(), item.packet()->timestamp_ish());
video_->pts_manager()->InsertPts(parsed_video_size_, item.packet()->has_timestamp_ish(),
item.packet()->timestamp_ish());
uint32_t frame_count = increased_size / 16;
LOG(DEBUG, "frame_count: 0x%x protected: %u", frame_count, IsPortSecure(kInputPort));
// Because TeeVp9AddHeaders() doesn't output the frame sizes within a superframe, we
// intentionally ignore those, even when the input data is non-protected, to keep the handling
// of protected and non-protected similar, for efficient test coverage.
new_queued_frame_sizes.clear();
new_queued_frame_sizes.push_back(after_repack_len - (frame_count - 1) * kVdecFifoAlign);
for (uint32_t i = 1; i < frame_count; ++i) {
new_queued_frame_sizes.push_back(kVdecFifoAlign);
}
parsed_video_size_ += after_repack_len + kFlushThroughBytes;
SubmitDataToStreamBuffer(paddr_base, paddr_size, vaddr_base, vaddr_size);
queued_frame_sizes_ = std::move(new_queued_frame_sizes);
ZX_DEBUG_ASSERT(!queued_frame_sizes_.empty());
LOG(DEBUG, "UpdateDecodeSize() (new)");
decoder->UpdateDecodeSize(queued_frame_sizes_.front());
queued_frame_sizes_.erase(queued_frame_sizes_.begin());
// At this point CodecInputItem is holding a packet pointer which may get
// re-used in a new CodecInputItem, but that's ok since CodecInputItem is
// going away here.
//
// ~return_input_packet, ~item
return;
}
}
bool CodecAdapterVp9::IsCurrentOutputBufferCollectionUsable(
uint32_t min_frame_count, uint32_t max_frame_count, uint32_t coded_width, uint32_t coded_height,
uint32_t stride, uint32_t display_width, uint32_t display_height) {
DLOG(
"min_frame_count: %u max_frame_count: %u coded_width: %u coded_height: %u stride: %u "
"display_width: %u display_height: %u",
min_frame_count, max_frame_count, coded_width, coded_height, stride, display_width,
display_height);
ZX_DEBUG_ASSERT(stride >= coded_width);
// We don't ask codec_impl about this, because as far as codec_impl is
// concerned, the output buffer collection might not be used for video
// frames. We could have common code for video decoders but for now we just
// check here.
//
// TODO(dustingreen): Some potential divisor check failures could be avoided
// if the corresponding value were rounded up according to the divisor before
// we get here.
if (!output_buffer_collection_info_) {
LOG(DEBUG, "!output_buffer_collection_info_");
return false;
}
fuchsia_sysmem2::BufferCollectionInfo& info = output_buffer_collection_info_.value();
ZX_DEBUG_ASSERT(info.settings()->image_format_constraints().has_value());
if (min_frame_count > info.buffers()->size()) {
LOG(DEBUG, "min_frame_count > info.buffer_count");
return false;
}
if (info.buffers()->size() > max_frame_count) {
// The vp9_decoder.cc won't exercise this path since the max is always the same, and we won't
// have allocated a collection with more than max_buffer_count.
LOG(DEBUG, "info.buffer_count > max_frame_count");
return false;
}
if (stride * coded_height * 3 / 2 > *info.settings()->buffer_settings()->size_bytes()) {
LOG(DEBUG, "stride * coded_height * 3 / 2 > info.settings.buffer_settings.size_bytes");
return false;
}
if (display_width %
info.settings()->image_format_constraints()->display_rect_alignment()->width() !=
0) {
// Let it probably fail later when trying to re-negotiate buffers.
LOG(DEBUG,
"display_width %% info.settings.image_format_constraints.display_width_divisor != 0");
return false;
}
if (display_height %
info.settings()->image_format_constraints()->display_rect_alignment()->height() !=
0) {
// Let it probably fail later when trying to re-negotiate buffers.
LOG(DEBUG,
"display_height %% info.settings.image_format_constraints.display_height_divisor != 0");
return false;
}
if (coded_width * coded_height >
*info.settings()->image_format_constraints()->max_width_times_height()) {
// Let it probably fail later when trying to re-negotiate buffers.
LOG(DEBUG, "coded_width * coded_height > max_coded_width_times_coded_height");
return false;
}
if (coded_width < info.settings()->image_format_constraints()->min_size()->width()) {
LOG(DEBUG,
"coded_width < info.settings.image_format_constraints.min_coded_width -- "
"coded_width: %d min_coded_width: %d",
coded_width, info.settings()->image_format_constraints()->min_size()->width());
return false;
}
if (coded_width > info.settings()->image_format_constraints()->max_size()->width()) {
LOG(DEBUG, "coded_width > info.settings.image_format_constraints.max_coded_width");
return false;
}
if (coded_width % info.settings()->image_format_constraints()->size_alignment()->width() != 0) {
// Let it probably fail later when trying to re-negotiate buffers.
LOG(DEBUG, "coded_width %% info.settings.image_format_constraints.coded_width_divisor != 0");
return false;
}
if (coded_height < info.settings()->image_format_constraints()->min_size()->height()) {
LOG(DEBUG, "coded_height < info.settings.image_format_constraints.min_coded_height");
return false;
}
if (coded_height > info.settings()->image_format_constraints()->max_size()->height()) {
LOG(DEBUG, "coded_height > info.settings.image_format_constraints.max_coded_height");
return false;
}
if (coded_height % info.settings()->image_format_constraints()->size_alignment()->height() != 0) {
// Let it probably fail later when trying to re-negotiate buffers.
LOG(DEBUG, "coded_height %% info.settings.image_format_constraints.coded_height_divisor != 0");
return false;
}
if (stride < *info.settings()->image_format_constraints()->min_bytes_per_row()) {
LOG(DEBUG,
"stride < info.settings.image_format_constraints.min_bytes_per_row -- stride: %d "
"min_bytes_per_row: %d",
stride, *info.settings()->image_format_constraints()->min_bytes_per_row());
return false;
}
if (stride > *info.settings()->image_format_constraints()->max_bytes_per_row()) {
LOG(DEBUG, "stride > info.settings.image_format_constraints.max_bytes_per_row");
return false;
}
if (stride % *info.settings()->image_format_constraints()->bytes_per_row_divisor() != 0) {
// Let it probably fail later when trying to re-negotiate buffers.
LOG(DEBUG, "stride %% info.settings.image_format_constraints.bytes_per_row_divisor != 0");
return false;
}
DLOG("returning true");
return true;
}
zx_status_t CodecAdapterVp9::InitializeFrames(uint32_t min_frame_count, uint32_t max_frame_count,
uint32_t coded_width, uint32_t coded_height,
uint32_t stride, uint32_t display_width,
uint32_t display_height, bool has_sar,
uint32_t sar_width, uint32_t sar_height) {
ZX_DEBUG_ASSERT(!has_sar);
ZX_DEBUG_ASSERT(sar_width == 1);
ZX_DEBUG_ASSERT(sar_height == 1);
// First handle the special case of EndOfStream marker showing up at the
// output. We want to notice if up to this point we've been decoding into
// buffers smaller than this. By noticing here, we avoid requiring the client
// to re-allocate buffers just before EOS.
if (display_width == kEndOfStreamWidth && display_height == kEndOfStreamHeight) {
bool is_output_end_of_stream = false;
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
if (is_input_end_of_stream_queued_to_core_) {
is_output_end_of_stream = true;
}
} // ~lock
if (is_output_end_of_stream) {
OnCoreCodecEos();
return ZX_ERR_STOP;
}
}
// This is called on a core codec thread, ordered with respect to emitted
// output frames. This method needs to block until either:
// * Format details have been delivered to the Codec client and the Codec
// client has configured corresponding output buffers.
// * The client has moved on by closing the current stream, in which case
// this method needs to fail quickly so the core codec can be stopped.
//
// The video_decoder_lock_ is held during this method. We don't release the
// video_decoder_lock_ while waiting for the client, because we want close of
// the current stream to wait for this method to return before starting the
// portion of stream close protected by video_decoder_lock_.
//
// The signalling to un-block this thread uses lock_.
//
// TODO(dustingreen): It can happen that the current set of buffers is already
// suitable for use under the new buffer constraints. However, some of the
// buffers can still be populated with data and used by other parts of the
// system, so to re-use buffers, we'll need a way to communicate which buffers
// are not presently available to decode into, even for what vp9_decoder.cc
// sees as a totally new set of buffers. The vp9_decoder.cc doesn't separate
// configuration of a buffer from marking that buffer ready to fill. For now,
// we always re-allocate buffers. Old buffers still active elsewhere in the
// system can continue to be referenced by those parts of the system - the
// importan thing for now is we avoid overwriting the content of those buffers
// by using an entirely new set of buffers for each stream for now.
// First stash some format and buffer count info needed to initialize frames
// before triggering mid-stream format change. Later, frames satisfying these
// stashed parameters will be handed to the decoder via InitializedFrames(),
// unless CoreCodecStopStream() happens first.
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
// For the moment, force this exact number of frames.
//
// TODO(dustingreen): plumb actual frame counts.
min_buffer_count_[kOutputPort] = min_frame_count;
max_buffer_count_[kOutputPort] = max_frame_count;
coded_width_ = coded_width;
coded_height_ = coded_height;
stride_ = stride;
display_width_ = display_width;
display_height_ = display_height;
} // ~lock
// After a stream switch, the new Vp9Decoder won't have any frames, and needs InitializedFrames()
// to get called to set up the frames, and the Vp9Decoder won't have checked
// IsCurrentOutputBufferCollectionUsable() itself since it knows it needs frames configured using
// InitializedFrames(). However, we can still check here whether the current buffer collection,
// that was (before the stream switch) used with a previous Vp9Decoder instance, can still
// be used with the new Vp9Decoder instance. This does require that we be able to indicate
// via InitializedFrames() the downstream usage count of each frame (0 if initially free).
if (IsCurrentOutputBufferCollectionUsable(static_cast<uint32_t>(all_output_buffers_.size()),
static_cast<uint32_t>(all_output_buffers_.size()),
coded_width, coded_height, stride, display_width,
display_height)) {
DLOG("IsCurrentOutputBufferCollectionUsable() true");
// The core codec won't output any more packets until we call InitializedFrames(), but when the
// core codec does output more packets, we need to send updated format info first.
//
// TODO(dustingreen): This may be unnecessary / redundant.
events_->onCoreCodecOutputFormatChange();
shared_fidl_thread_closure_queue_->Enqueue([this] {
// We have to run this on the shared fidl thread since that's what CodecImpl is using to
// process RecycleOutputPacket(); we need to avoid this running concurrently with
// CoreCodecRecycleOutputPacket().
MidStreamOutputBufferConfigInternal(false);
});
return ZX_OK;
}
// This will snap the current stream_lifetime_ordinal_, and call
// CoreCodecMidStreamOutputBufferReConfigPrepare() and
// CoreCodecMidStreamOutputBufferReConfigFinish() from the StreamControl
// thread, _iff_ the client hasn't already moved on to a new stream by then.
events_->onCoreCodecMidStreamOutputConstraintsChange(true);
return ZX_OK;
}
void CodecAdapterVp9::OnCoreCodecEos() {
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
ZX_DEBUG_ASSERT(is_input_end_of_stream_queued_to_core_);
} // ~lock
decoder_->SetPausedAtEndOfStream();
video_->AssertVideoDecoderLockHeld();
video_->TryToReschedule();
events_->onCoreCodecOutputEndOfStream(false);
}
void CodecAdapterVp9::OnCoreCodecFailStream(fuchsia::media::StreamError error) {
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
is_stream_failed_ = true;
} // ~lock
LOG(ERROR, "CodecAdapterVp9::OnCoreCodecFailStream()");
events_->onCoreCodecFailStream(error);
}
CodecPacket* CodecAdapterVp9::GetFreePacket(const CodecBuffer* buffer) {
std::lock_guard<std::mutex> lock(lock_);
uint32_t free_index = free_output_packets_.back();
free_output_packets_.pop_back();
auto* packet = all_output_packets_[free_index];
packet->SetBuffer(buffer);
return packet;
}
bool CodecAdapterVp9::IsPortSecureRequired(CodecPort port) {
ZX_DEBUG_ASSERT(secure_memory_mode_set_[port]);
return secure_memory_mode_[port] == fuchsia::mediacodec::SecureMemoryMode::ON;
}
bool CodecAdapterVp9::IsPortSecurePermitted(CodecPort port) {
ZX_DEBUG_ASSERT(secure_memory_mode_set_[port]);
return secure_memory_mode_[port] != fuchsia::mediacodec::SecureMemoryMode::OFF;
}
bool CodecAdapterVp9::IsPortSecure(CodecPort port) {
ZX_DEBUG_ASSERT(secure_memory_mode_set_[port]);
ZX_DEBUG_ASSERT(buffer_settings_[port]);
return *buffer_settings_[port]->buffer_settings()->is_secure();
}
bool CodecAdapterVp9::IsOutputSecure() {
// We need to know whether output is secure or not before we start accepting input, which means
// we need to know before output buffers are allocated, which means we can't rely on the result
// of sysmem BufferCollection allocation is_secure for output.
ZX_DEBUG_ASSERT(IsPortSecurePermitted(kOutputPort) == IsPortSecureRequired(kOutputPort));
return IsPortSecureRequired(kOutputPort);
}
} // namespace amlogic_decoder