blob: e54d4aa51dadfc546544ab83b912a2a7d257eaf3 [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/image_processor/image_processor_client.h"
#include <utility>
#include "third_party/libyuv/include/libyuv/planar_functions.h"
#include "base/bind_helpers.h"
#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/synchronization/waitable_event.h"
#include "build/build_config.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/test_data_util.h"
#include "media/base/video_frame.h"
#include "media/base/video_frame_layout.h"
#include "media/gpu/image_processor_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect.h"
namespace media {
namespace test {
namespace {
// TODO(crbug.com/917951): Move these functions to video_frame_helpers.h
// Find the file path for |file_name|.
base::FilePath GetFilePath(const base::FilePath::CharType* const file_name) {
// 1. Try to find |file_name| in the current directory.
base::FilePath file_path =
base::FilePath(base::FilePath::kCurrentDirectory).Append(file_name);
if (base::PathExists(file_path)) {
return file_path;
}
// 2. Try media::GetTestDataFilePath(|file_name|), that is,
// media/test/data/file_name. This is mainly for Try bot.
file_path = media::GetTestDataPath().Append(file_name);
LOG_ASSERT(base::PathExists(file_path)) << " Cannot find " << file_name;
return file_path;
}
// Copy |src_frame| into a new VideoFrame with |dst_layout|. The created
// VideoFrame's content is the same as |src_frame|. Returns nullptr on failure.
scoped_refptr<VideoFrame> CloneVideoFrameWithLayout(
const VideoFrame* const src_frame,
const VideoFrameLayout& dst_layout) {
LOG_ASSERT(src_frame->IsMappable());
LOG_ASSERT(src_frame->format() == dst_layout.format());
// Create VideoFrame, which allocates and owns data.
auto dst_frame = VideoFrame::CreateFrameWithLayout(
dst_layout, src_frame->visible_rect(), src_frame->natural_size(),
src_frame->timestamp(), false /* zero_initialize_memory*/);
if (!dst_frame) {
LOG(ERROR) << "Failed to create VideoFrame";
return nullptr;
}
// Copy every plane's content from |src_frame| to |dst_frame|.
const size_t num_planes = VideoFrame::NumPlanes(dst_layout.format());
LOG_ASSERT(dst_layout.planes().size() == num_planes);
LOG_ASSERT(src_frame->layout().planes().size() == num_planes);
for (size_t i = 0; i < num_planes; ++i) {
libyuv::CopyPlane(
src_frame->data(i), src_frame->layout().planes()[i].stride,
dst_frame->data(i), dst_frame->layout().planes()[i].stride,
VideoFrame::Columns(i, dst_frame->format(),
dst_frame->natural_size().width()),
VideoFrame::Rows(i, dst_frame->format(),
dst_frame->natural_size().height()));
}
return dst_frame;
}
// Create VideoFrame from |info| loading |info.file_name|. Return nullptr on
// failure.
scoped_refptr<VideoFrame> ReadVideoFrame(const VideoImageInfo& info) {
auto path = GetFilePath(info.file_name);
// First read file.
auto mapped_file = std::make_unique<base::MemoryMappedFile>();
if (!mapped_file->Initialize(path)) {
LOG(ERROR) << "Failed to read file: " << path;
return nullptr;
}
const auto format = info.pixel_format;
const auto visible_size = info.visible_size;
// Check the file length and md5sum.
LOG_ASSERT(mapped_file->length() ==
VideoFrame::AllocationSize(format, visible_size));
base::MD5Digest digest;
base::MD5Sum(mapped_file->data(), mapped_file->length(), &digest);
LOG_ASSERT(base::MD5DigestToBase16(digest) == info.md5sum);
// Create planes for layout. We cannot use WrapExternalData() because it calls
// GetDefaultLayout() and it supports only a few pixel formats.
const size_t num_planes = VideoFrame::NumPlanes(format);
std::vector<VideoFrameLayout::Plane> planes(num_planes);
const auto strides = VideoFrame::ComputeStrides(format, visible_size);
size_t offset = 0;
for (size_t i = 0; i < num_planes; ++i) {
planes[i].stride = strides[i];
planes[i].offset = offset;
offset += VideoFrame::PlaneSize(format, i, visible_size).GetArea();
}
auto layout = VideoFrameLayout::CreateWithPlanes(
format, visible_size, std::move(planes), {mapped_file->length()});
if (!layout) {
LOG(ERROR) << "Failed to create VideoFrameLayout";
return nullptr;
}
auto frame = VideoFrame::WrapExternalDataWithLayout(
*layout, gfx::Rect(visible_size), visible_size, mapped_file->data(),
mapped_file->length(), base::TimeDelta());
if (!frame) {
LOG(ERROR) << "Failed to create VideoFrame";
return nullptr;
}
// Automatically unmap the memory mapped file when the video frame is
// destroyed.
frame->AddDestructionObserver(base::BindOnce(
base::DoNothing::Once<std::unique_ptr<base::MemoryMappedFile>>(),
std::move(mapped_file)));
return frame;
}
} // namespace
// static
std::unique_ptr<ImageProcessorClient> ImageProcessorClient::Create(
const ImageProcessor::PortConfig& input_config,
const ImageProcessor::PortConfig& output_config,
size_t num_buffers,
bool store_processed_video_frames) {
auto ip_client =
base::WrapUnique(new ImageProcessorClient(store_processed_video_frames));
if (!ip_client->CreateImageProcessor(input_config, output_config,
num_buffers)) {
LOG(ERROR) << "Failed to create ImageProcessor";
return nullptr;
}
return ip_client;
}
ImageProcessorClient::ImageProcessorClient(bool store_processed_video_frames)
: image_processor_client_thread_("ImageProcessorClientThread"),
store_processed_video_frames_(store_processed_video_frames),
output_cv_(&output_lock_),
num_processed_frames_(0),
image_processor_error_count_(0) {
CHECK(image_processor_client_thread_.Start());
DETACH_FROM_THREAD(image_processor_client_thread_checker_);
}
ImageProcessorClient::~ImageProcessorClient() {
DCHECK_CALLED_ON_VALID_THREAD(test_main_thread_checker_);
CHECK(image_processor_client_thread_.IsRunning());
// Destroys |image_processor_| on |image_processor_client_thread_|.
image_processor_client_thread_.task_runner()->DeleteSoon(
FROM_HERE, image_processor_.release());
image_processor_client_thread_.Stop();
}
bool ImageProcessorClient::CreateImageProcessor(
const ImageProcessor::PortConfig& input_config,
const ImageProcessor::PortConfig& output_config,
size_t num_buffers) {
DCHECK_CALLED_ON_VALID_THREAD(test_main_thread_checker_);
base::WaitableEvent done(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
// base::Unretained(this) and base::ConstRef() are safe here because |this|,
// |input_config| and |output_config| must outlive because this task is
// blocking.
image_processor_client_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&ImageProcessorClient::CreateImageProcessorTask,
base::Unretained(this), base::ConstRef(input_config),
base::ConstRef(output_config), num_buffers, &done));
done.Wait();
if (!image_processor_) {
LOG(ERROR) << "Failed to create ImageProcessor";
return false;
}
return true;
}
void ImageProcessorClient::CreateImageProcessorTask(
const ImageProcessor::PortConfig& input_config,
const ImageProcessor::PortConfig& output_config,
size_t num_buffers,
base::WaitableEvent* done) {
DCHECK_CALLED_ON_VALID_THREAD(image_processor_client_thread_checker_);
// base::Unretained(this) for ErrorCB is safe here because the callback is
// executed on |image_processor_client_thread_| which is owned by this class.
image_processor_ = ImageProcessorFactory::Create(
input_config, output_config, {ImageProcessor::OutputMode::IMPORT},
num_buffers,
base::BindRepeating(&ImageProcessorClient::NotifyError,
base::Unretained(this)));
done->Signal();
}
scoped_refptr<VideoFrame> ImageProcessorClient::CreateInputFrame(
const VideoImageInfo& input_image_info) const {
DCHECK_CALLED_ON_VALID_THREAD(test_main_thread_checker_);
LOG_ASSERT(image_processor_);
auto mapped_frame = ReadVideoFrame(input_image_info);
const auto& input_layout = image_processor_->input_layout();
if (VideoFrame::IsStorageTypeMappable(
image_processor_->input_storage_type())) {
return CloneVideoFrameWithLayout(mapped_frame.get(), input_layout);
} else {
#if defined(OS_CHROMEOS)
LOG_ASSERT(image_processor_->input_storage_type() ==
VideoFrame::STORAGE_DMABUFS);
#endif
// TODO(crbug.com/917951): Support Dmabuf.
NOTIMPLEMENTED();
return nullptr;
}
}
scoped_refptr<VideoFrame> ImageProcessorClient::CreateOutputFrame(
const VideoImageInfo& output_image_info) const {
DCHECK_CALLED_ON_VALID_THREAD(test_main_thread_checker_);
LOG_ASSERT(image_processor_);
const auto& output_layout = image_processor_->output_layout();
if (VideoFrame::IsStorageTypeMappable(
image_processor_->input_storage_type())) {
return VideoFrame::CreateFrameWithLayout(
output_layout, gfx::Rect(output_image_info.visible_size),
output_image_info.visible_size, base::TimeDelta(),
false /* zero_initialize_memory*/
);
} else {
#if defined(OS_CHROMEOS)
LOG_ASSERT(image_processor_->input_storage_type() ==
VideoFrame::STORAGE_DMABUFS);
#endif
// TODO(crbug.com/917951): Support Dmabuf.
NOTIMPLEMENTED();
return nullptr;
}
}
void ImageProcessorClient::FrameReady(scoped_refptr<VideoFrame> frame) {
DCHECK_CALLED_ON_VALID_THREAD(image_processor_client_thread_checker_);
base::AutoLock auto_lock_(output_lock_);
if (store_processed_video_frames_)
output_video_frames_.push_back(std::move(frame));
num_processed_frames_++;
output_cv_.Signal();
}
bool ImageProcessorClient::WaitUntilNumImageProcessed(
size_t num_processed,
base::TimeDelta max_wait) {
base::TimeDelta time_waiting;
// NOTE: Acquire lock here does not matter, because
// base::ConditionVariable::TimedWait() unlocks output_lock_ at the start and
// locks again at the end.
base::AutoLock auto_lock_(output_lock_);
while (time_waiting < max_wait) {
const base::TimeTicks start_time = base::TimeTicks::Now();
output_cv_.TimedWait(max_wait);
time_waiting += base::TimeTicks::Now() - start_time;
if (num_processed_frames_ >= num_processed)
return true;
}
return false;
}
size_t ImageProcessorClient::GetNumOfProcessedImages() const {
base::AutoLock auto_lock_(output_lock_);
return num_processed_frames_;
}
std::vector<scoped_refptr<VideoFrame>>
ImageProcessorClient::GetProcessedImages() const {
LOG_ASSERT(store_processed_video_frames_);
base::AutoLock auto_lock_(output_lock_);
return output_video_frames_;
}
size_t ImageProcessorClient::GetErrorCount() const {
base::AutoLock auto_lock_(output_lock_);
return image_processor_error_count_;
}
void ImageProcessorClient::NotifyError() {
DCHECK_CALLED_ON_VALID_THREAD(image_processor_client_thread_checker_);
base::AutoLock auto_lock_(output_lock_);
image_processor_error_count_++;
}
void ImageProcessorClient::Process(const VideoImageInfo& input_info,
const VideoImageInfo& output_info) {
DCHECK_CALLED_ON_VALID_THREAD(test_main_thread_checker_);
auto input_frame = CreateInputFrame(input_info);
ASSERT_TRUE(input_frame);
auto output_frame = CreateOutputFrame(input_info);
ASSERT_TRUE(output_frame);
image_processor_client_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&ImageProcessorClient::ProcessTask, base::Unretained(this),
std::move(input_frame), std::move(output_frame)));
}
void ImageProcessorClient::ProcessTask(scoped_refptr<VideoFrame> input_frame,
scoped_refptr<VideoFrame> output_frame) {
DCHECK_CALLED_ON_VALID_THREAD(image_processor_client_thread_checker_);
// base::Unretained(this) and base::ConstRef() for FrameReadyCB is safe here
// because the callback is executed on |image_processor_client_thread_| which
// is owned by this class.
image_processor_->Process(
std::move(input_frame), std::move(output_frame),
BindToCurrentLoop(base::BindOnce(&ImageProcessorClient::FrameReady,
base::Unretained(this))));
}
} // namespace test
} // namespace media