| // 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/gpu/test/video_player/decoder_wrapper.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/single_thread_task_runner_thread_mode.h" |
| #include "base/task/thread_pool.h" |
| #include "build/build_config.h" |
| #include "gpu/config/gpu_driver_bug_workarounds.h" |
| #include "media/base/media_util.h" |
| #include "media/base/waiting.h" |
| #include "media/gpu/macros.h" |
| #include "media/gpu/test/video.h" |
| #include "media/gpu/test/video_frame_helpers.h" |
| #include "media/gpu/test/video_player/frame_renderer_dummy.h" |
| #include "media/gpu/test/video_player/test_vda_video_decoder.h" |
| #include "media/gpu/test/video_test_helpers.h" |
| #include "media/media_buildflags.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) |
| #include "media/gpu/chromeos/platform_video_frame_pool.h" |
| #include "media/gpu/chromeos/video_decoder_pipeline.h" |
| #endif // BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) |
| |
| namespace media { |
| namespace test { |
| |
| namespace { |
| // Callbacks can be called from any thread, but WeakPtrs are not thread-safe. |
| // This helper thunk wraps a WeakPtr into an 'Optional' value, so the WeakPtr is |
| // only dereferenced after rescheduling the task on the specified task runner. |
| template <typename F, typename... Args> |
| void CallbackThunk(absl::optional<base::WeakPtr<DecoderWrapper>> decoder_client, |
| scoped_refptr<base::SequencedTaskRunner> task_runner, |
| F f, |
| Args... args) { |
| DCHECK(decoder_client); |
| task_runner->PostTask(FROM_HERE, base::BindOnce(f, *decoder_client, args...)); |
| } |
| } // namespace |
| |
| DecoderWrapper::DecoderWrapper( |
| const DecoderListener::EventCallback& event_cb, |
| std::unique_ptr<FrameRendererDummy> renderer, |
| std::vector<std::unique_ptr<VideoFrameProcessor>> frame_processors, |
| const DecoderWrapperConfig& config) |
| : event_cb_(event_cb), |
| frame_renderer_(std::move(renderer)), |
| frame_processors_(std::move(frame_processors)), |
| decoder_wrapper_config_(config), |
| worker_task_runner_(base::ThreadPool::CreateSingleThreadTaskRunner( |
| {base::TaskPriority::USER_BLOCKING, base::MayBlock(), |
| base::WithBaseSyncPrimitives()}, |
| base::SingleThreadTaskRunnerThreadMode::DEDICATED)), |
| state_(DecoderWrapperState::kUninitialized) { |
| DCHECK(event_cb_); |
| DCHECK(frame_renderer_); |
| DETACH_FROM_SEQUENCE(worker_sequence_checker_); |
| |
| weak_this_ = weak_this_factory_.GetWeakPtr(); |
| } |
| |
| DecoderWrapper::~DecoderWrapper() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(parent_sequence_checker_); |
| |
| base::WaitableEvent done; |
| worker_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DecoderWrapper::DestroyDecoderTask, weak_this_, &done)); |
| done.Wait(); |
| |
| // Wait until the renderer and frame processors are done before destroying |
| // them. This needs to be done after destroying the decoder so no new frames |
| // will be queued while waiting. |
| WaitForRenderer(); |
| WaitForFrameProcessors(); |
| frame_renderer_ = nullptr; |
| frame_processors_.clear(); |
| } |
| |
| // static |
| std::unique_ptr<DecoderWrapper> DecoderWrapper::Create( |
| const DecoderListener::EventCallback& event_cb, |
| std::unique_ptr<FrameRendererDummy> frame_renderer, |
| std::vector<std::unique_ptr<VideoFrameProcessor>> frame_processors, |
| const DecoderWrapperConfig& config) { |
| auto wrapper = |
| base::WrapUnique(new DecoderWrapper(event_cb, std::move(frame_renderer), |
| std::move(frame_processors), config)); |
| wrapper->CreateDecoder(); |
| return wrapper; |
| } |
| |
| void DecoderWrapper::CreateDecoder() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(parent_sequence_checker_); |
| |
| base::WaitableEvent done; |
| worker_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DecoderWrapper::CreateDecoderTask, weak_this_, &done)); |
| done.Wait(); |
| } |
| |
| bool DecoderWrapper::WaitForFrameProcessors() { |
| bool success = true; |
| for (auto& frame_processor : frame_processors_) |
| success &= frame_processor->WaitUntilDone(); |
| return success; |
| } |
| |
| void DecoderWrapper::WaitForRenderer() { |
| ASSERT_TRUE(frame_renderer_); |
| frame_renderer_->WaitUntilRenderingDone(); |
| } |
| |
| void DecoderWrapper::Initialize(const Video* video) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(parent_sequence_checker_); |
| DCHECK(video); |
| |
| base::WaitableEvent done; |
| worker_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&DecoderWrapper::InitializeTask, weak_this_, |
| video, &done)); |
| done.Wait(); |
| } |
| |
| void DecoderWrapper::Play() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(parent_sequence_checker_); |
| |
| worker_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&DecoderWrapper::PlayTask, weak_this_)); |
| } |
| |
| void DecoderWrapper::Flush() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(parent_sequence_checker_); |
| |
| worker_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&DecoderWrapper::FlushTask, weak_this_)); |
| } |
| |
| void DecoderWrapper::Reset() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(parent_sequence_checker_); |
| |
| worker_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&DecoderWrapper::ResetTask, weak_this_)); |
| } |
| |
| void DecoderWrapper::CreateDecoderTask(base::WaitableEvent* done) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_); |
| DCHECK_EQ(state_, DecoderWrapperState::kUninitialized); |
| ASSERT_TRUE(!decoder_) << "Can't create decoder: already created"; |
| |
| switch (decoder_wrapper_config_.implementation) { |
| case DecoderImplementation::kVD: |
| #if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) |
| decoder_ = VideoDecoderPipeline::CreateForTesting( |
| base::SingleThreadTaskRunner::GetCurrentDefault(), |
| std::make_unique<NullMediaLog>(), |
| decoder_wrapper_config_.ignore_resolution_changes_to_smaller_vp9); |
| #endif // BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) |
| break; |
| case DecoderImplementation::kVDA: |
| case DecoderImplementation::kVDVDA: |
| // The video decoder client expects decoders to use the VD interface. We |
| // can use the TestVDAVideoDecoder wrapper here to test VDA-based video |
| // decoders. |
| decoder_ = std::make_unique<TestVDAVideoDecoder>( |
| decoder_wrapper_config_.implementation == |
| DecoderImplementation::kVDVDA, |
| // base::Unretained(this) is safe because |decoder_| is owned by |
| // |*this|. The lifetime of |decoder_| must be shorter than |*this|. |
| base::BindRepeating(&DecoderWrapper::OnResolutionChangedTask, |
| base::Unretained(this)), |
| gfx::ColorSpace(), frame_renderer_.get(), |
| decoder_wrapper_config_.linear_output); |
| break; |
| } |
| |
| done->Signal(); |
| } |
| |
| void DecoderWrapper::InitializeTask(const Video* video, |
| base::WaitableEvent* done) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_); |
| DCHECK(state_ == DecoderWrapperState::kUninitialized || |
| state_ == DecoderWrapperState::kIdle); |
| ASSERT_TRUE(decoder_) << "Can't initialize decoder: not created yet"; |
| ASSERT_TRUE(video); |
| |
| encoded_data_helper_ = |
| std::make_unique<EncodedDataHelper>(video->Data(), video->Codec()); |
| |
| // (Re-)initialize the decoder. |
| VideoDecoderConfig config( |
| video->Codec(), video->Profile(), |
| VideoDecoderConfig::AlphaMode::kIsOpaque, VideoColorSpace(), |
| kNoTransformation, video->Resolution(), gfx::Rect(video->Resolution()), |
| video->Resolution(), std::vector<uint8_t>(0), EncryptionScheme()); |
| input_video_codec_ = video->Codec(); |
| input_video_profile_ = video->Profile(); |
| |
| VideoDecoder::InitCB init_cb = base::BindOnce( |
| CallbackThunk<decltype(&DecoderWrapper::OnDecoderInitializedTask), |
| DecoderStatus>, |
| weak_this_, worker_task_runner_, |
| &DecoderWrapper::OnDecoderInitializedTask); |
| VideoDecoder::OutputCB output_cb = base::BindRepeating( |
| CallbackThunk<decltype(&DecoderWrapper::OnFrameReadyTask), |
| scoped_refptr<VideoFrame>>, |
| weak_this_, worker_task_runner_, &DecoderWrapper::OnFrameReadyTask); |
| |
| decoder_->Initialize(config, false, nullptr, std::move(init_cb), output_cb, |
| WaitingCB()); |
| |
| DCHECK_LE(decoder_wrapper_config_.max_outstanding_decode_requests, |
| static_cast<size_t>(decoder_->GetMaxDecodeRequests())); |
| |
| done->Signal(); |
| } |
| |
| void DecoderWrapper::DestroyDecoderTask(base::WaitableEvent* done) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_); |
| DCHECK_EQ(0u, num_outstanding_decode_requests_); |
| DVLOGF(4); |
| |
| // Invalidate all scheduled tasks. |
| weak_this_factory_.InvalidateWeakPtrs(); |
| |
| decoder_.reset(); |
| |
| state_ = DecoderWrapperState::kUninitialized; |
| done->Signal(); |
| } |
| |
| void DecoderWrapper::PlayTask() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_); |
| DVLOGF(4); |
| |
| // This method should only be called when the decoder client is idle. If |
| // called e.g. while flushing, the behavior is undefined. |
| ASSERT_EQ(state_, DecoderWrapperState::kIdle); |
| |
| // Start decoding the first fragments. While in the decoding state new |
| // fragments will automatically be fed to the decoder, when the decoder |
| // notifies us it reached the end of a bitstream buffer. |
| state_ = DecoderWrapperState::kDecoding; |
| for (size_t i = 0; |
| i < decoder_wrapper_config_.max_outstanding_decode_requests; ++i) { |
| DecodeNextFragmentTask(); |
| } |
| } |
| |
| void DecoderWrapper::DecodeNextFragmentTask() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_); |
| DVLOGF(4); |
| |
| // Stop decoding fragments if we're no longer in the decoding state. |
| if (state_ != DecoderWrapperState::kDecoding) |
| return; |
| |
| // Flush immediately when we reached the end of the stream. This changes the |
| // state to kFlushing so further decode tasks will be aborted. |
| if (encoded_data_helper_->ReachEndOfStream()) { |
| FlushTask(); |
| return; |
| } |
| |
| scoped_refptr<DecoderBuffer> bitstream_buffer = |
| encoded_data_helper_->GetNextBuffer(); |
| if (!bitstream_buffer) { |
| LOG(ERROR) << "Failed to get next video stream data"; |
| return; |
| } |
| bitstream_buffer->set_timestamp(base::TimeTicks::Now().since_origin()); |
| |
| bool has_config_info = false; |
| if (input_video_codec_ == media::VideoCodec::kH264 || |
| input_video_codec_ == media::VideoCodec::kHEVC) { |
| has_config_info = media::test::EncodedDataHelper::HasConfigInfo( |
| bitstream_buffer->data(), bitstream_buffer->data_size(), |
| input_video_profile_); |
| } |
| |
| VideoDecoder::DecodeCB decode_cb = base::BindOnce( |
| CallbackThunk<decltype(&DecoderWrapper::OnDecodeDoneTask), DecoderStatus>, |
| weak_this_, worker_task_runner_, &DecoderWrapper::OnDecodeDoneTask); |
| decoder_->Decode(std::move(bitstream_buffer), std::move(decode_cb)); |
| |
| num_outstanding_decode_requests_++; |
| |
| // Throw event when we encounter a config info in a H.264/HEVC stream. |
| if (has_config_info) |
| FireEvent(DecoderListener::Event::kConfigInfo); |
| } |
| |
| void DecoderWrapper::FlushTask() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_); |
| DVLOGF(4); |
| |
| // Changing the state to flushing will abort any pending decodes. |
| state_ = DecoderWrapperState::kFlushing; |
| |
| VideoDecoder::DecodeCB flush_done_cb = base::BindOnce( |
| CallbackThunk<decltype(&DecoderWrapper::OnFlushDoneTask), DecoderStatus>, |
| weak_this_, worker_task_runner_, &DecoderWrapper::OnFlushDoneTask); |
| decoder_->Decode(DecoderBuffer::CreateEOSBuffer(), std::move(flush_done_cb)); |
| |
| FireEvent(DecoderListener::Event::kFlushing); |
| } |
| |
| void DecoderWrapper::ResetTask() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_); |
| DVLOGF(4); |
| |
| // Changing the state to resetting will abort any pending decodes. |
| state_ = DecoderWrapperState::kResetting; |
| // TODO(dstaessens@) Allow resetting to any point in the stream. |
| encoded_data_helper_->Rewind(); |
| |
| decoder_->Reset(base::BindOnce( |
| CallbackThunk<decltype(&DecoderWrapper::OnResetDoneTask)>, weak_this_, |
| worker_task_runner_, &DecoderWrapper::OnResetDoneTask)); |
| FireEvent(DecoderListener::Event::kResetting); |
| } |
| |
| void DecoderWrapper::OnDecoderInitializedTask(DecoderStatus status) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_); |
| DCHECK(state_ == DecoderWrapperState::kUninitialized || |
| state_ == DecoderWrapperState::kIdle); |
| ASSERT_TRUE(status.is_ok()) << "Initializing decoder failed"; |
| |
| state_ = DecoderWrapperState::kIdle; |
| FireEvent(DecoderListener::Event::kInitialized); |
| } |
| |
| void DecoderWrapper::OnDecodeDoneTask(DecoderStatus status) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_); |
| DCHECK_NE(DecoderWrapperState::kIdle, state_); |
| ASSERT_TRUE(status != DecoderStatus::Codes::kAborted || |
| state_ == DecoderWrapperState::kResetting); |
| DVLOGF(4); |
| |
| num_outstanding_decode_requests_--; |
| |
| // Queue the next fragment to be decoded. |
| // TODO(mcasas): Introduce a minor delay here to avoid overrunning the driver; |
| // this is a provision for Mediatek devices and for the erroneous behaviour |
| // of feeding more encoded chunk here (the driver has likely not seen any |
| // encoded chunk enqueued at this point) and not in OnFrameReadyTask as it |
| // should (naively moving this task there doesn't work because it prevents the |
| // V4L2VideoDecoder backend from polling the device driver). |
| worker_task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&DecoderWrapper::DecodeNextFragmentTask, weak_this_), |
| #if BUILDFLAG(USE_V4L2_CODEC) |
| base::Milliseconds(1) |
| #else |
| base::Milliseconds(0) |
| #endif |
| ); |
| } |
| |
| void DecoderWrapper::OnFrameReadyTask(scoped_refptr<VideoFrame> video_frame) { |
| DVLOGF(4) << current_frame_index_; |
| DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_); |
| DCHECK(video_frame->metadata().power_efficient); |
| |
| frame_renderer_->RenderFrame(video_frame); |
| |
| for (auto& frame_processor : frame_processors_) |
| frame_processor->ProcessVideoFrame(video_frame, current_frame_index_); |
| |
| // Notify the test a frame has been decoded. We should only do this after |
| // scheduling the frame to be processed, so calling WaitForFrameProcessors() |
| // after receiving this event will always guarantee the frame to be processed. |
| FireEvent(DecoderListener::Event::kFrameDecoded); |
| |
| current_frame_index_++; |
| } |
| |
| void DecoderWrapper::OnFlushDoneTask(DecoderStatus status) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_); |
| DCHECK_EQ(0u, num_outstanding_decode_requests_); |
| |
| // Send an EOS frame to the renderer, so it can reset any internal state it |
| // might keep in preparation of the next stream of video frames. |
| frame_renderer_->RenderFrame(VideoFrame::CreateEOSFrame()); |
| state_ = DecoderWrapperState::kIdle; |
| FireEvent(DecoderListener::Event::kFlushDone); |
| } |
| |
| void DecoderWrapper::OnResetDoneTask() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_); |
| DCHECK_EQ(0u, num_outstanding_decode_requests_); |
| |
| // We finished resetting to a different point in the stream, so we should |
| // update the frame index. Currently only resetting to the start of the stream |
| // is supported, so we can set the frame index to zero here. |
| current_frame_index_ = 0; |
| |
| frame_renderer_->RenderFrame(VideoFrame::CreateEOSFrame()); |
| state_ = DecoderWrapperState::kIdle; |
| FireEvent(DecoderListener::Event::kResetDone); |
| } |
| |
| bool DecoderWrapper::OnResolutionChangedTask() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_); |
| |
| return FireEvent(DecoderListener::Event::kNewBuffersRequested); |
| } |
| |
| bool DecoderWrapper::FireEvent(DecoderListener::Event event) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_); |
| |
| bool continue_decoding = event_cb_.Run(event); |
| if (!continue_decoding) { |
| // Changing the state to idle will abort any pending decodes. |
| state_ = DecoderWrapperState::kIdle; |
| } |
| return continue_decoding; |
| } |
| |
| } // namespace test |
| } // namespace media |