-
Notifications
You must be signed in to change notification settings - Fork 6.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
media/gpu/test: Introduce VideoBitstream
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
Showing
3 changed files
with
323 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_ |