| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <va/va.h> |
| |
| #include <iostream> |
| #include <fstream> |
| #include <sstream> |
| #include <string> |
| |
| #include "base/command_line.h" |
| #include "base/files/memory_mapped_file.h" |
| #include "base/logging.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "build/chromeos_buildflags.h" |
| #include "media/filters/ivf_parser.h" |
| #include "media/gpu/vaapi/test/av1_decoder.h" |
| #include "media/gpu/vaapi/test/h264_decoder.h" |
| #if BUILDFLAG(ENABLE_PLATFORM_HEVC) |
| #include "media/gpu/vaapi/test/h265_decoder.h" |
| #endif |
| #include "media/gpu/vaapi/test/shared_va_surface.h" |
| #include "media/gpu/vaapi/test/vaapi_device.h" |
| #include "media/gpu/vaapi/test/video_decoder.h" |
| #include "media/gpu/vaapi/test/vp8_decoder.h" |
| #include "media/gpu/vaapi/test/vp9_decoder.h" |
| #include "media/gpu/vaapi/va_stubs.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| using media::vaapi_test::Av1Decoder; |
| using media::vaapi_test::H264Decoder; |
| #if BUILDFLAG(ENABLE_PLATFORM_HEVC) |
| using media::vaapi_test::H265Decoder; |
| #endif |
| using media::vaapi_test::SharedVASurface; |
| using media::vaapi_test::VaapiDevice; |
| using media::vaapi_test::VideoDecoder; |
| using media::vaapi_test::Vp8Decoder; |
| using media::vaapi_test::Vp9Decoder; |
| using media_gpu_vaapi::InitializeStubs; |
| using media_gpu_vaapi::kModuleVa; |
| using media_gpu_vaapi::kModuleVa_drm; |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| using media_gpu_vaapi::kModuleVa_prot; |
| #endif |
| using media_gpu_vaapi::StubPathMap; |
| |
| namespace { |
| |
| constexpr char kUsageMsg[] = |
| "usage: decode_test\n" |
| " --video=<video path>\n" |
| " --codec=<codec name>\n" |
| " [--frames=<number of frames to decode>]\n" |
| " [--fetch=<derive|get>]\n" |
| " [--out-prefix=<path prefix of decoded frame PNGs>]\n" |
| " [--md5[=<checksum path>]]\n" |
| " [--visible]\n" |
| " [--loop[=<n>]]\n" |
| " [--v=<log verbosity>]\n" |
| " [--help]\n"; |
| |
| constexpr char kHelpMsg[] = |
| "This binary decodes the IVF video in <video> path with specified video\n" |
| "<profile> via thinly wrapped libva calls.\n" |
| #if BUILDFLAG(ENABLE_PLATFORM_HEVC) |
| "Supported codecs: VP8, VP9 (profiles 0, 2), AV1 (profile 0), H264, and\n" |
| "H265.\n" |
| #else |
| "Supported codecs: VP8, VP9 (profiles 0, 2), AV1 (profile 0), H264.\n" |
| #endif |
| "\nThe following arguments are supported:\n" |
| " --video=<path>\n" |
| " Required. Path to IVF-formatted or HEVC-formatted video.\n" |
| #if BUILDFLAG(ENABLE_PLATFORM_HEVC) |
| " --codec=<codec name>\n" |
| " Required. The case-insensitive name of the codec to be used for\n" |
| " decoding. Valid codec names are VP8, VP9, AV1, H264, and H265.\n" |
| #else |
| " --codec=<codec name>\n" |
| " Required. The case-insensitive name of the codec to be used for\n" |
| " decoding. Valid codec names are VP8, VP9, AV1, and H264.\n" |
| #endif |
| " --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" |
| " --fetch=<derive|get>\n" |
| " Optional. If omitted, try to fetch VASurface data by any means.\n" |
| " Specifically, try, in order, vaDeriveImage, then if that fails,\n" |
| " vaCreateImage + vaGetImage. Otherwise, only attempt the\n" |
| " specified fetch policy.\n" |
| " NB: (b/201587517) AMD ignores this flag and always uses\n" |
| " vaGetImage.\n" |
| " --out-prefix=<string>\n" |
| " Optional. Save PNGs of decoded (and visible, if --visible is\n" |
| " specified) frames if and only if a path prefix (which may\n" |
| " specify a directory) is provided here, resulting in\n" |
| " e.g. frame_0.png, frame_1.png, etc. if passed \"frame\".\n" |
| " If specified along with --loop (see below), only saves the first\n" |
| " iteration of decoded frames.\n" |
| " If omitted, the output of this binary is error or lack thereof.\n" |
| " --md5[=<checksum path>]\n" |
| " Optional. If specified without a value, prints the md5 of each\n" |
| " decoded (and visible, if --visible is specified) frame in I420\n" |
| " format to stdout. If specified with a value, prints the md5 to\n" |
| " the specified value instead of stdout. Only supported when\n" |
| " vaDeriveImage() produces I420 and NV12 data for all frames\n" |
| " in the video.\n" |
| " --visible\n" |
| " Optional. If specified, applies post-decode processing (PNG\n" |
| " output, md5 hash) only to visible frames.\n" |
| " --loop[=<n>]\n" |
| " Optional. If specified with a value, loops decoding specified\n" |
| " times. If specified without, loops until terminated externally\n" |
| " or until an error occurs, at which point execution will\n" |
| " immediately terminate.\n" |
| " If specified with --frames, loops decoding that number of\n" |
| " leading frames. If specified with --out-prefix, loops decoding,\n" |
| " but only saves the first iteration of decoded frames.\n" |
| " --help\n" |
| " Display this help message and exit.\n"; |
| |
| // Creates the decoder for |stream_data| based on the user-provided |
| // value. If the user requests a valid codec that is not suitable |
| // for decoding |stream_data| the behavior will be undefined. Returns |
| // nullptr on failure. |
| std::unique_ptr<VideoDecoder> CreateDecoder( |
| const std::string& codec, |
| const VaapiDevice& va_device, |
| SharedVASurface::FetchPolicy fetch_policy, |
| const uint8_t* stream_data, |
| size_t stream_len) { |
| if (codec == "H264") |
| return std::make_unique<H264Decoder>(stream_data, stream_len, va_device, |
| fetch_policy); |
| #if BUILDFLAG(ENABLE_PLATFORM_HEVC) |
| if (codec == "H265") |
| return std::make_unique<H265Decoder>(stream_data, stream_len, va_device, |
| fetch_policy); |
| #endif |
| |
| auto ivf_parser = std::make_unique<media::IvfParser>(); |
| media::IvfFileHeader file_header{}; |
| if (!ivf_parser->Initialize(stream_data, stream_len, &file_header)) { |
| LOG(ERROR) << "Couldn't initialize IVF parser"; |
| return nullptr; |
| } |
| |
| if (codec == "AV1") |
| return std::make_unique<Av1Decoder>(std::move(ivf_parser), va_device, |
| fetch_policy); |
| if (codec == "VP8") |
| return std::make_unique<Vp8Decoder>(std::move(ivf_parser), va_device, |
| fetch_policy); |
| if (codec == "VP9") |
| return std::make_unique<Vp9Decoder>(std::move(ivf_parser), va_device, |
| fetch_policy); |
| |
| LOG(ERROR) << "Invalid codec requested: " << codec; |
| return nullptr; |
| } |
| |
| absl::optional<SharedVASurface::FetchPolicy> GetFetchPolicy( |
| const VaapiDevice& va_device, |
| const std::string& fetch_policy) { |
| // Always use kGetImage for AMD devices. |
| // TODO(b/201587517): remove this exception. |
| const std::string va_vendor_string = vaQueryVendorString(va_device.display()); |
| if (base::StartsWith(va_vendor_string, "Mesa Gallium driver", |
| base::CompareCase::SENSITIVE)) { |
| LOG(INFO) << "AMD driver detected, forcing vaGetImage"; |
| return SharedVASurface::FetchPolicy::kGetImage; |
| } |
| |
| if (fetch_policy.empty()) |
| return SharedVASurface::FetchPolicy::kAny; |
| if (base::EqualsCaseInsensitiveASCII(fetch_policy, "derive")) |
| return SharedVASurface::FetchPolicy::kDeriveImage; |
| if (base::EqualsCaseInsensitiveASCII(fetch_policy, "get")) |
| return SharedVASurface::FetchPolicy::kGetImage; |
| |
| LOG(ERROR) << "Unrecognized fetch policy " << fetch_policy; |
| return absl::nullopt; |
| } |
| |
| } // namespace |
| |
| 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 base::FilePath video_path = cmd->GetSwitchValuePath("video"); |
| if (video_path.empty()) { |
| LOG(ERROR) << "No input video path provided to decode."; |
| std::cerr << kUsageMsg; |
| return EXIT_FAILURE; |
| } |
| |
| const std::string codec = |
| base::ToUpperASCII(cmd->GetSwitchValueASCII("codec")); |
| if (codec.empty()) { |
| LOG(ERROR) << "No codec string was provided."; |
| std::cerr << kUsageMsg; |
| return EXIT_FAILURE; |
| } |
| |
| std::string output_prefix = cmd->GetSwitchValueASCII("out-prefix"); |
| |
| 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; |
| } |
| |
| std::ofstream md5_checksum_log; |
| const std::string md5_checksum_log_path = cmd->GetSwitchValueASCII("md5"); |
| if (!md5_checksum_log_path.empty()) { |
| md5_checksum_log.open(md5_checksum_log_path); |
| if (!md5_checksum_log.is_open()) { |
| LOG(ERROR) << "Could not open " << md5_checksum_log_path << " for writing"; |
| return EXIT_FAILURE; |
| } |
| } |
| |
| // Initialize VA stubs. |
| StubPathMap paths; |
| const std::string va_suffix(base::NumberToString(VA_MAJOR_VERSION + 1)); |
| paths[kModuleVa].push_back(std::string("libva.so.") + va_suffix); |
| paths[kModuleVa_drm].push_back(std::string("libva-drm.so.") + va_suffix); |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| paths[kModuleVa_prot].push_back(std::string("libva.so.") + va_suffix); |
| #endif |
| if (!InitializeStubs(paths)) { |
| LOG(ERROR) << "Failed to initialize VA stubs"; |
| return EXIT_FAILURE; |
| } |
| |
| // Set up video stream. |
| base::MemoryMappedFile stream; |
| if (!stream.Initialize(video_path)) { |
| LOG(ERROR) << "Couldn't open file: " << video_path; |
| return EXIT_FAILURE; |
| } |
| |
| const VaapiDevice va_device; |
| const bool loop_decode = cmd->HasSwitch("loop"); |
| const std::string loops = cmd->GetSwitchValueASCII("loop"); |
| int n_loops; |
| if (loops.empty()) { |
| n_loops = 0; |
| } else if (!base::StringToInt(loops, &n_loops) || n_loops <= 0) { |
| LOG(ERROR) << "Number of times to loop decode must be positive integer, " |
| << "got " << frames; |
| return EXIT_FAILURE; |
| } |
| bool first_loop = true; |
| |
| const auto fetch_policy = |
| GetFetchPolicy(va_device, cmd->GetSwitchValueASCII("fetch")); |
| if (!fetch_policy) { |
| return EXIT_FAILURE; |
| } |
| |
| do { |
| const std::unique_ptr<VideoDecoder> dec = CreateDecoder( |
| codec, va_device, *fetch_policy, stream.data(), stream.length()); |
| 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++) { |
| const VideoDecoder::Result res = dec->DecodeNextFrame(); |
| |
| if (res == VideoDecoder::kEOStream) { |
| LOG(INFO) << "End of stream."; |
| break; |
| } |
| |
| if (cmd->HasSwitch("visible") && !dec->LastDecodedFrameVisible()) |
| continue; |
| |
| if (!output_prefix.empty() && first_loop) { |
| dec->LastDecodedFrameToPNG( |
| base::StringPrintf("%s_%d.png", output_prefix.c_str(), i)); |
| } |
| if (cmd->HasSwitch("md5")) { |
| if (md5_checksum_log.is_open()) |
| md5_checksum_log << dec->LastDecodedFrameMD5Sum() << std::endl; |
| else |
| std::cout << dec->LastDecodedFrameMD5Sum() << std::endl; |
| } |
| } |
| |
| first_loop = false; |
| n_loops--; |
| } while (loop_decode && (loops.empty() || n_loops > 0)); |
| |
| LOG(INFO) << "Done reading."; |
| |
| // Closes log file if opened. |
| if (md5_checksum_log.is_open()) |
| md5_checksum_log.close(); |
| |
| return EXIT_SUCCESS; |
| } |