blob: 8cb1f5d110dfebc81fdc93ba65371b6119ad9ffb [file] [log] [blame]
// Copyright 2020 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_encoder/bitstream_file_writer.h"
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "media/gpu/test/video_test_helpers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace test {
// A helper class to write each frame to disk.
class BitstreamFileWriter::FrameFileWriter {
public:
FrameFileWriter(std::unique_ptr<IvfWriter> ivf_writer)
: ivf_writer_(std::move(ivf_writer)) {}
FrameFileWriter(base::File output_file)
: output_file_(std::move(output_file)) {}
bool WriteFrame(uint32_t data_size, uint64_t timestamp, const uint8_t* data) {
if (ivf_writer_) {
return ivf_writer_->WriteFrame(data_size, timestamp, data);
}
// For H.264.
LOG_ASSERT(output_file_.IsValid());
return output_file_.WriteAtCurrentPos(reinterpret_cast<const char*>(data),
data_size) ==
static_cast<int>(data_size);
}
private:
const std::unique_ptr<IvfWriter> ivf_writer_;
base::File output_file_;
};
BitstreamFileWriter::BitstreamFileWriter(
std::unique_ptr<FrameFileWriter> frame_file_writer,
absl::optional<size_t> spatial_layer_index_to_write,
absl::optional<size_t> temporal_layer_index_to_write,
const std::vector<gfx::Size>& spatial_layer_resolutions)
: frame_file_writer_(std::move(frame_file_writer)),
spatial_layer_index_to_write_(spatial_layer_index_to_write),
temporal_layer_index_to_write_(temporal_layer_index_to_write),
spatial_layer_resolutions_(spatial_layer_resolutions),
num_buffers_writing_(0),
num_errors_(0),
writer_thread_("BitstreamFileWriterThread"),
writer_cv_(&writer_lock_) {
DETACH_FROM_SEQUENCE(writer_thread_sequence_checker_);
}
BitstreamFileWriter::~BitstreamFileWriter() {
base::AutoLock auto_lock(writer_lock_);
DCHECK_EQ(0u, num_buffers_writing_);
writer_thread_.Stop();
}
// static
std::unique_ptr<BitstreamFileWriter> BitstreamFileWriter::Create(
const base::FilePath& output_filepath,
VideoCodec codec,
const gfx::Size& resolution,
uint32_t frame_rate,
uint32_t num_frames,
absl::optional<size_t> spatial_layer_index_to_write,
absl::optional<size_t> temporal_layer_index_to_write,
const std::vector<gfx::Size>& spatial_layer_resolutions) {
std::unique_ptr<FrameFileWriter> frame_file_writer;
if (!base::DirectoryExists(output_filepath.DirName()))
base::CreateDirectory(output_filepath.DirName());
if (codec == VideoCodec::kH264) {
base::File output_file(output_filepath, base::File::FLAG_CREATE_ALWAYS |
base::File::FLAG_WRITE);
LOG_ASSERT(output_file.IsValid());
frame_file_writer =
std::make_unique<FrameFileWriter>(std::move(output_file));
} else {
auto ivf_writer = std::make_unique<IvfWriter>(output_filepath);
if (!ivf_writer->WriteFileHeader(codec, resolution, frame_rate,
num_frames)) {
LOG(ERROR) << "Failed writing ivf header";
return nullptr;
}
frame_file_writer =
std::make_unique<FrameFileWriter>(std::move(ivf_writer));
}
auto bitstream_file_writer = base::WrapUnique(new BitstreamFileWriter(
std::move(frame_file_writer), spatial_layer_index_to_write,
temporal_layer_index_to_write, spatial_layer_resolutions));
if (!bitstream_file_writer->writer_thread_.Start()) {
LOG(ERROR) << "Failed to start file writer thread";
return nullptr;
}
return bitstream_file_writer;
}
void BitstreamFileWriter::ConstructSpatialIndices(
const std::vector<gfx::Size>& spatial_layer_resolutions) {
SEQUENCE_CHECKER(validator_thread_sequence_checker_);
CHECK(!spatial_layer_resolutions.empty());
CHECK_LE(spatial_layer_resolutions.size(), spatial_layer_resolutions_.size());
original_spatial_indices_.resize(spatial_layer_resolutions.size());
auto begin = std::find(spatial_layer_resolutions_.begin(),
spatial_layer_resolutions_.end(),
spatial_layer_resolutions.front());
CHECK(begin != spatial_layer_resolutions_.end());
uint8_t sid_offset = begin - spatial_layer_resolutions_.begin();
for (size_t i = 0; i < spatial_layer_resolutions.size(); ++i) {
CHECK_LT(sid_offset + i, spatial_layer_resolutions_.size());
CHECK_EQ(spatial_layer_resolutions[i],
spatial_layer_resolutions_[sid_offset + i]);
original_spatial_indices_[i] = sid_offset + i;
}
}
void BitstreamFileWriter::ProcessBitstream(
scoped_refptr<BitstreamRef> bitstream,
size_t frame_index) {
if (spatial_layer_index_to_write_ && bitstream->metadata.vp9) {
const Vp9Metadata& metadata = *bitstream->metadata.vp9;
if (bitstream->metadata.key_frame)
ConstructSpatialIndices(metadata.spatial_layer_resolutions);
const uint8_t spatial_idx = original_spatial_indices_[metadata.spatial_idx];
if (spatial_idx > *spatial_layer_index_to_write_ ||
(spatial_idx < *spatial_layer_index_to_write_ &&
!metadata.referenced_by_upper_spatial_layers)) {
// Skip |bitstream| because it contains a frame not needed by desired
// spatial layers.
return;
}
}
if (temporal_layer_index_to_write_) {
uint8_t temporal_idx = 255;
if (bitstream->metadata.vp9)
temporal_idx = bitstream->metadata.vp9->temporal_idx;
else if (bitstream->metadata.h264)
temporal_idx = bitstream->metadata.h264->temporal_idx;
CHECK_NE(temporal_idx, 255) << "No metadata about temporal idx";
if (temporal_idx > *temporal_layer_index_to_write_) {
// Skip |bitstream| because it contains a frame in upper layers than
// layers to be saved.
return;
}
}
base::AutoLock auto_lock(writer_lock_);
num_buffers_writing_++;
writer_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&BitstreamFileWriter::WriteBitstreamTask,
base::Unretained(this), std::move(bitstream),
frame_index));
}
void BitstreamFileWriter::WriteBitstreamTask(
scoped_refptr<BitstreamRef> bitstream,
size_t frame_index) {
DCHECK_CALLED_ON_VALID_SEQUENCE(writer_thread_sequence_checker_);
const DecoderBuffer& buffer = *bitstream->buffer.get();
bool success = frame_file_writer_->WriteFrame(
static_cast<uint32_t>(buffer.data_size()),
static_cast<uint64_t>(frame_index), buffer.data());
base::AutoLock auto_lock(writer_lock_);
num_errors_ += !success;
num_buffers_writing_--;
writer_cv_.Signal();
}
bool BitstreamFileWriter::WaitUntilDone() {
base::AutoLock auto_lock(writer_lock_);
while (num_buffers_writing_ > 0u)
writer_cv_.Wait();
LOG_IF(ERROR, num_errors_ > 0u) << "Detected error count: " << num_errors_;
return num_errors_ == 0;
}
} // namespace test
} // namespace media