| // Copyright 2019 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/test/video_player/test_vda_video_decoder.h" |
| |
| #include <utility> |
| #include <vector> |
| |
| #include <GLES2/gl2.h> |
| #include <GLES2/gl2ext.h> |
| |
| #include "base/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/task/post_task.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "media/base/media_log.h" |
| #include "media/base/video_frame.h" |
| #include "media/base/video_util.h" |
| #include "media/base/waiting.h" |
| #include "media/gpu/gpu_video_decode_accelerator_factory.h" |
| #include "media/gpu/macros.h" |
| #include "media/gpu/test/video.h" |
| #include "media/gpu/test/video_player/frame_renderer.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) |
| #include "media/gpu/chromeos/platform_video_frame_utils.h" |
| #include "media/gpu/chromeos/vd_video_decode_accelerator.h" |
| #include "media/gpu/chromeos/video_decoder_pipeline.h" |
| #endif // BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) |
| |
| namespace media { |
| namespace test { |
| |
| namespace { |
| // Size of the timestamp cache, needs to be large enough for frame-reordering. |
| constexpr size_t kTimestampCacheSize = 128; |
| } // namespace |
| |
| TestVDAVideoDecoder::TestVDAVideoDecoder( |
| bool use_vd_vda, |
| const gfx::ColorSpace& target_color_space, |
| FrameRenderer* const frame_renderer, |
| gpu::GpuMemoryBufferFactory* gpu_memory_buffer_factory) |
| : use_vd_vda_(use_vd_vda), |
| target_color_space_(target_color_space), |
| frame_renderer_(frame_renderer), |
| #if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) |
| gpu_memory_buffer_factory_(gpu_memory_buffer_factory), |
| #endif // BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) |
| decode_start_timestamps_(kTimestampCacheSize) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| |
| vda_wrapper_task_runner_ = base::ThreadTaskRunnerHandle::Get(); |
| |
| weak_this_ = weak_this_factory_.GetWeakPtr(); |
| } |
| |
| TestVDAVideoDecoder::~TestVDAVideoDecoder() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| |
| // Invalidate all scheduled tasks. |
| weak_this_factory_.InvalidateWeakPtrs(); |
| |
| decoder_ = nullptr; |
| |
| // Delete all video frames and related textures and the decoder. |
| video_frames_.clear(); |
| } |
| |
| VideoDecoderType TestVDAVideoDecoder::GetDecoderType() const { |
| return VideoDecoderType::kTesting; |
| } |
| |
| bool TestVDAVideoDecoder::IsPlatformDecoder() const { |
| return true; |
| } |
| |
| void TestVDAVideoDecoder::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(vda_wrapper_sequence_checker_); |
| |
| output_cb_ = output_cb; |
| |
| // Create decoder factory. |
| std::unique_ptr<GpuVideoDecodeAcceleratorFactory> decoder_factory; |
| bool hasGLContext = frame_renderer_->GetGLContext() != nullptr; |
| GpuVideoDecodeGLClient gl_client; |
| if (hasGLContext) { |
| gl_client.get_context = base::BindRepeating( |
| &FrameRenderer::GetGLContext, base::Unretained(frame_renderer_)); |
| gl_client.make_context_current = base::BindRepeating( |
| &FrameRenderer::AcquireGLContext, base::Unretained(frame_renderer_)); |
| gl_client.bind_image = base::BindRepeating( |
| [](uint32_t, uint32_t, const scoped_refptr<gl::GLImage>&, bool) { |
| return true; |
| }); |
| } |
| decoder_factory = GpuVideoDecodeAcceleratorFactory::Create(gl_client); |
| |
| if (!decoder_factory) { |
| ASSERT_TRUE(decoder_) << "Failed to create VideoDecodeAccelerator factory"; |
| std::move(init_cb).Run(StatusCode::kCodeOnlyForTesting); |
| return; |
| } |
| |
| // Create Decoder. |
| VideoDecodeAccelerator::Config vda_config(config.profile()); |
| vda_config.output_mode = VideoDecodeAccelerator::Config::OutputMode::IMPORT; |
| 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(); |
| |
| gpu::GpuDriverBugWorkarounds gpu_driver_bug_workarounds; |
| gpu::GpuPreferences gpu_preferences; |
| if (use_vd_vda_) { |
| #if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) |
| DVLOGF(2) << "Use VdVideoDecodeAccelerator"; |
| vda_config.is_deferred_initialization_allowed = true; |
| decoder_ = media::VdVideoDecodeAccelerator::Create( |
| base::BindRepeating(&media::VideoDecoderPipeline::Create), this, |
| vda_config, base::SequencedTaskRunnerHandle::Get()); |
| #endif // BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) |
| } else { |
| DVLOGF(2) << "Use original VDA"; |
| decoder_ = decoder_factory->CreateVDA( |
| this, vda_config, gpu_driver_bug_workarounds, gpu_preferences); |
| } |
| |
| if (!decoder_) { |
| ASSERT_TRUE(decoder_) << "Failed to create VideoDecodeAccelerator factory"; |
| std::move(init_cb).Run(StatusCode::kCodeOnlyForTesting); |
| return; |
| } |
| |
| if (!vda_config.is_deferred_initialization_allowed) |
| std::move(init_cb).Run(OkStatus()); |
| else |
| init_cb_ = std::move(init_cb); |
| } |
| |
| void TestVDAVideoDecoder::NotifyInitializationComplete(Status status) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| DCHECK(init_cb_); |
| |
| std::move(init_cb_).Run(status); |
| } |
| |
| void TestVDAVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer, |
| DecodeCB decode_cb) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| |
| // If the |buffer| is an EOS buffer the decoder must be flushed. |
| if (buffer->end_of_stream()) { |
| flush_cb_ = std::move(decode_cb); |
| decoder_->Flush(); |
| return; |
| } |
| |
| int32_t bitstream_buffer_id = GetNextBitstreamBufferId(); |
| decode_cbs_[bitstream_buffer_id] = std::move(decode_cb); |
| |
| // Record picture buffer decode start time. A cache is used because not each |
| // bitstream buffer decode will result in a call to PictureReady(). Pictures |
| // can be delivered in a different order than the decode operations, so we |
| // don't know when it's safe to purge old decode timestamps. Instead we use |
| // a cache with a large enough size to account for frame reordering. |
| decode_start_timestamps_.Put(bitstream_buffer_id, buffer->timestamp()); |
| |
| decoder_->Decode(std::move(buffer), bitstream_buffer_id); |
| } |
| |
| void TestVDAVideoDecoder::Reset(base::OnceClosure reset_cb) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| |
| reset_cb_ = std::move(reset_cb); |
| decoder_->Reset(); |
| } |
| |
| bool TestVDAVideoDecoder::NeedsBitstreamConversion() const { |
| return false; |
| } |
| |
| bool TestVDAVideoDecoder::CanReadWithoutStalling() const { |
| return true; |
| } |
| |
| int TestVDAVideoDecoder::GetMaxDecodeRequests() const { |
| return 4; |
| } |
| |
| void TestVDAVideoDecoder::ProvidePictureBuffers( |
| uint32_t requested_num_of_buffers, |
| VideoPixelFormat format, |
| uint32_t textures_per_buffer, |
| const gfx::Size& dimensions, |
| uint32_t texture_target) { |
| NOTIMPLEMENTED() << "VDA must call ProvidePictureBuffersWithVisibleRect()"; |
| } |
| |
| void TestVDAVideoDecoder::ProvidePictureBuffersWithVisibleRect( |
| uint32_t requested_num_of_buffers, |
| VideoPixelFormat format, |
| uint32_t textures_per_buffer, |
| const gfx::Size& dimensions, |
| const gfx::Rect& visible_rect, |
| uint32_t texture_target) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| ASSERT_EQ(textures_per_buffer, 1u); |
| DVLOGF(4) << "Requested " << requested_num_of_buffers |
| << " picture buffers with size " << dimensions.width() << "x" |
| << dimensions.height(); |
| |
| // Create a set of DMABuf-backed video frames. |
| std::vector<PictureBuffer> picture_buffers; |
| for (uint32_t i = 0; i < requested_num_of_buffers; ++i) { |
| picture_buffers.emplace_back(GetNextPictureBufferId(), dimensions); |
| } |
| |
| decoder_->AssignPictureBuffers(picture_buffers); |
| |
| // Create a video frame for each of the picture buffers and provide memory |
| // handles to the video frame's data to the decoder. |
| for (const PictureBuffer& picture_buffer : picture_buffers) { |
| scoped_refptr<VideoFrame> video_frame; |
| |
| #if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) |
| video_frame = CreatePlatformVideoFrame( |
| gpu_memory_buffer_factory_, format, dimensions, visible_rect, |
| visible_rect.size(), base::TimeDelta(), |
| gfx::BufferUsage::SCANOUT_VDA_WRITE); |
| #endif // BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) |
| |
| ASSERT_TRUE(video_frame) << "Failed to create video frame"; |
| video_frames_.emplace(picture_buffer.id(), video_frame); |
| gfx::GpuMemoryBufferHandle handle; |
| |
| #if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) |
| handle = CreateGpuMemoryBufferHandle(video_frame.get()); |
| DCHECK(!handle.is_null()); |
| #else |
| NOTREACHED(); |
| #endif // BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) |
| |
| ASSERT_TRUE(!handle.is_null()) << "Failed to create GPU memory handle"; |
| decoder_->ImportBufferForPicture(picture_buffer.id(), format, |
| std::move(handle)); |
| } |
| } |
| |
| void TestVDAVideoDecoder::DismissPictureBuffer(int32_t picture_buffer_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| |
| // Drop reference to the video frame associated with the picture buffer, so |
| // the video frame and related texture are automatically destroyed once the |
| // renderer and video frame processors are done using them. |
| ASSERT_EQ(video_frames_.erase(picture_buffer_id), 1u); |
| } |
| |
| void TestVDAVideoDecoder::PictureReady(const Picture& picture) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| DVLOGF(4) << "Picture buffer ID: " << picture.picture_buffer_id(); |
| |
| auto it = video_frames_.find(picture.picture_buffer_id()); |
| ASSERT_TRUE(it != video_frames_.end()); |
| scoped_refptr<VideoFrame> video_frame = it->second; |
| |
| // Look up the time at which the decode started. |
| auto timestamp_it = |
| decode_start_timestamps_.Peek(picture.bitstream_buffer_id()); |
| ASSERT_NE(timestamp_it, decode_start_timestamps_.end()); |
| video_frame->set_timestamp(timestamp_it->second); |
| |
| scoped_refptr<VideoFrame> wrapped_video_frame; |
| |
| // Wrap the video frame in another frame that calls ReusePictureBufferTask() |
| // upon destruction. When the renderer and video frame processors are done |
| // using the video frame, the associated picture buffer will automatically be |
| // flagged for reuse. WrapVideoFrame() is not supported for texture-based |
| // video frames (see http://crbug/362521) so we work around this by creating a |
| // new video frame using the same mailbox. |
| if (!picture.visible_rect().IsEmpty()) { |
| if (!video_frame->HasTextures()) { |
| wrapped_video_frame = VideoFrame::WrapVideoFrame( |
| video_frame, video_frame->format(), picture.visible_rect(), |
| picture.visible_rect().size()); |
| } else { |
| gpu::MailboxHolder mailbox_holders[media::VideoFrame::kMaxPlanes]; |
| mailbox_holders[0] = video_frame->mailbox_holder(0); |
| wrapped_video_frame = VideoFrame::WrapNativeTextures( |
| video_frame->format(), mailbox_holders, |
| VideoFrame::ReleaseMailboxCB(), video_frame->coded_size(), |
| picture.visible_rect(), picture.visible_rect().size(), |
| video_frame->timestamp()); |
| } |
| } else { |
| // This occurs in bitstream buffer in webrtc scenario. WrapNativeTexture() |
| // fails if visible_rect() is empty. Although the client of |
| // TestVdaVideoDecoder should ignore this frame, it is necessary to output |
| // the dummy frame to count up the number of output video frames. |
| wrapped_video_frame = |
| VideoFrame::CreateFrame(PIXEL_FORMAT_UNKNOWN, gfx::Size(), gfx::Rect(), |
| gfx::Size(), video_frame->timestamp()); |
| } |
| |
| DCHECK(wrapped_video_frame); |
| |
| // Flag that the video frame was decoded in a power efficient way. |
| wrapped_video_frame->metadata().power_efficient = true; |
| |
| // It's important to bind the original video frame to the destruction callback |
| // of the wrapped frame, to avoid deleting it before rendering of the wrapped |
| // frame is done. A reference to the video frame is already stored in |
| // |video_frames_| to map between picture buffers and frames, but that |
| // reference will be released when the decoder calls DismissPictureBuffer() |
| // (e.g. on a resolution change). |
| base::OnceClosure reuse_cb = |
| base::BindOnce(&TestVDAVideoDecoder::ReusePictureBufferThunk, weak_this_, |
| vda_wrapper_task_runner_, picture.picture_buffer_id()); |
| wrapped_video_frame->AddDestructionObserver(std::move(reuse_cb)); |
| output_cb_.Run(std::move(wrapped_video_frame)); |
| } |
| |
| // static |
| void TestVDAVideoDecoder::ReusePictureBufferThunk( |
| absl::optional<base::WeakPtr<TestVDAVideoDecoder>> vda_video_decoder, |
| scoped_refptr<base::SequencedTaskRunner> task_runner, |
| int32_t picture_buffer_id) { |
| DCHECK(vda_video_decoder); |
| task_runner->PostTask( |
| FROM_HERE, base::BindOnce(&TestVDAVideoDecoder::ReusePictureBufferTask, |
| *vda_video_decoder, picture_buffer_id)); |
| } |
| |
| // Called when a picture buffer is ready to be re-used. |
| void TestVDAVideoDecoder::ReusePictureBufferTask(int32_t picture_buffer_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| DCHECK(decoder_); |
| DVLOGF(4) << "Picture buffer ID: " << picture_buffer_id; |
| |
| // Notify the decoder the picture buffer can be reused. The decoder will only |
| // request a limited set of picture buffers, when it runs out it will wait |
| // until picture buffers are flagged for reuse. |
| decoder_->ReusePictureBuffer(picture_buffer_id); |
| } |
| |
| void TestVDAVideoDecoder::NotifyEndOfBitstreamBuffer( |
| int32_t bitstream_buffer_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| |
| auto it = decode_cbs_.find(bitstream_buffer_id); |
| ASSERT_TRUE(it != decode_cbs_.end()) |
| << "Couldn't find decode callback for picture buffer with id " |
| << bitstream_buffer_id; |
| |
| std::move(it->second).Run(DecodeStatus::OK); |
| decode_cbs_.erase(it); |
| } |
| |
| void TestVDAVideoDecoder::NotifyFlushDone() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| DCHECK(flush_cb_); |
| |
| std::move(flush_cb_).Run(DecodeStatus::OK); |
| } |
| |
| void TestVDAVideoDecoder::NotifyResetDone() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| DCHECK(reset_cb_); |
| |
| std::move(reset_cb_).Run(); |
| } |
| |
| void TestVDAVideoDecoder::NotifyError(VideoDecodeAccelerator::Error error) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| |
| switch (error) { |
| case VideoDecodeAccelerator::ILLEGAL_STATE: |
| LOG(ERROR) << "ILLEGAL_STATE"; |
| break; |
| case VideoDecodeAccelerator::INVALID_ARGUMENT: |
| LOG(ERROR) << "INVALID_ARGUMENT"; |
| break; |
| case VideoDecodeAccelerator::UNREADABLE_INPUT: |
| LOG(ERROR) << "UNREADABLE_INPUT"; |
| break; |
| case VideoDecodeAccelerator::PLATFORM_FAILURE: |
| LOG(ERROR) << "PLATFORM_FAILURE"; |
| break; |
| default: |
| LOG(ERROR) << "Unknown error " << error; |
| break; |
| } |
| } |
| |
| int32_t TestVDAVideoDecoder::GetNextBitstreamBufferId() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| // The bitstream buffer ID should always be positive, negative values are |
| // reserved for uninitialized buffers. |
| next_bitstream_buffer_id_ = (next_bitstream_buffer_id_ + 1) & 0x7FFFFFFF; |
| return next_bitstream_buffer_id_; |
| } |
| |
| int32_t TestVDAVideoDecoder::GetNextPictureBufferId() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); |
| // The picture buffer ID should always be positive, negative values are |
| // reserved for uninitialized buffers. |
| next_picture_buffer_id_ = (next_picture_buffer_id_ + 1) & 0x7FFFFFFF; |
| return next_picture_buffer_id_; |
| } |
| |
| } // namespace test |
| } // namespace media |