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) {