blob: 9129f301611da3a0003c6cdef74dfaef4fbe88a1 [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_frame_file_writer.h"
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "media/gpu/video_frame_mapper.h"
#include "media/gpu/video_frame_mapper_factory.h"
#include "ui/gfx/codec/png_codec.h"
namespace media {
namespace test {
VideoFrameFileWriter::VideoFrameFileWriter(const base::FilePath& output_folder,
OutputFormat output_format)
: output_folder_(output_folder),
output_format_(output_format),
num_frames_writing_(0),
frame_writer_thread_("FrameWriterThread"),
frame_writer_cv_(&frame_writer_lock_) {
DETACH_FROM_SEQUENCE(writer_sequence_checker_);
DETACH_FROM_SEQUENCE(writer_thread_sequence_checker_);
}
VideoFrameFileWriter::~VideoFrameFileWriter() {
base::AutoLock auto_lock(frame_writer_lock_);
DCHECK_EQ(0u, num_frames_writing_);
frame_writer_thread_.Stop();
}
// static
std::unique_ptr<VideoFrameFileWriter> VideoFrameFileWriter::Create(
const base::FilePath& output_folder,
OutputFormat output_format) {
// If the directory is not absolute, consider it relative to our working dir.
base::FilePath resolved_output_folder(output_folder);
if (!resolved_output_folder.IsAbsolute()) {
resolved_output_folder =
base::MakeAbsoluteFilePath(
base::FilePath(base::FilePath::kCurrentDirectory))
.Append(resolved_output_folder);
}
// Create the directory tree if it doesn't exist yet.
if (!DirectoryExists(resolved_output_folder)) {
base::CreateDirectory(resolved_output_folder);
}
auto frame_file_writer = base::WrapUnique(
new VideoFrameFileWriter(resolved_output_folder, output_format));
if (!frame_file_writer->Initialize()) {
LOG(ERROR) << "Failed to initialize VideoFrameFileWriter";
return nullptr;
}
return frame_file_writer;
}
bool VideoFrameFileWriter::Initialize() {
if (!frame_writer_thread_.Start()) {
LOG(ERROR) << "Failed to start file writer thread";
return false;
}
return true;
}
void VideoFrameFileWriter::ProcessVideoFrame(
scoped_refptr<const VideoFrame> video_frame,
size_t frame_index) {
base::AutoLock auto_lock(frame_writer_lock_);
num_frames_writing_++;
// Unretained is safe here, as we should not destroy the writer while there
// are still frames being written.
frame_writer_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VideoFrameFileWriter::ProcessVideoFrameTask,
base::Unretained(this), video_frame, frame_index));
}
bool VideoFrameFileWriter::WaitUntilDone() {
base::AutoLock auto_lock(frame_writer_lock_);
while (num_frames_writing_ > 0) {
frame_writer_cv_.Wait();
}
return true;
}
void VideoFrameFileWriter::ProcessVideoFrameTask(
scoped_refptr<const VideoFrame> video_frame,
size_t frame_index) {
DCHECK_CALLED_ON_VALID_SEQUENCE(writer_thread_sequence_checker_);
base::FilePath::StringType filename;
const gfx::Size& visible_size = video_frame->visible_rect().size();
base::SStringPrintf(&filename, FILE_PATH_LITERAL("frame_%04zu_%dx%d"),
frame_index, visible_size.width(), visible_size.height());
// Create VideoFrameMapper if not yet created. The decoder's output pixel
// format is not known yet when creating the VideoFrameWriter. We can only
// create the VideoFrameMapper upon receiving the first video frame.
if (!video_frame_mapper_) {
video_frame_mapper_ =
VideoFrameMapperFactory::CreateMapper(video_frame->format());
LOG_ASSERT(video_frame_mapper_) << "Failed to create VideoFrameMapper";
}
switch (output_format_) {
case OutputFormat::kPNG:
WriteVideoFramePNG(video_frame, base::FilePath(filename));
break;
case OutputFormat::kYUV:
WriteVideoFrameYUV(video_frame, base::FilePath(filename));
break;
}
base::AutoLock auto_lock(frame_writer_lock_);
num_frames_writing_--;
frame_writer_cv_.Signal();
}
void VideoFrameFileWriter::WriteVideoFramePNG(
scoped_refptr<const VideoFrame> video_frame,
const base::FilePath& filename) {
DCHECK_CALLED_ON_VALID_SEQUENCE(writer_thread_sequence_checker_);
DCHECK(video_frame_mapper_);
auto mapped_frame = video_frame_mapper_->Map(std::move(video_frame));
if (!mapped_frame) {
LOG(ERROR) << "Failed to map video frame";
return;
}
scoped_refptr<VideoFrame> argb_out_frame = mapped_frame;
if (argb_out_frame->format() != PIXEL_FORMAT_ARGB) {
argb_out_frame = ConvertVideoFrame(argb_out_frame.get(),
VideoPixelFormat::PIXEL_FORMAT_ARGB);
}
// Convert the ARGB frame to PNG.
std::vector<uint8_t> png_output;
const bool png_encode_status = gfx::PNGCodec::Encode(
argb_out_frame->data(VideoFrame::kARGBPlane), gfx::PNGCodec::FORMAT_BGRA,
argb_out_frame->visible_rect().size(),
argb_out_frame->stride(VideoFrame::kARGBPlane),
true, /* discard_transparency */
std::vector<gfx::PNGCodec::Comment>(), &png_output);
LOG_ASSERT(png_encode_status);
// Write the PNG data to file.
base::FilePath file_path(
output_folder_.Append(filename).AddExtension(FILE_PATH_LITERAL(".png")));
const int size = base::checked_cast<int>(png_output.size());
const int bytes_written = base::WriteFile(
file_path, reinterpret_cast<char*>(png_output.data()), size);
LOG_ASSERT(bytes_written == size);
}
void VideoFrameFileWriter::WriteVideoFrameYUV(
scoped_refptr<const VideoFrame> video_frame,
const base::FilePath& filename) {
DCHECK_CALLED_ON_VALID_SEQUENCE(writer_thread_sequence_checker_);
DCHECK(video_frame_mapper_);
auto mapped_frame = video_frame_mapper_->Map(std::move(video_frame));
if (!mapped_frame) {
LOG(ERROR) << "Failed to map video frame";
return;
}
scoped_refptr<VideoFrame> I420_out_frame = mapped_frame;
if (I420_out_frame->format() != PIXEL_FORMAT_I420) {
I420_out_frame = ConvertVideoFrame(I420_out_frame.get(),
VideoPixelFormat::PIXEL_FORMAT_I420);
}
// Write the YUV data to file.
base::FilePath file_path(
output_folder_.Append(filename)
.AddExtension(FILE_PATH_LITERAL(".yuv"))
.InsertBeforeExtension(FILE_PATH_LITERAL("_I420")));
base::File yuv_file(file_path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
const gfx::Size visible_size = I420_out_frame->visible_rect().size();
const VideoPixelFormat pixel_format = I420_out_frame->format();
const size_t num_planes = VideoFrame::NumPlanes(pixel_format);
for (size_t i = 0; i < num_planes; i++) {
const uint8_t* data = I420_out_frame->data(i);
const int stride = I420_out_frame->stride(i);
const size_t rows =
VideoFrame::Rows(i, pixel_format, visible_size.height());
const int row_bytes =
VideoFrame::RowBytes(i, pixel_format, visible_size.width());
LOG_ASSERT(stride > 0);
for (size_t row = 0; row < rows; ++row) {
if (yuv_file.WriteAtCurrentPos(
reinterpret_cast<const char*>(data + (stride * row)),
row_bytes) != row_bytes) {
LOG(ERROR) << "Failed to write plane #" << i << " to file: "
<< base::File::ErrorToString(base::File::GetLastFileError());
}
}
}
}
} // namespace test
} // namespace media