Skip to content

Commit

Permalink
media/gpu/test: Introduce VideoBitstream
Browse files Browse the repository at this point in the history
media::test::Video is too messy because it manages the bitstream
video data and the raw video data. We are going to decouple the class
to two classes, one for compressed video only and the other one for
the raw video only.
This CL introduces the media::test::VideoBitstream. It manages the
bitstream video.

Bug: b:282878791
Test: Build
Change-Id: Ia74bb889b0d6c9902f928351759eaff7a0565494
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4547033
Reviewed-by: Justin Green <greenjustin@google.com>
Auto-Submit: Hirokazu Honda <hiroh@chromium.org>
Commit-Queue: Hirokazu Honda <hiroh@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1150205}
  • Loading branch information
Hirokazu Honda authored and Chromium LUCI CQ committed May 29, 2023
1 parent f91001b commit e843295
Show file tree
Hide file tree
Showing 3 changed files with 323 additions and 0 deletions.
2 changes: 2 additions & 0 deletions media/gpu/test/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ source_set("helpers") {
"image_quality_metrics.h",
"video.cc",
"video.h",
"video_bitstream.cc",
"video_bitstream.h",
"video_frame_helpers.cc",
"video_frame_helpers.h",
]
Expand Down
226 changes: 226 additions & 0 deletions media/gpu/test/video_bitstream.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
// Copyright 2023 The Chromium Authors
// 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_bitstream.h"

#include "base/containers/contains.h"
#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace media::test {

namespace {
// Suffix appended to the video file path to get the metadata file path, if no
// explicit metadata file path was specified.
constexpr const base::FilePath::CharType* kMetadataSuffix =
FILE_PATH_LITERAL(".json");

// Converts the string to VideoCodecProfile. This returns absl::nullopt if
// it is not supported by video_decode_accelerator(_perf)_tests.
absl::optional<VideoCodecProfile> ConvertStringtoProfile(
const std::string& profile) {
if (profile == "H264PROFILE_BASELINE") {
return H264PROFILE_BASELINE;
} else if (profile == "H264PROFILE_MAIN") {
return H264PROFILE_MAIN;
} else if (profile == "H264PROFILE_HIGH") {
return H264PROFILE_HIGH;
} else if (profile == "VP8PROFILE_ANY") {
return VP8PROFILE_ANY;
} else if (profile == "VP9PROFILE_PROFILE0") {
return VP9PROFILE_PROFILE0;
} else if (profile == "VP9PROFILE_PROFILE2") {
return VP9PROFILE_PROFILE2;
} else if (profile == "AV1PROFILE_PROFILE_MAIN") {
return AV1PROFILE_PROFILE_MAIN;
} else if (profile == "HEVCPROFILE_MAIN") {
return HEVCPROFILE_MAIN;
} else if (profile == "HEVCPROFILE_MAIN10") {
return HEVCPROFILE_MAIN10;
} else {
LOG(ERROR) << profile << " is not supported";
return absl::nullopt;
}
}

// Loads the compressed video from |data_file_path|.
std::unique_ptr<base::MemoryMappedFile> LoadData(
const base::FilePath& data_file_path) {
auto memory_mapped_file = std::make_unique<base::MemoryMappedFile>();
if (!memory_mapped_file->Initialize(data_file_path,
base::MemoryMappedFile::READ_ONLY)) {
LOG(ERROR) << "Failed to read the file: " << data_file_path;
return nullptr;
}
return memory_mapped_file;
}
} // namespace

// Loads the metadata from |json_file_path|. The read values are filled into
// |metadata.|
// static
bool VideoBitstream::LoadMetadata(const base::FilePath& json_file_path,
Metadata& metadata) {
std::string json_data;
if (!base::ReadFileToString(json_file_path, &json_data)) {
return false;
}
auto metadata_result =
base::JSONReader::ReadAndReturnValueWithError(json_data);
if (!metadata_result.has_value()) {
LOG(ERROR) << "Failed to parse video metadata: " << json_file_path << ": "
<< metadata_result.error().message;
return false;
}
const base::Value::Dict& metadata_dict = metadata_result->GetDict();

const std::string* profile = metadata_dict.FindString("profile");
auto converted_profile = ConvertStringtoProfile(*profile);
if (!converted_profile) {
LOG(ERROR) << *profile << " is not supported";
return false;
}
metadata.profile = converted_profile.value();
metadata.codec = VideoCodecProfileToVideoCodec(metadata.profile);
CHECK_NE(metadata.codec, VideoCodec::kUnknown);

// Find the video's bit depth. This is optional.
absl::optional<int> bit_depth = metadata_dict.FindInt("bit_depth");
if (bit_depth.has_value()) {
metadata.bit_depth = base::checked_cast<uint8_t>(*bit_depth);
} else {
if (metadata.profile == VP9PROFILE_PROFILE2) {
LOG(ERROR) << "Bit depth is unspecified for VP9 profile 2";
return false;
}
constexpr uint8_t kDefaultBitDepth = 8u;
metadata.bit_depth = kDefaultBitDepth;
}

absl::optional<int> frame_rate = metadata_dict.FindInt("frame_rate");
if (!frame_rate.has_value()) {
LOG(ERROR) << "Key \"frame_rate\" is not found in " << json_file_path;
return false;
}
metadata.frame_rate = base::checked_cast<uint32_t>(*frame_rate);

absl::optional<int> num_frames = metadata_dict.FindInt("num_frames");
if (!num_frames.has_value()) {
LOG(ERROR) << "Key \"num_frames\" is not found in " << json_file_path;
return false;
}
metadata.num_frames = base::checked_cast<size_t>(*num_frames);

absl::optional<int> width = metadata_dict.FindInt("width");
if (!width.has_value()) {
LOG(ERROR) << "Key \"width\" is not found in " << json_file_path;
return false;
}
absl::optional<int> height = metadata_dict.FindInt("height");
if (!height) {
LOG(ERROR) << "Key \"height\" is not found in " << json_file_path;
return false;
}
metadata.resolution =
gfx::Size(static_cast<uint32_t>(*width), static_cast<uint32_t>(*height));

const base::Value::List* md5_checksums =
metadata_dict.FindList("md5_checksums");
for (const base::Value& checksum : *md5_checksums) {
metadata.frame_checksums.push_back(checksum.GetString());
}

return true;
}

VideoBitstream::VideoBitstream(
std::unique_ptr<base::MemoryMappedFile> memory_mapped_file,
const Metadata& metadata)
: memory_mapped_file_(std::move(memory_mapped_file)), metadata_(metadata) {}

VideoBitstream::~VideoBitstream() = default;

VideoBitstream::Metadata::Metadata() = default;
VideoBitstream::Metadata::~Metadata() = default;
VideoBitstream::Metadata::Metadata(const Metadata&) = default;
VideoBitstream::Metadata& VideoBitstream::Metadata::operator=(
const Metadata&) = default;

std::unique_ptr<VideoBitstream> VideoBitstream::Create(
const base::FilePath& file_path,
const base::FilePath& metadata_file_path) {
CHECK(!file_path.empty());
const base::FilePath data_file_path = ResolveFilePath(file_path);
if (data_file_path.empty()) {
LOG(ERROR) << "Video file not found: " << file_path;
return nullptr;
}
const base::FilePath json_file_path = ResolveFilePath(
metadata_file_path.empty() ? file_path.AddExtension(kMetadataSuffix)
: metadata_file_path);
if (json_file_path.empty()) {
LOG(ERROR) << "Metadata file not found: " << file_path;
return nullptr;
}

auto memory_mapped_file = LoadData(data_file_path);
if (!memory_mapped_file) {
return nullptr;
}

VideoBitstream::Metadata metadata;
if (!LoadMetadata(json_file_path, metadata)) {
LOG(ERROR) << "Failed to read metadata file: " << data_file_path;
return nullptr;
}
// We set |has_keyframeless_resolution_change| by looking at the file name.
const char* kKeyFrameLessResolutionChangeFiles[] = {
"frm_resize",
"sub8x8_sf",
};
metadata.has_keyframeless_resolution_change = std::find_if(
std::cbegin(kKeyFrameLessResolutionChangeFiles),
std::cend(kKeyFrameLessResolutionChangeFiles),
[filepath = data_file_path.value()](const char* substr) {
return base::Contains(base::ToLowerASCII(filepath), substr);
});
return base::WrapUnique(
new VideoBitstream(std::move(memory_mapped_file), metadata));
}

base::span<const uint8_t> VideoBitstream::Data() const {
CHECK(memory_mapped_file_ && memory_mapped_file_->IsValid());
return base::span<const uint8_t>(memory_mapped_file_->data(),
memory_mapped_file_->length());
}
// static
base::FilePath VideoBitstream::test_data_path_;

// static
void VideoBitstream::SetTestDataPath(const base::FilePath& test_data_path) {
test_data_path_ = test_data_path;
}

// static
base::FilePath VideoBitstream::ResolveFilePath(
const base::FilePath& file_path) {
base::FilePath resolved_path = file_path;

// Try to resolve the path into an absolute path. If the path doesn't exist,
// it might be relative to the test data dir.
if (!resolved_path.IsAbsolute()) {
resolved_path = base::MakeAbsoluteFilePath(
PathExists(resolved_path) ? resolved_path
: test_data_path_.Append(resolved_path));
}

return base::PathExists(resolved_path) ? resolved_path : base::FilePath();
}
} // namespace media::test
95 changes: 95 additions & 0 deletions media/gpu/test/video_bitstream.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef MEDIA_GPU_TEST_VIDEO_BITSTREAM_H_
#define MEDIA_GPU_TEST_VIDEO_BITSTREAM_H_

#include <memory>
#include <string>
#include <vector>

#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/time/time.h"
#include "media/base/video_codecs.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"

namespace base {
class MemoryMappedFile;
} // namespace base

namespace media::test {

// VideoBitstream owns the compressed video data (e.g. h264 and vp9) and
// provides the information about the video.
class VideoBitstream final {
public:
// Creates VideoBitstream by reading a compressed video from |file_path| and
// its metadata from |metadata_file_path|. Returns nullptr on fatal.
static std::unique_ptr<VideoBitstream> Create(
const base::FilePath& file_path,
const base::FilePath& metadata_file_path);

~VideoBitstream();
VideoBitstream(const VideoBitstream&) = delete;
VideoBitstream& operator=(const VideoBitstream&) = delete;
VideoBitstream(VideoBitstream&&) = delete;
VideoBitstream& operator=(VideoBitstream&&) = delete;

// Returns the compressed video data.
base::span<const uint8_t> Data() const;
VideoCodecProfile Profile() const { return metadata_.profile; }
VideoCodec Codec() const { return metadata_.codec; }
uint8_t BitDepth() const { return metadata_.bit_depth; }
uint32_t FrameRate() const { return metadata_.frame_rate; }
size_t NumFrames() const { return metadata_.num_frames; }
const gfx::Size& Resolution() const { return metadata_.resolution; }
const std::vector<std::string>& FrameChecksums() const {
return metadata_.frame_checksums;
}
base::TimeDelta Duration() const {
return base::Seconds(static_cast<double>(metadata_.num_frames) /
static_cast<double>(metadata_.frame_rate));
}
// Returns if the video has a resolution change event on non keyframe.
bool HasKeyFrameLessResolutionChange() const {
return metadata_.has_keyframeless_resolution_change;
}

// Set the default path to the test video data.
static void SetTestDataPath(const base::FilePath& test_data_path);

private:
struct Metadata {
Metadata();
~Metadata();
Metadata(const Metadata&);
Metadata& operator=(const Metadata&);

VideoCodecProfile profile;
VideoCodec codec;
uint8_t bit_depth;
uint32_t frame_rate;
size_t num_frames;
gfx::Size resolution;
std::vector<std::string> frame_checksums;
bool has_keyframeless_resolution_change;
};

VideoBitstream(std::unique_ptr<base::MemoryMappedFile> memory_mapped_file,
const Metadata& metadata);

static base::FilePath ResolveFilePath(const base::FilePath& file_path);
static bool LoadMetadata(const base::FilePath& json_file_path,
Metadata& metadata);

const std::unique_ptr<base::MemoryMappedFile> memory_mapped_file_;
const Metadata metadata_;

static base::FilePath test_data_path_;
};
} // namespace media::test

#endif // MEDIA_GPU_TEST_VIDEO_BITSTREAM_H_

0 comments on commit e843295

Please sign in to comment.