From: Mark Thompson <sw@jkqxz.net> To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] [PATCH v3 2/3] lavc: Add test for H.265 profile handling Date: Wed, 5 Jun 2024 20:59:43 +0100 Message-ID: <20240605195950.2297433-2-sw@jkqxz.net> (raw) In-Reply-To: <20240605195950.2297433-1-sw@jkqxz.net> --- 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 8ab4398b6c..e7973fb2e6 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -1297,7 +1297,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..37fc545558 --- /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 enum H265Profile 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 enum H265Profile 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++) { + enum H265Profile 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++) { + enum H265Profile 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 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
next prev parent reply other threads:[~2024-06-05 19:59 UTC|newest] Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top 2024-06-05 19:59 [FFmpeg-devel] [PATCH v3 1/3] lavc/h265_profile_level: Expand profile compatibility checking Mark Thompson 2024-06-05 19:59 ` Mark Thompson [this message] 2024-06-05 19:59 ` [FFmpeg-devel] [PATCH v3 3/3] lavc/vaapi_hevc: Don't require exact profiles Mark Thompson 2024-07-10 2:02 ` Wang, Fei W
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20240605195950.2297433-2-sw@jkqxz.net \ --to=sw@jkqxz.net \ --cc=ffmpeg-devel@ffmpeg.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel This inbox may be cloned and mirrored by anyone: git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git # If you have public-inbox 1.1+ installed, you may # initialize and index your mirror using the following commands: public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \ ffmpegdev@gitmailbox.com public-inbox-index ffmpegdev Example config snippet for mirrors. AGPL code for this site: git clone https://public-inbox.org/public-inbox.git