blob: 0fa43e198a4c656bd3ca95e0ad458a6843d83435 [file] [log] [blame]
// 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