| // 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_ = notifier; |
| } |
| |
| void H264Decoder::SetInitializeFramesHandler(InitializeFramesHandler handler) { |
| initialize_frames_handler_ = 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::mediacodec::CodecBufferData codec_buffer_data; |
| codec_buffer_data.set_vmo(fuchsia::mediacodec::CodecBufferDataVmo{ |
| .vmo_handle = std::move(frame_vmo), |
| .vmo_usable_start = 0, |
| .vmo_usable_size = frame_vmo_bytes, |
| }); |
| frames.emplace_back(CodecFrame{ |
| .codec_buffer_spec = |
| fuchsia::mediacodec::CodecBuffer{ |
| .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_(); |
| } |
| } |
| } |