blob: 28db99ae77832c7761f651fa9f3187839b99bb05 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <iostream>
#include <sstream>
#include <string>
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/hash/md5.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "media/gpu/v4l2/test/video_decoder.h"
#include "media/gpu/v4l2/test/vp8_decoder.h"
#include "media/gpu/v4l2/test/vp9_decoder.h"
#include "media/media_buildflags.h"
// AV1 stateless decoding not supported upstream yet
#if BUILDFLAG(IS_CHROMEOS)
#include "media/gpu/v4l2/test/av1_decoder.h"
#endif
#include "media/gpu/v4l2/test/h264_decoder.h"
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
#include "media/gpu/v4l2/test/h265_decoder.h"
#endif
// AV1 stateless decoding not supported upstream yet
#if BUILDFLAG(IS_CHROMEOS)
using media::v4l2_test::Av1Decoder;
#endif
using media::v4l2_test::H264Decoder;
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
using media::v4l2_test::H265Decoder;
#endif
using media::v4l2_test::VideoDecoder;
using media::v4l2_test::Vp8Decoder;
using media::v4l2_test::Vp9Decoder;
namespace {
constexpr char kUsageMsg[] =
"usage: v4l2_stateless_decoder\n"
" --video=<video path>\n"
" [--frames=<number of frames to decode>]\n"
" [--v=<log verbosity>]\n"
" [--output_format]=<output type of yuv or png>]\n"
" [--output_path_prefix=<output files path prefix>]\n"
" [--md5=<md_log_path>]\n"
" [--visible]\n"
" [--help]\n";
constexpr char kHelpMsg[] =
"This binary decodes the IVF video in <video> path with specified \n"
"video <profile> via thinly wrapped v4l2 calls.\n"
"Supported codecs: VP9 (profile 0), AV1 (profile 0), and H.265\n"
"\nThe following arguments are supported:\n"
" --video=<path>\n"
" Required. Path to IVF-formatted video to decode.\n"
" --frames=<int>\n"
" Optional. Number of frames to decode, defaults to all.\n"
" Override with a positive integer to decode at most that many.\n"
" --output_format=<str>\n"
" Optional. Output type for decoded frames, defaults to YUV.\n"
" YUV and PNG are supported.\n"
" --output_path_prefix=<path>\n"
" Optional. Prefix to the filepaths where raw YUV or PNG files\n"
" will be written. For example, setting <path> to \"test/test_\"\n"
" would result in output files of the form\n"
" \"test/test_000000.yuv\" \"test/test_000001.yuv\", etc.\n"
" --md5=<path>\n"
" Optional. If specified, computes md5 checksum. If specified\n"
" with argument, prints the md5 checksum of each decoded (and\n"
" visible, if --visible is specified) frame in I420 format\n"
" to specified file. Verbose level 2 will display md5 checksums.\n"
" --visible\n"
" Optional. If specified, computes md5 hash values only for\n"
" visible frames.\n"
" --help\n"
" Display this help message and exit.\n";
} // namespace
// Computes the md5 of given I420 data |yuv_plane| and prints the md5 to stdout.
// This functionality is needed for tast tests.
void ComputeAndPrintMD5hash(const std::vector<uint8_t>& yuv_plane,
const base::FilePath md5_log_location) {
base::MD5Digest md5_digest;
base::MD5Sum(yuv_plane, &md5_digest);
std::string md5_digest_b16 = MD5DigestToBase16(md5_digest);
if (!md5_log_location.empty()) {
if (!PathExists(md5_log_location))
WriteFile(md5_log_location, md5_digest_b16 + "\n");
else
AppendToFile(md5_log_location, md5_digest_b16 + "\n");
}
VLOG(2) << md5_digest_b16;
}
// Creates the appropriate decoder for |stream|, which points to IVF data.
// Returns nullptr on failure.
std::unique_ptr<VideoDecoder> CreateVideoDecoder(
const base::MemoryMappedFile& stream) {
CHECK(stream.IsValid());
std::unique_ptr<VideoDecoder> decoder;
// AV1 stateless decoding not supported upstream yet
#if BUILDFLAG(IS_CHROMEOS)
decoder = Av1Decoder::Create(stream);
#endif
if (!decoder)
decoder = Vp9Decoder::Create(stream);
if (!decoder)
decoder = H264Decoder::Create(stream);
if (!decoder)
decoder = Vp8Decoder::Create(stream);
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
if (!decoder) {
decoder = H265Decoder::Create(stream);
}
#endif
return decoder;
}
int main(int argc, char** argv) {
base::CommandLine::Init(argc, argv);
logging::LoggingSettings settings;
settings.logging_dest =
logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR;
logging::InitLogging(settings);
const base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
if (cmd->HasSwitch("help")) {
std::cout << kUsageMsg << "\n" << kHelpMsg;
return EXIT_SUCCESS;
}
const bool has_output_file = cmd->HasSwitch("output_path_prefix");
const std::string output_file_prefix =
cmd->GetSwitchValueASCII("output_path_prefix");
std::string output_format = "yuv";
if (has_output_file && cmd->HasSwitch("output_format")) {
output_format =
base::ToLowerASCII(cmd->GetSwitchValueASCII("output_format"));
if (output_format != "yuv" && output_format != "png") {
LOG(ERROR) << "Unsupported output format: " << output_format;
return EXIT_FAILURE;
}
}
const base::FilePath video_path = cmd->GetSwitchValuePath("video");
if (video_path.empty()) {
LOG(ERROR) << "No input video path provided to decode.\n" << kUsageMsg;
return EXIT_FAILURE;
}
const std::string frames = cmd->GetSwitchValueASCII("frames");
int n_frames;
if (frames.empty()) {
n_frames = 0;
} else if (!base::StringToInt(frames, &n_frames) || n_frames <= 0) {
LOG(ERROR) << "Number of frames to decode must be positive integer, got "
<< frames;
return EXIT_FAILURE;
}
const base::FilePath md5_log_location = cmd->GetSwitchValuePath("md5");
// Deletes md5 log file if it already exists.
if (PathExists(md5_log_location)) {
DeleteFile(md5_log_location);
}
// Set up video stream.
base::MemoryMappedFile stream;
if (!stream.Initialize(video_path)) {
LOG(ERROR) << "Couldn't open file: " << video_path;
return EXIT_FAILURE;
}
const std::unique_ptr<VideoDecoder> dec = CreateVideoDecoder(stream);
if (!dec) {
LOG(ERROR) << "Failed to create decoder for file: " << video_path;
return EXIT_FAILURE;
}
for (int i = 0; i < n_frames || n_frames == 0; i++) {
VLOG(1) << "Frame " << i << "...";
std::vector<uint8_t> y_plane;
std::vector<uint8_t> u_plane;
std::vector<uint8_t> v_plane;
gfx::Size size;
const VideoDecoder::Result res =
dec->DecodeNextFrame(i, y_plane, u_plane, v_plane, size);
if (res == VideoDecoder::kEOStream) {
VLOG(1) << "End of stream.";
break;
} else if (res == VideoDecoder::kError) {
LOG(ERROR) << "Unable to decode next frame.";
return EXIT_FAILURE;
}
if (cmd->HasSwitch("visible") && !dec->LastDecodedFrameVisible())
continue;
std::vector<uint8_t> yuv_plane(y_plane);
yuv_plane.insert(yuv_plane.end(), u_plane.begin(), u_plane.end());
yuv_plane.insert(yuv_plane.end(), v_plane.begin(), v_plane.end());
if (cmd->HasSwitch("md5"))
ComputeAndPrintMD5hash(yuv_plane, md5_log_location);
if (!has_output_file)
continue;
base::FilePath filename(base::StringPrintf(
"%s%.6d.%s", output_file_prefix.c_str(), i, output_format.c_str()));
base::File output_file(
filename, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (output_format == "yuv") {
output_file.Write(0, reinterpret_cast<const char*>(yuv_plane.data()),
yuv_plane.size());
} else {
std::vector<uint8_t> image_buffer = dec->ConvertYUVToPNG(
y_plane.data(), u_plane.data(), v_plane.data(), size);
output_file.Write(0, reinterpret_cast<char*>(image_buffer.data()),
image_buffer.size());
}
}
return EXIT_SUCCESS;
}