blob: 1f773e755bc176071be87e4ac040457b02f15b3d [file] [log] [blame]
// Copyright 2020 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 "h264_multi_decoder.h"
#include <lib/media/codec_impl/codec_buffer.h>
#include <lib/trace/event.h>
#include <cmath>
#include <fbl/algorithm.h>
#include "h264_utils.h"
#include "media/base/decoder_buffer.h"
#include "media/gpu/h264_decoder.h"
#include "parser.h"
#include "registers.h"
#include "util.h"
#include "watchdog.h"
namespace {
// See VLD_PADDING_SIZE.
constexpr uint32_t kPaddingSize = 1024;
const uint8_t kPadding[kPaddingSize] = {};
} // namespace
class AmlogicH264Picture : public media::H264Picture {
public:
explicit AmlogicH264Picture(std::shared_ptr<H264MultiDecoder::ReferenceFrame> pic)
: internal_picture(pic) {}
~AmlogicH264Picture() override {
auto pic = internal_picture.lock();
if (pic)
pic->in_internal_use = false;
}
std::weak_ptr<H264MultiDecoder::ReferenceFrame> internal_picture;
};
class MultiAccelerator : public media::H264Decoder::H264Accelerator {
public:
explicit MultiAccelerator(H264MultiDecoder* owner) : owner_(owner) {}
scoped_refptr<media::H264Picture> CreateH264Picture() override {
DLOG("Got MultiAccelerator::CreateH264Picture");
auto pic = owner_->GetUnusedReferenceFrame();
if (!pic)
return nullptr;
return std::make_shared<AmlogicH264Picture>(pic);
}
Status SubmitFrameMetadata(const media::H264SPS* sps, const media::H264PPS* pps,
const media::H264DPB& dpb,
const media::H264Picture::Vector& ref_pic_listp0,
const media::H264Picture::Vector& ref_pic_listb0,
const media::H264Picture::Vector& ref_pic_listb1,
scoped_refptr<media::H264Picture> pic) override {
DLOG("Got MultiAccelerator::SubmitFrameMetadata");
// Only allow decoding one frame at a time. The received picture interrupt will set this to
// false and trigger PumpDecoder again.
if (owner_->currently_decoding())
return Status::kTryAgain;
auto ref_pic = static_cast<AmlogicH264Picture*>(pic.get())->internal_picture.lock();
if (!ref_pic)
return Status::kFail;
constexpr uint8_t kHeader[] = {0, 0, 1};
owner_->SubmitDataToHardware(kHeader, sizeof(kHeader));
owner_->SubmitDataToHardware(sps->raw_data.data(), sps->raw_data.size());
owner_->SubmitDataToHardware(kHeader, sizeof(kHeader));
owner_->SubmitDataToHardware(pps->raw_data.data(), pps->raw_data.size());
current_sps_ = *sps;
owner_->SubmitFrameMetadata(ref_pic.get(), sps, pps, dpb);
return Status::kOk;
}
Status SubmitSlice(const media::H264PPS* pps, const media::H264SliceHeader* slice_hdr,
const media::H264Picture::Vector& ref_pic_list0,
const media::H264Picture::Vector& ref_pic_list1,
scoped_refptr<media::H264Picture> pic, const uint8_t* data, size_t size,
const std::vector<media::SubsampleEntry>& subsamples) override {
if (owner_->currently_decoding())
return Status::kTryAgain;
DLOG("Got MultiAccelerator::SubmitSlice");
constexpr uint8_t kHeader[] = {0, 0, 1};
owner_->SubmitDataToHardware(kHeader, sizeof(kHeader));
owner_->SubmitDataToHardware(data, size);
H264MultiDecoder::SliceData slice_data;
slice_data.sps = current_sps_;
slice_data.pps = *pps;
slice_data.header = *slice_hdr;
slice_data.pic = pic;
slice_data.ref_pic_list0 = ref_pic_list0;
slice_data.ref_pic_list1 = ref_pic_list1;
owner_->SubmitSliceData(std::move(slice_data));
return Status::kOk;
}
Status SubmitDecode(scoped_refptr<media::H264Picture> pic) override {
if (owner_->currently_decoding())
return Status::kTryAgain;
auto ref_pic = static_cast<AmlogicH264Picture*>(pic.get())->internal_picture.lock();
if (!ref_pic)
return Status::kFail;
DLOG("Got MultiAccelerator::SubmitDecode picture %d", ref_pic->index);
owner_->SubmitDataToHardware(kPadding, kPaddingSize);
owner_->StartFrameDecode();
return Status::kOk;
}
bool OutputPicture(scoped_refptr<media::H264Picture> pic) override {
auto ref_pic = static_cast<AmlogicH264Picture*>(pic.get())->internal_picture.lock();
if (!ref_pic)
return false;
DLOG("Got MultiAccelerator::OutputPicture picture %d", ref_pic->index);
owner_->OutputFrame(ref_pic.get(), pic->bitstream_id());
return true;
}
void Reset() override {}
Status SetStream(base::span<const uint8_t> stream,
const media::DecryptConfig* decrypt_config) override {
return Status::kOk;
}
private:
H264MultiDecoder* owner_;
media::H264SPS current_sps_;
};
using InitFlagReg = AvScratch2;
using HeadPaddingReg = AvScratch3;
using H264DecodeModeReg = AvScratch4;
using H264DecodeSeqInfo = AvScratch5;
using NalSearchCtl = AvScratch9;
using ErrorStatusReg = AvScratch9;
using H264AuxAddr = AvScratchC;
using H264DecodeSizeReg = AvScratchE;
using H264AuxDataSize = AvScratchH;
using FrameCounterReg = AvScratchI;
using DpbStatusReg = AvScratchJ;
using LmemDumpAddr = AvScratchL;
using DebugReg1 = AvScratchM;
using DebugReg2 = AvScratchN;
using H264DecodeInfo = M4ControlReg;
// AvScratch1
class StreamInfo : public TypedRegisterBase<DosRegisterIo, StreamInfo, uint32_t> {
public:
DEF_FIELD(7, 0, width_in_mbs);
DEF_FIELD(23, 8, total_mbs);
static auto Get() { return AddrType(0x09c1 * 4); }
};
// AvScratch2
class SequenceInfo : public TypedRegisterBase<DosRegisterIo, SequenceInfo, uint32_t> {
public:
DEF_BIT(0, aspect_ratio_info_present_flag);
DEF_BIT(1, timing_info_present_flag);
DEF_BIT(4, pic_struct_present_flag);
// relatively lower-confidence vs. other bits - not confirmed
DEF_BIT(6, fixed_frame_rate_flag);
DEF_FIELD(14, 13, chroma_format_idc);
DEF_BIT(15, frame_mbs_only_flag);
DEF_FIELD(23, 16, aspect_ratio_idc);
static auto Get() { return AddrType(0x09c2 * 4); }
};
// AvScratchB
class StreamInfo2 : public TypedRegisterBase<DosRegisterIo, StreamInfo2, uint32_t> {
public:
DEF_FIELD(7, 0, level_idc);
DEF_FIELD(15, 8, max_reference_size);
static auto Get() { return AddrType(0x09cb * 4); }
};
// AvScratchF
class CodecSettings : public TypedRegisterBase<DosRegisterIo, CodecSettings, uint32_t> {
public:
DEF_BIT(1, trickmode_i);
DEF_BIT(2, zeroed0);
DEF_BIT(3, drop_b_frames);
DEF_BIT(4, error_recovery_mode);
DEF_BIT(5, zeroed1);
DEF_BIT(6, ip_frames_only);
DEF_BIT(7, disable_fast_poc);
static auto Get() { return AddrType(0x09cf * 4); }
};
enum DecodeMode {
// Mode where multiple streams can be decoded, and input doesn't have to be
// broken into frame-sized chunks.
kDecodeModeMultiStreamBased = 0x2
};
// Actions written by CPU into DpbStatusReg to tell the firmware what to do.
enum H264Action {
// Start searching for the head of a frame to decode.
kH264ActionSearchHead = 0xf0,
// Done responding to a config request.
kH264ActionConfigDone = 0xf2,
// Decode a slice (not the first one) in a picture.
kH264ActionDecodeSlice = 0xf1,
// Decode the first slice in a new picture.
kH264ActionDecodeNewpic = 0xf3,
};
// Actions written by the firmware into DpbStatusReg before an interrupt to tell
// the CPU what to do.
enum H264Status {
// Configure the DPB.
kH264ConfigRequest = 0x11,
// Out of input data, so get more.
kH264DataRequest = 0x12,
// The firmware detected the hardware timed out while attempting to decode.
kH264DecodeTimeout = 0x21,
// kH264ActionSearchHead wasn't able to find a frame to decode.
kH264SearchBufempty = 0x22,
// Initialize the current set of reference frames and output buffer to be
// decoded into.
kH264SliceHeadDone = 0x1,
// Store the current frame into the DPB, or output it.
kH264PicDataDone = 0x2,
};
H264MultiDecoder::H264MultiDecoder(Owner* owner, Client* client, FrameDataProvider* provider,
bool is_secure)
: VideoDecoder(owner, client, is_secure), frame_data_provider_(provider) {
media_decoder_ = std::make_unique<media::H264Decoder>(std::make_unique<MultiAccelerator>(this),
media::H264PROFILE_HIGH);
use_parser_ = is_secure;
}
H264MultiDecoder::~H264MultiDecoder() {
if (owner_->IsDecoderCurrent(this)) {
owner_->watchdog()->Cancel();
owner_->core()->StopDecoding();
owner_->core()->WaitForIdle();
}
BarrierBeforeRelease();
}
zx_status_t H264MultiDecoder::Initialize() {
zx_status_t status = InitializeBuffers();
if (status != ZX_OK) {
DECODE_ERROR("Failed to initialize buffers");
return status;
}
return InitializeHardware();
}
zx_status_t H264MultiDecoder::LoadSecondaryFirmware(const uint8_t* data, uint32_t firmware_size) {
TRACE_DURATION("media", "H264MultiDecoder::LoadSecondaryFirmware");
ZX_DEBUG_ASSERT(!secondary_firmware_);
// For some reason, some portions of the firmware aren't loaded into the
// hardware directly, but are kept in main memory.
constexpr uint32_t kSecondaryFirmwareSize = 4 * 1024;
// Some sections of the input firmware are copied into multiple places in the output buffer, and 1
// part of the output buffer seems to be unused.
constexpr uint32_t kFirmwareSectionCount = 9;
constexpr uint32_t kSecondaryFirmwareBufferSize = kSecondaryFirmwareSize * kFirmwareSectionCount;
constexpr uint32_t kBufferAlignShift = 16;
auto result = InternalBuffer::CreateAligned(
"H264MultiSecondaryFirmware", &owner_->SysmemAllocatorSyncPtr(), owner_->bti(),
kSecondaryFirmwareBufferSize, 1 << kBufferAlignShift, /*is_secure*/ false,
/*is_writable=*/true, /*is_mapping_needed*/ true);
if (!result.is_ok()) {
DECODE_ERROR("Failed to make second firmware buffer: %d", result.error());
return result.error();
}
secondary_firmware_.emplace(result.take_value());
auto addr = static_cast<uint8_t*>(secondary_firmware_->virt_base());
// The secondary firmware is in a different order in the file than the main
// firmware expects it to have.
memcpy(addr + 0, data + 0x4000, kSecondaryFirmwareSize); // header
memcpy(addr + 0x1000, data + 0x2000, kSecondaryFirmwareSize); // data
memcpy(addr + 0x2000, data + 0x6000, kSecondaryFirmwareSize); // mmc
memcpy(addr + 0x3000, data + 0x3000, kSecondaryFirmwareSize); // list
memcpy(addr + 0x4000, data + 0x5000, kSecondaryFirmwareSize); // slice
memcpy(addr + 0x5000, data, 0x2000); // main
memcpy(addr + 0x5000 + 0x2000, data + 0x2000, kSecondaryFirmwareSize); // data copy 2
memcpy(addr + 0x5000 + 0x3000, data + 0x5000, kSecondaryFirmwareSize); // slice copy 2
ZX_DEBUG_ASSERT(0x5000 + 0x3000 + kSecondaryFirmwareSize == kSecondaryFirmwareBufferSize);
secondary_firmware_->CacheFlush(0, kSecondaryFirmwareBufferSize);
BarrierAfterFlush();
return ZX_OK;
}
constexpr uint32_t kAuxBufPrefixSize = 16 * 1024;
constexpr uint32_t kAuxBufSuffixSize = 0;
zx_status_t H264MultiDecoder::InitializeBuffers() {
// Don't use the TEE to load the firmware, since the version we're using on astro and sherlock
// doesn't support H264_Multi_Gxm.
FirmwareBlob::FirmwareType firmware_type = FirmwareBlob::FirmwareType::kDec_H264_Multi_Gxm;
uint8_t* data;
uint32_t firmware_size;
zx_status_t status =
owner_->firmware_blob()->GetFirmwareData(firmware_type, &data, &firmware_size);
if (status != ZX_OK)
return status;
static constexpr uint32_t kFirmwareSize = 4 * 4096;
const uint32_t kBufferAlignShift = 16;
if (firmware_size < kFirmwareSize) {
DECODE_ERROR("Firmware too small");
return ZX_ERR_INTERNAL;
}
{
auto create_result = InternalBuffer::CreateAligned(
"H264MultiFirmware", &owner_->SysmemAllocatorSyncPtr(), owner_->bti(), kFirmwareSize,
1 << kBufferAlignShift, /*is_secure=*/false, /*is_writable=*/true,
/*is_mapping_needed=*/true);
if (!create_result.is_ok()) {
DECODE_ERROR("Failed to make firmware buffer - %d", create_result.error());
return {};
}
firmware_ = create_result.take_value();
memcpy(firmware_->virt_base(), data, kFirmwareSize);
firmware_->CacheFlush(0, kFirmwareSize);
BarrierAfterFlush();
}
status = LoadSecondaryFirmware(data, firmware_size);
if (status != ZX_OK) {
return status;
}
constexpr uint32_t kBufferAlignment = 1 << 16;
constexpr uint32_t kCodecDataSize = 0x200000;
auto codec_data_create_result =
InternalBuffer::CreateAligned("H264MultiCodecData", &owner_->SysmemAllocatorSyncPtr(),
owner_->bti(), kCodecDataSize, kBufferAlignment, is_secure(),
/*is_writable=*/true, /*is_mapping_needed*/ false);
if (!codec_data_create_result.is_ok()) {
LOG(ERROR, "Failed to make codec data buffer - status: %d", codec_data_create_result.error());
return codec_data_create_result.error();
}
codec_data_.emplace(codec_data_create_result.take_value());
// Aux buf seems to be used for reading SEI data.
constexpr uint32_t kAuxBufSize = kAuxBufPrefixSize + kAuxBufSuffixSize;
auto aux_buf_create_result =
InternalBuffer::CreateAligned("H264AuxBuf", &owner_->SysmemAllocatorSyncPtr(), owner_->bti(),
kAuxBufSize, kBufferAlignment, /*is_secure=*/false,
/*is_writable=*/true, /*is_mapping_needed*/ false);
if (!aux_buf_create_result.is_ok()) {
LOG(ERROR, "Failed to make aux buffer - status: %d", aux_buf_create_result.error());
return aux_buf_create_result.error();
}
aux_buf_.emplace(aux_buf_create_result.take_value());
// Lmem is used to dump the AMRISC's local memory, which is needed for updating the DPB.
constexpr uint32_t kLmemBufSize = 4096;
auto lmem_create_result =
InternalBuffer::CreateAligned("H264AuxBuf", &owner_->SysmemAllocatorSyncPtr(), owner_->bti(),
kLmemBufSize, kBufferAlignment, /*is_secure=*/false,
/*is_writable=*/true, /*is_mapping_needed*/ true);
if (!lmem_create_result.is_ok()) {
LOG(ERROR, "Failed to make lmem buffer - status: %d", lmem_create_result.error());
return lmem_create_result.error();
}
lmem_.emplace(lmem_create_result.take_value());
return ZX_OK;
}
void H264MultiDecoder::ResetHardware() {
TRACE_DURATION("media", "H264MultiDecoder::ResetHardware");
DosSwReset0::Get().FromValue(0).set_vdec_mc(1).set_vdec_iqidct(1).set_vdec_vld_part(1).WriteTo(
owner_->dosbus());
DosSwReset0::Get().FromValue(0).WriteTo(owner_->dosbus());
// Reads are used for delaying running later code.
for (uint32_t i = 0; i < 3; i++) {
DosSwReset0::Get().ReadFrom(owner_->dosbus());
}
DosSwReset0::Get().FromValue(0).set_vdec_mc(1).set_vdec_iqidct(1).set_vdec_vld_part(1).WriteTo(
owner_->dosbus());
DosSwReset0::Get().FromValue(0).WriteTo(owner_->dosbus());
DosSwReset0::Get().FromValue(0).set_vdec_pic_dc(1).set_vdec_dblk(1).WriteTo(owner_->dosbus());
DosSwReset0::Get().FromValue(0).WriteTo(owner_->dosbus());
// Reads are used for delaying running later code.
for (uint32_t i = 0; i < 3; i++) {
DosSwReset0::Get().ReadFrom(owner_->dosbus());
}
auto temp = PowerCtlVld::Get().ReadFrom(owner_->dosbus());
temp.set_reg_value(temp.reg_value() | (1 << 9) | (1 << 6));
temp.WriteTo(owner_->dosbus());
}
zx_status_t H264MultiDecoder::InitializeHardware() {
TRACE_DURATION("media", "H264MultiDecoder::InitializeHardware");
ZX_DEBUG_ASSERT(state_ == DecoderState::kSwappedOut);
ZX_DEBUG_ASSERT(owner_->IsDecoderCurrent(this));
zx_status_t status =
owner_->SetProtected(VideoDecoder::Owner::ProtectableHardwareUnit::kVdec, is_secure());
if (status != ZX_OK)
return status;
status = owner_->core()->LoadFirmware(*firmware_);
if (status != ZX_OK)
return status;
ResetHardware();
AvScratchG::Get()
.FromValue(truncate_to_32(secondary_firmware_->phys_base()))
.WriteTo(owner_->dosbus());
PscaleCtrl::Get().FromValue(0).WriteTo(owner_->dosbus());
VdecAssistMbox1ClrReg::Get().FromValue(1).WriteTo(owner_->dosbus());
VdecAssistMbox1Mask::Get().FromValue(1).WriteTo(owner_->dosbus());
{
auto temp = MdecPicDcCtrl::Get().ReadFrom(owner_->dosbus()).set_nv12_output(true);
temp.set_reg_value(temp.reg_value() | (0xbf << 24));
temp.WriteTo(owner_->dosbus());
temp.set_reg_value(temp.reg_value() & ~(0xbf << 24));
temp.WriteTo(owner_->dosbus());
}
MdecPicDcMuxCtrl::Get().ReadFrom(owner_->dosbus()).set_bit31(0).WriteTo(owner_->dosbus());
MdecExtIfCfg0::Get().FromValue(0).WriteTo(owner_->dosbus());
MdecPicDcThresh::Get().FromValue(0x404038aa).WriteTo(owner_->dosbus());
// Signal that the DPB hasn't been initialized yet.
if (video_frames_.size() > 0) {
for (auto& frame : video_frames_) {
AncNCanvasAddr::Get(frame->index)
.FromValue((frame->uv_canvas->index() << 16) | (frame->uv_canvas->index() << 8) |
(frame->y_canvas->index()))
.WriteTo(owner_->dosbus());
}
AvScratch7::Get()
.FromValue(static_cast<uint32_t>((next_max_reference_size_ << 24) |
(video_frames_.size() << 16) |
(video_frames_.size() << 8)))
.WriteTo(owner_->dosbus());
} else {
AvScratch0::Get().FromValue(0).WriteTo(owner_->dosbus());
AvScratch9::Get().FromValue(0).WriteTo(owner_->dosbus());
}
DpbStatusReg::Get().FromValue(0).WriteTo(owner_->dosbus());
FrameCounterReg::Get().FromValue(0).WriteTo(owner_->dosbus());
constexpr uint32_t kBufferStartAddressOffset = 0x1000000;
constexpr uint32_t kDcacReadMargin = 64 * 1024;
uint32_t buffer_offset =
truncate_to_32(codec_data_->phys_base()) - kBufferStartAddressOffset + kDcacReadMargin;
AvScratch8::Get().FromValue(buffer_offset).WriteTo(owner_->dosbus());
CodecSettings::Get()
.ReadFrom(owner_->dosbus())
.set_drop_b_frames(0)
.set_zeroed0(0)
.set_error_recovery_mode(1)
.set_zeroed1(0)
.set_ip_frames_only(0)
.WriteTo(owner_->dosbus());
LmemDumpAddr::Get().FromValue(truncate_to_32(lmem_->phys_base())).WriteTo(owner_->dosbus());
DebugReg1::Get().FromValue(0).WriteTo(owner_->dosbus());
DebugReg2::Get().FromValue(0).WriteTo(owner_->dosbus());
H264DecodeInfo::Get().FromValue(1 << 13).WriteTo(owner_->dosbus());
// TODO(fxb/13483): Use real values.
constexpr uint32_t kBytesToDecode = 100000;
H264DecodeSizeReg::Get().FromValue(kBytesToDecode).WriteTo(owner_->dosbus());
ViffBitCnt::Get().FromValue(kBytesToDecode * 8).WriteTo(owner_->dosbus());
H264AuxAddr::Get().FromValue(truncate_to_32(aux_buf_->phys_base())).WriteTo(owner_->dosbus());
H264AuxDataSize::Get()
.FromValue(((kAuxBufPrefixSize / 16) << 16) | (kAuxBufSuffixSize / 16))
.WriteTo(owner_->dosbus());
H264DecodeModeReg::Get().FromValue(kDecodeModeMultiStreamBased).WriteTo(owner_->dosbus());
H264DecodeSeqInfo::Get().FromValue(seq_info2_).WriteTo(owner_->dosbus());
HeadPaddingReg::Get().FromValue(0).WriteTo(owner_->dosbus());
InitFlagReg::Get().FromValue(have_initialized_).WriteTo(owner_->dosbus());
have_initialized_ = true;
// TODO(fxb/13483): Set to 1 when SEI is supported.
NalSearchCtl::Get().FromValue(0).WriteTo(owner_->dosbus());
state_ = DecoderState::kInitialWaitingForInput;
return ZX_OK;
}
void H264MultiDecoder::StartFrameDecode() {
ZX_DEBUG_ASSERT(state_ == DecoderState::kInitialWaitingForInput ||
state_ == DecoderState::kStoppedWaitingForInput);
currently_decoding_ = true;
// For now, just use the decode size from InitializeHardware.
if (state_ == DecoderState::kInitialWaitingForInput) {
// TODO(fxb/13483): Use real value.
constexpr uint32_t kBytesToDecode = 100000;
ViffBitCnt::Get().FromValue(kBytesToDecode * 8).WriteTo(owner_->dosbus());
owner_->core()->StartDecoding();
}
DpbStatusReg::Get().FromValue(kH264ActionSearchHead).WriteTo(owner_->dosbus());
state_ = DecoderState::kRunning;
owner_->watchdog()->Start();
}
void H264MultiDecoder::ConfigureDpb() {
ZX_DEBUG_ASSERT(currently_decoding_);
ZX_DEBUG_ASSERT(!video_frames_.empty());
auto stream_info = StreamInfo::Get().ReadFrom(owner_->dosbus());
uint32_t mb_width = stream_info.width_in_mbs();
// The maximum supported image width is 4096 bytes. The value of width_in_mbs should be 256 in
// that case, but it wraps around since the field is only 8 bits. We need to correct for that
// special case.
if (!mb_width && stream_info.total_mbs())
mb_width = 256;
if (!mb_width) {
DECODE_ERROR("0 mb_width");
OnFatalError();
return;
}
uint32_t mb_height = stream_info.total_mbs() / mb_width;
// Check that the values derived from the stream buffer contents match the input that was parsed
// through media::H264Decoder.
if (mb_width != mb_width_ || mb_height != mb_height_) {
DECODE_ERROR("Non-matching mb_width %d mb_width_ %d mb_height %d mb_height_ %d", mb_width,
mb_width_, mb_height, mb_height_);
OnFatalError();
return;
}
seq_info2_ = AvScratch1::Get().ReadFrom(owner_->dosbus()).reg_value();
for (auto& frame : video_frames_) {
AncNCanvasAddr::Get(frame->index)
.FromValue((frame->uv_canvas->index() << 16) | (frame->uv_canvas->index() << 8) |
(frame->y_canvas->index()))
.WriteTo(owner_->dosbus());
}
AvScratch0::Get()
.FromValue(static_cast<uint32_t>((next_max_reference_size_ << 24) |
(video_frames_.size() << 16) | (video_frames_.size() << 8)))
.WriteTo(owner_->dosbus());
}
// This struct contains parameters for the current frame that are dumped from
// lmem
struct HardwareRenderParams {
uint16_t data[0x400];
static constexpr uint32_t kOffsetDelimiterLo = 0x2f;
static constexpr uint32_t kOffsetDelimiterHi = 0x30;
static constexpr uint32_t kNewPictureStructure = 0x7c;
static constexpr uint32_t kNalUnitType = 0x80;
static constexpr uint32_t kNalRefIdc = 0x81;
static constexpr uint32_t kSliceType = 0x82;
static constexpr uint32_t kLog2MaxFrameNum = 0x83;
static constexpr uint32_t kPicOrderCntType = 0x85;
static constexpr uint32_t kLog2MaxPicOrderCntLsb = 0x86;
static constexpr uint32_t kMode8x8Flag = 0x8c;
static constexpr uint32_t kEntropyCodingModeFlag = 0x8d;
static constexpr uint32_t kProfileIdcMmco = 0xe7;
// offset to dpb_max_buffer_frame.
static constexpr uint32_t kDpbStructStart = 0x100 + 24 * 8;
static constexpr uint32_t kPicOrderCntLsb = kDpbStructStart + 14;
static constexpr uint32_t kDeltaPicOrderCntBottom0 = kDpbStructStart + 19;
static constexpr uint32_t kDeltaPicOrderCntBottom1 = kDpbStructStart + 20;
// Read a pair of entries starting at |offset| as a 32-bit number.
uint32_t Read32(uint32_t offset) {
// Little endian.
return data[offset] | (static_cast<uint32_t>(data[offset + 1]) << 16);
}
void ReadFromLmem(InternalBuffer* lmem) {
lmem->CacheFlushInvalidate(0, sizeof(data));
uint16_t* input_params = reinterpret_cast<uint16_t*>(lmem->virt_base());
// Convert from middle-endian.
for (uint32_t i = 0; i < fbl::count_of(data); i += 4) {
for (uint32_t j = 0; j < 4; j++) {
data[i + j] = input_params[i + (3 - j)];
}
}
}
};
bool H264MultiDecoder::InitializeRefPics(
const std::vector<std::shared_ptr<media::H264Picture>>& ref_pic_list, uint32_t reg_offset) {
uint32_t ref_list[8] = {};
ZX_DEBUG_ASSERT(ref_pic_list.size() <= sizeof(ref_list));
for (uint32_t i = 0; i < ref_pic_list.size(); i++) {
DLOG("Getting pic list (for reg_offset %d) %d of %lu\n", reg_offset, i, ref_pic_list.size());
auto* amlogic_picture = static_cast<AmlogicH264Picture*>(ref_pic_list[i].get());
DLOG("amlogic_picture: %p", amlogic_picture);
// amlogic_picture may be null if the decoder was recently flushed. In that case we don't have
// information about what the reference frame was, so don't try to update it.
if (!amlogic_picture)
continue;
auto internal_picture = amlogic_picture->internal_picture.lock();
if (!internal_picture) {
DECODE_ERROR("InitializeRefPics reg_offset %d missing internal picture %d", reg_offset, i);
frame_data_provider_->AsyncResetStreamAfterCurrentFrame();
return false;
}
// Offset into AncNCanvasAddr registers.
uint32_t canvas_index = internal_picture->index;
constexpr uint32_t kFrameFlag = 0x3;
constexpr uint32_t kFieldTypeBitOffset = 5;
uint32_t cfg = canvas_index | (kFrameFlag << kFieldTypeBitOffset);
// Every dword stores 4 reference pics, lowest index in the highest bits.
uint32_t offset_into_dword = 8 * (3 - (i % 4));
ref_list[i / 4] |= (cfg << offset_into_dword);
}
H264BufferInfoIndex::Get().FromValue(reg_offset).WriteTo(owner_->dosbus());
for (uint32_t reg_value : ref_list) {
H264BufferInfoData::Get().FromValue(reg_value).WriteTo(owner_->dosbus());
}
return true;
}
void H264MultiDecoder::HandleSliceHeadDone() {
ZX_DEBUG_ASSERT(owner_->IsDecoderCurrent(this));
owner_->watchdog()->Cancel();
// Setup reference frames and output buffers before decoding.
HardwareRenderParams params;
params.ReadFromLmem(&*lmem_);
DLOG("NAL unit type: %d\n", params.data[HardwareRenderParams::kNalUnitType]);
DLOG("NAL ref_idc: %d\n", params.data[HardwareRenderParams::kNalRefIdc]);
DLOG("NAL slice_type: %d\n", params.data[HardwareRenderParams::kSliceType]);
DLOG("pic order cnt type: %d\n", params.data[HardwareRenderParams::kPicOrderCntType]);
DLOG("log2_max_frame_num: %d\n", params.data[HardwareRenderParams::kLog2MaxFrameNum]);
DLOG("log2_max_pic_order_cnt: %d\n", params.data[HardwareRenderParams::kLog2MaxPicOrderCntLsb]);
DLOG("entropy coding mode flag: %d\n", params.data[HardwareRenderParams::kEntropyCodingModeFlag]);
DLOG("profile idc mmc0: %d\n", params.data[HardwareRenderParams::kProfileIdcMmco]);
DLOG("Offset delimiter %d", params.Read32(HardwareRenderParams::kOffsetDelimiterLo));
DLOG("Mode 8x8 flags: 0x%x\n", params.data[HardwareRenderParams::kMode8x8Flag]);
current_frame_ = current_metadata_frame_;
if (slice_data_list_.empty()) {
DECODE_ERROR("No slice data for frame");
frame_data_provider_->AsyncResetStreamAfterCurrentFrame();
return;
}
SliceData slice_data = std::move(slice_data_list_.front());
slice_data_list_.pop_front();
// The following checks are to try to ensure what the hardware's parsing matches what H264Decoder
// parsed. They generally should only fail if the streambuffer contents don't match what was
// decoded.
// Slices 5-9 are equivalent for this purpose with slices 0-4 - see 7.4.3
constexpr uint32_t kSliceTypeMod = 5;
if (slice_data.header.slice_type % kSliceTypeMod !=
params.data[HardwareRenderParams::kSliceType] % kSliceTypeMod) {
DECODE_ERROR("Slice types don't match %d %d", slice_data.header.slice_type,
params.data[HardwareRenderParams::kSliceType]);
OnFatalError();
return;
}
// Check for interlacing.
constexpr uint32_t kPictureStructureFrame = 3;
if (params.data[HardwareRenderParams::kNewPictureStructure] != kPictureStructureFrame) {
DECODE_ERROR("Unexpected picture structure type %d",
params.data[HardwareRenderParams::kNewPictureStructure]);
OnFatalError();
return;
}
auto poc = poc_.ComputePicOrderCnt(&slice_data.sps, slice_data.header);
if (!poc) {
DECODE_ERROR("No poc");
frame_data_provider_->AsyncResetStreamAfterCurrentFrame();
return;
}
DLOG("Frame POC %d", poc.value());
DLOG("mb_adaptive_frame_field %d field pic pic flag %d\n",
slice_data.sps.mb_adaptive_frame_field_flag, slice_data.header.field_pic_flag);
H264CurrentPocIdxReset::Get().FromValue(0).WriteTo(owner_->dosbus());
// Assume all fields have the same POC, since the chromium code doesn't support interlacing.
// frame
H264CurrentPoc::Get().FromValue(poc.value()).WriteTo(owner_->dosbus());
// top field
H264CurrentPoc::Get().FromValue(poc.value()).WriteTo(owner_->dosbus());
// bottom field
H264CurrentPoc::Get().FromValue(poc.value()).WriteTo(owner_->dosbus());
CurrCanvasCtrl::Get()
.FromValue(0)
.set_canvas_index(current_frame_->index)
.WriteTo(owner_->dosbus());
// Unclear if reading from the register is actually necessary, or if this
// would always be the same as above.
uint32_t curr_canvas_index =
CurrCanvasCtrl::Get().ReadFrom(owner_->dosbus()).lower_canvas_index();
RecCanvasCtrl::Get().FromValue(curr_canvas_index).WriteTo(owner_->dosbus());
DbkrCanvasCtrl::Get().FromValue(curr_canvas_index).WriteTo(owner_->dosbus());
DbkwCanvasCtrl::Get().FromValue(curr_canvas_index).WriteTo(owner_->dosbus());
// Info for a progressive frame.
constexpr uint32_t kProgressiveFrameInfo = 0xf480;
current_frame_->info0 = kProgressiveFrameInfo;
// Top field
current_frame_->info1 = poc.value();
// Bottom field
current_frame_->info2 = poc.value();
current_frame_->is_long_term_reference = slice_data.pic->long_term;
H264BufferInfoIndex::Get().FromValue(16).WriteTo(owner_->dosbus());
// Store information about the properties of each canvas image.
for (uint32_t i = 0; i < video_frames_.size(); ++i) {
bool is_long_term = video_frames_[i]->is_long_term_reference;
if (is_long_term) {
// Everything is progressive, so mark as having both bottom and top as long-term refrences.
constexpr uint32_t kTopFieldLongTerm = 1 << 4;
constexpr uint32_t kBottomFieldLongTerm = 1 << 5;
video_frames_[i]->info0 |= kTopFieldLongTerm | kBottomFieldLongTerm;
}
uint32_t info_to_write = video_frames_[i]->info0;
if (video_frames_[i].get() == current_frame_) {
constexpr uint32_t kCurrentFrameBufInfo = 0xf;
info_to_write |= kCurrentFrameBufInfo;
}
ZX_DEBUG_ASSERT(video_frames_[i]->index == i);
H264BufferInfoData::Get().FromValue(info_to_write).WriteTo(owner_->dosbus());
H264BufferInfoData::Get().FromValue(video_frames_[i]->info1).WriteTo(owner_->dosbus());
H264BufferInfoData::Get().FromValue(video_frames_[i]->info2).WriteTo(owner_->dosbus());
}
if (!InitializeRefPics(slice_data.ref_pic_list0, 0))
return;
if (!InitializeRefPics(slice_data.ref_pic_list1, 8))
return;
// Wait for the hardware to finish processing its current mbs.
if (!SpinWaitForRegister(std::chrono::milliseconds(100), [&] {
return !H264CoMbRwCtl::Get().ReadFrom(owner_->dosbus()).busy();
})) {
DECODE_ERROR("Failed to wait for rw register nonbusy");
OnFatalError();
return;
}
constexpr uint32_t kMvRefDataSizePerMb = 96;
uint32_t mv_size = kMvRefDataSizePerMb;
if ((params.data[HardwareRenderParams::kMode8x8Flag] & 4) &&
(params.data[HardwareRenderParams::kMode8x8Flag] & 2)) {
// direct 8x8 mode seems to store 1/4 the data, so the offsets need to be less as well.
mv_size /= 4;
}
uint32_t mv_byte_offset = slice_data.header.first_mb_in_slice * mv_size;
H264CoMbWrAddr::Get()
.FromValue(truncate_to_32(current_frame_->reference_mv_buffer.phys_base()) + mv_byte_offset)
.WriteTo(owner_->dosbus());
// 8.4.1.2.1 - co-located motion vectors come from RefPictList1[0] for frames.
if (slice_data.ref_pic_list1.size() > 0) {
auto* amlogic_picture = static_cast<AmlogicH264Picture*>(slice_data.ref_pic_list1[0].get());
if (amlogic_picture) {
auto internal_picture = amlogic_picture->internal_picture.lock();
if (!internal_picture) {
DECODE_ERROR("Co-mb read buffer nonexistent");
frame_data_provider_->AsyncResetStreamAfterCurrentFrame();
return;
}
uint32_t read_addr =
truncate_to_32(internal_picture->reference_mv_buffer.phys_base()) + mv_byte_offset;
ZX_DEBUG_ASSERT(read_addr % 8 == 0);
H264CoMbRdAddr::Get().FromValue((read_addr >> 3) | (2u << 30)).WriteTo(owner_->dosbus());
}
}
if (slice_data.header.first_mb_in_slice == 0)
DpbStatusReg::Get().FromValue(kH264ActionDecodeNewpic).WriteTo(owner_->dosbus());
else
DpbStatusReg::Get().FromValue(kH264ActionDecodeSlice).WriteTo(owner_->dosbus());
owner_->watchdog()->Start();
}
void H264MultiDecoder::FlushFrames() {
auto res = media_decoder_->Flush();
DLOG("Got media decoder res %d", res);
}
void H264MultiDecoder::DumpStatus() {
DLOG("ViffBitCnt: %d", ViffBitCnt::Get().ReadFrom(owner_->dosbus()).reg_value());
DLOG("Viifolevel: %d", VldMemVififoLevel::Get().ReadFrom(owner_->dosbus()).reg_value());
DLOG("input offset: %d read offset: %d", owner_->core()->GetStreamInputOffset(),
owner_->core()->GetReadOffset());
DLOG("Error status reg %d mbymbx reg %d",
ErrorStatusReg::Get().ReadFrom(owner_->dosbus()).reg_value(),
MbyMbx::Get().ReadFrom(owner_->dosbus()).reg_value());
DLOG("DpbStatusReg 0x%x", DpbStatusReg::Get().ReadFrom(owner_->dosbus()).reg_value());
}
void H264MultiDecoder::HandlePicDataDone() {
ZX_DEBUG_ASSERT(current_frame_);
owner_->watchdog()->Cancel();
// TODO(fxb/13483): Get PTS
current_frame_ = nullptr;
current_metadata_frame_ = nullptr;
OutputReadyFrames();
state_ = DecoderState::kInitialWaitingForInput;
owner_->core()->StopDecoding();
// Set currently_decoding_ to false after OutputReadyFrames to avoid running PumpDecoder too
// early.
currently_decoding_ = false;
if (!slice_data_list_.empty()) {
DECODE_ERROR("Extra unexpected slice data for frame: %ld", slice_data_list_.size());
// This shouldn't happen if the client is behaving correctly.
OnFatalError();
return;
}
PropagatePotentialEos();
if (pending_config_change_) {
StartConfigChange();
} else {
owner_->TryToReschedule();
if (state_ == DecoderState::kInitialWaitingForInput) {
PumpDecoder();
}
}
}
void H264MultiDecoder::OutputReadyFrames() {
while (!frames_to_output_.empty()) {
uint32_t index = frames_to_output_.front();
frames_to_output_.pop_front();
client_->OnFrameReady(video_frames_[index]->frame);
}
}
void H264MultiDecoder::HandleHardwareError() {
owner_->watchdog()->Cancel();
owner_->core()->StopDecoding();
// We need to reset the hardware here or for some malformed hardware streams (e.g.
// bear_h264[638] = 44) the CPU will hang when trying to isolate VDEC1 power on shutdown.
ResetHardware();
frame_data_provider_->AsyncResetStreamAfterCurrentFrame();
}
void H264MultiDecoder::HandleInterrupt() {
ZX_DEBUG_ASSERT(owner_->IsDecoderCurrent(this));
// Clear interrupt
VdecAssistMbox1ClrReg::Get().FromValue(1).WriteTo(owner_->dosbus());
uint32_t decode_status = DpbStatusReg::Get().ReadFrom(owner_->dosbus()).reg_value();
TRACE_DURATION("media", "H264MultiDecoder::HandleInterrupt", "decode_status", decode_status);
DLOG("Got H264MultiDecoder::HandleInterrupt, decode status: %x", decode_status);
switch (decode_status) {
case kH264ConfigRequest: {
DpbStatusReg::Get().FromValue(kH264ActionConfigDone).WriteTo(owner_->dosbus());
ConfigureDpb();
break;
}
case kH264DataRequest:
DECODE_ERROR("Got unhandled data request");
HandleHardwareError();
break;
case kH264SliceHeadDone: {
HandleSliceHeadDone();
break;
}
case kH264PicDataDone: {
HandlePicDataDone();
break;
}
case kH264SearchBufempty:
DECODE_ERROR("Decoder got kH264SearchBufempty");
HandleHardwareError();
break;
case kH264DecodeTimeout:
DECODE_ERROR("Decoder got kH264DecodeTimeout");
HandleHardwareError();
break;
}
}
void H264MultiDecoder::PumpOrReschedule() {
if (state_ == DecoderState::kSwappedOut) {
owner_->TryToReschedule();
// TryToReschedule will pump the decoder (using SwappedIn) once the decoder is finally
// rescheduled.
} else {
PumpDecoder();
}
}
void H264MultiDecoder::ReturnFrame(std::shared_ptr<VideoFrame> frame) {
DLOG("H264MultiDecoder::ReturnFrame %d", frame->index);
ZX_DEBUG_ASSERT(frame->index < video_frames_.size());
ZX_DEBUG_ASSERT(video_frames_[frame->index]->frame == frame);
video_frames_[frame->index]->in_use = false;
waiting_for_surfaces_ = false;
PumpOrReschedule();
}
void H264MultiDecoder::CallErrorHandler() { OnFatalError(); }
void H264MultiDecoder::InitializedFrames(std::vector<CodecFrame> frames, uint32_t coded_width,
uint32_t coded_height, uint32_t stride) {
DLOG("H264MultiDecoder::InitializeFrame");
uint32_t frame_count = frames.size();
video_frames_.clear();
for (uint32_t i = 0; i < frame_count; ++i) {
auto frame = std::make_shared<VideoFrame>();
// While we'd like to pass in IO_BUFFER_CONTIG, since we know the VMO was
// allocated with zx_vmo_create_contiguous(), the io_buffer_init_vmo()
// treats that flag as an invalid argument, so instead we have to pretend as
// if it's a non-contiguous VMO, then validate that the VMO is actually
// contiguous later in aml_canvas_config() called by
// owner_->ConfigureCanvas() below.
assert(frames[i].codec_buffer_spec.has_data());
assert(frames[i].codec_buffer_spec.data().is_vmo());
assert(frames[i].codec_buffer_spec.data().vmo().has_vmo_handle());
zx_status_t status = io_buffer_init_vmo(
&frame->buffer, owner_->bti()->get(),
frames[i].codec_buffer_spec.data().vmo().vmo_handle().get(), 0, IO_BUFFER_RW);
if (status != ZX_OK) {
DECODE_ERROR("Failed to io_buffer_init_vmo() for frame - status: %d\n", status);
OnFatalError();
return;
}
io_buffer_cache_flush(&frame->buffer, 0, io_buffer_size(&frame->buffer, 0));
BarrierAfterFlush();
frame->hw_width = coded_width;
frame->hw_height = coded_height;
frame->coded_width = coded_width;
frame->coded_height = coded_height;
frame->stride = stride;
frame->uv_plane_offset = stride * coded_height;
frame->display_width = display_width_;
frame->display_height = display_height_;
frame->index = i;
// can be nullptr
frame->codec_buffer = frames[i].codec_buffer_ptr;
if (frames[i].codec_buffer_ptr) {
frames[i].codec_buffer_ptr->SetVideoFrame(frame);
}
// The ConfigureCanvas() calls validate that the VMO is physically
// contiguous, regardless of how the VMO was created.
auto y_canvas =
owner_->ConfigureCanvas(&frame->buffer, 0, frame->stride, frame->coded_height, 0, 0);
auto uv_canvas = owner_->ConfigureCanvas(&frame->buffer, frame->uv_plane_offset, frame->stride,
frame->coded_height / 2, 0, 0);
if (!y_canvas || !uv_canvas) {
OnFatalError();
return;
}
constexpr uint32_t kMvRefDataSizePerMb = 96;
uint32_t colocated_buffer_size =
fbl::round_up(mb_width_ * mb_height_ * kMvRefDataSizePerMb, ZX_PAGE_SIZE);
auto create_result =
InternalBuffer::Create("H264ReferenceMvs", &owner_->SysmemAllocatorSyncPtr(), owner_->bti(),
colocated_buffer_size, is_secure_,
/*is_writable=*/true, /*is_mapping_needed*/ false);
if (!create_result.is_ok()) {
LOG(ERROR, "Couldn't allocate reference mv buffer - status: %d", create_result.error());
OnFatalError();
return;
}
video_frames_.push_back(std::shared_ptr<ReferenceFrame>(
new ReferenceFrame{false, false, i, std::move(frame), std::move(y_canvas),
std::move(uv_canvas), create_result.take_value()}));
}
waiting_for_surfaces_ = false;
// Caller should trigger a PumpOrReschedule at this point.
}
void H264MultiDecoder::SubmitFrameMetadata(ReferenceFrame* reference_frame,
const media::H264SPS* sps, const media::H264PPS* pps,
const media::H264DPB& dpb) {
current_metadata_frame_ = reference_frame;
// Unclear why this is, but matches the linux decoder.
constexpr uint32_t kReferenceBufMargin = 4;
next_max_reference_size_ = sps->max_num_ref_frames + kReferenceBufMargin;
}
void H264MultiDecoder::SubmitSliceData(SliceData data) {
// Only queue up data in a list instead of starting the decode in hardware. We could try to submit
// it now, but that makes it more difficult to swap out if we only receive data for a partial
// frame from the client and would want to try to swap out between slices.
slice_data_list_.push_back(data);
}
void H264MultiDecoder::OutputFrame(ReferenceFrame* reference_frame, uint32_t pts_id) {
ZX_DEBUG_ASSERT(reference_frame->in_use);
auto it = id_to_pts_map_.find(pts_id);
if (it != id_to_pts_map_.end()) {
reference_frame->frame->has_pts = true;
reference_frame->frame->pts = it->second;
id_to_pts_map_.erase(it);
}
frames_to_output_.push_back(reference_frame->index);
// Don't output a frame that's currently being decoded into, and don't output frames out of order
// if one's already been queued up.
if ((frames_to_output_.size() == 1) && (current_metadata_frame_ != reference_frame)) {
OutputReadyFrames();
}
}
void H264MultiDecoder::SubmitDataToHardware(const uint8_t* data, size_t length) {
ZX_DEBUG_ASSERT(owner_->IsDecoderCurrent(this));
if (use_parser_) {
zx_status_t status =
owner_->SetProtected(VideoDecoder::Owner::ProtectableHardwareUnit::kParser, is_secure_);
if (status != ZX_OK) {
LOG(ERROR, "video_->SetProtected(kParser) failed - status: %d", status);
OnFatalError();
return;
}
// Pass nullptr because we'll handle syncing updates manually.
status = owner_->parser()->InitializeEsParser(nullptr);
if (status != ZX_OK) {
DECODE_ERROR("InitializeEsParser failed - status: %d", status);
OnFatalError();
return;
}
if (length > owner_->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(fxb/13483):
// Handle copying only as much as can fit and waiting for kH264DataRequest to continue
// copying the remainder.
DECODE_ERROR("Empty space in stream buffer %d too small for video data (%lu)",
owner_->GetStreamBufferEmptySpace(), length);
OnFatalError();
return;
}
owner_->parser()->SyncFromDecoderInstance(owner_->current_instance());
// TODO call ParseVideoPhyiscal when input buffers are physically contiguous, which will be true
// when DRM L1.
status = owner_->parser()->ParseVideo(data, length);
if (status != ZX_OK) {
DECODE_ERROR("Parsing video failed - status: %d", status);
OnFatalError();
return;
}
status = owner_->parser()->WaitForParsingCompleted(ZX_SEC(10));
if (status != ZX_OK) {
DECODE_ERROR("Parsing video timed out - status: %d", status);
owner_->parser()->CancelParsing();
OnFatalError();
return;
}
owner_->parser()->SyncToDecoderInstance(owner_->current_instance());
} else {
zx_status_t status = owner_->ProcessVideoNoParser(data, length);
if (status != ZX_OK) {
DECODE_ERROR("Failed to write video");
OnFatalError();
}
}
}
bool H264MultiDecoder::CanBeSwappedIn() {
if (fatal_error_)
return false;
if (waiting_for_surfaces_)
return false;
ZX_DEBUG_ASSERT(!pending_config_change_ || currently_decoding_);
// If there aren't any free output frames the decoder will be swapped in, hit kRanOutOfSurfaces,
// then be swapped out (if necessary). Similarly, if there isn't enough data for a complete frame
// it will be swapped in, will put what data exists in the stream buffer, then hit
// kRanOutOfStreamData before trying to decode any of it.
// TODO(fxb/13483): Wait for all requirements before swapping in the hardware to avoid unnecessary
// changes.
return current_decoder_buffer_ || frame_data_provider_->HasMoreInputData();
}
bool H264MultiDecoder::CanBeSwappedOut() const {
return state_ == DecoderState::kInitialWaitingForInput ||
state_ == DecoderState::kStoppedWaitingForInput;
}
void H264MultiDecoder::SetSwappedOut() {
ZX_DEBUG_ASSERT(state_ == DecoderState::kInitialWaitingForInput);
ZX_DEBUG_ASSERT(CanBeSwappedOut());
state_ = DecoderState::kSwappedOut;
}
void H264MultiDecoder::SwappedIn() { PumpDecoder(); }
void H264MultiDecoder::OnSignaledWatchdog() {
DECODE_ERROR("Hit watchdog");
HandleHardwareError();
}
void H264MultiDecoder::OnFatalError() {
if (!fatal_error_) {
fatal_error_ = true;
client_->OnError();
}
}
void H264MultiDecoder::ReceivedNewInput() { PumpOrReschedule(); }
void H264MultiDecoder::QueueInputEos() {
ZX_DEBUG_ASSERT(!input_eos_queued_);
input_eos_queued_ = true;
PropagatePotentialEos();
}
void H264MultiDecoder::PropagatePotentialEos() {
ZX_DEBUG_ASSERT(!in_pump_decoder_);
if (!input_eos_queued_ || sent_output_eos_to_client_)
return;
if (current_decoder_buffer_ || frame_data_provider_->HasMoreInputData())
return;
if (!media_decoder_->Flush()) {
DECODE_ERROR("Flush failed");
return;
}
// Wait so we can be sure that OnEos happens after all existing frames are output.
// HandlePicDataDone will call this method again once decoding finally finishes.
if (currently_decoding_)
return;
sent_output_eos_to_client_ = true;
client_->OnEos();
}
void H264MultiDecoder::RequestStreamReset() {
fatal_error_ = true;
frame_data_provider_->AsyncResetStreamAfterCurrentFrame();
owner_->TryToReschedule();
}
void H264MultiDecoder::StartConfigChange() {
TRACE_DURATION("media", "H264MultiDecoder::StartConfigChange");
ZX_DEBUG_ASSERT(pending_config_change_);
// We shouldn't try to run this if decoding is currently ongoing, since the interrupt handlers are
// using the current set of video_frames_.
ZX_DEBUG_ASSERT(!currently_decoding_);
ZX_DEBUG_ASSERT(frames_to_output_.empty());
video_frames_.clear();
zx::bti bti;
zx_status_t status = owner_->bti()->duplicate(ZX_RIGHT_SAME_RIGHTS, &bti);
if (status != ZX_OK) {
DECODE_ERROR("bti duplicate failed, status: %d\n", status);
return;
}
display_width_ = media_decoder_->GetVisibleRect().width();
display_height_ = media_decoder_->GetVisibleRect().height();
mb_width_ = media_decoder_->GetPicSize().width() / 16;
mb_height_ = media_decoder_->GetPicSize().height() / 16;
uint32_t min_frame_count = media_decoder_->GetRequiredNumOfPictures();
uint32_t max_frame_count = 24;
uint32_t coded_width = media_decoder_->GetPicSize().width();
uint32_t coded_height = media_decoder_->GetPicSize().height();
constexpr uint32_t kMaxDimension = 4096; // for both width and height.
if (coded_width > kMaxDimension || coded_height > kMaxDimension) {
DECODE_ERROR("Unsupported dimensions %dx%d", coded_width, coded_height);
RequestStreamReset();
return;
}
uint32_t stride = fbl::round_up(coded_width, 32u);
bool has_sar = false;
uint32_t sar_width = 1;
uint32_t sar_height = 1;
auto sar_size = media_decoder_->GetSarSize();
if (sar_size.width() > 0 && sar_size.height() > 0) {
has_sar = true;
sar_width = sar_size.width();
sar_height = sar_size.height();
}
client_->InitializeFrames(std::move(bti), min_frame_count, max_frame_count, coded_width,
coded_height, stride, display_width_, display_height_, has_sar,
sar_width, sar_height);
pending_config_change_ = false;
waiting_for_surfaces_ = true;
owner_->TryToReschedule();
}
void H264MultiDecoder::PumpDecoder() {
TRACE_DURATION("media", "H264MultiDecoder::PumpDecoder");
// Don't try to reenter media_decoder_->Decode().
if (in_pump_decoder_)
return;
while (true) {
if (waiting_for_surfaces_ || currently_decoding_ || pending_config_change_ ||
(state_ == DecoderState::kSwappedOut) || fatal_error_)
return;
ZX_DEBUG_ASSERT(!in_pump_decoder_);
in_pump_decoder_ = true;
auto res = media_decoder_->Decode();
in_pump_decoder_ = false;
DLOG("H264MultiDecoder::PumpDecoder Got result of %d\n", static_cast<int>(res));
if (res == media::AcceleratedVideoDecoder::kConfigChange) {
pending_config_change_ = true;
if (!currently_decoding_) {
StartConfigChange();
}
}
if (res == media::AcceleratedVideoDecoder::kRanOutOfStreamData) {
current_decoder_buffer_.reset();
auto next_decoder_buffer = frame_data_provider_->ReadMoreInputData(this);
if (next_decoder_buffer.data.empty()) {
DLOG("Not decoding because decoder ran out of inputs, state %d", static_cast<int>(state_));
PropagatePotentialEos();
owner_->TryToReschedule();
return;
}
current_decoder_buffer_ =
std::make_unique<media::DecoderBuffer>(std::move(next_decoder_buffer.data));
if (next_decoder_buffer.pts) {
id_to_pts_map_[next_pts_id_] = next_decoder_buffer.pts.value();
}
media_decoder_->SetStream(next_pts_id_++, *current_decoder_buffer_);
} else if (res == media::AcceleratedVideoDecoder::kRanOutOfSurfaces) {
waiting_for_surfaces_ = true;
owner_->TryToReschedule();
return;
} else if (res == media::AcceleratedVideoDecoder::kDecodeError) {
RequestStreamReset();
return;
} else if (res == media::AcceleratedVideoDecoder::kTryAgain) {
owner_->TryToReschedule();
return;
}
}
}
std::shared_ptr<H264MultiDecoder::ReferenceFrame> H264MultiDecoder::GetUnusedReferenceFrame() {
ZX_DEBUG_ASSERT(!pending_config_change_);
for (auto& frame : video_frames_) {
ZX_DEBUG_ASSERT(frame->frame->coded_width ==
static_cast<uint32_t>(media_decoder_->GetPicSize().width()));
ZX_DEBUG_ASSERT(frame->frame->coded_height ==
static_cast<uint32_t>(media_decoder_->GetPicSize().height()));
if (!frame->in_use && !frame->in_internal_use) {
frame->in_use = true;
frame->in_internal_use = true;
return frame;
}
}
return nullptr;
}
zx_status_t H264MultiDecoder::SetupProtection() {
return owner_->SetProtected(VideoDecoder::Owner::ProtectableHardwareUnit::kVdec, is_secure());
}