blob: b07e3f6542f640bafe0df04cd991015ebd8f3dad [file] [log] [blame]
// 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/test/video_player/video_decoder_client.h"
#include <string>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/video_frame.h"
#include "media/gpu/gpu_video_decode_accelerator_factory.h"
#include "media/gpu/test/video_decode_accelerator_unittest_helpers.h"
#include "media/gpu/test/video_frame_helpers.h"
#include "media/gpu/test/video_player/frame_renderer.h"
#define DVLOGF(level) DVLOG(level) << __func__ << "(): "
#define VLOGF(level) VLOG(level) << __func__ << "(): "
namespace media {
namespace test {
VideoDecoderClient::VideoDecoderClient(
const VideoPlayer::EventCallback& event_cb,
FrameRenderer* renderer,
const std::vector<VideoFrameProcessor*>& frame_processors,
const VideoDecoderClientConfig& config)
: event_cb_(event_cb),
frame_renderer_(renderer),
frame_processors_(frame_processors),
decoder_client_thread_("VDAClientDecoderThread"),
decoder_client_state_(VideoDecoderClientState::kUninitialized),
decoder_client_config_(config),
weak_this_factory_(this) {
DETACH_FROM_SEQUENCE(decoder_client_sequence_checker_);
weak_this_ = weak_this_factory_.GetWeakPtr();
}
VideoDecoderClient::~VideoDecoderClient() {
DCHECK_CALLED_ON_VALID_SEQUENCE(video_player_sequence_checker_);
Destroy();
}
// static
std::unique_ptr<VideoDecoderClient> VideoDecoderClient::Create(
const VideoPlayer::EventCallback& event_cb,
FrameRenderer* frame_renderer,
const std::vector<VideoFrameProcessor*>& frame_processors,
const VideoDecoderClientConfig& config) {
auto decoder_client = base::WrapUnique(new VideoDecoderClient(
event_cb, frame_renderer, frame_processors, config));
if (!decoder_client->Initialize()) {
return nullptr;
}
return decoder_client;
}
bool VideoDecoderClient::Initialize() {
DCHECK_CALLED_ON_VALID_SEQUENCE(video_player_sequence_checker_);
DCHECK(!decoder_client_thread_.IsRunning());
DCHECK(event_cb_.MaybeValid() && frame_renderer_);
if (!decoder_client_thread_.Start()) {
VLOGF(1) << "Failed to start decoder thread";
return false;
}
// Create a decoder factory, which will be used to create decoders.
base::WaitableEvent done;
decoder_client_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VideoDecoderClient::CreateDecoderFactoryTask,
weak_this_, &done));
done.Wait();
return true;
}
void VideoDecoderClient::Destroy() {
DCHECK_CALLED_ON_VALID_SEQUENCE(video_player_sequence_checker_);
DestroyDecoder();
decoder_client_thread_.Stop();
}
void VideoDecoderClient::CreateDecoder(
const VideoDecodeAccelerator::Config& config,
const std::vector<uint8_t>& stream) {
DCHECK_CALLED_ON_VALID_SEQUENCE(video_player_sequence_checker_);
base::WaitableEvent done;
decoder_client_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VideoDecoderClient::CreateDecoderTask,
weak_this_, config, &stream, &done));
done.Wait();
}
void VideoDecoderClient::DestroyDecoder() {
DCHECK_CALLED_ON_VALID_SEQUENCE(video_player_sequence_checker_);
base::WaitableEvent done;
decoder_client_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VideoDecoderClient::DestroyDecoderTask,
weak_this_, &done));
done.Wait();
}
void VideoDecoderClient::Play() {
DCHECK_CALLED_ON_VALID_SEQUENCE(video_player_sequence_checker_);
decoder_client_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VideoDecoderClient::PlayTask, weak_this_));
}
void VideoDecoderClient::Flush() {
DCHECK_CALLED_ON_VALID_SEQUENCE(video_player_sequence_checker_);
decoder_client_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VideoDecoderClient::FlushTask, weak_this_));
}
void VideoDecoderClient::Reset() {
DCHECK_CALLED_ON_VALID_SEQUENCE(video_player_sequence_checker_);
decoder_client_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VideoDecoderClient::ResetTask, weak_this_));
}
void VideoDecoderClient::ProvidePictureBuffers(
uint32_t requested_num_of_buffers,
VideoPixelFormat pixel_format,
uint32_t textures_per_buffer,
const gfx::Size& size,
uint32_t texture_target) {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
DCHECK_NE(pixel_format, PIXEL_FORMAT_UNKNOWN);
DCHECK_EQ(textures_per_buffer, 1u);
DVLOGF(4) << "Requested " << requested_num_of_buffers
<< " picture buffers with size " << size.height() << "x"
<< size.height();
// Create a set of picture buffers and give them to the decoder.
std::vector<PictureBuffer> picture_buffers;
for (uint32_t i = 0; i < requested_num_of_buffers; ++i)
picture_buffers.emplace_back(GetNextPictureBufferId(), size);
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 =
CreateVideoFrame(pixel_format, size);
LOG_ASSERT(video_frame) << "Failed to create video frame";
video_frames_.emplace(picture_buffer.id(), video_frame);
gfx::GpuMemoryBufferHandle handle =
CreateGpuMemoryBufferHandle(video_frame);
LOG_ASSERT(!handle.is_null()) << "Failed to create GPU memory handle";
decoder_->ImportBufferForPicture(picture_buffer.id(), pixel_format, handle);
}
}
void VideoDecoderClient::DismissPictureBuffer(int32_t picture_buffer_id) {
// TODO(dstaessens@) support dismissing picture buffers.
NOTIMPLEMENTED();
}
void VideoDecoderClient::PictureReady(const Picture& picture) {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
DVLOGF(4) << "Picture buffer ID: " << picture.picture_buffer_id();
event_cb_.Run(VideoPlayerEvent::kFrameDecoded);
auto it = video_frames_.find(picture.picture_buffer_id());
LOG_ASSERT(it != video_frames_.end());
scoped_refptr<VideoFrame> video_frame = it->second;
// Wrap the video frame in another video frame that calls
// ReusePictureBufferTask() upon destruction. When the renderer is done using
// the video frame, the associated picture buffer will automatically be
// flagged for reuse.
base::OnceClosure delete_cb = BindToCurrentLoop(
base::BindOnce(&VideoDecoderClient::ReusePictureBufferTask,
base::Unretained(this), picture.picture_buffer_id()));
scoped_refptr<VideoFrame> wrapped_video_frame = VideoFrame::WrapVideoFrame(
video_frame, video_frame->format(), video_frame->visible_rect(),
video_frame->visible_rect().size());
wrapped_video_frame->AddDestructionObserver(std::move(delete_cb));
frame_renderer_->RenderFrame(wrapped_video_frame);
for (VideoFrameProcessor* frame_processor : frame_processors_)
frame_processor->ProcessVideoFrame(video_frame, current_frame_index_);
current_frame_index_++;
}
void VideoDecoderClient::NotifyEndOfBitstreamBuffer(
int32_t bitstream_buffer_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
DCHECK_NE(VideoDecoderClientState::kIdle, decoder_client_state_);
DVLOGF(4);
num_outstanding_decode_requests_--;
// Queue the next fragment to be decoded.
decoder_client_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VideoDecoderClient::DecodeNextFragmentTask, weak_this_));
}
void VideoDecoderClient::NotifyFlushDone() {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
DCHECK_EQ(0u, num_outstanding_decode_requests_);
decoder_client_state_ = VideoDecoderClientState::kIdle;
event_cb_.Run(VideoPlayerEvent::kFlushDone);
}
void VideoDecoderClient::NotifyResetDone() {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_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;
decoder_client_state_ = VideoDecoderClientState::kIdle;
event_cb_.Run(VideoPlayerEvent::kResetDone);
}
void VideoDecoderClient::NotifyError(VideoDecodeAccelerator::Error error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
switch (error) {
case VideoDecodeAccelerator::ILLEGAL_STATE:
LOG(FATAL) << "ILLEGAL_STATE";
break;
case VideoDecodeAccelerator::INVALID_ARGUMENT:
LOG(FATAL) << "INVALID_ARGUMENT";
break;
case VideoDecodeAccelerator::UNREADABLE_INPUT:
LOG(FATAL) << "UNREADABLE_INPUT";
break;
case VideoDecodeAccelerator::PLATFORM_FAILURE:
LOG(FATAL) << "PLATFORM_FAILURE";
break;
default:
LOG(FATAL) << "Unknown error";
break;
}
}
void VideoDecoderClient::CreateDecoderFactoryTask(base::WaitableEvent* done) {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
LOG_ASSERT(!decoder_factory_) << "Decoder factory already created";
DVLOGF(4);
frame_renderer_->AcquireGLContext();
bool hasGLContext = frame_renderer_->GetGLContext() != nullptr;
frame_renderer_->ReleaseGLContext();
if (hasGLContext) {
decoder_factory_ = GpuVideoDecodeAcceleratorFactory::Create(
base::BindRepeating(&FrameRenderer::GetGLContext,
base::Unretained(frame_renderer_)),
base::BindRepeating([]() { return true; }),
base::BindRepeating([](uint32_t, uint32_t,
const scoped_refptr<gl::GLImage>&,
bool) { return true; }));
} else {
decoder_factory_ = GpuVideoDecodeAcceleratorFactory::CreateWithNoGL();
}
done->Signal();
}
void VideoDecoderClient::CreateDecoderTask(
VideoDecodeAccelerator::Config config,
const std::vector<uint8_t>* stream,
base::WaitableEvent* done) {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
LOG_ASSERT(decoder_factory_) << "Decoder factory not created yet";
LOG_ASSERT(!decoder_) << "Can't create decoder: already created";
DVLOGF(4) << "Profile: " << config.profile;
encoded_data_helper_ =
std::make_unique<EncodedDataHelper>(*stream, config.profile);
gpu::GpuDriverBugWorkarounds gpu_driver_bug_workarounds;
gpu::GpuPreferences gpu_preferences;
decoder_ = decoder_factory_->CreateVDA(
this, config, gpu_driver_bug_workarounds, gpu_preferences);
LOG_ASSERT(decoder_) << "Failed creating a VDA";
decoder_->TryToSetupDecodeOnSeparateThread(
weak_this_, base::ThreadTaskRunnerHandle::Get());
decoder_client_state_ = VideoDecoderClientState::kIdle;
done->Signal();
}
void VideoDecoderClient::DestroyDecoderTask(base::WaitableEvent* done) {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
DCHECK_EQ(VideoDecoderClientState::kIdle, decoder_client_state_);
DCHECK_EQ(0u, num_outstanding_decode_requests_);
DVLOGF(4);
// Invalidate all scheduled tasks.
weak_this_factory_.InvalidateWeakPtrs();
// Destroying a decoder requires an active GL context.
// TODO(dstaessens@) Investigate making the decoder manage the GL context.
if (decoder_) {
frame_renderer_->AcquireGLContext();
decoder_.reset();
frame_renderer_->ReleaseGLContext();
}
decoder_client_state_ = VideoDecoderClientState::kUninitialized;
done->Signal();
}
void VideoDecoderClient::DecodeNextFragmentTask() {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
DVLOGF(4);
// Stop decoding fragments if we're no longer in the decoding state.
if (decoder_client_state_ != VideoDecoderClientState::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;
}
std::string fragment_bytes = encoded_data_helper_->GetBytesForNextData();
size_t fragment_size = fragment_bytes.size();
if (fragment_size == 0) {
LOG(ERROR) << "Stream fragment has size 0";
return;
}
// Create shared memory to store the fragment.
// TODO(dstaessens@) Investigate using import mode when allocating memory.
base::SharedMemory shm;
bool success = shm.CreateAndMapAnonymous(fragment_size);
LOG_ASSERT(success) << "Failed to create shared memory";
memcpy(shm.memory(), fragment_bytes.data(), fragment_size);
base::SharedMemoryHandle handle = shm.TakeHandle();
LOG_ASSERT(handle.IsValid()) << "Failed to take shared memory handle";
int32_t bitstream_buffer_id = GetNextBitstreamBufferId();
BitstreamBuffer bitstream_buffer(bitstream_buffer_id, handle, fragment_size);
DVLOGF(4) << "Bitstream buffer id: " << bitstream_buffer_id;
decoder_->Decode(bitstream_buffer);
num_outstanding_decode_requests_++;
}
void VideoDecoderClient::PlayTask() {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_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(decoder_client_state_, VideoDecoderClientState::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.
decoder_client_state_ = VideoDecoderClientState::kDecoding;
for (size_t i = 0; i < decoder_client_config_.max_outstanding_decode_requests;
++i)
DecodeNextFragmentTask();
}
void VideoDecoderClient::FlushTask() {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
DVLOGF(4);
// Changing the state to flushing will abort any pending decodes.
decoder_client_state_ = VideoDecoderClientState::kFlushing;
decoder_->Flush();
event_cb_.Run(VideoPlayerEvent::kFlushing);
}
void VideoDecoderClient::ResetTask() {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_sequence_checker_);
DVLOGF(4);
// Changing the state to resetting will abort any pending decodes.
decoder_client_state_ = VideoDecoderClientState::kResetting;
// TODO(dstaessens@) Allow resetting to any point in the stream.
encoded_data_helper_->Rewind();
decoder_->Reset();
event_cb_.Run(VideoPlayerEvent::kResetting);
}
void VideoDecoderClient::ReusePictureBufferTask(int32_t picture_buffer_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_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);
}
int32_t VideoDecoderClient::GetNextBitstreamBufferId() {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_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 VideoDecoderClient::GetNextPictureBufferId() {
DCHECK_CALLED_ON_VALID_SEQUENCE(decoder_client_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