blob: b05b9b2ce8dade61be8ac1cbe15969a5677df337 [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_frame_validator.h"
#include "base/bind.h"
#include "base/files/file.h"
#include "base/md5.h"
#include "base/memory/ptr_util.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/stringprintf.h"
#include "media/base/video_frame.h"
#include "media/gpu/test/video_decode_accelerator_unittest_helpers.h"
#include "media/gpu/test/video_frame_mapper.h"
#include "media/gpu/test/video_frame_mapper_factory.h"
namespace media {
namespace test {
// static
std::unique_ptr<VideoFrameValidator> VideoFrameValidator::Create(
const std::vector<std::string>& expected_frame_checksums) {
auto video_frame_mapper = VideoFrameMapperFactory::CreateMapper();
if (!video_frame_mapper) {
LOG(ERROR) << "Failed to create VideoFrameMapper.";
return nullptr;
}
auto video_frame_validator = base::WrapUnique(new VideoFrameValidator(
VideoFrameValidator::CHECK, base::FilePath(), expected_frame_checksums,
std::move(video_frame_mapper)));
if (!video_frame_validator->Initialize()) {
LOG(ERROR) << "Failed to initialize VideoFrameValidator.";
return nullptr;
}
return video_frame_validator;
}
// static
std::unique_ptr<VideoFrameValidator> VideoFrameValidator::Create(
uint32_t flags,
const base::FilePath& prefix_output_yuv,
const std::vector<std::string>& expected_frame_checksums) {
if ((flags & VideoFrameValidator::OUTPUTYUV) && prefix_output_yuv.empty()) {
LOG(ERROR) << "Prefix of yuv files isn't specified with dump flags.";
return nullptr;
}
auto video_frame_mapper = VideoFrameMapperFactory::CreateMapper();
if (!video_frame_mapper) {
LOG(ERROR) << "Failed to create VideoFrameMapper.";
return nullptr;
}
auto video_frame_validator = base::WrapUnique(new VideoFrameValidator(
flags, prefix_output_yuv, std::move(expected_frame_checksums),
std::move(video_frame_mapper)));
if (!video_frame_validator->Initialize()) {
LOG(ERROR) << "Failed to initialize VideoFrameValidator.";
return nullptr;
}
return video_frame_validator;
}
VideoFrameValidator::VideoFrameValidator(
uint32_t flags,
const base::FilePath& prefix_output_yuv,
std::vector<std::string> expected_frame_checksums,
std::unique_ptr<VideoFrameMapper> video_frame_mapper)
: flags_(flags),
prefix_output_yuv_(prefix_output_yuv),
expected_frame_checksums_(std::move(expected_frame_checksums)),
video_frame_mapper_(std::move(video_frame_mapper)),
num_frames_validating_(0),
frame_validator_thread_("FrameValidatorThread"),
frame_validator_cv_(&frame_validator_lock_) {
DETACH_FROM_SEQUENCE(validator_sequence_checker_);
DETACH_FROM_SEQUENCE(validator_thread_sequence_checker_);
}
VideoFrameValidator::~VideoFrameValidator() {
Destroy();
}
bool VideoFrameValidator::Initialize() {
if (!frame_validator_thread_.Start()) {
LOG(ERROR) << "Failed to start frame validator thread";
return false;
}
return true;
}
void VideoFrameValidator::Destroy() {
frame_validator_thread_.Stop();
base::AutoLock auto_lock(frame_validator_lock_);
DCHECK_EQ(0u, num_frames_validating_);
}
const std::vector<std::string>& VideoFrameValidator::GetFrameChecksums() const {
return frame_checksums_;
}
std::vector<VideoFrameValidator::MismatchedFrameInfo>
VideoFrameValidator::GetMismatchedFramesInfo() const {
base::AutoLock auto_lock(frame_validator_lock_);
return mismatched_frames_;
}
size_t VideoFrameValidator::GetMismatchedFramesCount() const {
base::AutoLock auto_lock(frame_validator_lock_);
return mismatched_frames_.size();
}
void VideoFrameValidator::ProcessVideoFrame(
scoped_refptr<const VideoFrame> video_frame,
size_t frame_index) {
DCHECK_CALLED_ON_VALID_SEQUENCE(validator_sequence_checker_);
base::AutoLock auto_lock(frame_validator_lock_);
num_frames_validating_++;
// Unretained is safe here, as we should not destroy the validator while there
// are still frames being validated.
frame_validator_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VideoFrameValidator::ProcessVideoFrameTask,
base::Unretained(this), video_frame, frame_index));
}
bool VideoFrameValidator::WaitUntilDone() {
base::AutoLock auto_lock(frame_validator_lock_);
while (num_frames_validating_ > 0) {
frame_validator_cv_.Wait();
}
if (mismatched_frames_.size() > 0u) {
LOG(ERROR) << mismatched_frames_.size() << " frames failed to validate.";
return false;
}
return true;
}
void VideoFrameValidator::ProcessVideoFrameTask(
const scoped_refptr<const VideoFrame> video_frame,
size_t frame_index) {
DCHECK_CALLED_ON_VALID_SEQUENCE(validator_thread_sequence_checker_);
auto standard_frame = CreateStandardizedFrame(video_frame);
if (!standard_frame) {
return;
}
std::string computed_md5 = ComputeMD5FromVideoFrame(standard_frame);
base::AutoLock auto_lock(frame_validator_lock_);
frame_checksums_.push_back(computed_md5);
if (flags_ & Flags::CHECK) {
LOG_IF(FATAL, frame_index >= expected_frame_checksums_.size())
<< "Frame number is over than the number of read md5 values in file.";
const auto& expected_md5 = expected_frame_checksums_[frame_index];
if (computed_md5 != expected_md5) {
mismatched_frames_.push_back(
MismatchedFrameInfo{frame_index, computed_md5, expected_md5});
}
}
if (flags_ & Flags::OUTPUTYUV) {
LOG_IF(WARNING, !WriteI420ToFile(frame_index, standard_frame.get()))
<< "Failed to write yuv into file.";
}
num_frames_validating_--;
frame_validator_cv_.Signal();
}
scoped_refptr<VideoFrame> VideoFrameValidator::CreateStandardizedFrame(
scoped_refptr<const VideoFrame> video_frame) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(validator_thread_sequence_checker_);
auto mapped_frame = video_frame_mapper_->Map(std::move(video_frame));
if (!mapped_frame) {
LOG(FATAL) << "Failed to map decoded picture.";
return nullptr;
}
return ConvertVideoFrame(mapped_frame.get(), PIXEL_FORMAT_I420);
}
std::string VideoFrameValidator::ComputeMD5FromVideoFrame(
scoped_refptr<VideoFrame> video_frame) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(validator_thread_sequence_checker_);
base::MD5Context context;
base::MD5Init(&context);
VideoFrame::HashFrameForTesting(&context, *video_frame.get());
base::MD5Digest digest;
base::MD5Final(&digest, &context);
return MD5DigestToBase16(digest);
}
// TODO(dstaessens@) Move to video_frame_writer.h
bool VideoFrameValidator::WriteI420ToFile(
size_t frame_index,
const VideoFrame* const video_frame) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(validator_thread_sequence_checker_);
if (video_frame->format() != PIXEL_FORMAT_I420) {
LOG(ERROR) << "No I420 format frame.";
return false;
}
if (video_frame->storage_type() !=
VideoFrame::StorageType::STORAGE_OWNED_MEMORY) {
LOG(ERROR) << "Video frame doesn't own memory.";
return false;
}
const int width = video_frame->visible_rect().width();
const int height = video_frame->visible_rect().height();
base::FilePath::StringType output_yuv_fname;
base::SStringPrintf(&output_yuv_fname,
FILE_PATH_LITERAL("%04zu_%dx%d_I420.yuv"), frame_index,
width, height);
base::File yuv_file(prefix_output_yuv_.AddExtension(output_yuv_fname),
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_APPEND);
const size_t num_planes = VideoFrame::NumPlanes(video_frame->format());
for (size_t i = 0; i < num_planes; i++) {
size_t plane_w = VideoFrame::Columns(i, video_frame->format(), width);
size_t plane_h = VideoFrame::Rows(i, video_frame->format(), height);
int data_size = base::checked_cast<int>(plane_w * plane_h);
const uint8_t* data = video_frame->data(i);
if (yuv_file.Write(0, reinterpret_cast<const char*>(data), data_size) !=
data_size) {
LOG(ERROR) << "Fail to write file in plane #" << i;
return false;
}
}
return true;
}
} // namespace test
} // namespace media