| // Copyright 2018 The Chromium 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 "media/gpu/ipc/service/vda_video_decoder.h" |
| |
| #include <string.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "gpu/config/gpu_driver_bug_workarounds.h" |
| #include "gpu/config/gpu_info.h" |
| #include "gpu/config/gpu_preferences.h" |
| #include "media/base/async_destroy_video_decoder.h" |
| #include "media/base/bitstream_buffer.h" |
| #include "media/base/decoder_buffer.h" |
| #include "media/base/media_log.h" |
| #include "media/base/video_aspect_ratio.h" |
| #include "media/base/video_codecs.h" |
| #include "media/base/video_types.h" |
| #include "media/base/video_util.h" |
| #include "media/gpu/buildflags.h" |
| #include "media/gpu/gpu_video_accelerator_util.h" |
| #include "media/gpu/gpu_video_decode_accelerator_factory.h" |
| #include "media/video/picture.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gl/gl_image.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| // Generates nonnegative bitstream buffer IDs, which are assumed to be unique. |
| int32_t NextID(int32_t* counter) { |
| int32_t value = *counter; |
| *counter = (*counter + 1) & 0x3FFFFFFF; |
| return value; |
| } |
| |
| scoped_refptr<CommandBufferHelper> CreateCommandBufferHelper( |
| VdaVideoDecoder::GetStubCB get_stub_cb) { |
| gpu::CommandBufferStub* stub = std::move(get_stub_cb).Run(); |
| |
| if (!stub) { |
| DVLOG(1) << "Failed to obtain command buffer stub"; |
| return nullptr; |
| } |
| |
| return CommandBufferHelper::Create(stub); |
| } |
| |
| bool BindImage(scoped_refptr<CommandBufferHelper> command_buffer_helper, |
| uint32_t client_texture_id, |
| uint32_t texture_target, |
| const scoped_refptr<gl::GLImage>& image, |
| bool can_bind_to_sampler) { |
| return command_buffer_helper->BindImage(client_texture_id, image.get(), |
| can_bind_to_sampler); |
| } |
| |
| std::unique_ptr<VideoDecodeAccelerator> CreateAndInitializeVda( |
| const gpu::GpuPreferences& gpu_preferences, |
| const gpu::GpuDriverBugWorkarounds& gpu_workarounds, |
| scoped_refptr<CommandBufferHelper> command_buffer_helper, |
| VideoDecodeAccelerator::Client* client, |
| MediaLog* media_log, |
| const VideoDecodeAccelerator::Config& config) { |
| GpuVideoDecodeGLClient gl_client; |
| gl_client.get_context = base::BindRepeating( |
| &CommandBufferHelper::GetGLContext, command_buffer_helper); |
| gl_client.make_context_current = base::BindRepeating( |
| &CommandBufferHelper::MakeContextCurrent, command_buffer_helper); |
| gl_client.bind_image = base::BindRepeating(&BindImage, command_buffer_helper); |
| gl_client.is_passthrough = command_buffer_helper->IsPassthrough(); |
| gl_client.supports_arb_texture_rectangle = |
| command_buffer_helper->SupportsTextureRectangle(); |
| |
| std::unique_ptr<GpuVideoDecodeAcceleratorFactory> factory = |
| GpuVideoDecodeAcceleratorFactory::Create(gl_client); |
| // Note: GpuVideoDecodeAcceleratorFactory may create and initialize more than |
| // one VDA. It is therefore important that VDAs do not call client methods |
| // from Initialize(). |
| return factory->CreateVDA(client, config, gpu_workarounds, gpu_preferences, |
| media_log); |
| } |
| |
| bool IsProfileSupported( |
| const VideoDecodeAccelerator::SupportedProfiles& supported_profiles, |
| VideoCodecProfile profile, |
| gfx::Size coded_size) { |
| for (const auto& supported_profile : supported_profiles) { |
| if (supported_profile.profile == profile && |
| !supported_profile.encrypted_only && |
| gfx::Rect(supported_profile.max_resolution) |
| .Contains(gfx::Rect(coded_size)) && |
| gfx::Rect(coded_size) |
| .Contains(gfx::Rect(supported_profile.min_resolution))) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<VideoDecoder> VdaVideoDecoder::Create( |
| scoped_refptr<base::SingleThreadTaskRunner> parent_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner, |
| std::unique_ptr<MediaLog> media_log, |
| const gfx::ColorSpace& target_color_space, |
| const gpu::GpuPreferences& gpu_preferences, |
| const gpu::GpuDriverBugWorkarounds& gpu_workarounds, |
| GetStubCB get_stub_cb) { |
| auto* decoder = new VdaVideoDecoder( |
| std::move(parent_task_runner), std::move(gpu_task_runner), |
| std::move(media_log), target_color_space, |
| base::BindOnce(&PictureBufferManager::Create), |
| base::BindOnce(&CreateCommandBufferHelper, std::move(get_stub_cb)), |
| base::BindRepeating(&CreateAndInitializeVda, gpu_preferences, |
| gpu_workarounds), |
| GpuVideoAcceleratorUtil::ConvertGpuToMediaDecodeCapabilities( |
| GpuVideoDecodeAcceleratorFactory::GetDecoderCapabilities( |
| gpu_preferences, gpu_workarounds))); |
| |
| return std::make_unique<AsyncDestroyVideoDecoder<VdaVideoDecoder>>( |
| base::WrapUnique(decoder)); |
| } |
| |
| VdaVideoDecoder::VdaVideoDecoder( |
| scoped_refptr<base::SingleThreadTaskRunner> parent_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner, |
| std::unique_ptr<MediaLog> media_log, |
| const gfx::ColorSpace& target_color_space, |
| CreatePictureBufferManagerCB create_picture_buffer_manager_cb, |
| CreateCommandBufferHelperCB create_command_buffer_helper_cb, |
| CreateAndInitializeVdaCB create_and_initialize_vda_cb, |
| const VideoDecodeAccelerator::Capabilities& vda_capabilities) |
| : parent_task_runner_(std::move(parent_task_runner)), |
| gpu_task_runner_(std::move(gpu_task_runner)), |
| media_log_(std::move(media_log)), |
| target_color_space_(target_color_space), |
| create_command_buffer_helper_cb_( |
| std::move(create_command_buffer_helper_cb)), |
| create_and_initialize_vda_cb_(std::move(create_and_initialize_vda_cb)), |
| vda_capabilities_(vda_capabilities), |
| timestamps_(128) { |
| DVLOG(1) << __func__; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| DCHECK_EQ(vda_capabilities_.flags, 0U); |
| DCHECK(media_log_); |
| |
| gpu_weak_this_ = gpu_weak_this_factory_.GetWeakPtr(); |
| parent_weak_this_ = parent_weak_this_factory_.GetWeakPtr(); |
| |
| picture_buffer_manager_ = |
| std::move(create_picture_buffer_manager_cb) |
| .Run(base::BindRepeating(&VdaVideoDecoder::ReusePictureBuffer, |
| gpu_weak_this_)); |
| } |
| |
| void VdaVideoDecoder::DestroyAsync(std::unique_ptr<VdaVideoDecoder> decoder) { |
| DVLOG(1) << __func__; |
| DCHECK(decoder); |
| DCHECK(decoder->parent_task_runner_->BelongsToCurrentThread()); |
| |
| // TODO(sandersd): The documentation says that DestroyAsync() fires any |
| // pending callbacks. |
| |
| // Prevent any more callbacks to this thread. |
| decoder->parent_weak_this_factory_.InvalidateWeakPtrs(); |
| |
| // Pass ownership of the destruction process over to the GPU thread. |
| auto* gpu_task_runner = decoder->gpu_task_runner_.get(); |
| gpu_task_runner->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::CleanupOnGpuThread, std::move(decoder))); |
| } |
| |
| void VdaVideoDecoder::CleanupOnGpuThread( |
| std::unique_ptr<VdaVideoDecoder> decoder) { |
| DVLOG(2) << __func__; |
| DCHECK(decoder); |
| DCHECK(decoder->gpu_task_runner_->BelongsToCurrentThread()); |
| |
| // VDA destruction is likely to result in reentrant calls to |
| // NotifyEndOfBitstreamBuffer(). Invalidating |gpu_weak_vda_| ensures that we |
| // don't call back into |vda_| during its destruction. |
| decoder->gpu_weak_vda_factory_ = nullptr; |
| decoder->vda_ = nullptr; |
| decoder->media_log_ = nullptr; |
| |
| // Because |parent_weak_this_| was invalidated in Destroy(), picture buffer |
| // dismissals since then have been dropped on the floor. |
| decoder->picture_buffer_manager_->DismissAllPictureBuffers(); |
| } |
| |
| VdaVideoDecoder::~VdaVideoDecoder() { |
| DVLOG(1) << __func__; |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| DCHECK(!gpu_weak_vda_); |
| } |
| |
| VideoDecoderType VdaVideoDecoder::GetDecoderType() const { |
| DVLOG(3) << __func__; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| // TODO(tmathmeyer) query the accelerator for it's implementation type and |
| // return that instead. |
| return VideoDecoderType::kVda; |
| } |
| |
| void VdaVideoDecoder::Initialize(const VideoDecoderConfig& config, |
| bool low_delay, |
| CdmContext* cdm_context, |
| InitCB init_cb, |
| const OutputCB& output_cb, |
| const WaitingCB& waiting_cb) { |
| DVLOG(1) << __func__ << "(" << config.AsHumanReadableString() << ")"; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| DCHECK(config.IsValidConfig()); |
| DCHECK(!init_cb_); |
| DCHECK(!flush_cb_); |
| DCHECK(!reset_cb_); |
| DCHECK(decode_cbs_.empty()); |
| |
| if (has_error_) { |
| // TODO(tmathmeyer) generic error, please remove. |
| parent_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(std::move(init_cb), |
| StatusCode::kGenericErrorPleaseRemove)); |
| return; |
| } |
| |
| bool reinitializing = config_.IsValidConfig(); |
| |
| // Store |init_cb| ASAP so that EnterErrorState() can use it. Leave |config_| |
| // alone for now so that the checks can inspect it. |
| init_cb_ = std::move(init_cb); |
| output_cb_ = output_cb; |
| |
| // Verify that the configuration is supported. |
| if (reinitializing && config.codec() != config_.codec()) { |
| MEDIA_LOG(ERROR, media_log_) << "Codec cannot be changed"; |
| EnterErrorState(); |
| return; |
| } |
| |
| if (!IsProfileSupported(vda_capabilities_.supported_profiles, |
| config.profile(), config.coded_size())) { |
| MEDIA_LOG(INFO, media_log_) << "Unsupported profile"; |
| EnterErrorState(); |
| return; |
| } |
| |
| // TODO(sandersd): Change this to a capability if any VDA starts supporting |
| // alpha channels. This is believed to be impossible right now because VPx |
| // alpha channel data is passed in side data, which isn't sent to VDAs. |
| if (config.alpha_mode() != VideoDecoderConfig::AlphaMode::kIsOpaque) { |
| MEDIA_LOG(INFO, media_log_) << "Alpha formats are not supported"; |
| EnterErrorState(); |
| return; |
| } |
| |
| // Encrypted streams are not supported by design. To support encrypted stream, |
| // use a hardware VideoDecoder directly. |
| if (config.is_encrypted()) { |
| MEDIA_LOG(INFO, media_log_) << "Encrypted streams are not supported"; |
| EnterErrorState(); |
| return; |
| } |
| |
| // VaapiVideoDecodeAccelerator doesn't support profile change, the different |
| // profiles from the initial profile will causes an issue in AMD driver |
| // (https://crbug.com/929565). We should support reinitialization for profile |
| // changes. We limit this support as small as possible for safety. |
| const bool is_profile_change = |
| #if BUILDFLAG(IS_CHROMEOS_ASH) && BUILDFLAG(USE_VAAPI) |
| config_.profile() != config.profile(); |
| #else |
| false; |
| #endif |
| |
| // Hardware decoders require ColorSpace to be set beforehand to provide |
| // correct HDR output. |
| const bool is_hdr_color_space_change = |
| config_.profile() == media::VP9PROFILE_PROFILE2 && |
| config_.color_space_info() != config.color_space_info(); |
| |
| // The configuration is supported. |
| config_ = config; |
| |
| if (reinitializing) { |
| if (is_profile_change || is_hdr_color_space_change) { |
| MEDIA_LOG(INFO, media_log_) << "Reinitializing video decode accelerator " |
| << "for profile change"; |
| gpu_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&VdaVideoDecoder::ReinitializeOnGpuThread, |
| gpu_weak_this_)); |
| } else { |
| parent_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&VdaVideoDecoder::InitializeDone, |
| parent_weak_this_, OkStatus())); |
| } |
| return; |
| } |
| |
| gpu_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::InitializeOnGpuThread, gpu_weak_this_)); |
| } |
| |
| void VdaVideoDecoder::ReinitializeOnGpuThread() { |
| DVLOG(2) << __func__; |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| DCHECK(vda_initialized_); |
| DCHECK(vda_); |
| DCHECK(!reinitializing_); |
| |
| reinitializing_ = true; |
| gpu_weak_vda_factory_ = nullptr; |
| vda_ = nullptr; |
| vda_initialized_ = false; |
| InitializeOnGpuThread(); |
| } |
| |
| void VdaVideoDecoder::InitializeOnGpuThread() { |
| DVLOG(2) << __func__; |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| DCHECK(!vda_); |
| DCHECK(!vda_initialized_); |
| |
| // Set up |command_buffer_helper_|. |
| if (!reinitializing_) { |
| command_buffer_helper_ = std::move(create_command_buffer_helper_cb_).Run(); |
| if (!command_buffer_helper_) { |
| parent_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::InitializeDone, parent_weak_this_, |
| StatusCode::kDecoderInitializeNeverCompleted)); |
| return; |
| } |
| |
| picture_buffer_manager_->Initialize(gpu_task_runner_, |
| command_buffer_helper_); |
| } |
| |
| // Convert the configuration. |
| VideoDecodeAccelerator::Config vda_config; |
| vda_config.profile = config_.profile(); |
| // vda_config.cdm_id = [Encrypted streams are not supported] |
| // vda_config.overlay_info = [Only used by AVDA] |
| vda_config.encryption_scheme = config_.encryption_scheme(); |
| vda_config.is_deferred_initialization_allowed = false; |
| vda_config.initial_expected_coded_size = config_.coded_size(); |
| vda_config.container_color_space = config_.color_space_info(); |
| vda_config.target_color_space = target_color_space_; |
| vda_config.hdr_metadata = config_.hdr_metadata(); |
| // vda_config.sps = [Only used by AVDA] |
| // vda_config.pps = [Only used by AVDA] |
| // vda_config.output_mode = [Only used by ARC] |
| // vda_config.supported_output_formats = [Only used by PPAPI] |
| |
| // Create and initialize the VDA. |
| vda_ = create_and_initialize_vda_cb_.Run(command_buffer_helper_, this, |
| media_log_.get(), vda_config); |
| if (!vda_) { |
| parent_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::InitializeDone, parent_weak_this_, |
| StatusCode::kDecoderInitializeNeverCompleted)); |
| return; |
| } |
| |
| gpu_weak_vda_factory_ = |
| std::make_unique<base::WeakPtrFactory<VideoDecodeAccelerator>>( |
| vda_.get()); |
| gpu_weak_vda_ = gpu_weak_vda_factory_->GetWeakPtr(); |
| |
| vda_initialized_ = true; |
| decode_on_parent_thread_ = vda_->TryToSetupDecodeOnSeparateThread( |
| parent_weak_this_, parent_task_runner_); |
| |
| parent_task_runner_->PostTask(FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::InitializeDone, |
| parent_weak_this_, OkStatus())); |
| } |
| |
| void VdaVideoDecoder::InitializeDone(Status status) { |
| DVLOG(1) << __func__ << " success = (" << status.code() << ")"; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| |
| if (has_error_) |
| return; |
| |
| if (!status.is_ok()) { |
| // TODO(sandersd): This adds an unnecessary PostTask(). |
| EnterErrorState(); |
| return; |
| } |
| |
| reinitializing_ = false; |
| std::move(init_cb_).Run(std::move(status)); |
| } |
| |
| void VdaVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer, |
| DecodeCB decode_cb) { |
| DVLOG(3) << __func__ << "(" << (buffer->end_of_stream() ? "EOS" : "") << ")"; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| DCHECK(!init_cb_); |
| DCHECK(!flush_cb_); |
| DCHECK(!reset_cb_); |
| DCHECK(buffer->end_of_stream() || !buffer->decrypt_config()); |
| |
| if (has_error_) { |
| parent_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(decode_cb), DecodeStatus::DECODE_ERROR)); |
| return; |
| } |
| |
| // Convert EOS frame to Flush(). |
| if (buffer->end_of_stream()) { |
| flush_cb_ = std::move(decode_cb); |
| gpu_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VideoDecodeAccelerator::Flush, gpu_weak_vda_)); |
| return; |
| } |
| |
| // Assign a bitstream buffer ID and record the decode request. |
| int32_t bitstream_buffer_id = NextID(&bitstream_buffer_id_); |
| timestamps_.Put(bitstream_buffer_id, buffer->timestamp()); |
| decode_cbs_[bitstream_buffer_id] = std::move(decode_cb); |
| |
| if (decode_on_parent_thread_) { |
| vda_->Decode(std::move(buffer), bitstream_buffer_id); |
| return; |
| } |
| |
| gpu_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::DecodeOnGpuThread, gpu_weak_this_, |
| std::move(buffer), bitstream_buffer_id)); |
| } |
| |
| void VdaVideoDecoder::DecodeOnGpuThread(scoped_refptr<DecoderBuffer> buffer, |
| int32_t bitstream_id) { |
| DVLOG(3) << __func__; |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| |
| if (!gpu_weak_vda_) |
| return; |
| |
| vda_->Decode(std::move(buffer), bitstream_id); |
| } |
| |
| void VdaVideoDecoder::Reset(base::OnceClosure reset_cb) { |
| DVLOG(2) << __func__; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| DCHECK(!init_cb_); |
| // Note: |flush_cb_| may be non-null. If so, the flush can be completed by |
| // NotifyResetDone(). |
| DCHECK(!reset_cb_); |
| |
| if (has_error_) { |
| parent_task_runner_->PostTask(FROM_HERE, std::move(reset_cb)); |
| return; |
| } |
| |
| reset_cb_ = std::move(reset_cb); |
| gpu_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&VideoDecodeAccelerator::Reset, gpu_weak_vda_)); |
| } |
| |
| bool VdaVideoDecoder::NeedsBitstreamConversion() const { |
| DVLOG(3) << __func__; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| |
| // TODO(sandersd): Can we move bitstream conversion into VdaVideoDecoder and |
| // always return false? |
| return config_.codec() == VideoCodec::kH264 || |
| config_.codec() == VideoCodec::kHEVC; |
| } |
| |
| bool VdaVideoDecoder::CanReadWithoutStalling() const { |
| DVLOG(3) << __func__; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| |
| return picture_buffer_manager_->CanReadWithoutStalling(); |
| } |
| |
| int VdaVideoDecoder::GetMaxDecodeRequests() const { |
| DVLOG(3) << __func__; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| |
| return 4; |
| } |
| |
| void VdaVideoDecoder::NotifyInitializationComplete(Status status) { |
| DVLOG(2) << __func__ << "(" << status.code() << ")"; |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| DCHECK(vda_initialized_); |
| |
| NOTIMPLEMENTED(); |
| } |
| |
| void VdaVideoDecoder::ProvidePictureBuffers(uint32_t requested_num_of_buffers, |
| VideoPixelFormat format, |
| uint32_t textures_per_buffer, |
| const gfx::Size& dimensions, |
| uint32_t texture_target) { |
| DVLOG(2) << __func__ << "(" << requested_num_of_buffers << ", " << format |
| << ", " << textures_per_buffer << ", " << dimensions.ToString() |
| << ", " << texture_target << ")"; |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| DCHECK(vda_initialized_); |
| |
| gpu_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::ProvidePictureBuffersAsync, |
| gpu_weak_this_, requested_num_of_buffers, format, |
| textures_per_buffer, dimensions, texture_target)); |
| } |
| |
| void VdaVideoDecoder::ProvidePictureBuffersAsync(uint32_t count, |
| VideoPixelFormat pixel_format, |
| uint32_t planes, |
| gfx::Size texture_size, |
| GLenum texture_target) { |
| DVLOG(2) << __func__; |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| DCHECK_GT(count, 0U); |
| |
| if (!gpu_weak_vda_) |
| return; |
| |
| // TODO(sandersd): VDAs should always be explicit. |
| if (pixel_format == PIXEL_FORMAT_UNKNOWN) |
| pixel_format = PIXEL_FORMAT_XRGB; |
| |
| std::vector<PictureBuffer> picture_buffers = |
| picture_buffer_manager_->CreatePictureBuffers( |
| count, pixel_format, planes, texture_size, texture_target, |
| vda_->GetSharedImageTextureAllocationMode()); |
| if (picture_buffers.empty()) { |
| parent_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::EnterErrorState, parent_weak_this_)); |
| return; |
| } |
| |
| DCHECK(gpu_weak_vda_); |
| vda_->AssignPictureBuffers(std::move(picture_buffers)); |
| } |
| |
| void VdaVideoDecoder::DismissPictureBuffer(int32_t picture_buffer_id) { |
| DVLOG(2) << __func__ << "(" << picture_buffer_id << ")"; |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| DCHECK(vda_initialized_); |
| |
| if (parent_task_runner_->BelongsToCurrentThread()) { |
| if (!parent_weak_this_) |
| return; |
| DismissPictureBufferOnParentThread(picture_buffer_id); |
| return; |
| } |
| |
| parent_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::DismissPictureBufferOnParentThread, |
| parent_weak_this_, picture_buffer_id)); |
| } |
| |
| void VdaVideoDecoder::DismissPictureBufferOnParentThread( |
| int32_t picture_buffer_id) { |
| DVLOG(2) << __func__ << "(" << picture_buffer_id << ")"; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| |
| if (!picture_buffer_manager_->DismissPictureBuffer(picture_buffer_id)) |
| EnterErrorState(); |
| } |
| |
| void VdaVideoDecoder::PictureReady(const Picture& picture) { |
| DVLOG(3) << __func__ << "(" << picture.picture_buffer_id() << ")"; |
| DCHECK(vda_initialized_); |
| |
| if (parent_task_runner_->BelongsToCurrentThread()) { |
| if (!parent_weak_this_) |
| return; |
| // Note: This optimization is only correct if the output callback does not |
| // reentrantly call Decode(). MojoVideoDecoderService is safe, but there is |
| // no guarantee in the media::VideoDecoder interface definition. |
| PictureReadyOnParentThread(picture); |
| return; |
| } |
| |
| parent_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&VdaVideoDecoder::PictureReadyOnParentThread, |
| parent_weak_this_, picture)); |
| } |
| |
| void VdaVideoDecoder::PictureReadyOnParentThread(Picture picture) { |
| DVLOG(3) << __func__ << "(" << picture.picture_buffer_id() << ")"; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| |
| if (has_error_) |
| return; |
| |
| // Substitute the container's visible rect if the VDA didn't specify one. |
| gfx::Rect visible_rect = picture.visible_rect(); |
| if (visible_rect.IsEmpty()) |
| visible_rect = config_.visible_rect(); |
| |
| // Look up the decode timestamp. |
| base::TimeDelta timestamp; |
| int32_t bitstream_buffer_id = picture.bitstream_buffer_id(); |
| const auto timestamp_it = timestamps_.Peek(bitstream_buffer_id); |
| if (timestamp_it == timestamps_.end()) { |
| DLOG(ERROR) << "Unknown bitstream buffer " << bitstream_buffer_id; |
| // TODO(sandersd): This should be fatal but DXVA VDA is triggering it, and |
| // playback works if we ignore the error (use a zero timestamp). |
| // |
| // EnterErrorState(); |
| // return; |
| } else { |
| timestamp = timestamp_it->second; |
| } |
| |
| // Create a VideoFrame for the picture. |
| scoped_refptr<VideoFrame> frame = picture_buffer_manager_->CreateVideoFrame( |
| picture, timestamp, visible_rect, |
| config_.aspect_ratio().GetNaturalSize(visible_rect)); |
| if (!frame) { |
| EnterErrorState(); |
| return; |
| } |
| frame->set_hdr_metadata(config_.hdr_metadata()); |
| |
| output_cb_.Run(std::move(frame)); |
| } |
| |
| void VdaVideoDecoder::NotifyEndOfBitstreamBuffer(int32_t bitstream_buffer_id) { |
| DVLOG(3) << __func__ << "(" << bitstream_buffer_id << ")"; |
| DCHECK(vda_initialized_); |
| |
| if (parent_task_runner_->BelongsToCurrentThread()) { |
| if (!parent_weak_this_) |
| return; |
| // Note: This optimization is only correct if the decode callback does not |
| // reentrantly call Decode(). MojoVideoDecoderService is safe, but there is |
| // no guarantee in the media::VideoDecoder interface definition. |
| NotifyEndOfBitstreamBufferOnParentThread(bitstream_buffer_id); |
| return; |
| } |
| |
| parent_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::NotifyEndOfBitstreamBufferOnParentThread, |
| parent_weak_this_, bitstream_buffer_id)); |
| } |
| |
| void VdaVideoDecoder::NotifyEndOfBitstreamBufferOnParentThread( |
| int32_t bitstream_buffer_id) { |
| DVLOG(3) << __func__ << "(" << bitstream_buffer_id << ")"; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| |
| if (has_error_) |
| return; |
| |
| // Look up the decode callback. |
| const auto decode_cb_it = decode_cbs_.find(bitstream_buffer_id); |
| if (decode_cb_it == decode_cbs_.end()) { |
| DLOG(ERROR) << "Unknown bitstream buffer " << bitstream_buffer_id; |
| EnterErrorState(); |
| return; |
| } |
| |
| // Run a local copy in case the decode callback modifies |decode_cbs_|. |
| DecodeCB decode_cb = std::move(decode_cb_it->second); |
| decode_cbs_.erase(decode_cb_it); |
| std::move(decode_cb).Run(DecodeStatus::OK); |
| } |
| |
| void VdaVideoDecoder::NotifyFlushDone() { |
| DVLOG(2) << __func__; |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| DCHECK(vda_initialized_); |
| |
| parent_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&VdaVideoDecoder::NotifyFlushDoneOnParentThread, |
| parent_weak_this_)); |
| } |
| |
| void VdaVideoDecoder::NotifyFlushDoneOnParentThread() { |
| DVLOG(2) << __func__; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| |
| if (has_error_) |
| return; |
| |
| // Protect against incorrect calls from the VDA. |
| if (!flush_cb_) |
| return; |
| |
| DCHECK(decode_cbs_.empty()); |
| std::move(flush_cb_).Run(DecodeStatus::OK); |
| } |
| |
| void VdaVideoDecoder::NotifyResetDone() { |
| DVLOG(2) << __func__; |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| DCHECK(vda_initialized_); |
| |
| parent_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&VdaVideoDecoder::NotifyResetDoneOnParentThread, |
| parent_weak_this_)); |
| } |
| |
| void VdaVideoDecoder::NotifyResetDoneOnParentThread() { |
| DVLOG(2) << __func__; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| |
| if (has_error_) |
| return; |
| |
| // If NotifyFlushDone() has not been called yet, it never will be. |
| // |
| // We use an on-stack WeakPtr to detect Destroy() being called. A correct |
| // client should not call Decode() or Reset() while there is a reset pending, |
| // but we should handle that safely as well. |
| // |
| // TODO(sandersd): This is similar to DestroyCallbacks(); see about merging |
| // them. |
| base::WeakPtr<VdaVideoDecoder> weak_this = parent_weak_this_; |
| |
| std::map<int32_t, DecodeCB> local_decode_cbs = std::move(decode_cbs_); |
| decode_cbs_.clear(); |
| for (auto& it : local_decode_cbs) { |
| std::move(it.second).Run(DecodeStatus::ABORTED); |
| if (!weak_this) |
| return; |
| } |
| |
| if (weak_this && flush_cb_) |
| std::move(flush_cb_).Run(DecodeStatus::ABORTED); |
| |
| if (weak_this) |
| std::move(reset_cb_).Run(); |
| } |
| |
| void VdaVideoDecoder::NotifyError(VideoDecodeAccelerator::Error error) { |
| DVLOG(1) << __func__ << "(" << error << ")"; |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| DCHECK(vda_initialized_); |
| |
| // Invalidate |gpu_weak_vda_| so that we won't make any more |vda_| calls. |
| gpu_weak_vda_factory_ = nullptr; |
| |
| parent_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&VdaVideoDecoder::NotifyErrorOnParentThread, |
| parent_weak_this_, error)); |
| } |
| |
| gpu::SharedImageStub* VdaVideoDecoder::GetSharedImageStub() const { |
| return command_buffer_helper_->GetSharedImageStub(); |
| } |
| |
| CommandBufferHelper* VdaVideoDecoder::GetCommandBufferHelper() const { |
| return command_buffer_helper_.get(); |
| } |
| |
| void VdaVideoDecoder::NotifyErrorOnParentThread( |
| VideoDecodeAccelerator::Error error) { |
| DVLOG(1) << __func__ << "(" << error << ")"; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| |
| MEDIA_LOG(ERROR, media_log_) << "VDA Error " << error; |
| |
| EnterErrorState(); |
| } |
| |
| void VdaVideoDecoder::ReusePictureBuffer(int32_t picture_buffer_id) { |
| DVLOG(3) << __func__ << "(" << picture_buffer_id << ")"; |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| |
| if (!gpu_weak_vda_) |
| return; |
| |
| vda_->ReusePictureBuffer(picture_buffer_id); |
| } |
| |
| void VdaVideoDecoder::EnterErrorState() { |
| DVLOG(1) << __func__; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| DCHECK(parent_weak_this_); |
| |
| if (has_error_) |
| return; |
| |
| // Start rejecting client calls immediately. |
| has_error_ = true; |
| |
| // Destroy callbacks aynchronously to avoid calling them on a client stack. |
| parent_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::DestroyCallbacks, parent_weak_this_)); |
| } |
| |
| void VdaVideoDecoder::DestroyCallbacks() { |
| DVLOG(3) << __func__; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| DCHECK(parent_weak_this_); |
| DCHECK(has_error_); |
| |
| // We use an on-stack WeakPtr to detect Destroy() being called. Note that any |
| // calls to Initialize(), Decode(), or Reset() are asynchronously rejected |
| // when |has_error_| is set. |
| base::WeakPtr<VdaVideoDecoder> weak_this = parent_weak_this_; |
| |
| std::map<int32_t, DecodeCB> local_decode_cbs = std::move(decode_cbs_); |
| decode_cbs_.clear(); |
| for (auto& it : local_decode_cbs) { |
| std::move(it.second).Run(DecodeStatus::DECODE_ERROR); |
| if (!weak_this) |
| return; |
| } |
| |
| if (weak_this && flush_cb_) |
| std::move(flush_cb_).Run(DecodeStatus::DECODE_ERROR); |
| |
| // Note: |reset_cb_| cannot return failure, so the client won't actually find |
| // out about the error until another operation is attempted. |
| if (weak_this && reset_cb_) |
| std::move(reset_cb_).Run(); |
| |
| if (weak_this && init_cb_) |
| std::move(init_cb_).Run(StatusCode::kDecoderInitializeNeverCompleted); |
| } |
| |
| } // namespace media |