| // 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 <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "gpu/command_buffer/service/gpu_preferences.h" |
| #include "gpu/config/gpu_driver_bug_workarounds.h" |
| #include "gpu/config/gpu_info.h" |
| #include "media/base/bitstream_buffer.h" |
| #include "media/base/decoder_buffer.h" |
| #include "media/base/media_log.h" |
| #include "media/base/video_codecs.h" |
| #include "media/base/video_types.h" |
| #include "media/base/video_util.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) { |
| std::unique_ptr<GpuVideoDecodeAcceleratorFactory> factory = |
| GpuVideoDecodeAcceleratorFactory::Create( |
| base::BindRepeating(&CommandBufferHelper::GetGLContext, |
| command_buffer_helper), |
| base::BindRepeating(&CommandBufferHelper::MakeContextCurrent, |
| command_buffer_helper), |
| base::BindRepeating(&BindImage, command_buffer_helper)); |
| // 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<VdaVideoDecoder, std::default_delete<VideoDecoder>> |
| VdaVideoDecoder::Create( |
| scoped_refptr<base::SingleThreadTaskRunner> parent_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner, |
| MediaLog* media_log, |
| const gpu::GpuPreferences& gpu_preferences, |
| const gpu::GpuDriverBugWorkarounds& gpu_workarounds, |
| GetStubCB get_stub_cb) { |
| // Constructed in a variable to avoid _CheckUniquePtr() PRESUBMIT.py regular |
| // expressions, which do not understand custom deleters. |
| // TODO(sandersd): Extend base::WrapUnique() to handle this. |
| std::unique_ptr<VdaVideoDecoder, std::default_delete<VideoDecoder>> ptr( |
| new VdaVideoDecoder( |
| std::move(parent_task_runner), std::move(gpu_task_runner), media_log, |
| base::BindOnce(&PictureBufferManager::Create), |
| base::BindOnce(&CreateCommandBufferHelper, std::move(get_stub_cb)), |
| base::BindOnce(&CreateAndInitializeVda, gpu_preferences, |
| gpu_workarounds), |
| GpuVideoAcceleratorUtil::ConvertGpuToMediaDecodeCapabilities( |
| GpuVideoDecodeAcceleratorFactory::GetDecoderCapabilities( |
| gpu_preferences, gpu_workarounds)))); |
| return ptr; |
| } |
| |
| // TODO(sandersd): Take and use a MediaLog. This will require making |
| // MojoMediaLog threadsafe. |
| VdaVideoDecoder::VdaVideoDecoder( |
| scoped_refptr<base::SingleThreadTaskRunner> parent_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner, |
| MediaLog* media_log, |
| 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_(media_log), |
| 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), |
| gpu_weak_this_factory_(this), |
| parent_weak_this_factory_(this) { |
| 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::Destroy() { |
| DVLOG(1) << __func__; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| |
| // TODO(sandersd): The documentation says that Destroy() fires any pending |
| // callbacks. |
| |
| // Prevent any more callbacks to this thread. |
| parent_weak_this_factory_.InvalidateWeakPtrs(); |
| |
| // Pass ownership of the destruction process over to the GPU thread. |
| gpu_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::DestroyOnGpuThread, gpu_weak_this_)); |
| } |
| |
| void VdaVideoDecoder::DestroyOnGpuThread() { |
| DVLOG(2) << __func__; |
| DCHECK(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. |
| gpu_weak_vda_factory_ = nullptr; |
| vda_ = nullptr; |
| |
| delete this; |
| } |
| |
| VdaVideoDecoder::~VdaVideoDecoder() { |
| DVLOG(1) << __func__; |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| DCHECK(!gpu_weak_vda_); |
| } |
| |
| std::string VdaVideoDecoder::GetDisplayName() const { |
| DVLOG(3) << __func__; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| |
| return "VdaVideoDecoder"; |
| } |
| |
| void VdaVideoDecoder::Initialize( |
| const VideoDecoderConfig& config, |
| bool low_delay, |
| CdmContext* cdm_context, |
| const InitCB& init_cb, |
| const OutputCB& output_cb, |
| const WaitingForDecryptionKeyCB& waiting_for_decryption_key_cb) { |
| DVLOG(1) << __func__ << "(" << config.AsHumanReadableString() << ")"; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| DCHECK(config.IsValidConfig()); |
| DCHECK(init_cb_.is_null()); |
| DCHECK(flush_cb_.is_null()); |
| DCHECK(reset_cb_.is_null()); |
| DCHECK(decode_cbs_.empty()); |
| |
| if (has_error_) { |
| parent_task_runner_->PostTask(FROM_HERE, base::BindOnce(init_cb, false)); |
| 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_ = 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 (!IsOpaque(config.format())) { |
| MEDIA_LOG(INFO, media_log_) << "Alpha formats are not supported"; |
| EnterErrorState(); |
| return; |
| } |
| |
| if (config.is_encrypted()) { |
| MEDIA_LOG(INFO, media_log_) << "Encrypted streams are not supported"; |
| EnterErrorState(); |
| return; |
| } |
| |
| // The configuration is supported. |
| config_ = config; |
| |
| if (reinitializing) { |
| parent_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&VdaVideoDecoder::InitializeDone, |
| parent_weak_this_, true)); |
| return; |
| } |
| |
| gpu_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::InitializeOnGpuThread, gpu_weak_this_)); |
| } |
| |
| void VdaVideoDecoder::InitializeOnGpuThread() { |
| DVLOG(2) << __func__; |
| DCHECK(gpu_task_runner_->BelongsToCurrentThread()); |
| DCHECK(!vda_); |
| |
| // Set up |command_buffer_helper_|. |
| scoped_refptr<CommandBufferHelper> 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_, false)); |
| 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(); |
| // TODO(sandersd): Plumb |target_color_space| from DefaultRenderFactory. |
| // vda_config.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_ = std::move(create_and_initialize_vda_cb_) |
| .Run(command_buffer_helper, this, this, vda_config); |
| if (!vda_) { |
| parent_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&VdaVideoDecoder::InitializeDone, |
| parent_weak_this_, false)); |
| return; |
| } |
| |
| // TODO(sandersd): TryToSetupDecodeOnSeparateThread(). |
| gpu_weak_vda_factory_.reset( |
| new base::WeakPtrFactory<VideoDecodeAccelerator>(vda_.get())); |
| gpu_weak_vda_ = gpu_weak_vda_factory_->GetWeakPtr(); |
| vda_initialized_ = true; |
| |
| parent_task_runner_->PostTask(FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::InitializeDone, |
| parent_weak_this_, true)); |
| } |
| |
| void VdaVideoDecoder::InitializeDone(bool status) { |
| DVLOG(1) << __func__ << "(" << status << ")"; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| |
| if (has_error_) |
| return; |
| |
| if (!status) { |
| // TODO(sandersd): This adds an unnecessary PostTask(). |
| EnterErrorState(); |
| return; |
| } |
| |
| base::ResetAndReturn(&init_cb_).Run(true); |
| } |
| |
| void VdaVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer, |
| const DecodeCB& decode_cb) { |
| DVLOG(3) << __func__ << "(" << (buffer->end_of_stream() ? "EOS" : "") << ")"; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| DCHECK(init_cb_.is_null()); |
| DCHECK(flush_cb_.is_null()); |
| DCHECK(reset_cb_.is_null()); |
| DCHECK(buffer->end_of_stream() || !buffer->decrypt_config()); |
| |
| if (has_error_) { |
| parent_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(decode_cb, DecodeStatus::DECODE_ERROR)); |
| return; |
| } |
| |
| // Convert EOS frame to Flush(). |
| if (buffer->end_of_stream()) { |
| flush_cb_ = 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] = decode_cb; |
| |
| 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(const base::RepeatingClosure& reset_cb) { |
| DVLOG(2) << __func__; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| DCHECK(init_cb_.is_null()); |
| // Note: |flush_cb_| may not be null. |
| DCHECK(reset_cb_.is_null()); |
| |
| if (has_error_) { |
| parent_task_runner_->PostTask(FROM_HERE, reset_cb); |
| return; |
| } |
| |
| reset_cb_ = 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() == kCodecH264 || config_.codec() == kCodecHEVC; |
| } |
| |
| 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(bool success) { |
| DVLOG(2) << __func__ << "(" << success << ")"; |
| 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); |
| 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 (!picture_buffer_manager_->DismissPictureBuffer(picture_buffer_id)) { |
| parent_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VdaVideoDecoder::EnterErrorState, parent_weak_this_)); |
| return; |
| } |
| } |
| |
| void VdaVideoDecoder::PictureReady(const Picture& picture) { |
| DVLOG(3) << __func__ << "(" << picture.picture_buffer_id() << ")"; |
| DCHECK(vda_initialized_); |
| |
| if (parent_task_runner_->BelongsToCurrentThread()) { |
| // 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. |
| 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; |
| EnterErrorState(); |
| return; |
| } |
| |
| // Create a VideoFrame for the picture. |
| scoped_refptr<VideoFrame> frame = picture_buffer_manager_->CreateVideoFrame( |
| picture, timestamp_it->second, visible_rect, |
| GetNaturalSize(visible_rect, config_.GetPixelAspectRatio())); |
| if (!frame) { |
| EnterErrorState(); |
| return; |
| } |
| |
| 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()) { |
| // 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 = decode_cb_it->second; |
| decode_cbs_.erase(decode_cb_it); |
| 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; |
| |
| DCHECK(decode_cbs_.empty()); |
| base::ResetAndReturn(&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 = decode_cbs_; |
| decode_cbs_.clear(); |
| for (const auto& it : local_decode_cbs) { |
| it.second.Run(DecodeStatus::ABORTED); |
| if (!weak_this) |
| return; |
| } |
| |
| if (weak_this && !flush_cb_.is_null()) |
| base::ResetAndReturn(&flush_cb_).Run(DecodeStatus::ABORTED); |
| |
| if (weak_this) |
| base::ResetAndReturn(&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)); |
| } |
| |
| 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::AddEvent(std::unique_ptr<MediaLogEvent> event) { |
| DVLOG(1) << __func__; |
| |
| if (parent_task_runner_->BelongsToCurrentThread()) { |
| AddEventOnParentThread(std::move(event)); |
| return; |
| } |
| |
| // Hop to the parent thread to be sure we don't call into |media_log_| after |
| // Destroy() returns. |
| parent_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&VdaVideoDecoder::AddEventOnParentThread, |
| parent_weak_this_, std::move(event))); |
| } |
| |
| void VdaVideoDecoder::AddEventOnParentThread( |
| std::unique_ptr<MediaLogEvent> event) { |
| DVLOG(1) << __func__; |
| DCHECK(parent_task_runner_->BelongsToCurrentThread()); |
| |
| media_log_->AddEvent(std::move(event)); |
| } |
| |
| 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 = decode_cbs_; |
| decode_cbs_.clear(); |
| for (const auto& it : local_decode_cbs) { |
| it.second.Run(DecodeStatus::DECODE_ERROR); |
| if (!weak_this) |
| return; |
| } |
| |
| if (weak_this && !flush_cb_.is_null()) |
| base::ResetAndReturn(&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_.is_null()) |
| base::ResetAndReturn(&reset_cb_).Run(); |
| |
| if (weak_this && !init_cb_.is_null()) |
| base::ResetAndReturn(&init_cb_).Run(false); |
| } |
| |
| } // namespace media |