| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/fuchsia/video/fuchsia_video_decoder.h" |
| |
| #include <fuchsia/sysmem/cpp/fidl.h> |
| #include <lib/zx/eventpair.h> |
| #include <vulkan/vulkan.h> |
| |
| #include "base/command_line.h" |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/task/bind_post_task.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "components/viz/common/gpu/raster_context_provider.h" |
| #include "components/viz/common/resources/shared_image_format.h" |
| #include "gpu/command_buffer/client/client_shared_image.h" |
| #include "gpu/command_buffer/client/context_support.h" |
| #include "gpu/command_buffer/client/shared_image_interface.h" |
| #include "gpu/command_buffer/common/shared_image_usage.h" |
| #include "gpu/ipc/common/gpu_memory_buffer_impl_native_pixmap.h" |
| #include "media/base/cdm_context.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/video_aspect_ratio.h" |
| #include "media/base/video_color_space.h" |
| #include "media/base/video_frame.h" |
| #include "media/cdm/fuchsia/fuchsia_cdm_context.h" |
| #include "media/cdm/fuchsia/fuchsia_stream_decryptor.h" |
| #include "media/fuchsia/common/decrypting_sysmem_buffer_stream.h" |
| #include "media/fuchsia/common/passthrough_sysmem_buffer_stream.h" |
| #include "media/fuchsia/common/stream_processor_helper.h" |
| #include "media/mojo/mojom/fuchsia_media.mojom.h" |
| #include "ui/gfx/buffer_types.h" |
| #include "ui/gfx/client_native_pixmap_factory.h" |
| #include "ui/ozone/public/client_native_pixmap_factory_ozone.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| // Number of output buffers allocated "for camping". This value is passed to |
| // sysmem to ensure that we get one output buffer for the frame currently |
| // displayed on the screen. |
| constexpr uint32_t kOutputBuffersForCamping = 1; |
| |
| // Maximum number of frames we expect to have queued up while playing video. |
| // Higher values require more memory for output buffers. Lower values make it |
| // more likely that renderer will stall because decoded frames are not available |
| // on time. |
| constexpr uint32_t kMaxUsedOutputBuffers = 5; |
| |
| // Use 2 buffers for decoder input. Allocating more than one buffers ensures |
| // that when the decoder is done working on one packet it will have another one |
| // waiting in the queue. Limiting number of buffers to 2 allows to minimize |
| // required memory, without significant effect on performance. |
| constexpr size_t kNumInputBuffers = 2; |
| |
| // Some codecs do not support splitting video frames across multiple input |
| // buffers, so the buffers need to be large enough to fit all video frames. The |
| // buffer size is calculated to fit 1080p I420 frame with MinCR=2 (per H264 |
| // spec), plus 128KiB for SEI/SPS/PPS. (note that the same size is used for all |
| // codecs, not just H264). |
| constexpr size_t kInputBufferSize = 1920 * 1080 * 3 / 2 / 2 + 128 * 1024; |
| |
| const fuchsia::sysmem::PixelFormatType kSupportedPixelFormats[] = { |
| fuchsia::sysmem::PixelFormatType::NV12, |
| fuchsia::sysmem::PixelFormatType::I420, |
| fuchsia::sysmem::PixelFormatType::YV12, |
| }; |
| const fuchsia::sysmem::ColorSpaceType kSupportedColorSpaces[] = { |
| fuchsia::sysmem::ColorSpaceType::REC601_NTSC, |
| fuchsia::sysmem::ColorSpaceType::REC601_NTSC_FULL_RANGE, |
| fuchsia::sysmem::ColorSpaceType::REC601_PAL, |
| fuchsia::sysmem::ColorSpaceType::REC601_PAL_FULL_RANGE, |
| fuchsia::sysmem::ColorSpaceType::REC709, |
| }; |
| |
| std::optional<gfx::Size> ParseMinBufferSize() { |
| std::string min_buffer_size_arg = |
| base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| switches::kMinVideoDecoderOutputBufferSize); |
| if (min_buffer_size_arg.empty()) |
| return std::nullopt; |
| size_t width; |
| size_t height; |
| if (sscanf(min_buffer_size_arg.c_str(), "%zux%zu" SCNu32, &width, &height) != |
| 2) { |
| LOG(WARNING) << "Invalid value for --" |
| << switches::kMinVideoDecoderOutputBufferSize << ": '" |
| << min_buffer_size_arg << "'"; |
| return std::nullopt; |
| } |
| return gfx::Size(width, height); |
| } |
| |
| std::optional<gfx::Size> GetMinBufferSize() { |
| static std::optional<gfx::Size> value = ParseMinBufferSize(); |
| return value; |
| } |
| |
| } // namespace |
| |
| // Helper used to hold mailboxes for the output textures. OutputMailbox may |
| // outlive FuchsiaVideoDecoder if is referenced by a VideoFrame. |
| class FuchsiaVideoDecoder::OutputMailbox { |
| public: |
| OutputMailbox( |
| scoped_refptr<viz::RasterContextProvider> raster_context_provider, |
| gfx::GpuMemoryBufferHandle gmb_handle, |
| gfx::Size& size, |
| gfx::BufferFormat& buffer_format, |
| gfx::ClientNativePixmapFactory* pixmap_factory, |
| const gfx::ColorSpace& color_space) |
| : raster_context_provider_(raster_context_provider), |
| size_(size), |
| weak_factory_(this) { |
| uint32_t usage = gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | |
| gpu::SHARED_IMAGE_USAGE_SCANOUT | |
| gpu::SHARED_IMAGE_USAGE_VIDEO_DECODE; |
| |
| if (IsMultiPlaneFormatForHardwareVideoEnabled()) { |
| // The GMB is either YUV_420_BIPLANAR (SIF kNV12) or YVU_420 (SIF kYV12). |
| auto shared_image_format = viz::MultiPlaneFormat::kNV12; |
| switch (buffer_format) { |
| case gfx::BufferFormat::YUV_420_BIPLANAR: |
| break; |
| case gfx::BufferFormat::YVU_420: |
| shared_image_format = viz::MultiPlaneFormat::kYV12; |
| break; |
| default: |
| NOTREACHED_NORETURN(); |
| } |
| shared_image_format.SetPrefersExternalSampler(); |
| |
| shared_image_ = |
| raster_context_provider_->SharedImageInterface()->CreateSharedImage( |
| shared_image_format, size, color_space, kTopLeft_GrSurfaceOrigin, |
| kPremul_SkAlphaType, usage, "FuchsiaVideoDecoder", |
| std::move(gmb_handle)); |
| } else { |
| // Note that we are keeping |gmb| creation intact here for the sake of not |
| // changing this path. This path should anyways go away when we fully move |
| // to supporting MultiPlanarSI above. |
| auto gmb = gpu::GpuMemoryBufferImplNativePixmap::CreateFromHandle( |
| pixmap_factory, std::move(gmb_handle), size, buffer_format, |
| gfx::BufferUsage::GPU_READ, |
| gpu::GpuMemoryBufferImpl::DestructionCallback()); |
| |
| shared_image_ = |
| raster_context_provider_->SharedImageInterface()->CreateSharedImage( |
| gmb.get(), nullptr, gfx::BufferPlane::DEFAULT, color_space, |
| kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, usage, |
| "FuchsiaVideoDecoder"); |
| } |
| |
| create_sync_token_ = raster_context_provider_->SharedImageInterface() |
| ->GenVerifiedSyncToken(); |
| } |
| |
| OutputMailbox(const OutputMailbox&) = delete; |
| OutputMailbox& operator=(const OutputMailbox&) = delete; |
| |
| ~OutputMailbox() { |
| raster_context_provider_->SharedImageInterface()->DestroySharedImage( |
| release_sync_token_, std::move(shared_image_)); |
| } |
| |
| const gpu::Mailbox& mailbox() { return shared_image_->mailbox(); } |
| |
| const gfx::Size& size() { return size_; } |
| |
| // Create a new video frame that wraps the mailbox. |reuse_callback| will be |
| // called when the mailbox can be reused. |
| scoped_refptr<VideoFrame> CreateFrame(VideoPixelFormat pixel_format, |
| const gfx::Size& coded_size, |
| const gfx::Rect& visible_rect, |
| const gfx::Size& natural_size, |
| base::TimeDelta timestamp, |
| base::OnceClosure reuse_callback) { |
| DCHECK(!is_used_); |
| is_used_ = true; |
| reuse_callback_ = std::move(reuse_callback); |
| |
| gpu::MailboxHolder mailboxes[VideoFrame::kMaxPlanes]; |
| mailboxes[0].mailbox = shared_image_->mailbox(); |
| |
| if (create_sync_token_.HasData()) { |
| mailboxes[0].sync_token = create_sync_token_; |
| create_sync_token_.Clear(); |
| } |
| |
| auto frame = VideoFrame::WrapNativeTextures( |
| pixel_format, mailboxes, |
| base::BindPostTaskToCurrentDefault(base::BindOnce( |
| &OutputMailbox::OnFrameDestroyed, base::Unretained(this))), |
| coded_size, visible_rect, natural_size, timestamp); |
| |
| if (IsMultiPlaneFormatForHardwareVideoEnabled()) { |
| frame->set_shared_image_format_type( |
| media::SharedImageFormatType::kSharedImageFormatExternalSampler); |
| } |
| |
| // Request a fence we'll wait on before reusing the buffer. |
| frame->metadata().read_lock_fences_enabled = true; |
| |
| return frame; |
| } |
| |
| // Called by FuchsiaVideoDecoder when it no longer needs this mailbox. |
| void Release() { |
| if (is_used_) { |
| // The mailbox is referenced by a VideoFrame. It will be deleted as soon |
| // as the frame is destroyed. |
| DCHECK(reuse_callback_); |
| reuse_callback_ = base::OnceClosure(); |
| } else { |
| delete this; |
| } |
| } |
| |
| private: |
| void OnFrameDestroyed(const gpu::SyncToken& sync_token) { |
| DCHECK(is_used_); |
| is_used_ = false; |
| release_sync_token_ = sync_token; |
| |
| if (!reuse_callback_) { |
| // If the mailbox cannot be reused then we can just delete it. |
| delete this; |
| return; |
| } |
| |
| raster_context_provider_->ContextSupport()->SignalSyncToken( |
| release_sync_token_, |
| base::BindPostTaskToCurrentDefault(base::BindOnce( |
| &OutputMailbox::OnSyncTokenSignaled, weak_factory_.GetWeakPtr()))); |
| } |
| |
| void OnSyncTokenSignaled() { |
| release_sync_token_.Clear(); |
| std::move(reuse_callback_).Run(); |
| } |
| |
| const scoped_refptr<viz::RasterContextProvider> raster_context_provider_; |
| |
| gfx::Size size_; |
| |
| scoped_refptr<gpu::ClientSharedImage> shared_image_; |
| |
| gpu::SyncToken create_sync_token_; |
| gpu::SyncToken release_sync_token_; |
| |
| // Set to true when the mailbox is referenced by a video frame. |
| bool is_used_ = false; |
| |
| base::OnceClosure reuse_callback_; |
| |
| base::WeakPtrFactory<OutputMailbox> weak_factory_; |
| }; |
| |
| FuchsiaVideoDecoder::FuchsiaVideoDecoder( |
| scoped_refptr<viz::RasterContextProvider> raster_context_provider, |
| const mojo::SharedRemote<media::mojom::FuchsiaMediaCodecProvider>& |
| media_codec_provider, |
| bool allow_overlays) |
| : raster_context_provider_(raster_context_provider), |
| media_codec_provider_(media_codec_provider), |
| use_overlays_for_video_(allow_overlays), |
| sysmem_allocator_("CrFuchsiaVideoDecoder"), |
| client_native_pixmap_factory_( |
| ui::CreateClientNativePixmapFactoryOzone()) { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| DCHECK(raster_context_provider_); |
| } |
| |
| FuchsiaVideoDecoder::~FuchsiaVideoDecoder() { |
| // Reset SysmemBufferStream to ensure it doesn't try to send new packets when |
| // the |decoder_| is destroyed. |
| sysmem_buffer_stream_.reset(); |
| decoder_.reset(); |
| |
| // Release mailboxes used for output frames. |
| ReleaseOutputBuffers(); |
| } |
| |
| bool FuchsiaVideoDecoder::IsPlatformDecoder() const { |
| return true; |
| } |
| |
| bool FuchsiaVideoDecoder::SupportsDecryption() const { |
| return true; |
| } |
| |
| VideoDecoderType FuchsiaVideoDecoder::GetDecoderType() const { |
| return VideoDecoderType::kFuchsia; |
| } |
| |
| void FuchsiaVideoDecoder::Initialize(const VideoDecoderConfig& config, |
| bool low_delay, |
| CdmContext* cdm_context, |
| InitCB init_cb, |
| const OutputCB& output_cb, |
| const WaitingCB& waiting_cb) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(output_cb); |
| DCHECK(decode_callbacks_.empty()); |
| |
| auto done_callback = base::BindPostTaskToCurrentDefault(std::move(init_cb)); |
| |
| // There should be no pending decode request, so DropInputQueue() is not |
| // expected to fail. |
| bool result = DropInputQueue(DecoderStatus::Codes::kAborted); |
| DCHECK(result); |
| |
| output_cb_ = output_cb; |
| waiting_cb_ = waiting_cb; |
| |
| // Keep decoder and decryptor if the configuration hasn't changed. |
| if (decoder_ && current_config_.codec() == config.codec() && |
| current_config_.is_encrypted() == config.is_encrypted()) { |
| std::move(done_callback).Run(DecoderStatus::Codes::kOk); |
| return; |
| } |
| |
| sysmem_buffer_stream_.reset(); |
| decoder_.reset(); |
| |
| // Initialize the stream. |
| bool secure_input = false; |
| DecoderStatus status = InitializeSysmemBufferStream( |
| config.is_encrypted(), cdm_context, &secure_input); |
| |
| if (!status.is_ok()) { |
| std::move(done_callback).Run(status); |
| return; |
| } |
| |
| media::mojom::VideoDecoderSecureMemoryMode secure_mode = |
| media::mojom::VideoDecoderSecureMemoryMode::CLEAR; |
| if (secure_input) { |
| if (!use_overlays_for_video_) { |
| DLOG(ERROR) << "Protected content can be rendered only using overlays."; |
| std::move(done_callback) |
| .Run(DecoderStatus(DecoderStatus::Codes::kUnsupportedConfig, |
| FROM_HERE)); |
| return; |
| } |
| secure_mode = media::mojom::VideoDecoderSecureMemoryMode::SECURE; |
| } else if (use_overlays_for_video_ && |
| base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kForceProtectedVideoOutputBuffers)) { |
| secure_mode = media::mojom::VideoDecoderSecureMemoryMode::SECURE_OUTPUT; |
| } |
| protected_output_ = |
| secure_mode != media::mojom::VideoDecoderSecureMemoryMode::CLEAR; |
| |
| // Reset output buffers since we won't be able to re-use them. |
| ReleaseOutputBuffers(); |
| |
| fuchsia::media::StreamProcessorPtr decoder; |
| media_codec_provider_->CreateVideoDecoder(config.codec(), secure_mode, |
| decoder.NewRequest()); |
| decoder_ = std::make_unique<StreamProcessorHelper>(std::move(decoder), this); |
| |
| current_config_ = config; |
| |
| // Default to REC601 when the colorspace is not specified in the container. |
| // TODO(crbug.com/1364366): HW decoders currently don't provide accurate |
| // color space information to sysmem. Once that issue is resolved, we'll |
| // need to update this logic accordingly. |
| if (!current_config_.color_space_info().IsSpecified()) |
| current_config_.set_color_space_info(VideoColorSpace::REC601()); |
| |
| std::move(done_callback).Run(DecoderStatus::Codes::kOk); |
| } |
| |
| void FuchsiaVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer, |
| DecodeCB decode_cb) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!decoder_) { |
| // Post the callback to the current sequence as DecoderStream doesn't expect |
| // Decode() to complete synchronously. |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kFailed)); |
| return; |
| } |
| |
| decode_callbacks_.push_back(std::move(decode_cb)); |
| |
| sysmem_buffer_stream_->EnqueueBuffer(std::move(buffer)); |
| } |
| |
| void FuchsiaVideoDecoder::Reset(base::OnceClosure closure) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| DropInputQueue(DecoderStatus::Codes::kAborted); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE, |
| std::move(closure)); |
| } |
| |
| bool FuchsiaVideoDecoder::NeedsBitstreamConversion() const { |
| return true; |
| } |
| |
| bool FuchsiaVideoDecoder::CanReadWithoutStalling() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return num_used_output_buffers_ < kMaxUsedOutputBuffers; |
| } |
| |
| int FuchsiaVideoDecoder::GetMaxDecodeRequests() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return max_decoder_requests_; |
| } |
| |
| void FuchsiaVideoDecoder::SetClientNativePixmapFactoryForTests( |
| std::unique_ptr<gfx::ClientNativePixmapFactory> factory) { |
| client_native_pixmap_factory_ = std::move(factory); |
| } |
| |
| DecoderStatus FuchsiaVideoDecoder::InitializeSysmemBufferStream( |
| bool is_encrypted, |
| CdmContext* cdm_context, |
| bool* out_secure_mode) { |
| DCHECK(!sysmem_buffer_stream_); |
| |
| *out_secure_mode = false; |
| |
| // By default queue as many decode requests as the input buffers available |
| // with one extra request to be able to send a new InputBuffer immediately. |
| max_decoder_requests_ = kNumInputBuffers + 1; |
| |
| if (is_encrypted) { |
| // `waiting_cb_` is required for encrypted streams. |
| DCHECK(waiting_cb_); |
| |
| // Caller makes sure |cdm_context| is available if the stream is encrypted. |
| if (!cdm_context) { |
| DLOG(ERROR) << "No cdm context for encrypted stream."; |
| return DecoderStatus::Codes::kUnsupportedEncryptionMode; |
| } |
| |
| // Use FuchsiaStreamDecryptor with FuchsiaCdm (it doesn't support |
| // media::Decryptor interface). Otherwise (e.g. for ClearKey CDM) use |
| // DecryptingSysmemBufferStream. |
| FuchsiaCdmContext* fuchsia_cdm = cdm_context->GetFuchsiaCdmContext(); |
| if (fuchsia_cdm) { |
| *out_secure_mode = base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableProtectedVideoBuffers); |
| sysmem_buffer_stream_ = |
| fuchsia_cdm->CreateStreamDecryptor(*out_secure_mode); |
| |
| // For optimal performance allow more requests to fill the decryptor |
| // queue. |
| max_decoder_requests_ += FuchsiaStreamDecryptor::kInputBufferCount; |
| } else { |
| sysmem_buffer_stream_ = std::make_unique<DecryptingSysmemBufferStream>( |
| &sysmem_allocator_, cdm_context, Decryptor::kVideo); |
| } |
| } else { |
| sysmem_buffer_stream_ = |
| std::make_unique<PassthroughSysmemBufferStream>(&sysmem_allocator_); |
| } |
| |
| sysmem_buffer_stream_->Initialize(this, kInputBufferSize, kNumInputBuffers); |
| |
| return DecoderStatus::Codes::kOk; |
| } |
| |
| void FuchsiaVideoDecoder::OnSysmemBufferStreamBufferCollectionToken( |
| fuchsia::sysmem::BufferCollectionTokenPtr token) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(decoder_); |
| decoder_->SetInputBufferCollectionToken(std::move(token)); |
| } |
| |
| void FuchsiaVideoDecoder::OnSysmemBufferStreamOutputPacket( |
| StreamProcessorHelper::IoPacket packet) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| packet.AddOnDestroyClosure( |
| base::BindOnce(&FuchsiaVideoDecoder::CallNextDecodeCallback, |
| decode_callbacks_weak_factory_.GetWeakPtr())); |
| decoder_->Process(std::move(packet)); |
| } |
| |
| void FuchsiaVideoDecoder::OnSysmemBufferStreamEndOfStream() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| decoder_->ProcessEos(); |
| } |
| |
| void FuchsiaVideoDecoder::OnSysmemBufferStreamError() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| OnError(); |
| } |
| |
| void FuchsiaVideoDecoder::OnSysmemBufferStreamNoKey() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| waiting_cb_.Run(WaitingReason::kNoDecryptionKey); |
| } |
| |
| void FuchsiaVideoDecoder::OnStreamProcessorAllocateOutputBuffers( |
| const fuchsia::media::StreamBufferConstraints& output_constraints) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| ReleaseOutputBuffers(); |
| |
| output_buffer_collection_ = sysmem_allocator_.AllocateNewCollection(); |
| |
| output_buffer_collection_->CreateSharedToken( |
| base::BindOnce(&StreamProcessorHelper::CompleteOutputBuffersAllocation, |
| base::Unretained(decoder_.get())), |
| "codec"); |
| output_buffer_collection_->CreateSharedToken( |
| base::BindOnce(&FuchsiaVideoDecoder::SetBufferCollectionTokenForGpu, |
| base::Unretained(this)), |
| "gpu"); |
| |
| fuchsia::sysmem::BufferCollectionConstraints buffer_constraints; |
| buffer_constraints.usage.none = fuchsia::sysmem::noneUsage; |
| buffer_constraints.min_buffer_count_for_camping = kOutputBuffersForCamping; |
| buffer_constraints.min_buffer_count_for_shared_slack = |
| kMaxUsedOutputBuffers - kOutputBuffersForCamping; |
| |
| buffer_constraints.image_format_constraints_count = |
| std::size(kSupportedPixelFormats); |
| for (size_t pixel_format_index = 0; |
| pixel_format_index < std::size(kSupportedPixelFormats); |
| ++pixel_format_index) { |
| auto& image_format_constraints = |
| buffer_constraints.image_format_constraints[pixel_format_index]; |
| image_format_constraints.pixel_format.type = |
| kSupportedPixelFormats[pixel_format_index]; |
| image_format_constraints.pixel_format.has_format_modifier = true; |
| image_format_constraints.pixel_format.format_modifier.value = |
| fuchsia::sysmem::FORMAT_MODIFIER_LINEAR; |
| |
| image_format_constraints.color_spaces_count = |
| std::size(kSupportedColorSpaces); |
| for (size_t i = 0; i < std::size(kSupportedColorSpaces); ++i) { |
| image_format_constraints.color_space[i].type = kSupportedColorSpaces[i]; |
| } |
| } |
| |
| auto min_buffer_size = GetMinBufferSize(); |
| if (min_buffer_size) { |
| for (size_t pixel_format_index = 0; |
| pixel_format_index < std::size(kSupportedPixelFormats); |
| ++pixel_format_index) { |
| auto& image_format_constraints = |
| buffer_constraints.image_format_constraints[pixel_format_index]; |
| image_format_constraints.required_max_coded_width = |
| min_buffer_size->width(); |
| image_format_constraints.required_max_coded_height = |
| min_buffer_size->height(); |
| } |
| } |
| |
| output_buffer_collection_->Initialize(std::move(buffer_constraints), |
| "ChromiumVideoDecoderOutput"); |
| } |
| |
| void FuchsiaVideoDecoder::OnStreamProcessorEndOfStream() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Decode() is not supposed to be called again after EOF. |
| DCHECK_EQ(decode_callbacks_.size(), 1U); |
| CallNextDecodeCallback(); |
| } |
| |
| void FuchsiaVideoDecoder::OnStreamProcessorOutputFormat( |
| fuchsia::media::StreamOutputFormat output_format) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| auto* format = output_format.mutable_format_details(); |
| if (!format->has_domain() || !format->domain().is_video() || |
| !format->domain().video().is_uncompressed()) { |
| DLOG(ERROR) << "Received OnOutputFormat() with invalid format."; |
| OnError(); |
| return; |
| } |
| |
| output_format_ = std::move(format->mutable_domain()->video().uncompressed()); |
| } |
| |
| void FuchsiaVideoDecoder::OnStreamProcessorOutputPacket( |
| StreamProcessorHelper::IoPacket output_packet) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| fuchsia::sysmem::PixelFormatType sysmem_pixel_format = |
| output_format_.image_format.pixel_format.type; |
| |
| VideoPixelFormat pixel_format; |
| gfx::BufferFormat buffer_format; |
| VkFormat vk_format; |
| switch (sysmem_pixel_format) { |
| case fuchsia::sysmem::PixelFormatType::NV12: |
| pixel_format = PIXEL_FORMAT_NV12; |
| buffer_format = gfx::BufferFormat::YUV_420_BIPLANAR; |
| vk_format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM; |
| break; |
| |
| case fuchsia::sysmem::PixelFormatType::I420: |
| case fuchsia::sysmem::PixelFormatType::YV12: |
| pixel_format = PIXEL_FORMAT_I420; |
| buffer_format = gfx::BufferFormat::YVU_420; |
| vk_format = VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM; |
| break; |
| |
| default: |
| DLOG(ERROR) << "Unsupported pixel format: " |
| << static_cast<int>(sysmem_pixel_format); |
| OnError(); |
| return; |
| } |
| |
| size_t buffer_index = output_packet.buffer_index(); |
| |
| if (buffer_index >= output_mailboxes_.size()) |
| output_mailboxes_.resize(buffer_index + 1, nullptr); |
| |
| auto coded_size = gfx::Size(output_format_.primary_width_pixels, |
| output_format_.primary_height_pixels); |
| |
| if (output_mailboxes_[buffer_index] && |
| output_mailboxes_[buffer_index]->size() != coded_size) { |
| output_mailboxes_[buffer_index]->Release(); |
| output_mailboxes_[buffer_index] = nullptr; |
| } |
| |
| if (!output_mailboxes_[buffer_index]) { |
| gfx::GpuMemoryBufferHandle gmb_handle; |
| gmb_handle.type = gfx::NATIVE_PIXMAP; |
| auto status = output_buffer_collection_handle_.duplicate( |
| ZX_RIGHT_SAME_RIGHTS, |
| &gmb_handle.native_pixmap_handle.buffer_collection_handle); |
| ZX_DCHECK(status == ZX_OK, status); |
| gmb_handle.native_pixmap_handle.buffer_index = buffer_index; |
| |
| output_mailboxes_[buffer_index] = new OutputMailbox( |
| raster_context_provider_, std::move(gmb_handle), coded_size, |
| buffer_format, client_native_pixmap_factory_.get(), |
| current_config_.color_space_info().ToGfxColorSpace()); |
| } else { |
| raster_context_provider_->SharedImageInterface()->UpdateSharedImage( |
| gpu::SyncToken(), output_mailboxes_[buffer_index]->mailbox()); |
| } |
| |
| auto display_rect = gfx::Rect(output_format_.primary_display_width_pixels, |
| output_format_.primary_display_height_pixels); |
| |
| VideoAspectRatio aspect_ratio = current_config_.aspect_ratio(); |
| if (!aspect_ratio.IsValid() && output_format_.has_pixel_aspect_ratio) { |
| aspect_ratio = |
| VideoAspectRatio::PAR(output_format_.pixel_aspect_ratio_width, |
| output_format_.pixel_aspect_ratio_height); |
| } |
| |
| auto timestamp = output_packet.timestamp(); |
| |
| // SendInputPacket() sets timestamp for all packets sent to the decoder, so we |
| // expect to receive timestamp for all decoded frames. Missing timestamp |
| // indicates a bug in the decoder implementation. |
| if (timestamp == kNoTimestamp) { |
| LOG(ERROR) << "Received frame without timestamp."; |
| OnError(); |
| return; |
| } |
| |
| num_used_output_buffers_++; |
| |
| auto frame = output_mailboxes_[buffer_index]->CreateFrame( |
| pixel_format, coded_size, display_rect, |
| aspect_ratio.GetNaturalSize(display_rect), timestamp, |
| base::BindOnce(&FuchsiaVideoDecoder::ReleaseOutputPacket, |
| base::Unretained(this), std::move(output_packet))); |
| |
| VkSamplerYcbcrModelConversion ycbcr_conversion = |
| (current_config_.color_space_info().matrix == |
| VideoColorSpace::MatrixID::BT709) |
| ? VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709 |
| : VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601; |
| |
| // Currently sysmem doesn't specify location of chroma samples relative to |
| // luma (see fxbug.dev/13677). Assume they are cosited with luma. YCbCr info |
| // here must match the values passed for the same buffer in |
| // ui::SysmemBufferCollection::CreateVkImage() (see |
| // ui/ozone/platform/flatland/flatland_sysmem_buffer_collection.cc). |
| // |format_features| are resolved later in the GPU process before this info is |
| // passed to Skia. |
| frame->set_ycbcr_info(gpu::VulkanYCbCrInfo( |
| vk_format, /*external_format=*/0, ycbcr_conversion, |
| VK_SAMPLER_YCBCR_RANGE_ITU_NARROW, VK_CHROMA_LOCATION_COSITED_EVEN, |
| VK_CHROMA_LOCATION_COSITED_EVEN, /*format_features=*/0)); |
| |
| // Mark the frame as power-efficient since (software decoders are used only in |
| // tests). |
| frame->metadata().power_efficient = true; |
| |
| // Allow this video frame to be promoted as an overlay, because it was |
| // registered with an ImagePipe. |
| frame->metadata().allow_overlay = use_overlays_for_video_; |
| |
| if (protected_output_) { |
| frame->metadata().protected_video = true; |
| frame->metadata().hw_protected = true; |
| } |
| |
| output_cb_.Run(std::move(frame)); |
| } |
| |
| void FuchsiaVideoDecoder::OnStreamProcessorNoKey() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Decoder is not expected to produce NoKey() error. |
| DLOG(ERROR) << "Video decoder failed with DECRYPTOR_NO_KEY expectedly"; |
| OnError(); |
| } |
| |
| void FuchsiaVideoDecoder::OnStreamProcessorError() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| OnError(); |
| } |
| |
| void FuchsiaVideoDecoder::CallNextDecodeCallback() { |
| DCHECK(!decode_callbacks_.empty()); |
| auto cb = std::move(decode_callbacks_.front()); |
| decode_callbacks_.pop_front(); |
| |
| std::move(cb).Run(DecoderStatus::Codes::kOk); |
| } |
| |
| bool FuchsiaVideoDecoder::DropInputQueue(DecoderStatus status) { |
| // Invalidate callbacks for CallNextDecodeCallback(), so the callbacks are not |
| // called when the |decoder_| is dropped below. The callbacks are called |
| // explicitly later. |
| decode_callbacks_weak_factory_.InvalidateWeakPtrs(); |
| |
| if (decoder_) { |
| decoder_->Reset(); |
| } |
| |
| if (sysmem_buffer_stream_) { |
| sysmem_buffer_stream_->Reset(); |
| } |
| |
| // Get a fresh WeakPtr, to use to check whether any DecodeCB deletes |this|. |
| auto weak_this = decode_callbacks_weak_factory_.GetWeakPtr(); |
| |
| for (auto& cb : decode_callbacks_) { |
| std::move(cb).Run(status); |
| if (!weak_this) |
| return false; |
| } |
| decode_callbacks_.clear(); |
| |
| return true; |
| } |
| |
| void FuchsiaVideoDecoder::OnError() { |
| sysmem_buffer_stream_.reset(); |
| decoder_.reset(); |
| |
| ReleaseOutputBuffers(); |
| |
| DropInputQueue(DecoderStatus::Codes::kFailed); |
| } |
| |
| void FuchsiaVideoDecoder::SetBufferCollectionTokenForGpu( |
| fuchsia::sysmem::BufferCollectionTokenPtr token) { |
| // Register the new collection with the GPU process. |
| DCHECK(!output_buffer_collection_handle_); |
| |
| zx::eventpair service_handle; |
| auto status = zx::eventpair::create(0, &output_buffer_collection_handle_, |
| &service_handle); |
| ZX_DCHECK(status == ZX_OK, status); |
| raster_context_provider_->SharedImageInterface() |
| ->RegisterSysmemBufferCollection( |
| std::move(service_handle), token.Unbind().TakeChannel(), |
| gfx::BufferFormat::YUV_420_BIPLANAR, gfx::BufferUsage::GPU_READ, |
| use_overlays_for_video_ /*register_with_image_pipe*/); |
| |
| // Exact number of buffers sysmem will allocate is unknown here. |
| // |output_mailboxes_| is resized when we start receiving output frames. |
| DCHECK(output_mailboxes_.empty()); |
| } |
| |
| void FuchsiaVideoDecoder::ReleaseOutputBuffers() { |
| // Release the buffer collection. |
| num_used_output_buffers_ = 0; |
| if (output_buffer_collection_) { |
| output_buffer_collection_.reset(); |
| } |
| |
| // Release all output mailboxes. |
| for (OutputMailbox* mailbox : output_mailboxes_) { |
| if (mailbox) |
| mailbox->Release(); |
| } |
| output_mailboxes_.clear(); |
| |
| // Tell the GPU process to drop the buffer collection. |
| output_buffer_collection_handle_.reset(); |
| } |
| |
| void FuchsiaVideoDecoder::ReleaseOutputPacket( |
| StreamProcessorHelper::IoPacket output_packet) { |
| DCHECK_GT(num_used_output_buffers_, 0U); |
| num_used_output_buffers_--; |
| } |
| |
| } // namespace media |