[FFmpeg-devel] [PATCH v2 2/3] lavc: Add test for H.265 profile handling
Mark Thompson
sw at jkqxz.net
Mon May 6 21:47:20 EEST 2024
---
libavcodec/Makefile | 2 +-
libavcodec/tests/.gitignore | 1 +
libavcodec/tests/h265_profiles.c | 440 +++++++++++++++++++++++++++++++
tests/fate/libavcodec.mak | 5 +
4 files changed, 447 insertions(+), 1 deletion(-)
create mode 100644 libavcodec/tests/h265_profiles.c
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index cff6347bdb..15e38ded28 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -1307,7 +1307,7 @@ TESTPROGS-$(CONFIG_MJPEG_ENCODER) += mjpegenc_huffman
TESTPROGS-$(HAVE_MMX) += motion
TESTPROGS-$(CONFIG_MPEGVIDEO) += mpeg12framerate
TESTPROGS-$(CONFIG_H264_METADATA_BSF) += h264_levels
-TESTPROGS-$(CONFIG_HEVC_METADATA_BSF) += h265_levels
+TESTPROGS-$(CONFIG_HEVC_METADATA_BSF) += h265_levels h265_profiles
TESTPROGS-$(CONFIG_RANGECODER) += rangecoder
TESTPROGS-$(CONFIG_SNOW_ENCODER) += snowenc
diff --git a/libavcodec/tests/.gitignore b/libavcodec/tests/.gitignore
index 0df4ae10a0..bf29f03911 100644
--- a/libavcodec/tests/.gitignore
+++ b/libavcodec/tests/.gitignore
@@ -10,6 +10,7 @@
/golomb
/h264_levels
/h265_levels
+/h265_profiles
/htmlsubtitles
/iirfilter
/jpeg2000dwt
diff --git a/libavcodec/tests/h265_profiles.c b/libavcodec/tests/h265_profiles.c
new file mode 100644
index 0000000000..6a0df58d0b
--- /dev/null
+++ b/libavcodec/tests/h265_profiles.c
@@ -0,0 +1,440 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/bprint.h"
+#include "libavutil/common.h"
+#include "libavcodec/h265_profile_level.h"
+
+
+enum {
+ TYPE_MAIN,
+ TYPE_SP,
+ TYPE_REXT_INTER,
+ TYPE_REXT_INTRA,
+ TYPE_HT,
+ TYPE_HT_INTRA,
+ TYPE_SCC,
+ TYPE_HT_SCC,
+ TYPE_COUNT,
+};
+enum {
+ DEPTH_8,
+ DEPTH_10,
+ DEPTH_12,
+ DEPTH_14,
+ DEPTH_16,
+ DEPTH_COUNT,
+};
+enum {
+ CHROMA_MONO,
+ CHROMA_420,
+ CHROMA_422,
+ CHROMA_444,
+ CHROMA_COUNT,
+};
+
+// Table of all profiles indexed by chroma subsampling, bit depth and
+// profile type. This is currently only being used to verify that
+// profile properties are correct, but if there is some other need for
+// this lookup in lavc then the table should be moved to the common
+// profile-level code. All profiles should appear exactly once in this
+// table (also verified by a test below).
+static int profile_table[CHROMA_COUNT][DEPTH_COUNT][TYPE_COUNT] = {
+ [CHROMA_MONO] = {
+ [DEPTH_8] = {
+ [TYPE_REXT_INTER] = H265_PROFILE_MONOCHROME,
+ },
+ [DEPTH_10] = {
+ [TYPE_REXT_INTER] = H265_PROFILE_MONOCHROME_10,
+ },
+ [DEPTH_12] = {
+ [TYPE_REXT_INTER] = H265_PROFILE_MONOCHROME_12,
+ },
+ [DEPTH_16] = {
+ [TYPE_REXT_INTER] = H265_PROFILE_MONOCHROME_16,
+ },
+ },
+ [CHROMA_420] = {
+ [DEPTH_8] = {
+ [TYPE_MAIN] = H265_PROFILE_MAIN,
+ [TYPE_SP] = H265_PROFILE_MAIN_STILL_PICTURE,
+ [TYPE_REXT_INTRA] = H265_PROFILE_MAIN_INTRA,
+ [TYPE_SCC] = H265_PROFILE_SCREEN_EXTENDED_MAIN,
+ },
+ [DEPTH_10] = {
+ [TYPE_MAIN] = H265_PROFILE_MAIN_10,
+ [TYPE_SP] = H265_PROFILE_MAIN_10_STILL_PICTURE,
+ [TYPE_REXT_INTRA] = H265_PROFILE_MAIN_10_INTRA,
+ [TYPE_SCC] = H265_PROFILE_SCREEN_EXTENDED_MAIN_10,
+ },
+ [DEPTH_12] = {
+ [TYPE_REXT_INTER] = H265_PROFILE_MAIN_12,
+ [TYPE_REXT_INTRA] = H265_PROFILE_MAIN_12_INTRA,
+ },
+ },
+ [CHROMA_422] ={
+ [DEPTH_10] = {
+ [TYPE_REXT_INTER] = H265_PROFILE_MAIN_422_10,
+ [TYPE_REXT_INTRA] = H265_PROFILE_MAIN_422_10_INTRA,
+ },
+ [DEPTH_12] = {
+ [TYPE_REXT_INTER] = H265_PROFILE_MAIN_422_12,
+ [TYPE_REXT_INTRA] = H265_PROFILE_MAIN_422_12_INTRA,
+ },
+ },
+ [CHROMA_444] ={
+ [DEPTH_8] = {
+ [TYPE_SP] = H265_PROFILE_MAIN_444_STILL_PICTURE,
+ [TYPE_REXT_INTER] = H265_PROFILE_MAIN_444,
+ [TYPE_REXT_INTRA] = H265_PROFILE_MAIN_444_INTRA,
+ [TYPE_HT] = H265_PROFILE_HIGH_THROUGHPUT_444,
+ [TYPE_SCC] = H265_PROFILE_SCREEN_EXTENDED_MAIN_444,
+ [TYPE_HT_SCC] = H265_PROFILE_SCREEN_EXTENDED_HIGH_THROUGHPUT_444,
+ },
+ [DEPTH_10] = {
+ [TYPE_REXT_INTER] = H265_PROFILE_MAIN_444_10,
+ [TYPE_REXT_INTRA] = H265_PROFILE_MAIN_444_10_INTRA,
+ [TYPE_HT] = H265_PROFILE_HIGH_THROUGHPUT_444_10,
+ [TYPE_SCC] = H265_PROFILE_SCREEN_EXTENDED_MAIN_444_10,
+ [TYPE_HT_SCC] = H265_PROFILE_SCREEN_EXTENDED_HIGH_THROUGHPUT_444_10,
+ },
+ [DEPTH_12] = {
+ [TYPE_REXT_INTER] = H265_PROFILE_MAIN_444_12,
+ [TYPE_REXT_INTRA] = H265_PROFILE_MAIN_444_12_INTRA,
+ },
+ [DEPTH_14] = {
+ [TYPE_HT] = H265_PROFILE_HIGH_THROUGHPUT_444_14,
+ [TYPE_HT_SCC] = H265_PROFILE_SCREEN_EXTENDED_HIGH_THROUGHPUT_444_14,
+ },
+ [DEPTH_16] = {
+ [TYPE_SP] = H265_PROFILE_MAIN_444_16_STILL_PICTURE,
+ [TYPE_REXT_INTRA] = H265_PROFILE_MAIN_444_16_INTRA,
+ [TYPE_HT_INTRA] = H265_PROFILE_HIGH_THROUGHPUT_444_16_INTRA,
+ },
+ },
+};
+
+static int check_flags(const H265ProfileDescriptor *desc,
+ int chroma, int depth)
+{
+ int errors = 0;
+ if (chroma > CHROMA_MONO && desc->max_monochrome == 1) {
+ av_log(NULL, AV_LOG_ERROR, "%s requires monochrome.\n",
+ desc->name);
+ ++errors;
+ }
+ if (chroma > CHROMA_422 && desc->max_420chroma == 1) {
+ av_log(NULL, AV_LOG_ERROR, "%s requires 4:2:0.\n",
+ desc->name);
+ ++errors;
+ }
+ if (chroma > CHROMA_444 && desc->max_422chroma == 1) {
+ av_log(NULL, AV_LOG_ERROR, "%s requires 4:2:2.\n",
+ desc->name);
+ ++errors;
+ }
+ if (depth > DEPTH_8 && desc->max_8bit == 1) {
+ av_log(NULL, AV_LOG_ERROR, "%s requires 8-bit.\n",
+ desc->name);
+ ++errors;
+ }
+ if (depth > DEPTH_10 && desc->max_10bit == 1) {
+ av_log(NULL, AV_LOG_ERROR, "%s requires 10-bit.\n",
+ desc->name);
+ ++errors;
+ }
+ if (depth > DEPTH_12 && desc->max_12bit == 1) {
+ av_log(NULL, AV_LOG_ERROR, "%s requires 12-bit.\n",
+ desc->name);
+ ++errors;
+ }
+ if (depth > DEPTH_14 && desc->max_14bit == 1) {
+ av_log(NULL, AV_LOG_ERROR, "%s requires 14-bit.\n",
+ desc->name);
+ ++errors;
+ }
+ return errors;
+}
+
+static int check_profile_table(void)
+{
+ // Ensure that the profile table contains every non-still-picture
+ // profile exactly once, and that a profile for a given chroma mode
+ // and depth actually supports that chroma mode and depth.
+ int errors = 0;
+
+ for (int p = H265_PROFILE_MONOCHROME; p < H265_PROFILE_COUNT; p++) {
+ const H265ProfileDescriptor *desc = ff_h265_get_profile(p);
+ int found = 0;
+
+ for (int type= 0; type < TYPE_COUNT; type++) {
+ for (int chroma = 0; chroma < CHROMA_COUNT; chroma++) {
+ for (int depth = 0; depth < DEPTH_COUNT; depth++) {
+ if (profile_table[chroma][depth][type] == p) {
+ ++found;
+ errors += check_flags(desc, chroma, depth);
+ }
+ }
+ }
+ }
+
+ if (found != 1) {
+ av_log(NULL, AV_LOG_ERROR,
+ "%s appears %d times in the profile table.\n",
+ desc->name, found);
+
+ ++errors;
+ }
+ }
+ return errors;
+}
+
+static int get_profile(int type, int chroma, int depth)
+{
+ av_assert0(type >= 0 && type < TYPE_COUNT);
+ av_assert0(chroma >= 0 && chroma < CHROMA_COUNT);
+ av_assert0(depth >= 0 && depth < DEPTH_COUNT);
+ return profile_table[chroma][depth][type];
+}
+
+static void minimal_ptl_from_desc(H265RawProfileTierLevel *ptl,
+ const H265ProfileDescriptor *desc)
+{
+ *ptl = (H265RawProfileTierLevel) {
+ .general_profile_space = 0,
+ .general_profile_idc = desc->profile_idc,
+ // Tier/interlace/stereo/layering/level flags are ignored in
+ // this test.
+ };
+
+ ptl->general_profile_compatibility_flag[desc->profile_idc] = 1;
+
+#define FLAG(name) do { \
+ ptl->general_ ## name ## _constraint_flag = desc->name; \
+ } while (0)
+
+ FLAG(max_14bit);
+ FLAG(max_12bit);
+ FLAG(max_10bit);
+ FLAG(max_8bit);
+ FLAG(max_422chroma);
+ FLAG(max_420chroma);
+ FLAG(max_monochrome);
+ FLAG(intra);
+ FLAG(one_picture_only);
+ FLAG(lower_bit_rate);
+
+#undef FLAG
+}
+
+static void bprint_ptl(AVBPrint *buf, const H265RawProfileTierLevel *ptl)
+{
+ av_bprintf(buf, "profile_space %d tier %d profile_idc %d",
+ ptl->general_profile_space,
+ ptl->general_tier_flag,
+ ptl->general_profile_idc);
+
+ av_bprintf(buf, " profile_compatibility { ");
+ for (int i = 0; i < 32; i++)
+ av_bprintf(buf, "%d",
+ ptl->general_profile_compatibility_flag[i]);
+ av_bprintf(buf, " }");
+
+ av_bprintf(buf, " progressive %d interlaced %d",
+ ptl->general_progressive_source_flag,
+ ptl->general_interlaced_source_flag);
+ av_bprintf(buf, " non_packed %d frame_only %d",
+ ptl->general_non_packed_constraint_flag,
+ ptl->general_frame_only_constraint_flag);
+
+#define profile_compatible(x) (ptl->general_profile_idc == (x) || \
+ ptl->general_profile_compatibility_flag[x])
+ if (profile_compatible(4) || profile_compatible(5) ||
+ profile_compatible(6) || profile_compatible(7) ||
+ profile_compatible(8) || profile_compatible(9) ||
+ profile_compatible(10) || profile_compatible(11)) {
+
+ av_bprintf(buf, " 12bit %d", ptl->general_max_12bit_constraint_flag);
+ av_bprintf(buf, " 10bit %d", ptl->general_max_10bit_constraint_flag);
+ av_bprintf(buf, " 8bit %d", ptl->general_max_8bit_constraint_flag);
+ av_bprintf(buf, " 422chroma %d", ptl->general_max_422chroma_constraint_flag);
+ av_bprintf(buf, " 420chroma %d", ptl->general_max_420chroma_constraint_flag);
+ av_bprintf(buf, " monochrome %d", ptl->general_max_monochrome_constraint_flag);
+ av_bprintf(buf, " intra %d", ptl->general_intra_constraint_flag);
+ av_bprintf(buf, " one_picture %d", ptl->general_one_picture_only_constraint_flag);
+ av_bprintf(buf, " lower_bit_rate %d", ptl->general_lower_bit_rate_constraint_flag);
+
+ if (profile_compatible(5) || profile_compatible(9) ||
+ profile_compatible(10) || profile_compatible(11)) {
+ av_bprintf(buf, " 14bit %d", ptl->general_max_14bit_constraint_flag);
+ }
+
+ if (profile_compatible(1) || profile_compatible(2) ||
+ profile_compatible(3) || profile_compatible(4) ||
+ profile_compatible(5) || profile_compatible(9) ||
+ profile_compatible(11)) {
+ av_bprintf(buf, " inbld %d", ptl->general_inbld_flag);
+ }
+ }
+#undef profile_compatible
+
+ av_bprintf(buf, " level %d", ptl->general_level_idc);
+}
+
+
+#define CHECK_COMPATIBILITY(expected, ptl, profile) do { \
+ if (expected == !ff_h265_profile_compatible(ptl, profile)) { \
+ AVBPrint buf; \
+ char *str; \
+ const H265ProfileDescriptor *desc = \
+ ff_h265_get_profile(profile); \
+ av_log(NULL, AV_LOG_ERROR, expected ? \
+ "Incorrectly incompatible with %s:\n" : \
+ "Incorrectly compatible with %s:\n", \
+ desc->name); \
+ av_bprint_init(&buf, 1024, -1); \
+ bprint_ptl(&buf, ptl); \
+ av_bprint_finalize(&buf, &str); \
+ av_log(NULL, AV_LOG_ERROR, "%s\n", str); \
+ return 1; \
+ } \
+ } while (0)
+
+static int check_simple_rext_profiles(void)
+{
+ static const H265RawProfileTierLevel rext_420_8 = {
+ .general_profile_space = 0,
+ .general_profile_idc = 4,
+ .general_tier_flag = 0,
+ .general_profile_compatibility_flag[4] = 1,
+ .general_max_12bit_constraint_flag = 1,
+ .general_max_10bit_constraint_flag = 1,
+ .general_max_8bit_constraint_flag = 1,
+ .general_max_422chroma_constraint_flag = 1,
+ .general_max_420chroma_constraint_flag = 1,
+ .general_max_monochrome_constraint_flag = 0,
+ .general_intra_constraint_flag = 0,
+ .general_one_picture_only_constraint_flag = 0,
+ .general_lower_bit_rate_constraint_flag = 1,
+ };
+ static const H265RawProfileTierLevel rext_420_10 = {
+ .general_profile_space = 0,
+ .general_profile_idc = 4,
+ .general_tier_flag = 0,
+ .general_profile_compatibility_flag[4] = 1,
+ .general_max_12bit_constraint_flag = 1,
+ .general_max_10bit_constraint_flag = 1,
+ .general_max_8bit_constraint_flag = 0,
+ .general_max_422chroma_constraint_flag = 1,
+ .general_max_420chroma_constraint_flag = 1,
+ .general_max_monochrome_constraint_flag = 0,
+ .general_intra_constraint_flag = 0,
+ .general_one_picture_only_constraint_flag = 0,
+ .general_lower_bit_rate_constraint_flag = 1,
+ };
+ static const H265RawProfileTierLevel rext_422_8 = {
+ .general_profile_space = 0,
+ .general_profile_idc = 4,
+ .general_tier_flag = 0,
+ .general_profile_compatibility_flag[4] = 1,
+ .general_max_12bit_constraint_flag = 1,
+ .general_max_10bit_constraint_flag = 1,
+ .general_max_8bit_constraint_flag = 1,
+ .general_max_422chroma_constraint_flag = 1,
+ .general_max_420chroma_constraint_flag = 0,
+ .general_max_monochrome_constraint_flag = 0,
+ .general_intra_constraint_flag = 0,
+ .general_one_picture_only_constraint_flag = 0,
+ .general_lower_bit_rate_constraint_flag = 1,
+ };
+
+ CHECK_COMPATIBILITY(0, &rext_420_8, H265_PROFILE_MAIN);
+ CHECK_COMPATIBILITY(0, &rext_420_8, H265_PROFILE_MAIN_10);
+ CHECK_COMPATIBILITY(1, &rext_420_8, H265_PROFILE_MAIN_12);
+ CHECK_COMPATIBILITY(1, &rext_420_8, H265_PROFILE_MAIN_422_10);
+ CHECK_COMPATIBILITY(1, &rext_420_8, H265_PROFILE_MAIN_444);
+ CHECK_COMPATIBILITY(1, &rext_420_8, H265_PROFILE_MAIN_444_10);
+
+ CHECK_COMPATIBILITY(0, &rext_420_10, H265_PROFILE_MAIN);
+ CHECK_COMPATIBILITY(0, &rext_420_10, H265_PROFILE_MAIN_10);
+ CHECK_COMPATIBILITY(1, &rext_420_10, H265_PROFILE_MAIN_12);
+ CHECK_COMPATIBILITY(1, &rext_420_10, H265_PROFILE_MAIN_422_10);
+ CHECK_COMPATIBILITY(0, &rext_420_10, H265_PROFILE_MAIN_444);
+ CHECK_COMPATIBILITY(1, &rext_420_10, H265_PROFILE_MAIN_444_10);
+
+ CHECK_COMPATIBILITY(0, &rext_422_8, H265_PROFILE_MAIN);
+ CHECK_COMPATIBILITY(0, &rext_422_8, H265_PROFILE_MAIN_10);
+ CHECK_COMPATIBILITY(0, &rext_422_8, H265_PROFILE_MAIN_12);
+ CHECK_COMPATIBILITY(1, &rext_422_8, H265_PROFILE_MAIN_422_10);
+ CHECK_COMPATIBILITY(1, &rext_422_8, H265_PROFILE_MAIN_444);
+ CHECK_COMPATIBILITY(1, &rext_422_8, H265_PROFILE_MAIN_444_10);
+
+ return 0;
+}
+
+int main(void)
+{
+ if (check_profile_table())
+ return 1;
+
+ if (check_simple_rext_profiles())
+ return 1;
+
+ // Check compatibility pairs between all profiles of the same type.
+ // Profile A should be compatibile with a profile B which supports
+ // at least the same chroma subsampling and at least the same depth
+ // (including if A and B are the same).
+ for (int type = TYPE_REXT_INTER; type < TYPE_COUNT; type++) {
+ for (int chroma_a = 0; chroma_a < CHROMA_COUNT; chroma_a++) {
+ for (int depth_a = 0; depth_a < DEPTH_COUNT; depth_a++) {
+ int profile_a;
+ const H265ProfileDescriptor *desc_a;
+ H265RawProfileTierLevel ptl_a;
+
+ profile_a = get_profile(type, chroma_a, depth_a);
+ if (profile_a == H265_PROFILE_INVALID)
+ continue;
+ desc_a = ff_h265_get_profile(profile_a);
+ minimal_ptl_from_desc(&ptl_a, desc_a);
+
+ for (int chroma_b = 0; chroma_b < CHROMA_COUNT; chroma_b++) {
+ for (int depth_b = 0; depth_b < DEPTH_COUNT; depth_b++) {
+ int profile_b;
+ const H265ProfileDescriptor *desc_b;
+ int expect_compatible = (depth_b >= depth_a &&
+ chroma_b >= chroma_a);
+
+ profile_b = get_profile(type, chroma_b, depth_b);
+ if (profile_b == H265_PROFILE_INVALID)
+ continue;
+ desc_b = ff_h265_get_profile(profile_b);
+
+ av_log(NULL, AV_LOG_INFO,
+ "%d: A (%s: %d,%d) B (%s: %d,%d)\n", type,
+ desc_a->name, chroma_a, depth_a,
+ desc_b->name, chroma_b, depth_b);
+ CHECK_COMPATIBILITY(expect_compatible,
+ &ptl_a, profile_b);
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/tests/fate/libavcodec.mak b/tests/fate/libavcodec.mak
index 1a5694fa5f..a47e8b8237 100644
--- a/tests/fate/libavcodec.mak
+++ b/tests/fate/libavcodec.mak
@@ -71,6 +71,11 @@ fate-h265-levels: libavcodec/tests/h265_levels$(EXESUF)
fate-h265-levels: CMD = run libavcodec/tests/h265_levels$(EXESUF)
fate-h265-levels: REF = /dev/null
+FATE_LIBAVCODEC-$(CONFIG_HEVC_METADATA_BSF) += fate-h265-profiles
+fate-h265-profiles: libavcodec/tests/h265_profiles$(EXESUF)
+fate-h265-profiles: CMD = run libavcodec/tests/h265_profiles$(EXESUF)
+fate-h265-profiles: REF = /dev/null
+
FATE_LIBAVCODEC-$(CONFIG_IIRFILTER) += fate-iirfilter
fate-iirfilter: libavcodec/tests/iirfilter$(EXESUF)
fate-iirfilter: CMD = run libavcodec/tests/iirfilter$(EXESUF)
--
2.43.0
More information about the ffmpeg-devel
mailing list