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