blob: fd2b8f658fa954c69c51c4e7d1995f9b867d5a87 [file] [log] [blame]
// 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/bind.h"
#include "base/memory/ptr_util.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"
#include "media/gpu/chromeos/video_frame_converter.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::Create(
gpu::GpuDriverBugWorkarounds(), base::ThreadTaskRunnerHandle::Get(),
std::make_unique<PlatformVideoFramePool>(),
std::make_unique<VideoFrameConverter>(),
std::make_unique<NullMediaLog>(),
/*oop_video_decoder=*/{});
#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