blob: 4270d6865f077037bbd1b3d97ca2b3ae7480d441 [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 "h264_decoder.h"
#include <lib/media/codec_impl/codec_buffer.h>
#include <lib/media/codec_impl/codec_frame.h>
#include <lib/media/codec_impl/codec_packet.h>
#include <zx/vmo.h>
#include "firmware_blob.h"
#include "macros.h"
#include "memory_barriers.h"
#include "pts_manager.h"
static const uint32_t kBufferAlignShift = 4 + 12;
// AvScratch1
class StreamInfo
: public TypedRegisterBase<DosRegisterIo, StreamInfo, uint32_t> {
public:
DEF_FIELD(7, 0, width_in_mbs);
DEF_FIELD(23, 8, total_mbs);
DEF_FIELD(30, 24, max_reference_size);
DEF_BIT(31, mv_size_flag);
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); }
};
// AvScratch3
class SampleAspectRatioInfo
: public TypedRegisterBase<DosRegisterIo, SampleAspectRatioInfo, uint32_t> {
public:
DEF_FIELD(15, 0, sar_width);
DEF_FIELD(31, 16, sar_height);
static auto Get() { return AddrType(0x09c3 * 4); }
};
// AvScratch6
class CropInfo : public TypedRegisterBase<DosRegisterIo, CropInfo, uint32_t> {
public:
// All quantities are the number of pixels to be cropped from each side.
DEF_FIELD(7, 0, bottom);
DEF_FIELD(15, 8, top); // Ignored
DEF_FIELD(23, 16, right);
DEF_FIELD(31, 24, left); // Ignored
static auto Get() { return AddrType(0x09c6 * 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); }
};
// AvScratchInfo1+
class PicInfo : public TypedRegisterBase<DosRegisterIo, PicInfo, uint32_t> {
public:
DEF_FIELD(4, 0, buffer_index);
DEF_BIT(9, error);
DEF_BIT(15, eos);
DEF_FIELD(31, 16, stream_offset);
static auto Get(uint32_t i) { return AddrType((0x09c1 + i) * 4); }
};
// 0 means "Unspecified"
constexpr uint32_t kAspectRatioIdcExtendedSar = 255;
// This struct type doesn't need a name, since we only read this one static
// instance.
struct {
const uint8_t sar_width;
const uint8_t sar_height;
} kSarTable[] = {
// 0 - entry 0 in this table is never read, but it's only 2 bytes so we just
// let it exist since subtracting 1 from aspect_ratio_idc would probably
// take
// ~2 code bytes or more anyway.
{0, 0},
// 1
{1, 1},
// 2
{12, 11},
// 3
{10, 11},
// 4
{16, 11},
// 5
{40, 33},
// 6
{24, 11},
// 7
{20, 11},
// 8
{32, 11},
// 9
{80, 33},
// 10
{18, 11},
// 11
{15, 11},
// 12
{64, 33},
// 13
{160, 99},
// 14
{4, 3},
// 15
{3, 2},
// 16
{2, 1},
};
static uint32_t GetMaxDpbSize(uint32_t level_idc, uint32_t width_in_mbs,
uint32_t height_in_mbs) {
// From Table A-1 of the h.264 spec.
// https://www.itu.int/rec/T-REC-H.264-201704-I/en
uint32_t max_dpb_mbs;
switch (level_idc) {
case 10:
max_dpb_mbs = 396;
break;
case 11:
max_dpb_mbs = 900;
break;
case 12:
case 13:
case 20:
max_dpb_mbs = 2376;
break;
case 21:
max_dpb_mbs = 4752;
break;
case 22:
case 30:
max_dpb_mbs = 8100;
break;
case 31:
max_dpb_mbs = 18000;
break;
case 32:
max_dpb_mbs = 20480;
break;
case 40:
case 41:
max_dpb_mbs = 32768;
break;
case 42:
max_dpb_mbs = 34816;
break;
case 50:
max_dpb_mbs = 110400;
break;
case 51:
case 52:
max_dpb_mbs = 184320;
break;
case 60:
case 61:
case 62:
max_dpb_mbs = 696320;
break;
default:
return 0;
}
uint32_t num_mbs = width_in_mbs * height_in_mbs;
if (!num_mbs)
return 0;
return std::min(16u, (max_dpb_mbs + num_mbs - 1) / num_mbs);
}
H264Decoder::~H264Decoder() {
owner_->core()->StopDecoding();
owner_->core()->WaitForIdle();
BarrierBeforeRelease();
io_buffer_release(&reference_mv_buffer_);
io_buffer_release(&codec_data_);
io_buffer_release(&sei_data_buffer_);
io_buffer_release(&secondary_firmware_);
}
zx_status_t H264Decoder::ResetHardware() {
DosSwReset0::Get()
.FromValue((1 << 7) | (1 << 6) | (1 << 4))
.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((1 << 7) | (1 << 6) | (1 << 4))
.WriteTo(owner_->dosbus());
DosSwReset0::Get().FromValue(0).WriteTo(owner_->dosbus());
DosSwReset0::Get().FromValue((1 << 9) | (1 << 8)).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());
return ZX_OK;
}
zx_status_t H264Decoder::LoadSecondaryFirmware(const uint8_t* data,
uint32_t firmware_size) {
// 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;
constexpr uint32_t kSecondaryFirmwareBufferSize = kSecondaryFirmwareSize * 5;
{
zx_status_t status = io_buffer_init_aligned(
&secondary_firmware_, owner_->bti(), kSecondaryFirmwareBufferSize,
kBufferAlignShift, IO_BUFFER_RW | IO_BUFFER_CONTIG);
if (status != ZX_OK) {
DECODE_ERROR("Failed to make second firmware buffer: %d", status);
return status;
}
auto addr = static_cast<uint8_t*>(io_buffer_virt(&secondary_firmware_));
// 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
}
io_buffer_cache_flush(&secondary_firmware_, 0, kSecondaryFirmwareBufferSize);
return ZX_OK;
}
zx_status_t H264Decoder::Initialize() {
uint8_t* data;
uint32_t firmware_size;
zx_status_t status = owner_->firmware_blob()->GetFirmwareData(
FirmwareBlob::FirmwareType::kH264, &data, &firmware_size);
if (status != ZX_OK)
return status;
status = owner_->core()->LoadFirmware(data, firmware_size);
if (status != ZX_OK)
return status;
if (!WaitForRegister(std::chrono::milliseconds(100), [this]() {
return !(DcacDmaCtrl::Get().ReadFrom(owner_->dosbus()).reg_value() &
0x8000);
})) {
DECODE_ERROR("Waiting for DCAC DMA timed out\n");
return ZX_ERR_TIMED_OUT;
}
if (!WaitForRegister(std::chrono::milliseconds(100), [this]() {
return !(LmemDmaCtrl::Get().ReadFrom(owner_->dosbus()).reg_value() &
0x8000);
})) {
DECODE_ERROR("Waiting for LMEM DMA timed out\n");
return ZX_ERR_TIMED_OUT;
}
status = ResetHardware();
if (status != ZX_OK)
return status;
PscaleCtrl::Get().FromValue(0).WriteTo(owner_->dosbus());
AvScratch0::Get().FromValue(0).WriteTo(owner_->dosbus());
const uint32_t kCodecDataSize = 0x1ee000;
status = io_buffer_init_aligned(&codec_data_, owner_->bti(), kCodecDataSize,
kBufferAlignShift,
IO_BUFFER_RW | IO_BUFFER_CONTIG);
if (status != ZX_OK) {
DECODE_ERROR("Failed to make codec data buffer: %d\n", status);
return status;
}
io_buffer_cache_flush(&codec_data_, 0, kCodecDataSize);
status = LoadSecondaryFirmware(data, firmware_size);
if (status != ZX_OK)
return status;
enum {
kBufferStartAddressOffset = 0x1000000,
};
BarrierAfterFlush(); // For codec_data and secondary_firmware_
// This may wrap if the address is less than the buffer start offset.
uint32_t buffer_offset =
truncate_to_32(io_buffer_phys(&codec_data_)) - kBufferStartAddressOffset;
AvScratch1::Get().FromValue(buffer_offset).WriteTo(owner_->dosbus());
AvScratchG::Get()
.FromValue(truncate_to_32(io_buffer_phys(&secondary_firmware_)))
.WriteTo(owner_->dosbus());
AvScratch7::Get().FromValue(0).WriteTo(owner_->dosbus());
AvScratch8::Get().FromValue(0).WriteTo(owner_->dosbus());
AvScratch9::Get().FromValue(0).WriteTo(owner_->dosbus());
VdecAssistMbox1ClrReg::Get().FromValue(1).WriteTo(owner_->dosbus());
VdecAssistMbox1Mask::Get().FromValue(1).WriteTo(owner_->dosbus());
MdecPicDcCtrl::Get()
.ReadFrom(owner_->dosbus())
.set_nv12_output(true)
.WriteTo(owner_->dosbus());
CodecSettings::Get()
.ReadFrom(owner_->dosbus())
.set_zeroed0(0)
.set_drop_b_frames(false)
.set_error_recovery_mode(1)
.set_zeroed1(0)
.set_ip_frames_only(0)
.set_disable_fast_poc(0)
.WriteTo(owner_->dosbus());
status = io_buffer_init_aligned(&sei_data_buffer_, owner_->bti(), 8 * 1024,
kBufferAlignShift,
IO_BUFFER_RW | IO_BUFFER_CONTIG);
if (status != ZX_OK) {
DECODE_ERROR("Failed to make sei data buffer: %d", status);
return status;
}
io_buffer_cache_flush(&sei_data_buffer_, 0,
io_buffer_size(&sei_data_buffer_, 0));
BarrierAfterFlush();
AvScratchI::Get()
.FromValue(truncate_to_32(io_buffer_phys(&sei_data_buffer_)) -
buffer_offset)
.WriteTo(owner_->dosbus());
AvScratchJ::Get().FromValue(0).WriteTo(owner_->dosbus());
MdecPicDcThresh::Get().FromValue(0x404038aa).WriteTo(owner_->dosbus());
owner_->core()->StartDecoding();
return ZX_OK;
}
void H264Decoder::SetFrameReadyNotifier(FrameReadyNotifier notifier) {
notifier_ = std::move(notifier);
}
void H264Decoder::SetInitializeFramesHandler(InitializeFramesHandler handler) {
initialize_frames_handler_ = std::move(handler);
}
void H264Decoder::SetErrorHandler(fit::closure error_handler) {
error_handler_ = std::move(error_handler);
}
void H264Decoder::InitializedFrames(std::vector<CodecFrame> frames,
uint32_t width, uint32_t height,
uint32_t stride) {
ZX_DEBUG_ASSERT(state_ == DecoderState::kWaitingForNewFrames);
uint32_t frame_count = frames.size();
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.
zx_status_t status = io_buffer_init_vmo(
&frame->buffer, owner_->bti(),
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->uv_plane_offset = width * height;
frame->stride = width;
frame->width = width;
frame->height = 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->height, 0, 0);
auto uv_canvas =
owner_->ConfigureCanvas(&frame->buffer, frame->uv_plane_offset,
frame->stride, frame->height / 2, 0, 0);
if (!y_canvas || !uv_canvas) {
OnFatalError();
return;
}
AncNCanvasAddr::Get(i)
.FromValue((uv_canvas->index() << 16) | (uv_canvas->index() << 8) |
(y_canvas->index()))
.WriteTo(owner_->dosbus());
video_frames_.push_back(
{std::move(frame), std::move(y_canvas), std::move(uv_canvas)});
}
AvScratch0::Get().FromValue(next_av_scratch0_).WriteTo(owner_->dosbus());
state_ = DecoderState::kRunning;
}
zx_status_t H264Decoder::InitializeFrames(uint32_t frame_count, uint32_t width,
uint32_t height,
uint32_t display_width,
uint32_t display_height, bool has_sar,
uint32_t sar_width,
uint32_t sar_height) {
video_frames_.clear();
returned_frames_.clear();
uint64_t frame_vmo_bytes = width * height * 3 / 2;
display_width_ = display_width;
display_height_ = display_height;
// Regardless of local allocation of VMOs or remote allocation of VMOs, we
// first represent the frames this way. This representation conveys the
// potentially-non-zero offset into the VMO, and allows sharing code further
// down.
std::vector<CodecFrame> frames;
if (initialize_frames_handler_) {
::zx::bti duplicated_bti;
zx_status_t dup_result =
::zx::unowned_bti(owner_->bti())
->duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicated_bti);
if (dup_result != ZX_OK) {
DECODE_ERROR("Failed to duplicate BTI - status: %d\n", dup_result);
return dup_result;
}
zx_status_t initialize_result = initialize_frames_handler_(
std::move(duplicated_bti), frame_count, width, height, width,
display_width, display_height, has_sar, sar_width, sar_height);
if (initialize_result != ZX_OK) {
if (initialize_result != ZX_ERR_STOP) {
DECODE_ERROR("initialize_frames_handler_() failed - status: %d\n",
initialize_result);
}
return initialize_result;
}
} else {
for (uint32_t i = 0; i < frame_count; ++i) {
// aml_canvas_config() requires contiguous VMOs, and will validate that
// each frame VMO is actually physically contiguous. So create with
// zx_vmo_create_contiguous() here.
::zx::vmo frame_vmo;
zx_status_t vmo_create_result = zx_vmo_create_contiguous(
owner_->bti(), frame_vmo_bytes, 0, frame_vmo.reset_and_get_address());
if (vmo_create_result != ZX_OK) {
DECODE_ERROR("H264Decoder::InitializeFrames() failed - status: %d\n",
vmo_create_result);
return vmo_create_result;
}
fuchsia::media::StreamBufferData codec_buffer_data;
codec_buffer_data.set_vmo(fuchsia::media::StreamBufferDataVmo{
.vmo_handle = std::move(frame_vmo),
.vmo_usable_start = 0,
.vmo_usable_size = frame_vmo_bytes,
});
frames.emplace_back(CodecFrame{
.codec_buffer_spec =
fuchsia::media::StreamBuffer{
.buffer_lifetime_ordinal =
next_non_codec_buffer_lifetime_ordinal_,
.buffer_index = i,
.data = std::move(codec_buffer_data),
},
.codec_buffer_ptr = nullptr,
});
}
next_non_codec_buffer_lifetime_ordinal_++;
InitializedFrames(std::move(frames), width, height, width);
}
return ZX_OK;
}
void H264Decoder::ReturnFrame(std::shared_ptr<VideoFrame> video_frame) {
returned_frames_.push_back(video_frame);
TryReturnFrames();
}
void H264Decoder::TryReturnFrames() {
while (!returned_frames_.empty()) {
std::shared_ptr<VideoFrame> frame = returned_frames_.back();
if (frame->index >= video_frames_.size() ||
frame != video_frames_[frame->index].frame) {
// Possible if the stream size changed.
returned_frames_.pop_back();
continue;
}
if (AvScratch7::Get().ReadFrom(owner_->dosbus()).reg_value() == 0) {
AvScratch7::Get().FromValue(frame->index + 1).WriteTo(owner_->dosbus());
} else if (AvScratch8::Get().ReadFrom(owner_->dosbus()).reg_value() == 0) {
AvScratch8::Get().FromValue(frame->index + 1).WriteTo(owner_->dosbus());
} else {
// Neither return slot is free, so give up for now. An interrupt
// signaling completion of a frame should cause this to be tried again.
// TODO: Try returning frames again after a delay, to ensure this won't
// hang forever.
return;
}
returned_frames_.pop_back();
}
}
zx_status_t H264Decoder::InitializeStream() {
ZX_DEBUG_ASSERT(state_ == DecoderState::kRunning);
state_ = DecoderState::kWaitingForNewFrames;
BarrierBeforeRelease(); // For reference_mv_buffer_
if (io_buffer_is_valid(&reference_mv_buffer_))
io_buffer_release(&reference_mv_buffer_);
// StreamInfo AKA AvScratch1.
auto stream_info = StreamInfo::Get().ReadFrom(owner_->dosbus());
// SequenceInfo AKA AvScratch2.
auto sequence_info = SequenceInfo::Get().ReadFrom(owner_->dosbus());
// SampleAspectRatioInfo AKA AvScratch3
auto sar_info = SampleAspectRatioInfo::Get().ReadFrom(owner_->dosbus());
uint32_t level_idc = AvScratchA::Get().ReadFrom(owner_->dosbus()).reg_value();
uint32_t mb_mv_byte = stream_info.mv_size_flag() ? 24 : 96;
uint32_t mb_width = stream_info.width_in_mbs();
if (!mb_width && stream_info.total_mbs())
mb_width = 256;
if (!mb_width) {
DECODE_ERROR("Width is 0 macroblocks\n");
// Not returning ZX_ERR_IO_DATA_INTEGRITY, because this isn't an explicit
// integrity check.
return ZX_ERR_INTERNAL;
}
uint32_t mb_height = stream_info.total_mbs() / mb_width;
constexpr uint32_t kActualDPBSize = 24;
uint32_t max_dpb_size = GetMaxDpbSize(level_idc, mb_width, mb_height);
if (max_dpb_size == 0) {
max_dpb_size = kActualDPBSize;
} else {
max_dpb_size = std::min(max_dpb_size, kActualDPBSize);
}
uint32_t max_reference_size =
std::min(stream_info.max_reference_size(), kActualDPBSize - 1);
max_dpb_size = std::max(max_reference_size, max_dpb_size);
max_reference_size++;
// Rounding to 4 macroblocks is for matching the linux driver, in case the
// hardware happens to round up as well.
uint32_t mv_buffer_size = fbl::round_up(mb_height, 4u) *
fbl::round_up(mb_width, 4u) * mb_mv_byte *
max_reference_size;
zx_status_t status =
io_buffer_init(&reference_mv_buffer_, owner_->bti(), mv_buffer_size,
IO_BUFFER_RW | IO_BUFFER_CONTIG);
if (status != ZX_OK) {
DECODE_ERROR("Couldn't allocate reference mv buffer\n");
return status;
}
io_buffer_cache_flush(&reference_mv_buffer_, 0,
io_buffer_size(&reference_mv_buffer_, 0));
BarrierAfterFlush();
AvScratch1::Get()
.FromValue(truncate_to_32(io_buffer_phys(&reference_mv_buffer_)))
.WriteTo(owner_->dosbus());
// In the linux driver AvScratch3 is used to communicate about the display
// canvas.
AvScratch3::Get().FromValue(0).WriteTo(owner_->dosbus());
AvScratch4::Get()
.FromValue(truncate_to_32(io_buffer_phys(&reference_mv_buffer_)) +
mv_buffer_size)
.WriteTo(owner_->dosbus());
auto crop_info = CropInfo::Get().ReadFrom(owner_->dosbus());
uint32_t display_width = mb_width * 16 - crop_info.right();
uint32_t display_height = mb_height * 16 - crop_info.bottom();
// Canvas width must be a multiple of 32 bytes.
uint32_t frame_width = fbl::round_up(mb_width * 16, 32u);
uint32_t frame_height = mb_height * 16;
// Sample aspect ratio - normalize as sar_width : sar_height.
//
// The has_sar will be true for any explicitly-specified SAR, and false for
// all other cases (both explicitly "Unspecified" and "Reserved" cases that we
// don't recognize).
bool has_sar = false;
uint32_t sar_width = 1;
uint32_t sar_height = 1;
if (sequence_info.aspect_ratio_info_present_flag()) {
uint32_t aspect_ratio_idc = sequence_info.aspect_ratio_idc();
if (aspect_ratio_idc == kAspectRatioIdcExtendedSar) {
sar_width = sar_info.sar_width();
sar_height = sar_info.sar_height();
has_sar = true;
if (sar_width == 0 || sar_height == 0) {
// spec says this condition means "considered unspecified"
sar_width = 1;
sar_height = 1;
has_sar = false;
}
} else {
ZX_DEBUG_ASSERT(aspect_ratio_idc != kAspectRatioIdcExtendedSar);
// aspect_ratio_idc == 0 and "Reserved" values are treated the same way as
// each other, and both cases don't run the body of the following "if". We
// treat "Reserved" the same as "Unspecified" instead of flagging an error
// because it seems extremely unlikely that any "Reserved" value in this
// context would have meaning beyond specifying sar_width and sar_height.
// So for "Reserved" values we just end up with has_sar false, which
// should allow _something_ to be displayed even if the displayed frames
// have the wrong SAR.
if (aspect_ratio_idc >= 1 && aspect_ratio_idc <= 16) {
sar_width = kSarTable[aspect_ratio_idc].sar_width;
sar_height = kSarTable[aspect_ratio_idc].sar_height;
has_sar = true;
}
ZX_DEBUG_ASSERT(aspect_ratio_idc != 0 ||
(!has_sar && sar_width == 1 && sar_height == 1));
ZX_DEBUG_ASSERT(has_sar || (sar_width == 1 && sar_height == 1));
ZX_DEBUG_ASSERT(sar_width != 0 && sar_height != 0);
}
}
next_av_scratch0_ =
(max_reference_size << 24) | (kActualDPBSize << 16) | (max_dpb_size << 8);
// TODO(dustingreen): Plumb min and max frame counts, with max at least
// kActualDPBSize (24 or higher if possible), and min sufficient to allow
// decode to proceed without tending to leave the decoder idle for long if the
// client immediately releases each frame (just barely enough to decode as
// long as the client never camps on even one frame).
status =
InitializeFrames(kActualDPBSize, frame_width, frame_height, display_width,
display_height, has_sar, sar_width, sar_height);
if (status != ZX_OK) {
if (status != ZX_ERR_STOP) {
DECODE_ERROR("InitializeFrames() failed: status: %d\n", status);
}
return status;
}
return ZX_OK;
}
void H264Decoder::ReceivedFrames(uint32_t frame_count) {
uint32_t error_count =
AvScratchD::Get().ReadFrom(owner_->dosbus()).reg_value();
// This hit_eos is _not_ the same as the is_end_of_stream in PtsOut below.
bool hit_eos = false;
for (uint32_t i = 0; i < frame_count && !hit_eos; i++) {
auto pic_info = PicInfo::Get(i).ReadFrom(owner_->dosbus());
uint32_t buffer_index = pic_info.buffer_index();
uint32_t slice_type =
(AvScratchH::Get().ReadFrom(owner_->dosbus()).reg_value() >> (i * 4)) &
0xf;
if (pic_info.eos())
hit_eos = true;
// TODO(dustingreen): We'll need to bit-extend (nearest wins to allow for
// re-ordering) this value to uint64_t, so that PTSs for frames after 4GiB
// still work.
uint32_t stream_byte_offset = pic_info.stream_offset();
stream_byte_offset |=
((AvScratch::Get(0xa + i / 2).ReadFrom(owner_->dosbus()).reg_value() >>
((i % 2) * 16)) &
0xffff)
<< 16;
PtsManager::LookupResult pts_result =
pts_manager_->Lookup(stream_byte_offset);
video_frames_[buffer_index].frame->has_pts = pts_result.has_pts();
video_frames_[buffer_index].frame->pts = pts_result.pts();
if (pts_result.is_end_of_stream()) {
// TODO(dustingreen): Handle this once we're able to detect this way. For
// now, ignore but print an obvious message.
printf("##### UNHANDLED END OF STREAM DETECTED #####\n");
break;
}
if (notifier_)
notifier_(video_frames_[buffer_index].frame);
DLOG("Got buffer %d error %d error_count %d slice_type %d offset %x\n",
buffer_index, pic_info.error(), error_count, slice_type,
pic_info.stream_offset());
}
AvScratch0::Get().FromValue(0).WriteTo(owner_->dosbus());
}
enum {
kCommandInitializeStream = 1,
kCommandNewFrames = 2,
kCommandSwitchStreams = 3,
kCommandFatalError = 6,
kCommandGotFirstOffset = 9,
};
void H264Decoder::SwitchStreams() {
// Signal that we're ready to allocate new frames for the new stream.
AvScratch7::Get().FromValue(0).WriteTo(owner_->dosbus());
AvScratch8::Get().FromValue(0).WriteTo(owner_->dosbus());
AvScratch9::Get().FromValue(0).WriteTo(owner_->dosbus());
// Signal firmware that command has been processed.
AvScratch0::Get().FromValue(0).WriteTo(owner_->dosbus());
}
void H264Decoder::HandleInterrupt() {
// Stop processing on fatal error.
if (fatal_error_)
return;
VdecAssistMbox1ClrReg::Get().FromValue(1).WriteTo(owner_->dosbus());
// Some returned frames may have been buffered up earlier, so try to return
// them now that the firmware had a chance to do some work.
TryReturnFrames();
// The core signals the main processor what command to run using AvScratch0.
// The main processor returns a result using AvScratch0 to trigger the decoder
// to continue (possibly 0, if no result is needed).
auto scratch0 = AvScratch0::Get().ReadFrom(owner_->dosbus());
DLOG("Got command: %x\n", scratch0.reg_value());
uint32_t cpu_command = scratch0.reg_value() & 0xff;
switch (cpu_command) {
case kCommandInitializeStream: {
// For now, this can block for a while until buffers are allocated, or
// until it fails. One of the ways it can fail is if the Codec client
// closes the current stream at the Codec interface level (not exactly the
// same thing as "stream" here).
zx_status_t status = InitializeStream();
if (status != ZX_OK) {
OnFatalError();
}
} break;
case kCommandNewFrames:
ReceivedFrames((scratch0.reg_value() >> 8) & 0xff);
break;
case kCommandSwitchStreams:
SwitchStreams();
break;
case kCommandFatalError: {
auto error_count =
AvScratchD::Get().ReadFrom(owner_->dosbus()).reg_value();
DECODE_ERROR("Decoder fatal error %d\n", error_count);
OnFatalError();
// Don't write to AvScratch0, so the decoder won't continue.
break;
}
case kCommandGotFirstOffset: {
uint32_t first_offset =
AvScratch1::Get().ReadFrom(owner_->dosbus()).reg_value();
DLOG("First offset: %d\n", first_offset);
AvScratch0::Get().FromValue(0).WriteTo(owner_->dosbus());
break;
}
default:
DECODE_ERROR("Got unknown command: %d\n", cpu_command);
return;
}
auto sei_itu35_flags =
AvScratchJ::Get().ReadFrom(owner_->dosbus()).reg_value();
if (sei_itu35_flags & (1 << 15)) {
DLOG("Got Supplemental Enhancement Information buffer");
AvScratchJ::Get().FromValue(0).WriteTo(owner_->dosbus());
}
}
void H264Decoder::OnFatalError() {
if (!fatal_error_) {
fatal_error_ = true;
if (error_handler_) {
error_handler_();
}
}
}