| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "codec_adapter_h264_multi.h" |
| |
| #include <lib/fidl/cpp/clone.h> |
| #include <lib/fit/defer.h> |
| #include <lib/sync/completion.h> |
| #include <lib/trace/event.h> |
| #include <lib/zx/bti.h> |
| #include <zircon/assert.h> |
| #include <zircon/threads.h> |
| #include <zircon/time.h> |
| |
| #include <limits> |
| #include <mutex> |
| #include <optional> |
| |
| #include <bind/fuchsia/amlogic/platform/sysmem/heap/cpp/bind.h> |
| #include <bind/fuchsia/sysmem/heap/cpp/bind.h> |
| #include <fbl/algorithm.h> |
| #include <src/lib/memory_barriers/memory_barriers.h> |
| |
| #include "device_ctx.h" |
| #include "h264_multi_decoder.h" |
| #include "macros.h" |
| #include "pts_manager.h" |
| #include "vdec1.h" |
| |
| namespace amlogic_decoder { |
| |
| namespace { |
| 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); |
| } |
| |
| template <typename T> |
| constexpr T AlignUpConstexpr(T value, T divisor) { |
| return (value + divisor - 1) / divisor * divisor; |
| } |
| |
| // For experimentation purposes, this allows easily switching (with a local edit) to allowing larger |
| // input buffers, and using a larger stream buffer. When this is false, the input buffer max size |
| // and stream buffer size are tuned for decoding 1080p safely (barely). |
| constexpr bool k4kInputFrames = false; |
| |
| // See VLD_PADDING_SIZE. |
| constexpr uint32_t kPaddingSize = 1024; |
| |
| // This should be enough space to hold all headers (such as SEI, SPS, PPS), plus the kPaddingSize |
| // padding the decoder adds after each header that is delivered in its own packet, before a max-size |
| // frame. This should be large enough to also hold any zero-padding in the input stream before a |
| // max-size frame (typically none). |
| constexpr uint32_t kBigHeadersBytes = 128 * 1024; |
| |
| // This is enough to decode 4:2:0 1920x1080 with MinCR 2, assuming headers before the frame don't |
| // exceed 128KiB. |
| constexpr uint32_t k1080pMaxCompressedFrameSize = 1920 * 1080 * 3 / 2 / 2; |
| constexpr uint32_t kDci4kMaxCompressedFrameSize = 4096u * 2160 * 3 / 2 / 2; |
| |
| constexpr uint32_t k1080pMaxCompressedFrameSizeIncludingHeaders = |
| k1080pMaxCompressedFrameSize + kBigHeadersBytes; |
| constexpr uint32_t kDci4kMaxCompressedFrameSizeIncludingHeaders = |
| kDci4kMaxCompressedFrameSize + kBigHeadersBytes; |
| |
| constexpr uint32_t kMaxCompressedFrameSizeIncludingHeaders = |
| k4kInputFrames ? kDci4kMaxCompressedFrameSizeIncludingHeaders |
| : k1080pMaxCompressedFrameSizeIncludingHeaders; |
| |
| constexpr uint32_t kStreamBufferReadAlignment = 512; |
| // It might be reasonable to remove this adjustment, given some experimentation to see if the |
| // kStreamBufferReadAlignment is sufficient on its own to make kStreamBufferSize work. |
| constexpr uint32_t kReadNotEqualWriteAdjustment = 1; |
| |
| // The ZX_PAGE_SIZE alignment is just because we won't really allocate a partial page via sysmem |
| // anyway, so we may as well use the rest of the last needed page even if kStreamBufferReadAlignment |
| // might technically work. |
| // |
| // The first kPaddingSize is to be able to flush through a first frame. The second kPaddingSize is |
| // because the first kPaddingSize is still in the stream buffer at the time we're decoding the |
| // second frame, because the first frame ended just after the first frame's payload data as far as |
| // the HW is concerned (despite the need for padding to cause the first frame to complete). |
| constexpr uint32_t kStreamBufferSize = |
| AlignUpConstexpr(kMaxCompressedFrameSizeIncludingHeaders + 2 * kPaddingSize + |
| kStreamBufferReadAlignment + kReadNotEqualWriteAdjustment, |
| static_cast<uint32_t>(ZX_PAGE_SIZE)); |
| static_assert(kStreamBufferSize % ZX_PAGE_SIZE == 0); |
| |
| // For now we rely on a compressed input frame to be contained entirely in a single buffer. While |
| // this minimum size may work for some demo streams, for now clients are expected to set a larger |
| // min_buffer_size for input, in their BufferCollectionConstraints. A recommended expression for |
| // min_buffer_size is max_width * max_height * 3 / 2 / 2 + 128 * 1024. This recommended expression |
| // accounts for MinCR (see h264 spec) of 2 which is worst-case, and allows for SEI/SPS/PPS that's up |
| // to 128 KiB which is probably enough for those headers. |
| constexpr uint32_t kInputPerPacketBufferBytesMin = 512 * 1024; |
| |
| const uint32_t kInputPerPacketBufferBytesMax = kMaxCompressedFrameSizeIncludingHeaders; |
| |
| constexpr uint32_t kInputBufferCountForCodecMin = 1; |
| constexpr uint32_t kInputBufferCountForCodecMax = 64; |
| |
| } // namespace |
| |
| CodecAdapterH264Multi::CodecAdapterH264Multi(std::mutex& lock, |
| CodecAdapterEvents* codec_adapter_events, |
| DeviceCtx* device) |
| : AmlogicCodecAdapter(lock, codec_adapter_events), |
| device_(device), |
| video_(device_->video()), |
| core_loop_(&kAsyncLoopConfigNoAttachToCurrentThread), |
| shared_fidl_thread_closure_queue_(std::in_place, |
| device->driver()->shared_fidl_loop()->dispatcher(), |
| device->driver()->shared_fidl_thread()) { |
| ZX_DEBUG_ASSERT(device_); |
| ZX_DEBUG_ASSERT(video_); |
| ZX_DEBUG_ASSERT(secure_memory_mode_[kInputPort] == fuchsia::mediacodec::SecureMemoryMode::OFF); |
| ZX_DEBUG_ASSERT(secure_memory_mode_[kOutputPort] == fuchsia::mediacodec::SecureMemoryMode::OFF); |
| thrd_t thrd; |
| zx_status_t status; |
| status = core_loop_.StartThread("H264 Core loop", &thrd); |
| ZX_ASSERT(status == ZX_OK); |
| |
| device_->SetThreadProfile(zx::unowned_thread(thrd_get_zx_handle(thrd)), |
| ThreadRole::kH264MultiCore); |
| |
| status = resource_loop_.StartThread("Resource loop", &thrd); |
| ZX_ASSERT(status == ZX_OK); |
| |
| device_->SetThreadProfile(zx::unowned_thread(thrd_get_zx_handle(thrd)), |
| ThreadRole::kH264MultiResource); |
| } |
| |
| CodecAdapterH264Multi::~CodecAdapterH264Multi() { |
| // We need to delete the shared_fidl_thread_closure_queue_ on its dispatcher thread, per the |
| // rules of ~ClosureQueue. |
| sync_completion_t shared_fidl_finished; |
| auto run_on_shared_fidl = [this, &shared_fidl_finished] { |
| shared_fidl_thread_closure_queue_.reset(); |
| sync_completion_signal(&shared_fidl_finished); |
| }; |
| if (thrd_current() == device_->driver()->shared_fidl_thread()) { |
| run_on_shared_fidl(); |
| } else { |
| shared_fidl_thread_closure_queue_->Enqueue(run_on_shared_fidl); |
| } |
| sync_completion_wait(&shared_fidl_finished, ZX_TIME_INFINITE); |
| |
| core_loop_.Shutdown(); |
| resource_loop_.Shutdown(); |
| } |
| |
| void CodecAdapterH264Multi::SetCodecDiagnostics(CodecDiagnostics* codec_diagnostics) { |
| codec_diagnostics_ = codec_diagnostics->CreateDriverCodec("H264"); |
| } |
| |
| std::optional<media_metrics::StreamProcessorEvents2MigratedMetricDimensionImplementation> |
| CodecAdapterH264Multi::CoreCodecMetricsImplementation() { |
| return media_metrics:: |
| StreamProcessorEvents2MigratedMetricDimensionImplementation_AmlogicDecoderH264; |
| } |
| |
| bool CodecAdapterH264Multi::IsCoreCodecRequiringOutputConfigForFormatDetection() { return false; } |
| |
| bool CodecAdapterH264Multi::IsCoreCodecMappedBufferUseful(CodecPort port) { |
| if (port == kInputPort) { |
| // Returning true here essentially means that we may be able to make use of mapped buffers if |
| // they're possible. However if is_secure true, we won't get a mapping and we don't really need |
| // a mapping, other than for avcC. If avcC shows up on input, we'll fail then. |
| // |
| // TODO(https://fxbug.dev/42110593): Add the failure when avcC shows up when is_secure, as |
| // described above. |
| return true; |
| } else { |
| ZX_DEBUG_ASSERT(port == kOutputPort); |
| return false; |
| } |
| } |
| |
| bool CodecAdapterH264Multi::IsCoreCodecHwBased(CodecPort port) { return true; } |
| |
| zx::unowned_bti CodecAdapterH264Multi::CoreCodecBti() { return zx::unowned_bti(video_->bti()); } |
| |
| void CodecAdapterH264Multi::CoreCodecInit( |
| const fuchsia::media::FormatDetails& initial_input_format_details) { |
| initial_input_format_details_ = fidl::Clone(initial_input_format_details); |
| latest_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 CodecAdapterH264Multi::CoreCodecSetSecureMemoryMode( |
| CodecPort port, fuchsia::mediacodec::SecureMemoryMode secure_memory_mode) { |
| // TODO(https://fxbug.dev/42116143): Ideally a codec list from the main CodecFactory would avoid |
| // reporting support for secure output or input when !is_tee_available(), which likely will mean |
| // reporting that in list from driver's local codec factory up to main factory. The main |
| // CodecFactory could also avoid handing out a codec that can't do secure output / input when the |
| // TEE isn't available, so we wouldn't end up here. |
| if (secure_memory_mode != fuchsia::mediacodec::SecureMemoryMode::OFF && |
| !video_->is_tee_available()) { |
| events_->onCoreCodecFailCodec( |
| "BUG 40198 - Codec factory should catch earlier when secure requested without TEE."); |
| return; |
| } |
| secure_memory_mode_[port] = secure_memory_mode; |
| } |
| |
| void CodecAdapterH264Multi::OnFrameReady(std::shared_ptr<VideoFrame> frame) { |
| TRACE_DURATION("media", "CodecAdapterH264Multi::OnFrameReady", "index", frame->index); |
| output_stride_ = frame->stride; |
| const CodecBuffer* buffer = frame->codec_buffer; |
| ZX_DEBUG_ASSERT(buffer); |
| |
| uint64_t total_size_bytes = frame->stride * frame->coded_height * 3 / 2; |
| if (total_size_bytes > std::numeric_limits<uint32_t>::max()) { |
| OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN); |
| return; |
| } |
| |
| // The Codec interface requires that emitted frames are cache clean. 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(jbauman): avoid unnecessary cache ops when in RAM domain or when the buffer isn't |
| // mappable. |
| { |
| TRACE_DURATION("media", "cache invalidate"); |
| if (!IsOutputSecure()) { |
| buffer->CacheFlushAndInvalidate(0, frame->stride * frame->coded_height); |
| buffer->CacheFlushAndInvalidate(frame->uv_plane_offset, |
| frame->stride * frame->coded_height / 2); |
| } |
| } |
| |
| // We intentionally _don't_ use the packet with same index as the buffer (in |
| // general - it's fine that they sometimes match), to avoid clients building |
| // up inappropriate dependency on buffer index being the same as packet |
| // index (as nice as that would be, VP9, and maybe others, don't get along |
| // with that in general, so ... force clients to treat packet index and |
| // buffer index as separate things). |
| // |
| // Associate buffer with packet while the packet is in-flight. |
| CodecPacket* packet = GetFreePacket(buffer); |
| // With h.264, we know that an emitted buffer implies an available output |
| // packet, because h.264 doesn't put the same output buffer in flight more |
| // than once concurrently, and we have as many output packets as buffers. |
| // This contrasts with VP9 which has unbounded show_existing_frame. |
| ZX_DEBUG_ASSERT(packet); |
| |
| packet->SetStartOffset(0); |
| packet->SetValidLengthBytes(static_cast<uint32_t>(total_size_bytes)); |
| |
| if (frame->has_pts) { |
| packet->SetTimstampIsh(frame->pts); |
| } else { |
| packet->ClearTimestampIsh(); |
| } |
| |
| events_->onCoreCodecOutputPacket(packet, false, false); |
| } |
| |
| void CodecAdapterH264Multi::OnError() { |
| LOG(ERROR, "OnError()"); |
| OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN); |
| } |
| |
| // 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 CodecAdapterH264Multi::CoreCodecStartStream() { |
| { // scope lock |
| std::lock_guard<std::mutex> lock(lock_); |
| is_input_format_details_pending_ = true; |
| // At least until proven otherwise. |
| is_avcc_ = false; |
| is_input_end_of_stream_queued_ = false; |
| is_stream_failed_ = false; |
| } // ~lock |
| |
| // Encapsulate stream buffer allocation in closure so that it can be posted on the resource thread |
| auto resource_init_function = fit::closure([this] { // scope lock |
| TRACE_DURATION("media", "Decoder Initialization"); |
| std::lock_guard<std::mutex> lock(*video_->video_decoder_lock()); |
| // The output port is the one we really care about for is_secure of the decoder, since the HW |
| // can read from secure or non-secure even when in secure mode, but can only write to secure |
| // memory when in secure mode. |
| // |
| // Must create under lock to ensure that a potential other instance that incremented power |
| // ref(s) first is fully done un-gating clocks. |
| auto decoder = std::make_unique<H264MultiDecoder>( |
| video_, this, this, std::move(decoder_internal_buffers_), IsOutputSecure()); |
| if (codec_diagnostics_) { |
| decoder->SetCodecDiagnostics(&codec_diagnostics_.value()); |
| } |
| if (decoder->InitializeBuffers() != ZX_OK) { |
| events_->onCoreCodecFailCodec("InitializeBuffers() failed"); |
| return; |
| } |
| decoder_ = decoder.get(); |
| auto decoder_instance = |
| std::make_unique<DecoderInstance>(std::move(decoder), video_->vdec1_core()); |
| StreamBuffer* buffer = decoder_instance->stream_buffer(); |
| video_->AddNewDecoderInstance(std::move(decoder_instance)); |
| std::optional<InternalBuffer> saved_stream_buffer = std::move(saved_stream_buffer_); |
| saved_stream_buffer_.reset(); |
| if (video_->AllocateStreamBuffer(buffer, kStreamBufferSize, std::move(saved_stream_buffer), |
| /*use_parser=*/true, IsOutputSecure()) != ZX_OK) { |
| // Log here instead of in AllocateStreamBuffer() since video_ doesn't know which codec this is |
| // for. |
| events_->onCoreCodecLogEvent( |
| media_metrics::StreamProcessorEvents2MigratedMetricDimensionEvent::AllocationError); |
| events_->onCoreCodecFailCodec("AllocateStreamBuffer() failed"); |
| return; |
| } |
| // ~lock |
| }); |
| PostAndBlockResourceTask(std::move(resource_init_function)); |
| } |
| |
| void CodecAdapterH264Multi::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 CodecAdapterH264Multi::CoreCodecQueueInputPacket(CodecPacket* packet) { |
| QueueInputItem(CodecInputItem::Packet(packet)); |
| } |
| |
| void CodecAdapterH264Multi::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. |
| |
| { // scope lock |
| std::lock_guard<std::mutex> lock(lock_); |
| is_input_end_of_stream_queued_ = true; |
| } // ~lock |
| |
| QueueInputItem(CodecInputItem::EndOfStream()); |
| } |
| |
| // TODO(dustingreen): See comment on CoreCodecStartStream() re. not deleting |
| // creating as much stuff for each stream. |
| void CodecAdapterH264Multi::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())); |
| } |
| } |
| } |
| |
| // TODO(dustingreen): See comment on CoreCodecStartStream() re. not deleting |
| // creating as much stuff for each stream. |
| std::list<CodecInputItem> CodecAdapterH264Multi::CoreCodecStopStreamInternal() { |
| std::list<CodecInputItem> input_items_result; |
| { // scope lock |
| std::unique_lock<std::mutex> lock(lock_); |
| bool is_cancelling_input_processing = true; |
| std::condition_variable stop_input_processing_condition; |
| async::PostTask(core_loop_.dispatcher(), |
| [this, &stop_input_processing_condition, &input_items_result, |
| &is_cancelling_input_processing] { |
| { // scope lock |
| std::lock_guard<std::mutex> lock(lock_); |
| 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 |
| LOG(DEBUG, "RemoveDecoder()..."); |
| |
| auto resource_destroy_function = fit::closure([this] { |
| TRACE_DURATION("media", "Decoder Destruction"); |
| std::lock_guard<std::mutex> decoder_lock(*video_->video_decoder_lock()); |
| if (decoder_) { |
| video_->RemoveDecoderWithCallbackLocked(decoder_, [this](DecoderInstance* instance) { |
| // Stash the internal buffers so next decoder instance won't need to allocate new ones |
| // unless the buffers are the wrong size or wrong is_secure() etc. This saves time during |
| // seek and can save time during dimension changes when achieved by stream switching (in |
| // contrast to dimension changes achieved within a single stream, which don't remove the |
| // decoder during the dimension change when successful). |
| decoder_internal_buffers_.emplace(decoder_->TakeInternalBuffers()); |
| ZX_DEBUG_ASSERT(!saved_stream_buffer_); |
| saved_stream_buffer_ = std::move(instance->stream_buffer()->optional_buffer()); |
| }); |
| decoder_ = nullptr; |
| } |
| }); |
| PostAndBlockResourceTask(std::move(resource_destroy_function)); |
| |
| LOG(DEBUG, "RemoveDecoder() done."); |
| return input_items_result; |
| } |
| |
| void CodecAdapterH264Multi::CoreCodecAddBuffer(CodecPort port, const CodecBuffer* buffer) { |
| if (port != kOutputPort) { |
| return; |
| } |
| ZX_DEBUG_ASSERT(port == kOutputPort); |
| |
| // This flush is to eliminate any dirty cache lines. Our only choices are flush or flush and |
| // invalidate, so it's maybe slightly cheaper to only flush. We don't care what's being flushed |
| // here, if anything, since the buffer will be overwritten by HW decoding into the buffer anyway. |
| // |
| // There's a flush+invalidate later after the HW is done decoding, which we do for the invalidate |
| // part. For that flush to not overwrite anything the HW wrote to the buffer, this flush |
| // eliminates any dirty cache lines that might otherwise get flushed after HW has written to the |
| // buffer. |
| if (!IsOutputSecure()) { |
| ZX_DEBUG_ASSERT(buffer->size() <= std::numeric_limits<uint32_t>::max()); |
| buffer->CacheFlush(0, static_cast<uint32_t>(buffer->size())); |
| } |
| |
| all_output_buffers_.push_back(buffer); |
| } |
| |
| void CodecAdapterH264Multi::CoreCodecConfigureBuffers( |
| CodecPort port, const std::vector<std::unique_ptr<CodecPacket>>& packets) { |
| if (port != kOutputPort) { |
| return; |
| } |
| ZX_DEBUG_ASSERT(port == kOutputPort); |
| // output |
| |
| 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 CodecAdapterH264Multi::CoreCodecRecycleOutputPacket(CodecPacket* packet) { |
| if (packet->is_new()) { |
| packet->SetIsNew(false); |
| return; |
| } |
| ZX_DEBUG_ASSERT(!packet->is_new()); |
| |
| // A recycled packet will have a buffer set because the packet is in-flight |
| // until put on the free list, and has a buffer associated while in-flight. |
| const CodecBuffer* buffer = packet->buffer(); |
| ZX_DEBUG_ASSERT(buffer); |
| |
| // Eliminate any dirty CPU cache lines, so that later when we do a flush+invalidate after HW is |
| // done writing to the buffer, we won't be writing over anything the HW wrote. |
| if (!IsOutputSecure()) { |
| ZX_DEBUG_ASSERT(buffer->size() <= std::numeric_limits<uint32_t>::max()); |
| buffer->CacheFlush(0, static_cast<uint32_t>(buffer->size())); |
| } |
| |
| { // scope lock |
| std::lock_guard<std::mutex> lock(lock_); |
| // Getting the buffer is all we needed the packet for. The packet won't get re-used until it |
| // goes back on the free list below. |
| // |
| // This must be done under lock_ to synchronize with InitializeFrames() with |
| // IsCurrentOutputBufferCollectionUsable() true. In particular we need to this synchronized to |
| // be able to tell InitializedFrames() exactly which CodecFrame(s) are initially free vs. |
| // initially used, based on which CodecFrame(s) correspond to a packet that is not free and has |
| // a specific buffer which corresponds to a not-free CodecFrame. |
| packet->SetBuffer(nullptr); |
| free_output_packets_.push_back(packet->packet_index()); |
| } // ~lock |
| |
| async::PostTask(core_loop_.dispatcher(), [this, video_frame = buffer->video_frame()]() { |
| std::lock_guard<std::mutex> lock(*video_->video_decoder_lock()); |
| std::shared_ptr<VideoFrame> frame = 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; |
| } |
| if (!decoder_) |
| return; |
| // Potentially this also pumps the decoder under video_decoder_lock. |
| decoder_->ReturnFrame(std::move(frame)); |
| }); |
| } |
| |
| void CodecAdapterH264Multi::CoreCodecEnsureBuffersNotConfigured(CodecPort port) { |
| DLOG("port: %u", 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> |
| CodecAdapterH264Multi::CoreCodecBuildNewOutputConstraints( |
| uint64_t stream_lifetime_ordinal, uint64_t new_output_buffer_constraints_version_ordinal, |
| bool buffer_constraints_action_required) { |
| // This decoder produces NV12. |
| |
| auto config = std::make_unique<fuchsia::media::StreamOutputConstraints>(); |
| |
| config->set_stream_lifetime_ordinal(stream_lifetime_ordinal); |
| |
| auto* constraints = config->mutable_buffer_constraints(); |
| |
| // For the moment, we always require buffer reallocation for any output constraints change. |
| 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); |
| |
| // Ensure that if the client allocates its max + the server max that it won't go over the hardware |
| // limit (max_buffer_count). |
| if (max_buffer_count_[kOutputPort] <= min_buffer_count_[kOutputPort]) { |
| events_->onCoreCodecFailCodec("Impossible for client to satisfy buffer counts"); |
| return nullptr; |
| } |
| |
| return config; |
| } |
| |
| fuchsia_sysmem2::BufferCollectionConstraints |
| CodecAdapterH264Multi::CoreCodecGetBufferCollectionConstraints2( |
| CodecPort port, const fuchsia::media::StreamBufferConstraints& stream_buffer_constraints, |
| const fuchsia::media::StreamBufferPartialSettings& partial_settings) { |
| fuchsia_sysmem2::BufferCollectionConstraints result; |
| |
| // The CodecImpl won't hand us the sysmem token, so we shouldn't expect to |
| // have the token here. |
| ZX_DEBUG_ASSERT(!partial_settings.has_sysmem_token()); |
| |
| if (port == kInputPort) { |
| // We don't override CoreCodecBuildNewInputConstraints() for now, so pick these up from what was |
| // set by default implementation of CoreCodecBuildNewInputConstraints(). |
| min_buffer_count_[kInputPort] = kInputBufferCountForCodecMin; |
| max_buffer_count_[kInputPort] = kInputBufferCountForCodecMax; |
| } |
| |
| ZX_DEBUG_ASSERT(min_buffer_count_[port] != 0); |
| ZX_DEBUG_ASSERT(max_buffer_count_[port] != 0); |
| |
| result.min_buffer_count_for_camping() = min_buffer_count_[port]; |
| |
| // Some slack is nice overall, but avoid having each participant ask for |
| // dedicated slack. Using sysmem the client will ask for it's own buffers for |
| // camping and any slack, so the codec doesn't need to ask for any extra on |
| // behalf of the client. |
| ZX_DEBUG_ASSERT(!result.min_buffer_count_for_dedicated_slack().has_value()); |
| ZX_DEBUG_ASSERT(!result.min_buffer_count_for_shared_slack().has_value()); |
| result.max_buffer_count() = max_buffer_count_[port]; |
| |
| uint32_t per_packet_buffer_bytes_min; |
| uint32_t per_packet_buffer_bytes_max; |
| if (port == kInputPort) { |
| per_packet_buffer_bytes_min = kInputPerPacketBufferBytesMin; |
| per_packet_buffer_bytes_max = kInputPerPacketBufferBytesMax; |
| } else { |
| ZX_DEBUG_ASSERT(port == kOutputPort); |
| // NV12, based on min stride. |
| per_packet_buffer_bytes_min = min_stride_ * height_ * 3 / 2; |
| // At least for now, don't cap the per-packet buffer size for output. The |
| // HW only cares about the portion we set up for output anyway, and the |
| // client has no way to force output to occur into portions of the output |
| // buffer beyond what's implied by the max supported image dimensions. |
| per_packet_buffer_bytes_max = 0xFFFFFFFF; |
| } |
| |
| auto& bmc = result.buffer_memory_constraints().emplace(); |
| bmc.min_size_bytes() = per_packet_buffer_bytes_min; |
| bmc.max_size_bytes() = per_packet_buffer_bytes_max; |
| // amlogic requires physically contiguous on both input and output |
| bmc.physically_contiguous_required() = true; |
| bmc.secure_required() = IsPortSecureRequired(port); |
| bmc.cpu_domain_supported() = !IsPortSecureRequired(port); |
| bmc.ram_domain_supported() = !IsPortSecureRequired(port) && (port == kOutputPort); |
| |
| bmc.permitted_heaps().emplace(); |
| |
| if (IsPortSecurePermitted(port)) { |
| bmc.inaccessible_domain_supported() = true; |
| std::string secure_heap = (port == kInputPort) |
| ? bind_fuchsia_amlogic_platform_sysmem_heap::HEAP_TYPE_SECURE_VDEC |
| : bind_fuchsia_amlogic_platform_sysmem_heap::HEAP_TYPE_SECURE; |
| |
| fuchsia_sysmem2::Heap heap; |
| heap.heap_type() = std::move(secure_heap); |
| bmc.permitted_heaps()->emplace_back(std::move(heap)); |
| } |
| |
| if (!IsPortSecureRequired(port)) { |
| fuchsia_sysmem2::Heap heap; |
| heap.heap_type() = bind_fuchsia_sysmem_heap::HEAP_TYPE_SYSTEM_RAM; |
| bmc.permitted_heaps()->emplace_back(std::move(heap)); |
| } |
| |
| if (port == kOutputPort) { |
| auto& image_constraints = result.image_format_constraints().emplace().emplace_back(); |
| image_constraints.pixel_format() = fuchsia_images2::PixelFormat::kNv12; |
| image_constraints.pixel_format_modifier() = fuchsia_images2::PixelFormatModifier::kLinear; |
| // TODO(https://fxbug.dev/42084950): confirm that REC709 is always what we want here, or plumb |
| // actual YUV color space if it can ever be REC601_*. Since 2020 and 2100 |
| // are minimum 10 bits per Y sample and we're outputting NV12, 601 is the |
| // only other potential possibility here. |
| image_constraints.color_spaces() = {fuchsia_images2::ColorSpace::kRec709}; |
| |
| // The non-"required_" fields indicate the decoder's ability to potentially |
| // output frames at various dimensions as coded in the stream. Aside from |
| // the current stream being somewhere in these bounds, these have nothing to |
| // do with the current stream in particular. |
| image_constraints.min_size() = {16, 16}; |
| // This intentionally isn't the _height_ of a 4096x2176 frame, it's |
| // intentionally the _width_ of a 4096x2176 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 4096x2176 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 4096x2176's width in either dimension, so we don't. |
| image_constraints.max_size() = {4096, 4096}; |
| image_constraints.min_bytes_per_row() = 16; |
| // no hard-coded max stride, at least for now |
| image_constraints.max_bytes_per_row() = 0xFFFFFFFF; |
| image_constraints.max_width_times_height() = 4096 * 2176; |
| image_constraints.size_alignment() = {16, 16}; |
| image_constraints.bytes_per_row_divisor() = H264MultiDecoder::kStrideAlignment; |
| // Even though we only ever output at offset 0, sysmem defaults start_offset_divisor to the |
| // image format alignment which is 2 for NV12. Since we are a producer, we should fully specify |
| // here so late attach clients don't have to specify it explicitly. |
| image_constraints.start_offset_divisor() = 2; |
| // Odd display dimensions are permitted, but these don't imply odd NV12 |
| // dimensions - those are constrainted by coded_width_divisor and |
| // coded_height_divisor which are both 16. |
| image_constraints.display_rect_alignment() = {1, 1}; |
| |
| // The decoder is producing frames and the decoder has no choice but to |
| // produce frames at their coded size. The decoder wants to potentially be |
| // able to support a stream with dynamic resolution, potentially including |
| // dimensions both less than and greater than the dimensions that led to the |
| // current need to allocate a BufferCollection. For this reason, the |
| // required_ fields are set to the exact current dimensions, and the |
| // permitted (non-required_) fields is set to the full potential range that |
| // the decoder could potentially output. If an initiator wants to require a |
| // larger range of dimensions that includes the required range indicated |
| // here (via a-priori knowledge of the potential stream dimensions), an |
| // initiator is free to do so. |
| image_constraints.required_min_size() = {width_, height_}; |
| image_constraints.required_max_size() = {width_, height_}; |
| } else { |
| ZX_DEBUG_ASSERT(!result.image_format_constraints().has_value()); |
| } |
| |
| // We don't have to fill out usage - CodecImpl takes care of that. |
| ZX_DEBUG_ASSERT(!result.usage().has_value()); |
| |
| return result; |
| } |
| |
| void CodecAdapterH264Multi::CoreCodecSetBufferCollectionInfo( |
| CodecPort port, const fuchsia_sysmem2::BufferCollectionInfo& buffer_collection_info) { |
| ZX_DEBUG_ASSERT( |
| *buffer_collection_info.settings()->buffer_settings()->is_physically_contiguous()); |
| if (port == kOutputPort) { |
| ZX_DEBUG_ASSERT(buffer_collection_info.settings()->image_format_constraints().has_value()); |
| ZX_DEBUG_ASSERT( |
| *buffer_collection_info.settings()->image_format_constraints()->pixel_format() == |
| fuchsia_images2::PixelFormat::kNv12); |
| auto info_clone_result = |
| sysmem::V2CloneBufferCollectionInfo(buffer_collection_info, ZX_RIGHT_SAME_RIGHTS); |
| // failure to duplicate handles is treated similarly to OOM; fatal to this process |
| ZX_ASSERT(info_clone_result.is_ok()); |
| output_buffer_collection_info_ = info_clone_result.take_value(); |
| } |
| // clone / copy |
| buffer_settings_[port] = *buffer_collection_info.settings(); |
| ZX_DEBUG_ASSERT(IsPortSecure(port) || !IsPortSecureRequired(port)); |
| ZX_DEBUG_ASSERT(!IsPortSecure(port) || IsPortSecurePermitted(port)); |
| // TODO(dustingreen): Remove after secure video decode works e2e. |
| LOG(DEBUG, |
| "CodecAdapterH264Multi::CoreCodecSetBufferCollectionInfo() - IsPortSecure(): %u port: %u", |
| IsPortSecure(port), port); |
| } |
| |
| fuchsia::media::StreamOutputFormat CodecAdapterH264Multi::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 = width_; |
| video_uncompressed.primary_height_pixels = height_; |
| video_uncompressed.secondary_width_pixels = width_ / 2; |
| video_uncompressed.secondary_height_pixels = 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_ * height_; |
| video_uncompressed.tertiary_start_offset = output_stride_ * height_ + 1; |
| video_uncompressed.primary_pixel_stride = 1; |
| video_uncompressed.secondary_pixel_stride = 2; |
| video_uncompressed.primary_display_width_pixels = display_width_; |
| video_uncompressed.primary_display_height_pixels = display_height_; |
| video_uncompressed.has_pixel_aspect_ratio = has_sar_; |
| video_uncompressed.pixel_aspect_ratio_width = sar_width_; |
| video_uncompressed.pixel_aspect_ratio_height = sar_height_; |
| |
| // TODO(dustingreen): Deprecate and remove fields set above. Use only these fields (or newer |
| // variant of these fields; TBD): |
| video_uncompressed.image_format.pixel_format.type = fuchsia::sysmem::PixelFormatType::NV12; |
| video_uncompressed.image_format.coded_width = width_; |
| video_uncompressed.image_format.coded_height = height_; |
| DLOG("h264 coded_width: %u coded_height: %u", width_, height_); |
| video_uncompressed.image_format.bytes_per_row = output_stride_; |
| video_uncompressed.image_format.display_width = display_width_; |
| video_uncompressed.image_format.display_height = 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 = has_sar_; |
| video_uncompressed.image_format.pixel_aspect_ratio_width = sar_width_; |
| video_uncompressed.image_format.pixel_aspect_ratio_height = sar_height_; |
| |
| 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 CodecAdapterH264Multi::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 CodecAdapterH264Multi::CoreCodecMidStreamOutputBufferReConfigFinish() { |
| MidStreamOutputBufferConfigInternal(true); |
| } |
| |
| void CodecAdapterH264Multi::MidStreamOutputBufferConfigInternal(bool did_reallocate_buffers) { |
| // Now that the client has configured output buffers, we need to hand those |
| // back to the core codec via InitializedFrames. |
| |
| std::vector<CodecFrame> frames; |
| uint32_t width; |
| uint32_t height; |
| uint32_t stride; |
| { // scope lock |
| std::lock_guard<std::mutex> lock(lock_); |
| // Now we need to populate the frames_out vector. |
| for (uint32_t i = 0; i < all_output_buffers_.size(); i++) { |
| ZX_DEBUG_ASSERT(all_output_buffers_[i]->index() == i); |
| frames.emplace_back(*all_output_buffers_[i]); |
| frames.back().initial_usage_count() = 0; |
| } |
| for (auto* codec_packet : all_output_packets_) { |
| // The buffer() being non-null corresponds to the packet index not being in |
| // free_output_packets_. In other words, the non-null buffer() fields among all packets is |
| // all the used buffers. The buffer indexes and frames indexes are the same due to how frames |
| // is populated above. |
| if (codec_packet->buffer()) { |
| // This won't happen if we're doing a CoreCodecMidStreamOutputBufferReConfigFinish(). In |
| // that case we cleared all the packets and buffers and allocated new ones, so there won't |
| // be any packet with an assigned buffer, since there aren't any packets from before. |
| // |
| // On the other hand if we're telling an H264MultiDecoder about buffers that aren't new and |
| // may still be in flight on output, we some are not initially free ("initially" as in when |
| // InitializedFrames() is called). |
| // |
| // When !did_reallocate_buffers, we know that CoreCodecRecycleOutputPacket() won't be |
| // running for the entire MidStreamOutputBufferConfigInternal(). It's really that fact |
| // rather than the present lock_ interval that makes this initial_usage_count() stuff |
| // synchronize properly with CoreCodecRecycleOutputPacket(). |
| ZX_DEBUG_ASSERT(!did_reallocate_buffers); |
| // h.264 doesn't have anything like vp9's show_existing_frame, so a given buffer is only |
| // downstream up to once at a time, so we know we won't see any CodecFrame/CodecBuffer |
| // that's currently referenced by more than one packet. |
| ZX_DEBUG_ASSERT(frames[codec_packet->buffer()->index()].initial_usage_count() == 0); |
| frames[codec_packet->buffer()->index()].initial_usage_count() = 1; |
| } |
| } |
| width = width_; |
| height = height_; |
| stride = fbl::round_up(width, *output_buffer_collection_info_->settings() |
| ->image_format_constraints() |
| ->bytes_per_row_divisor()); |
| } // ~lock |
| |
| auto resource_init_function = |
| fit::closure([this, width, height, stride, frames = std::move(frames)]() mutable { |
| TRACE_DURATION("media", "Decoder Frame Initialization"); |
| std::lock_guard<std::mutex> lock(*video_->video_decoder_lock()); |
| decoder_->InitializedFrames(std::move(frames), width, height, stride); |
| }); |
| // When we're really doing a mid-stream change, or when two consecutive streams are effectively |
| // part of an overall logical stream from the user's point of view (such as an upper-layer |
| // mid-video dimension change that ends up switching streams at this layer), posting over to a |
| // "resource" thread with different scheduler profile isn't really all that rigorous, since the |
| // resource setup aspects are inherently part of what needs to get done to achieve consistent |
| // output timing of decoded frames. But by doing this we can avoid needing to boost the scheduler |
| // profile budget for the current thread, at least for now (which again, isn't particularly |
| // rigorous, but it's why this is posting and immediately waiting). It's also relevant that the |
| // scheduler presently has an "anti-abuse" behavior where a thread gets de-scheduled each time it |
| // enables a deadline profile, so that's why we post-and-wait here instead of having the current |
| // thread switch its own scheduler deadline profile off/on. This is entirely about optimizing |
| // stream startup duration in the common case (which of course matters), not about rigor for |
| // scheduling aspects of mid-stream dimension change (which is fine for now, but may be improved). |
| PostAndBlockResourceTask(std::move(resource_init_function)); |
| |
| async::PostTask(core_loop_.dispatcher(), [this] { |
| std::lock_guard<std::mutex> lock(*video_->video_decoder_lock()); |
| if (!decoder_) { |
| return; |
| } |
| // Something else may have come along since InitializedFrames and pumped the decoder, but that's |
| // ok. |
| decoder_->PumpOrReschedule(); |
| }); |
| } |
| |
| void CodecAdapterH264Multi::PostAndBlockResourceTask(fit::closure task_function) { |
| sync_completion_t resource_finished; |
| auto task = |
| fit::closure([&resource_finished, task_function = std::move(task_function)]() mutable { |
| task_function(); |
| sync_completion_signal(&resource_finished); |
| }); |
| |
| zx_status_t task_result = async::PostTask(resource_loop_.dispatcher(), std::move(task)); |
| if (task_result != ZX_OK) { |
| LOG(ERROR, "Could not post task to resource thread"); |
| } |
| |
| sync_completion_wait(&resource_finished, ZX_TIME_INFINITE); |
| } |
| |
| void CodecAdapterH264Multi::QueueInputItem(CodecInputItem input_item, bool at_front) { |
| { // 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 (at_front) { |
| input_queue_.emplace_front(std::move(input_item)); |
| } else { |
| input_queue_.emplace_back(std::move(input_item)); |
| } |
| if (!have_queued_trigger_decoder_) { |
| have_queued_trigger_decoder_ = true; |
| async::PostTask(core_loop_.dispatcher(), [this]() { |
| { |
| std::lock_guard<std::mutex> lock(lock_); |
| have_queued_trigger_decoder_ = false; |
| } |
| std::lock_guard<std::mutex> lock(*video_->video_decoder_lock()); |
| if (!decoder_) |
| return; |
| decoder_->ReceivedNewInput(); |
| }); |
| } |
| } // ~lock |
| } |
| |
| CodecInputItem CodecAdapterH264Multi::DequeueInputItem() { |
| { // scope lock |
| std::lock_guard<std::mutex> lock(lock_); |
| if (is_stream_failed_ || input_queue_.empty()) { |
| return CodecInputItem::Invalid(); |
| } |
| CodecInputItem to_ret = std::move(input_queue_.front()); |
| input_queue_.pop_front(); |
| return to_ret; |
| } // ~lock |
| } |
| |
| std::optional<H264MultiDecoder::DataInput> CodecAdapterH264Multi::ReadMoreInputData() { |
| H264MultiDecoder::DataInput result; |
| while (true) { |
| CodecInputItem item = DequeueInputItem(); |
| if (!item.is_valid()) { |
| return std::nullopt; |
| } |
| |
| 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_)); |
| |
| latest_input_format_details_ = fidl::Clone(item.format_details()); |
| |
| is_input_format_details_pending_ = true; |
| continue; |
| } |
| |
| if (item.is_end_of_stream()) { |
| result.is_eos = true; |
| is_input_end_of_stream_queued_to_core_ = true; |
| return result; |
| } |
| |
| ZX_DEBUG_ASSERT(item.is_packet()); |
| |
| if (is_input_format_details_pending_) { |
| is_input_format_details_pending_ = false; |
| std::vector<uint8_t> oob_bytes = ParseCodecOobBytes(); |
| if (!oob_bytes.empty()) { |
| result.length = oob_bytes.size(); |
| result.data = std::move(oob_bytes); |
| // Put packet back for next call to ReadMoreInputData(). |
| QueueInputItem(std::move(item), true); |
| return result; |
| } |
| } |
| |
| fit::deferred_callback return_input_packet = fit::defer_callback(fit::closure( |
| [this, packet = item.packet()] { events_->onCoreCodecInputPacketDone(packet); })); |
| |
| uint8_t* data = item.packet()->buffer()->base() + item.packet()->start_offset(); |
| uint32_t len = item.packet()->valid_length_bytes(); |
| auto parsed_input_data = ParseVideo(item.packet()->buffer(), &return_input_packet, data, len); |
| if (!parsed_input_data) { |
| continue; |
| } |
| result = std::move(parsed_input_data.value()); |
| if (result.codec_buffer && !IsPortSecure(kInputPort)) { |
| // In case input is still dirty in CPU cache. |
| ZX_DEBUG_ASSERT(result.length <= std::numeric_limits<uint32_t>::max()); |
| result.codec_buffer->CacheFlush(result.buffer_start_offset, |
| static_cast<uint32_t>(result.length)); |
| } |
| if (item.packet()->has_timestamp_ish()) { |
| result.pts = item.packet()->timestamp_ish(); |
| } |
| |
| return result; |
| // ~item |
| } |
| } |
| |
| bool CodecAdapterH264Multi::HasMoreInputData() { |
| std::lock_guard<std::mutex> lock(lock_); |
| return !input_queue_.empty(); |
| } |
| |
| std::vector<uint8_t> CodecAdapterH264Multi::ParseCodecOobBytes() { |
| // Our latest oob_bytes may contain SPS/PPS info. If we have any |
| // such info, the core codec needs it (possibly converted first). |
| |
| // If there's no OOB info, then there's nothing to do, as all such info will |
| // be in-band in normal packet-based AnnexB NALs (including start codes and |
| // start code emulation prevention bytes). |
| if (!latest_input_format_details_.has_oob_bytes() || |
| latest_input_format_details_.oob_bytes().empty()) { |
| // success |
| return {}; |
| } |
| |
| const std::vector<uint8_t>* oob = &latest_input_format_details_.oob_bytes(); |
| |
| // We need to deliver Annex B style SPS/PPS to this core codec, regardless of |
| // what format the oob_bytes is in. |
| |
| // The oob_bytes can be in two different forms, which can be detected by |
| // the value of the first byte: |
| // |
| // 0 - Annex B form already. The 0 is the first byte of a start code. |
| // 1 - AVCC form, which we'll convert to Annex B form. AVCC version 1. There |
| // is no AVCC version 0. |
| // anything else - fail. |
| // |
| // In addition, we need to know if AVCC or not since we need to know whether |
| // to add start code emulation prevention bytes or not. And if it's AVCC, |
| // how many bytes long the pseudo_nal_length field is - that field is before |
| // each input NAL. |
| |
| // We already checked empty() above. |
| ZX_DEBUG_ASSERT(oob->size() >= 1); |
| switch ((*oob)[0]) { |
| case 0: |
| is_avcc_ = false; |
| return *oob; |
| case 1: { |
| // This applies to both the oob data and the input packet payload data. |
| // Both are AVCC, or both are AnnexB. |
| is_avcc_ = true; |
| |
| /* |
| AVCC OOB data layout (bits): |
| [0] (8) - version 1 |
| [1] (8) - h264 profile # |
| [2] (8) - compatible profile bits |
| [3] (8) - h264 level (eg. 31 == "3.1") |
| [4] (6) - reserved, can be set to all 1s |
| (2) - pseudo_nal_length_field_bytes_ - 1 |
| [5] (3) - reserved, can be set to all 1s |
| (5) - sps_count |
| (16) - sps_bytes |
| (8*sps_bytes) - SPS nal_unit_type (that byte) + SPS data as RBSP. |
| (8) - pps_count |
| (16) - pps_bytes |
| (8*pps_bytes) - PPS nal_unit_type (that byte) + PPS data as RBSP. |
| */ |
| |
| // We accept 0 SPS and/or 0 PPS, but typically there's one of each. At |
| // minimum the oob buffer needs to be large enough to contain both the |
| // sps_count and pps_count fields, which is a min of 7 bytes. |
| if (oob->size() < 7) { |
| LOG(ERROR, "oob->size() < 7"); |
| OnCoreCodecFailStream(fuchsia::media::StreamError::INVALID_INPUT_FORMAT_DETAILS); |
| return {}; |
| } |
| // All pseudo-NALs in input packet payloads will use the |
| // parsed count of bytes of the length field. Convert SPS/PPS inline to AnnexB format so we |
| // can return it directly, as ParseVideo won't be called on this data. |
| pseudo_nal_length_field_bytes_ = ((*oob)[4] & 0x3) + 1; |
| uint32_t sps_count = (*oob)[5] & 0x1F; |
| uint32_t offset = 6; |
| std::vector<uint8_t> accumulation; |
| for (uint32_t i = 0; i < sps_count; ++i) { |
| if (offset + 2 > oob->size()) { |
| LOG(ERROR, "offset + 2 > oob->size()"); |
| OnCoreCodecFailStream(fuchsia::media::StreamError::INVALID_INPUT_FORMAT_DETAILS); |
| return {}; |
| } |
| uint32_t sps_length = (*oob)[offset] * 256 + (*oob)[offset + 1]; |
| if (offset + 2 + sps_length > oob->size()) { |
| LOG(ERROR, "offset + 2 + sps_length > oob->size()"); |
| OnCoreCodecFailStream(fuchsia::media::StreamError::INVALID_INPUT_FORMAT_DETAILS); |
| return {}; |
| } |
| offset += 2; // sps_bytes |
| accumulation.push_back(0); |
| accumulation.push_back(0); |
| accumulation.push_back(0); |
| accumulation.push_back(1); |
| for (uint32_t i = 0; i < sps_length; i++) { |
| accumulation.push_back(oob->data()[offset + i]); |
| } |
| offset += sps_length; |
| } |
| if (offset + 1 > oob->size()) { |
| LOG(ERROR, "offset + 1 > oob->size()"); |
| OnCoreCodecFailStream(fuchsia::media::StreamError::INVALID_INPUT_FORMAT_DETAILS); |
| return {}; |
| } |
| uint32_t pps_count = (*oob)[offset++]; |
| for (uint32_t i = 0; i < pps_count; ++i) { |
| if (offset + 2 > oob->size()) { |
| LOG(ERROR, "offset + 2 > oob->size()"); |
| OnCoreCodecFailStream(fuchsia::media::StreamError::INVALID_INPUT_FORMAT_DETAILS); |
| return {}; |
| } |
| uint32_t pps_length = (*oob)[offset] * 256 + (*oob)[offset + 1]; |
| if (offset + 2 + pps_length > oob->size()) { |
| LOG(ERROR, "offset + 2 + pps_length > oob->size()"); |
| OnCoreCodecFailStream(fuchsia::media::StreamError::INVALID_INPUT_FORMAT_DETAILS); |
| return {}; |
| } |
| offset += 2; // pps_bytes |
| accumulation.push_back(0); |
| accumulation.push_back(0); |
| accumulation.push_back(0); |
| accumulation.push_back(1); |
| for (uint32_t i = 0; i < pps_length; i++) { |
| accumulation.push_back(oob->data()[offset + i]); |
| } |
| offset += pps_length; |
| } |
| return accumulation; |
| } |
| default: |
| LOG(ERROR, "unexpected first oob byte"); |
| OnCoreCodecFailStream(fuchsia::media::StreamError::INVALID_INPUT_FORMAT_DETAILS); |
| return {}; |
| } |
| } |
| |
| std::optional<H264MultiDecoder::DataInput> CodecAdapterH264Multi::ParseVideo( |
| const CodecBuffer* buffer, fit::deferred_callback* return_input_packet, const uint8_t* data, |
| uint32_t length) { |
| if (is_avcc_) { |
| return ParseVideoAvcc(data, length); |
| // ~return_input_packet |
| } else { |
| return ParseVideoAnnexB(buffer, return_input_packet, data, length); |
| } |
| } |
| |
| std::optional<H264MultiDecoder::DataInput> CodecAdapterH264Multi::ParseVideoAvcc( |
| const uint8_t* data, uint32_t length) { |
| // We don't necessarily know that is_avcc_ is true on entry to this method. |
| // We use this method to send the decoder a bunch of 0x00 sometimes, which |
| // will call this method regardless of is_avcc_ or not. |
| |
| // So far, the "avcC"/"AVCC" we've seen has emulation prevention bytes on it |
| // already. So we don't add those here. But if we did need to add them, we'd |
| // add them here. |
| |
| // For now we assume the heap is pretty fast and doesn't mind the size thrash, |
| // but maybe we'll want to keep a buffer around (we'll optimize only if/when |
| // we determine this is actually a problem). We only actually use this buffer |
| // if is_avcc_ (which is not uncommon). |
| |
| // We do parse more than one pseudo_nal per input packet. |
| // |
| // No splitting NALs across input packets, for now. |
| // |
| // TODO(dustingreen): Allow splitting NALs across input packets (not a small |
| // change). Probably also move into a source_set for sharing with other |
| // CodecAdapter(s). |
| |
| // Count the input pseudo_nal(s) |
| uint32_t pseudo_nal_count = 0; |
| uint32_t i = 0; |
| while (i < length) { |
| if (i + pseudo_nal_length_field_bytes_ > length) { |
| LOG(ERROR, "i + pseudo_nal_length_field_bytes_ > length"); |
| OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN); |
| return {}; |
| } |
| // Read pseudo_nal_length field, which is a field which can be 1-4 bytes |
| // long because AVCC/avcC. |
| uint32_t pseudo_nal_length = 0; |
| for (uint32_t length_byte = 0; length_byte < pseudo_nal_length_field_bytes_; ++length_byte) { |
| pseudo_nal_length = pseudo_nal_length * 256 + data[i + length_byte]; |
| } |
| i += pseudo_nal_length_field_bytes_; |
| if (i + pseudo_nal_length > length) { |
| LOG(ERROR, "i + pseudo_nal_length > length"); |
| OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN); |
| return {}; |
| } |
| i += pseudo_nal_length; |
| ++pseudo_nal_count; |
| } |
| |
| static constexpr uint32_t kStartCodeBytes = 4; |
| uint32_t local_length = length - pseudo_nal_count * pseudo_nal_length_field_bytes_ + |
| pseudo_nal_count * kStartCodeBytes; |
| auto local_buffer = std::make_unique<uint8_t[]>(local_length); |
| uint8_t* local_data = local_buffer.get(); |
| |
| i = 0; |
| uint32_t o = 0; |
| while (i < length) { |
| if (i + pseudo_nal_length_field_bytes_ > length) { |
| LOG(ERROR, "i + pseudo_nal_length_field_bytes_ > length"); |
| OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN); |
| return {}; |
| } |
| uint32_t pseudo_nal_length = 0; |
| for (uint32_t length_byte = 0; length_byte < pseudo_nal_length_field_bytes_; ++length_byte) { |
| pseudo_nal_length = pseudo_nal_length * 256 + data[i + length_byte]; |
| } |
| i += pseudo_nal_length_field_bytes_; |
| if (i + pseudo_nal_length > length) { |
| LOG(ERROR, "i + pseudo_nal_length > length"); |
| OnCoreCodecFailStream(fuchsia::media::StreamError::DECODER_UNKNOWN); |
| return {}; |
| } |
| |
| local_data[o++] = 0; |
| local_data[o++] = 0; |
| local_data[o++] = 0; |
| local_data[o++] = 1; |
| |
| memcpy(&local_data[o], &data[i], pseudo_nal_length); |
| o += pseudo_nal_length; |
| i += pseudo_nal_length; |
| } |
| ZX_DEBUG_ASSERT(o == local_length); |
| ZX_DEBUG_ASSERT(i == length); |
| |
| return ParseVideoAnnexB(nullptr, nullptr, local_data, local_length); |
| } |
| |
| std::optional<H264MultiDecoder::DataInput> CodecAdapterH264Multi::ParseVideoAnnexB( |
| const CodecBuffer* buffer, fit::deferred_callback* return_input_packet, const uint8_t* data, |
| uint32_t length) { |
| ZX_DEBUG_ASSERT(data); |
| ZX_DEBUG_ASSERT(!!buffer == !!return_input_packet); |
| H264MultiDecoder::DataInput result; |
| result.length = length; |
| if (!buffer) { |
| result.data = std::vector<uint8_t>(data, data + length); |
| } else { |
| ZX_DEBUG_ASSERT(buffer); |
| // Caller is required to ensure that data is within [base()..base()+size()). |
| ZX_DEBUG_ASSERT(data >= buffer->base()); |
| ZX_DEBUG_ASSERT(data < buffer->base() + buffer->size()); |
| ZX_DEBUG_ASSERT(data - buffer->base() <= std::numeric_limits<uint32_t>::max()); |
| ZX_DEBUG_ASSERT(return_input_packet); |
| result.codec_buffer = buffer; |
| result.buffer_start_offset = static_cast<uint32_t>(data - buffer->base()); |
| result.return_input_packet = std::move(*return_input_packet); |
| } |
| return std::move(result); |
| } |
| |
| void CodecAdapterH264Multi::OnEos() { events_->onCoreCodecOutputEndOfStream(false); } |
| |
| zx_status_t CodecAdapterH264Multi::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) { |
| // This is called on a core codec thread, ordered with respect to emitted |
| // output frames. |
| // |
| // Frame initialization is async. |
| // |
| // If existing buffers are suitable, we can init / re-init frames without |
| // re-allocating buffers, so we don't need the client's help (or sysmem's), |
| // and it's faster. |
| // |
| // If existing buffers are not suitable, this completes when either the client |
| // has configured output buffers and we've done core codec |
| // InitializedFrames(), or until the cilent has moved on by closing the |
| // current stream. |
| // |
| // The video_decoder_lock_ is held during this method. |
| // |
| // First stash some format and buffer count info needed to initialize frames |
| // before triggering re-init of frames / 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_); |
| |
| min_buffer_count_[kOutputPort] = min_frame_count; |
| max_buffer_count_[kOutputPort] = max_frame_count; |
| width_ = coded_width; |
| height_ = coded_height; |
| min_stride_ = stride; |
| display_width_ = display_width; |
| display_height_ = display_height; |
| has_sar_ = has_sar; |
| sar_width_ = sar_width; |
| sar_height_ = sar_height; |
| } // ~lock |
| |
| // After a stream switch, the new H264MultiDecoder won't have any frames, and needs |
| // InitializedFrames() to get called to set up the frames, and won't have checked |
| // IsCurrentOutputBufferCollectionUsable() itself since it knows it needs frames configured using |
| // InitializedFrames(). However, we can still check here whether the current buffer collection, |
| // that was (before the stream switch) used with a previous H264MultiDecoder instance, can still |
| // be used with the new H264MultiDecoder instance. This does require that we be able to indicate |
| // via InitializedFrames() which frames are presently usable vs. which are still downstream and |
| // not yet returned. |
| if (IsCurrentOutputBufferCollectionUsable(min_frame_count, max_frame_count, coded_width, |
| coded_height, stride, display_width, display_height)) { |
| DLOG("IsCurrentOutputBufferCollectionUsable() true"); |
| // The core codec won't output any more packets until we call InitializedFrames(), but when the |
| // core codec does output more packets, we need to send updated format info first. |
| // |
| // TODO(dustingreen): This may be unnecessary / redundant. |
| events_->onCoreCodecOutputFormatChange(); |
| shared_fidl_thread_closure_queue_->Enqueue([this] { |
| // We have to run this on the shared fidl thread since that's what CodecImpl is using to |
| // process RecycleOutputPacket(); we need to avoid this running concurrently with |
| // CoreCodecRecycleOutputPacket(). |
| MidStreamOutputBufferConfigInternal(false); |
| }); |
| return ZX_OK; |
| } |
| |
| // If we don't have a current output BufferCollection or can't re-use it due to unsuitable |
| // constraints, we need to trigger a mid-stream output constraints change to trigger a new |
| // BufferCollection to be allocated that's consistent with the new constraints. |
| // |
| // 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; |
| } |
| |
| bool CodecAdapterH264Multi::IsCurrentOutputBufferCollectionUsable( |
| uint32_t min_frame_count, uint32_t max_frame_count, uint32_t coded_width, uint32_t coded_height, |
| uint32_t stride, uint32_t display_width, uint32_t display_height) { |
| DLOG( |
| "min_frame_count: %u max_frame_count: %u coded_width: %u coded_height: %u stride: %u " |
| "display_width: %u display_height: %u", |
| min_frame_count, max_frame_count, coded_width, coded_height, stride, display_width, |
| display_height); |
| ZX_DEBUG_ASSERT(stride >= coded_width); |
| // We don't ask codec_impl about this, because as far as codec_impl is |
| // concerned, the output buffer collection might not be used for video |
| // frames. We could have common code for video decoders but for now we just |
| // check here. |
| // |
| // TODO(dustingreen): Some potential divisor check failures could be avoided |
| // if the corresponding value were rounded up according to the divisor before |
| // we get here. |
| if (!output_buffer_collection_info_) { |
| LOG(DEBUG, "!output_buffer_collection_info_"); |
| return false; |
| } |
| fuchsia_sysmem2::BufferCollectionInfo& info = output_buffer_collection_info_.value(); |
| ZX_DEBUG_ASSERT(info.settings()->image_format_constraints().has_value()); |
| if (min_frame_count > info.buffers()->size()) { |
| LOG(DEBUG, "min_frame_count > info.buffers().size()"); |
| return false; |
| } |
| if (min_frame_count > min_buffer_count_[kOutputPort]) { |
| LOG(DEBUG, "min_frame_count > min_buffer_count_[kOutputPort]"); |
| return false; |
| } |
| if (info.buffers()->size() > max_frame_count) { |
| // The h264_multi_decoder.cc won't exercise this path since the max is always the same, and we |
| // won't have allocated a collection with more than max_buffer_count. |
| LOG(DEBUG, "info.buffer_count > max_frame_count"); |
| return false; |
| } |
| if (stride * coded_height * 3 / 2 > *info.settings()->buffer_settings()->size_bytes()) { |
| LOG(DEBUG, "stride * coded_height * 3 / 2 > info.settings.buffer_settings.size_bytes"); |
| return false; |
| } |
| if (display_width % |
| info.settings()->image_format_constraints()->display_rect_alignment()->width() != |
| 0) { |
| // Let it probably fail later when trying to re-negotiate buffers. |
| LOG(DEBUG, |
| "display_width %% info.settings.image_format_constraints.display_width_divisor != 0"); |
| return false; |
| } |
| if (display_height % |
| info.settings()->image_format_constraints()->display_rect_alignment()->height() != |
| 0) { |
| // Let it probably fail later when trying to re-negotiate buffers. |
| LOG(DEBUG, |
| "display_height %% info.settings.image_format_constraints.display_height_divisor != 0"); |
| return false; |
| } |
| if (coded_width * coded_height > |
| *info.settings()->image_format_constraints()->max_width_times_height()) { |
| // Let it probably fail later when trying to re-negotiate buffers. |
| LOG(DEBUG, "coded_width * coded_height > max_coded_width_times_coded_height"); |
| return false; |
| } |
| |
| if (coded_width < info.settings()->image_format_constraints()->min_size()->width()) { |
| LOG(DEBUG, |
| "coded_width < info.settings.image_format_constraints.min_coded_width -- " |
| "coded_width: %d min_coded_width: %d", |
| coded_width, info.settings()->image_format_constraints()->min_size()->width()); |
| return false; |
| } |
| if (coded_width > info.settings()->image_format_constraints()->max_size()->width()) { |
| LOG(DEBUG, "coded_width > info.settings.image_format_constraints.max_coded_width"); |
| return false; |
| } |
| if (coded_width % info.settings()->image_format_constraints()->size_alignment()->width() != 0) { |
| // Let it probably fail later when trying to re-negotiate buffers. |
| LOG(DEBUG, "coded_width %% info.settings.image_format_constraints.coded_width_divisor != 0"); |
| return false; |
| } |
| if (coded_height < info.settings()->image_format_constraints()->min_size()->height()) { |
| LOG(DEBUG, "coded_height < info.settings.image_format_constraints.min_coded_height"); |
| return false; |
| } |
| if (coded_height > info.settings()->image_format_constraints()->max_size()->height()) { |
| LOG(DEBUG, "coded_height > info.settings.image_format_constraints.max_coded_height"); |
| return false; |
| } |
| if (coded_height % info.settings()->image_format_constraints()->size_alignment()->height() != 0) { |
| // Let it probably fail later when trying to re-negotiate buffers. |
| LOG(DEBUG, "coded_height %% info.settings.image_format_constraints.coded_height_divisor != 0"); |
| return false; |
| } |
| if (stride < *info.settings()->image_format_constraints()->min_bytes_per_row()) { |
| LOG(DEBUG, |
| "stride < info.settings.image_format_constraints.min_bytes_per_row -- stride: %d " |
| "min_bytes_per_row: %d", |
| stride, *info.settings()->image_format_constraints()->min_bytes_per_row()); |
| return false; |
| } |
| if (stride > *info.settings()->image_format_constraints()->max_bytes_per_row()) { |
| LOG(DEBUG, "stride > info.settings.image_format_constraints.max_bytes_per_row"); |
| return false; |
| } |
| if (stride % *info.settings()->image_format_constraints()->bytes_per_row_divisor() != 0) { |
| // Let it probably fail later when trying to re-negotiate buffers. |
| LOG(DEBUG, "stride %% info.settings.image_format_constraints.bytes_per_row_divisor != 0"); |
| return false; |
| } |
| |
| DLOG("returning true"); |
| return true; |
| } |
| |
| void CodecAdapterH264Multi::AsyncPumpDecoder() { |
| async::PostTask(core_loop_.dispatcher(), [this] { |
| std::lock_guard<std::mutex> lock(*video_->video_decoder_lock()); |
| if (!decoder_) |
| return; |
| // Something else may have come along since InitializedFrames and pumped the decoder, but that's |
| // ok. |
| decoder_->PumpOrReschedule(); |
| }); |
| } |
| |
| void CodecAdapterH264Multi::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(); |
| } |
| |
| void CodecAdapterH264Multi::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 StreamControl thread (current |
| // 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 CodecAdapterH264Multi::CoreCodecSetStreamControlProfile( |
| zx::unowned_thread stream_control_thread) { |
| device_->SetThreadProfile(std::move(stream_control_thread), ThreadRole::kH264MultiStreamControl); |
| } |
| |
| void CodecAdapterH264Multi::OnCoreCodecFailStream(fuchsia::media::StreamError error) { |
| { // scope lock |
| std::lock_guard<std::mutex> lock(lock_); |
| is_stream_failed_ = true; |
| } |
| LOG(INFO, "calling events_->onCoreCodecFailStream(): %u", static_cast<uint32_t>(error)); |
| events_->onCoreCodecFailStream(error); |
| } |
| |
| CodecPacket* CodecAdapterH264Multi::GetFreePacket(const CodecBuffer* buffer) { |
| std::lock_guard<std::mutex> lock(lock_); |
| // The h264 decoder won't repeatedly output a buffer multiple times |
| // concurrently, so a free buffer (for which the caller needs a packet) |
| // implies a free packet. |
| ZX_DEBUG_ASSERT(!free_output_packets_.empty()); |
| uint32_t free_index = free_output_packets_.back(); |
| free_output_packets_.pop_back(); |
| CodecPacket* packet = all_output_packets_[free_index]; |
| // Associate the buffer with the packet while the packet is in-flight. We don't strictly need to |
| // be doing this under lock_, but doesn't hurt, and it's easier to understand how things work with |
| // this under lock_. |
| packet->SetBuffer(buffer); |
| return packet; |
| } |
| |
| bool CodecAdapterH264Multi::IsPortSecureRequired(CodecPort port) { |
| return secure_memory_mode_[port] == fuchsia::mediacodec::SecureMemoryMode::ON; |
| } |
| |
| bool CodecAdapterH264Multi::IsPortSecurePermitted(CodecPort port) { |
| return secure_memory_mode_[port] != fuchsia::mediacodec::SecureMemoryMode::OFF; |
| } |
| |
| bool CodecAdapterH264Multi::IsPortSecure(CodecPort port) { |
| ZX_DEBUG_ASSERT(buffer_settings_[port]); |
| return *buffer_settings_[port]->buffer_settings()->is_secure(); |
| } |
| |
| bool CodecAdapterH264Multi::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 |