Add parsing of YuvSubsampling from VP9 and AV1 codec strings.
This is a precursor to having the encoder encode in 422, 444
formats. As part of this change, some codec strings are now
marked as invalid if subsampling and profile are incompatible.
R=jzern
Bug: 1116617, 1116564, 1378115
Change-Id: I025601061808e7ce9e1d3050ff1e4a5e0d2f1e52
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5125843
Commit-Queue: Dale Curtis <dalecurtis@chromium.org>
Reviewed-by: James Zern <jzern@google.com>
Cr-Commit-Position: refs/heads/main@{#1246728}
diff --git a/media/base/media_types.h b/media/base/media_types.h
index ed9b7c3..5802cce 100644
--- a/media/base/media_types.h
+++ b/media/base/media_types.h
@@ -11,6 +11,7 @@
#include "media/base/video_codecs.h"
#include "media/base/video_color_space.h"
#include "media/base/video_decoder_config.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
namespace media {
@@ -18,6 +19,13 @@
// These are generally a subset of {Audio|Video}DecoderConfig classes, which can
// only be created after demuxing.
+enum class YuvSubsampling {
+ k400,
+ k420,
+ k422,
+ k444,
+};
+
struct MEDIA_EXPORT AudioType {
static AudioType FromDecoderConfig(const AudioDecoderConfig& config);
@@ -34,6 +42,7 @@
VideoCodecLevel level = kNoVideoCodecLevel;
VideoColorSpace color_space;
gfx::HdrMetadataType hdr_metadata_type = gfx::HdrMetadataType::kNone;
+ absl::optional<YuvSubsampling> subsampling;
};
MEDIA_EXPORT bool operator==(const AudioType& x, const AudioType& y);
diff --git a/media/base/video_codec_string_parsers.cc b/media/base/video_codec_string_parsers.cc
index 1018e74..75ebcea 100644
--- a/media/base/video_codec_string_parsers.cc
+++ b/media/base/video_codec_string_parsers.cc
@@ -33,6 +33,7 @@
VideoType result = {
.codec = VideoCodec::kVP9,
.color_space = VideoColorSpace::REC709(),
+ .subsampling = YuvSubsampling::k420,
};
std::vector<std::string> fields = base::SplitString(
@@ -116,9 +117,27 @@
return result;
}
const int chroma_subsampling = values[3];
- if (chroma_subsampling > 3) {
- DVLOG(3) << __func__ << " Invalid chroma subsampling ("
- << chroma_subsampling << ")";
+ switch (chroma_subsampling) {
+ case 0:
+ case 1:
+ result.subsampling = YuvSubsampling::k420;
+ break;
+ case 2:
+ result.subsampling = YuvSubsampling::k422;
+ break;
+ case 3:
+ result.subsampling = YuvSubsampling::k444;
+ break;
+ default:
+ DVLOG(3) << __func__ << " Invalid chroma subsampling ("
+ << chroma_subsampling << ")";
+ return absl::nullopt;
+ }
+
+ if (result.subsampling != YuvSubsampling::k420 && profile_idc != 1 &&
+ profile_idc != 3) {
+ DVLOG(3) << __func__
+ << " 4:2:2 and 4:4:4 are only supported in profile 1, 3";
return absl::nullopt;
}
@@ -219,6 +238,7 @@
VideoType result = {
.codec = VideoCodec::kAV1,
.color_space = VideoColorSpace::REC709(),
+ .subsampling = YuvSubsampling::k420,
};
if (fields[0] != "av01") {
@@ -302,6 +322,9 @@
}
if (values.size() <= 5) {
+ if (monochrome == 1) {
+ result.subsampling = YuvSubsampling::k400;
+ }
return result;
}
@@ -331,6 +354,24 @@
return absl::nullopt;
}
+ if (subsampling_x == '0' && subsampling_y == '0' && monochrome == 0) {
+ result.subsampling = YuvSubsampling::k444;
+ if (result.profile == AV1PROFILE_PROFILE_MAIN) {
+ DVLOG(3) << __func__ << "4:4:4 isn't supported in main profile.";
+ return absl::nullopt;
+ }
+ } else if (subsampling_x == '1' && subsampling_y == '0' && monochrome == 0) {
+ result.subsampling = YuvSubsampling::k422;
+ if (result.profile != AV1PROFILE_PROFILE_PRO) {
+ DVLOG(3) << __func__ << "4:2:2 is only supported in pro profile.";
+ return absl::nullopt;
+ }
+ } else if (subsampling_x == '1' && subsampling_y == '1' && monochrome == 0) {
+ result.subsampling = YuvSubsampling::k420;
+ } else if (subsampling_x == '1' && subsampling_y == '1' && monochrome == 1) {
+ result.subsampling = YuvSubsampling::k400;
+ }
+
if (values.size() <= 6) {
return result;
}
diff --git a/media/base/video_codec_string_parsers_unittest.cc b/media/base/video_codec_string_parsers_unittest.cc
index db2dcd08..7555447 100644
--- a/media/base/video_codec_string_parsers_unittest.cc
+++ b/media/base/video_codec_string_parsers_unittest.cc
@@ -29,6 +29,7 @@
EXPECT_EQ(VP9PROFILE_PROFILE0, result->profile);
EXPECT_EQ(10u, result->level);
EXPECT_EQ(VideoColorSpace::TransferID::BT709, result->color_space.transfer);
+ EXPECT_EQ(YuvSubsampling::k420, result->subsampling);
}
// Verify profile's 1, 2, and 3 parse correctly.
@@ -81,13 +82,48 @@
EXPECT_FALSE(ParseNewStyleVp9CodecID("vp09.02.10.13"));
// Verify chroma subsampling values.
- EXPECT_TRUE(ParseNewStyleVp9CodecID("vp09.02.10.10.00"));
- EXPECT_TRUE(ParseNewStyleVp9CodecID("vp09.02.10.10.01"));
- EXPECT_TRUE(ParseNewStyleVp9CodecID("vp09.02.10.10.02"));
- EXPECT_TRUE(ParseNewStyleVp9CodecID("vp09.02.10.10.03"));
+ {
+ auto result = ParseNewStyleVp9CodecID("vp09.02.10.10.00");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(YuvSubsampling::k420, result->subsampling);
+ }
+ {
+ auto result = ParseNewStyleVp9CodecID("vp09.02.10.10.01");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(YuvSubsampling::k420, result->subsampling);
+ }
+ {
+ auto result = ParseNewStyleVp9CodecID("vp09.01.10.10.02");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(YuvSubsampling::k422, result->subsampling);
+ }
+ {
+ auto result = ParseNewStyleVp9CodecID("vp09.03.10.10.02");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(YuvSubsampling::k422, result->subsampling);
+ }
+ {
+ auto result = ParseNewStyleVp9CodecID("vp09.01.10.10.03");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(YuvSubsampling::k444, result->subsampling);
+ }
+ {
+ auto result = ParseNewStyleVp9CodecID("vp09.03.10.10.03");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(YuvSubsampling::k444, result->subsampling);
+ }
+
// Values 4 - 7 are reserved.
EXPECT_FALSE(ParseNewStyleVp9CodecID("vp09.02.10.10.04"));
+ // Test invalid profile + sampling combinations.
+ EXPECT_FALSE(ParseNewStyleVp9CodecID("vp09.00.10.10.02"));
+ EXPECT_FALSE(ParseNewStyleVp9CodecID("vp09.00.10.10.02"));
+ EXPECT_FALSE(ParseNewStyleVp9CodecID("vp09.02.10.10.02"));
+ EXPECT_FALSE(ParseNewStyleVp9CodecID("vp09.02.10.10.02"));
+ EXPECT_FALSE(ParseNewStyleVp9CodecID("vp09.02.10.10.03"));
+ EXPECT_FALSE(ParseNewStyleVp9CodecID("vp09.02.10.10.03"));
+
// Verify a few color profiles.
// BT709
EXPECT_TRUE(ParseNewStyleVp9CodecID("vp09.02.10.10.00.01"));
@@ -142,6 +178,7 @@
EXPECT_EQ(AV1PROFILE_PROFILE_MAIN, result->profile);
EXPECT_EQ(4u, result->level);
EXPECT_EQ(VideoColorSpace::TransferID::BT709, result->color_space.transfer);
+ EXPECT_EQ(YuvSubsampling::k420, result->subsampling);
}
// Verify high and pro profiles parse correctly.
@@ -235,19 +272,55 @@
for (int i = 0; i <= 9; ++i) {
const std::string codec_string = base::StringPrintf("av01.0.00M.08.%d", i);
SCOPED_TRACE(codec_string);
- EXPECT_EQ(i < 2, !!ParseAv1CodecId(codec_string));
+ if (i < 2) {
+ auto result = ParseAv1CodecId(codec_string);
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result->subsampling,
+ i == 0 ? YuvSubsampling::k420 : YuvSubsampling::k400);
+ } else {
+ EXPECT_FALSE(ParseAv1CodecId(codec_string));
+ }
}
}
TEST(ParseAv1CodecId, VerifyOptionalSubsampling) {
// chroma subsampling values are {0,1}{0,1}{0,3} with the last value always
// zero if either of the first two values are zero.
- EXPECT_TRUE(ParseAv1CodecId("av01.0.00M.10.0.000"));
- EXPECT_TRUE(ParseAv1CodecId("av01.0.00M.10.0.100"));
- EXPECT_TRUE(ParseAv1CodecId("av01.0.00M.10.0.010"));
- EXPECT_TRUE(ParseAv1CodecId("av01.0.00M.10.0.111"));
- EXPECT_TRUE(ParseAv1CodecId("av01.0.00M.10.0.112"));
- EXPECT_TRUE(ParseAv1CodecId("av01.0.00M.10.0.113"));
+ {
+ auto result = ParseAv1CodecId("av01.1.00M.10.0.000");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result->subsampling, YuvSubsampling::k444);
+ }
+ {
+ auto result = ParseAv1CodecId("av01.2.00M.10.0.000");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result->subsampling, YuvSubsampling::k444);
+ }
+ {
+ auto result = ParseAv1CodecId("av01.2.00M.10.0.100");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result->subsampling, YuvSubsampling::k422);
+ }
+ {
+ auto result = ParseAv1CodecId("av01.0.00M.10.0.010");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result->subsampling, YuvSubsampling::k420);
+ }
+ {
+ auto result = ParseAv1CodecId("av01.0.00M.10.0.111");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result->subsampling, YuvSubsampling::k420);
+ }
+ {
+ auto result = ParseAv1CodecId("av01.0.00M.10.0.112");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result->subsampling, YuvSubsampling::k420);
+ }
+ {
+ auto result = ParseAv1CodecId("av01.0.00M.10.0.113");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result->subsampling, YuvSubsampling::k420);
+ }
// Invalid cases.
EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.101"));
@@ -256,11 +329,26 @@
EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.011"));
EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.012"));
EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.013"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.100"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.1.00M.10.0.100"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.000"));
// The last-value may be non-zero if the first two values are non-zero.
- EXPECT_TRUE(ParseAv1CodecId("av01.0.00M.10.0.110"));
- EXPECT_TRUE(ParseAv1CodecId("av01.0.00M.10.0.100"));
- EXPECT_TRUE(ParseAv1CodecId("av01.0.00M.10.0.010"));
+ {
+ auto result = ParseAv1CodecId("av01.0.00M.10.0.110");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result->subsampling, YuvSubsampling::k420);
+ }
+ {
+ auto result = ParseAv1CodecId("av01.2.00M.10.0.100");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result->subsampling, YuvSubsampling::k422);
+ }
+ {
+ auto result = ParseAv1CodecId("av01.0.00M.10.0.010");
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result->subsampling, YuvSubsampling::k420);
+ }
for (int i = 2; i <= 9; ++i) {
for (int j = 2; j <= 9; ++j) {
@@ -286,30 +374,30 @@
// BT709
{
- auto result = ParseAv1CodecId("av01.0.00M.10.0.000.01");
+ auto result = ParseAv1CodecId("av01.0.00M.10.0.110.01");
ASSERT_TRUE(result);
EXPECT_EQ(VideoColorSpace::PrimaryID::BT709, result->color_space.primaries);
}
// BT2020
{
- auto result = ParseAv1CodecId("av01.0.00M.10.0.000.09");
+ auto result = ParseAv1CodecId("av01.0.00M.10.0.110.09");
ASSERT_TRUE(result);
EXPECT_EQ(VideoColorSpace::PrimaryID::BT2020,
result->color_space.primaries);
}
// 0 is invalid.
- EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.000.00"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.110.00"));
// 23 - 255 are reserved.
- EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.000.23"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.110.23"));
// Leading zeros must be provided.
- EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.000.01.1"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.110.01.1"));
// Negative values are not allowed.
- EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.000.01.-1"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.110.01.-1"));
// Verify a few common EOTFs parse correctly.
for (int eotf : {1, 4, 6, 14, 15, 13, 16}) {
- auto codec_string = base::StringPrintf("av01.0.00M.10.0.000.01.%02d", eotf);
+ auto codec_string = base::StringPrintf("av01.0.00M.10.0.110.01.%02d", eotf);
auto result = ParseAv1CodecId(codec_string);
ASSERT_TRUE(result) << "eotf=" << eotf;
EXPECT_EQ(static_cast<VideoColorSpace::TransferID>(eotf),
@@ -317,52 +405,52 @@
}
// Verify 0 and 3 are reserved EOTF values.
- EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.000.01.00"));
- EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.000.01.03"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.110.01.00"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.110.01.03"));
// Leading zeros must be provided.
- EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.000.01.01.1"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.110.01.01.1"));
// Negative values are not allowed.
- EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.000.01.01.-1"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.110.01.01.-1"));
// Verify a few matrix coefficients.
{
- auto result = ParseAv1CodecId("av01.0.00M.10.0.000.01.01.00");
+ auto result = ParseAv1CodecId("av01.0.00M.10.0.110.01.01.00");
ASSERT_TRUE(result);
EXPECT_EQ(VideoColorSpace::MatrixID::RGB, result->color_space.matrix);
}
{
- auto result = ParseAv1CodecId("av01.0.00M.10.0.000.01.01.01");
+ auto result = ParseAv1CodecId("av01.0.00M.10.0.110.01.01.01");
ASSERT_TRUE(result);
EXPECT_EQ(VideoColorSpace::MatrixID::BT709, result->color_space.matrix);
}
{
- auto result = ParseAv1CodecId("av01.0.00M.10.0.000.01.01.10");
+ auto result = ParseAv1CodecId("av01.0.00M.10.0.110.01.01.10");
ASSERT_TRUE(result);
EXPECT_EQ(VideoColorSpace::MatrixID::BT2020_CL, result->color_space.matrix);
}
// Values 12 - 255 reserved. Though 12 at least is a valid value we should
// support in the future. https://crbug.com/854290
- EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.000.01.01.12"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.110.01.01.12"));
// Leading zeros are not allowed.
- EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.000.01.01.00.00"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.110.01.01.00.00"));
// Negative values are not allowed.
- EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.000.01.01.00.-1"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.110.01.01.00.-1"));
// Verify full range flag (boolean 0 or 1).
{
- auto result = ParseAv1CodecId("av01.0.00M.10.0.000.01.01.00.0");
+ auto result = ParseAv1CodecId("av01.0.00M.10.0.110.01.01.00.0");
ASSERT_TRUE(result);
EXPECT_EQ(gfx::ColorSpace::RangeID::LIMITED, result->color_space.range);
}
{
- auto result = ParseAv1CodecId("av01.0.00M.10.0.000.01.01.00.1");
+ auto result = ParseAv1CodecId("av01.0.00M.10.0.110.01.01.00.1");
ASSERT_TRUE(result);
EXPECT_EQ(gfx::ColorSpace::RangeID::FULL, result->color_space.range);
}
- EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.000.01.01.00.2"));
+ EXPECT_FALSE(ParseAv1CodecId("av01.0.00M.10.0.110.01.01.00.2"));
}
TEST(ParseHEVCCodecIdTest, InvalidHEVCCodecIds) {