diff mbox

[FFmpeg-devel,v3,26/41] lavc/h264: Add common code for level handling

Message ID 20180822234514.10571-27-sw@jkqxz.net
State Superseded
Headers show

Commit Message

Mark Thompson Aug. 22, 2018, 11:44 p.m. UTC
Including a unit test.
---
 libavcodec/Makefile            |   3 +-
 libavcodec/h264_levels.c       | 130 +++++++++++++++++++++++
 libavcodec/h264_levels.h       |  53 ++++++++++
 libavcodec/tests/.gitignore    |   1 +
 libavcodec/tests/h264_levels.c | 183 +++++++++++++++++++++++++++++++++
 tests/fate/libavcodec.mak      |   5 +
 6 files changed, 374 insertions(+), 1 deletion(-)
 create mode 100644 libavcodec/h264_levels.c
 create mode 100644 libavcodec/h264_levels.h
 create mode 100644 libavcodec/tests/h264_levels.c

Comments

James Almer Aug. 23, 2018, 3:09 p.m. UTC | #1
On 8/22/2018 8:44 PM, Mark Thompson wrote:
> Including a unit test.
> ---
>  libavcodec/Makefile            |   3 +-
>  libavcodec/h264_levels.c       | 130 +++++++++++++++++++++++
>  libavcodec/h264_levels.h       |  53 ++++++++++
>  libavcodec/tests/.gitignore    |   1 +
>  libavcodec/tests/h264_levels.c | 183 +++++++++++++++++++++++++++++++++
>  tests/fate/libavcodec.mak      |   5 +
>  6 files changed, 374 insertions(+), 1 deletion(-)
>  create mode 100644 libavcodec/h264_levels.c
>  create mode 100644 libavcodec/h264_levels.h
>  create mode 100644 libavcodec/tests/h264_levels.c
> 
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index cbbfc9af2e..d07a9073af 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -355,7 +355,7 @@ OBJS-$(CONFIG_H264_OMX_ENCODER)        += omx.o
>  OBJS-$(CONFIG_H264_QSV_DECODER)        += qsvdec_h2645.o
>  OBJS-$(CONFIG_H264_QSV_ENCODER)        += qsvenc_h264.o
>  OBJS-$(CONFIG_H264_RKMPP_DECODER)      += rkmppdec.o
> -OBJS-$(CONFIG_H264_VAAPI_ENCODER)      += vaapi_encode_h264.o
> +OBJS-$(CONFIG_H264_VAAPI_ENCODER)      += h264_levels.o vaapi_encode_h264.o
>  OBJS-$(CONFIG_H264_VIDEOTOOLBOX_ENCODER) += videotoolboxenc.o
>  OBJS-$(CONFIG_H264_V4L2M2M_DECODER)    += v4l2_m2m_dec.o
>  OBJS-$(CONFIG_H264_V4L2M2M_ENCODER)    += v4l2_m2m_enc.o
> @@ -1134,6 +1134,7 @@ TESTPROGS-$(CONFIG_IDCTDSP)               += dct
>  TESTPROGS-$(CONFIG_IIRFILTER)             += iirfilter
>  TESTPROGS-$(HAVE_MMX)                     += motion
>  TESTPROGS-$(CONFIG_MPEGVIDEO)             += mpeg12framerate
> +TESTPROGS-$(CONFIG_H264_VAAPI_ENCODER)    += h264_levels

Needing h264_vaapi_encoder to test this is not ideal. You'd be limiting
the amount of fate clients testing an internal module to those with an
unrelated external encoder enabled.

Maybe just add h264_levels.o to h264parse, and check for that instead?
Alternatively, since you need to add that object to h264_metadata to fix
the build failure Michael reported for patch 28, you could check for
that bsf instead.
diff mbox

Patch

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index cbbfc9af2e..d07a9073af 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -355,7 +355,7 @@  OBJS-$(CONFIG_H264_OMX_ENCODER)        += omx.o
 OBJS-$(CONFIG_H264_QSV_DECODER)        += qsvdec_h2645.o
 OBJS-$(CONFIG_H264_QSV_ENCODER)        += qsvenc_h264.o
 OBJS-$(CONFIG_H264_RKMPP_DECODER)      += rkmppdec.o
-OBJS-$(CONFIG_H264_VAAPI_ENCODER)      += vaapi_encode_h264.o
+OBJS-$(CONFIG_H264_VAAPI_ENCODER)      += h264_levels.o vaapi_encode_h264.o
 OBJS-$(CONFIG_H264_VIDEOTOOLBOX_ENCODER) += videotoolboxenc.o
 OBJS-$(CONFIG_H264_V4L2M2M_DECODER)    += v4l2_m2m_dec.o
 OBJS-$(CONFIG_H264_V4L2M2M_ENCODER)    += v4l2_m2m_enc.o
@@ -1134,6 +1134,7 @@  TESTPROGS-$(CONFIG_IDCTDSP)               += dct
 TESTPROGS-$(CONFIG_IIRFILTER)             += iirfilter
 TESTPROGS-$(HAVE_MMX)                     += motion
 TESTPROGS-$(CONFIG_MPEGVIDEO)             += mpeg12framerate
+TESTPROGS-$(CONFIG_H264_VAAPI_ENCODER)    += h264_levels
 TESTPROGS-$(CONFIG_RANGECODER)            += rangecoder
 TESTPROGS-$(CONFIG_SNOW_ENCODER)          += snowenc
 
diff --git a/libavcodec/h264_levels.c b/libavcodec/h264_levels.c
new file mode 100644
index 0000000000..6b4e18a914
--- /dev/null
+++ b/libavcodec/h264_levels.c
@@ -0,0 +1,130 @@ 
+/*
+ * 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 "avcodec.h"
+#include "h264_levels.h"
+
+// H.264 table A-1.
+static const H264LevelDescriptor h264_levels[] = {
+    // Name          MaxMBPS                   MaxBR              MinCR
+    //  | level_idc     |       MaxFS            |    MaxCPB        | MaxMvsPer2Mb
+    //  |     | cs3f    |         |  MaxDpbMbs   |       |  MaxVmvR |   |
+    { "1",   10, 0,     1485,     99,    396,     64,    175,   64, 2,  0 },
+    { "1b",  10, 1,     1485,     99,    396,    128,    350,   64, 2,  0 },
+    { "1b",   9, 0,     1485,     99,    396,    128,    350,   64, 2,  0 },
+    { "1.1", 11, 0,     3000,    396,    900,    192,    500,  128, 2,  0 },
+    { "1.2", 12, 0,     6000,    396,   2376,    384,   1000,  128, 2,  0 },
+    { "1.3", 13, 0,    11880,    396,   2376,    768,   2000,  128, 2,  0 },
+    { "2",   20, 0,    11880,    396,   2376,   2000,   2000,  128, 2,  0 },
+    { "2.1", 21, 0,    19800,    792,   4752,   4000,   4000,  256, 2,  0 },
+    { "2.2", 22, 0,    20250,   1620,   8100,   4000,   4000,  256, 2,  0 },
+    { "3",   30, 0,    40500,   1620,   8100,  10000,  10000,  256, 2, 32 },
+    { "3.1", 31, 0,   108000,   3600,  18000,  14000,  14000,  512, 4, 16 },
+    { "3.2", 32, 0,   216000,   5120,  20480,  20000,  20000,  512, 4, 16 },
+    { "4",   40, 0,   245760,   8192,  32768,  20000,  25000,  512, 4, 16 },
+    { "4.1", 41, 0,   245760,   8192,  32768,  50000,  62500,  512, 2, 16 },
+    { "4.2", 42, 0,   522240,   8704,  34816,  50000,  62500,  512, 2, 16 },
+    { "5",   50, 0,   589824,  22080, 110400, 135000, 135000,  512, 2, 16 },
+    { "5.1", 51, 0,   983040,  36864, 184320, 240000, 240000,  512, 2, 16 },
+    { "5.2", 52, 0,  2073600,  36864, 184320, 240000, 240000,  512, 2, 16 },
+    { "6",   60, 0,  4177920, 139264, 696320, 240000, 240000, 8192, 2, 16 },
+    { "6.1", 61, 0,  8355840, 139264, 696320, 480000, 480000, 8192, 2, 16 },
+    { "6.2", 62, 0, 16711680, 139264, 696320, 800000, 800000, 8192, 2, 16 },
+};
+
+// H.264 table A-2 plus values from A-1.
+static const struct {
+    int profile_idc;
+    int cpb_br_vcl_factor;
+    int cpb_br_nal_factor;
+} h264_br_factors[] = {
+    {  66, 1000, 1200 },
+    {  77, 1000, 1200 },
+    {  88, 1000, 1200 },
+    { 100, 1250, 1500 },
+    { 110, 3000, 3600 },
+    { 122, 4000, 4800 },
+    { 244, 4000, 4800 },
+    {  44, 4000, 4800 },
+};
+
+// We are only ever interested in the NAL bitrate factor.
+static int h264_get_br_factor(int profile_idc)
+{
+    int i;
+    for (i = 0; i < FF_ARRAY_ELEMS(h264_br_factors); i++) {
+        if (h264_br_factors[i].profile_idc == profile_idc)
+            return h264_br_factors[i].cpb_br_nal_factor;
+    }
+    // Default to the non-high profile value if not specified.
+    return 1200;
+}
+
+const H264LevelDescriptor *ff_h264_get_level(int level_idc,
+                                             int constraint_set3_flag)
+{
+    int i;
+    for (i = 0; i < FF_ARRAY_ELEMS(h264_levels); i++) {
+        if (h264_levels[i].level_idc            == level_idc &&
+            h264_levels[i].constraint_set3_flag == constraint_set3_flag)
+            return &h264_levels[i];
+    }
+    return NULL;
+}
+
+const H264LevelDescriptor *ff_h264_guess_level(int profile_idc,
+                                               int64_t bitrate,
+                                               int width, int height,
+                                               int max_dec_frame_buffering)
+{
+    int width_mbs  = (width  + 15) / 16;
+    int height_mbs = (height + 15) / 16;
+    int no_cs3f = !(profile_idc == 66 ||
+                    profile_idc == 77 ||
+                    profile_idc == 88);
+    int i;
+
+    for (i = 0; i < FF_ARRAY_ELEMS(h264_levels); i++) {
+        const H264LevelDescriptor *level = &h264_levels[i];
+
+        if (level->constraint_set3_flag && no_cs3f)
+            continue;
+
+        if (bitrate > level->max_br * h264_get_br_factor(profile_idc))
+            continue;
+
+        if (width_mbs  * height_mbs > level->max_fs)
+            continue;
+        if (width_mbs  * width_mbs  > 8 * level->max_fs)
+            continue;
+        if (height_mbs * height_mbs > 8 * level->max_fs)
+            continue;
+
+        if (width_mbs && height_mbs) {
+            int max_dpb_frames =
+                FFMIN(level->max_dpb_mbs / (width_mbs * height_mbs), 16);
+            if (max_dec_frame_buffering > max_dpb_frames)
+                continue;
+        }
+
+        return level;
+    }
+
+    // No usable levels found - frame is too big or bitrate is too high.
+    return NULL;
+}
diff --git a/libavcodec/h264_levels.h b/libavcodec/h264_levels.h
new file mode 100644
index 0000000000..4189fc61c2
--- /dev/null
+++ b/libavcodec/h264_levels.h
@@ -0,0 +1,53 @@ 
+/*
+ * 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
+ */
+
+#ifndef AVCODEC_H264_LEVELS_H
+#define AVCODEC_H264_LEVELS_H
+
+
+#include <stdint.h>
+
+typedef struct H264LevelDescriptor {
+    const char *name;
+    uint8_t     level_idc;
+    uint8_t     constraint_set3_flag;
+    uint32_t    max_mbps;
+    uint32_t    max_fs;
+    uint32_t    max_dpb_mbs;
+    uint32_t    max_br;
+    uint32_t    max_cpb;
+    uint16_t    max_v_mv_r;
+    uint8_t     min_cr;
+    uint8_t     max_mvs_per_2mb;
+} H264LevelDescriptor;
+
+const H264LevelDescriptor *ff_h264_get_level(int level_idc,
+                                             int constraint_set3_flag);
+
+/**
+ * Guess the level of a stream from some parameters.
+ *
+ * Unknown parameters may be zero, in which case they are ignored.
+ */
+const H264LevelDescriptor *ff_h264_guess_level(int profile_idc,
+                                               int64_t bitrate,
+                                               int width, int height,
+                                               int max_dec_frame_buffering);
+
+
+#endif /* AVCODEC_H264_LEVELS_H */
diff --git a/libavcodec/tests/.gitignore b/libavcodec/tests/.gitignore
index 350fb2990c..73945a7c82 100644
--- a/libavcodec/tests/.gitignore
+++ b/libavcodec/tests/.gitignore
@@ -7,6 +7,7 @@ 
 /fft-fixed
 /fft-fixed32
 /golomb
+/h264_levels
 /htmlsubtitles
 /iirfilter
 /imgconvert
diff --git a/libavcodec/tests/h264_levels.c b/libavcodec/tests/h264_levels.c
new file mode 100644
index 0000000000..794517eb6c
--- /dev/null
+++ b/libavcodec/tests/h264_levels.c
@@ -0,0 +1,183 @@ 
+/*
+ * 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/common.h"
+#include "libavcodec/h264_levels.h"
+
+static const struct {
+    int width;
+    int height;
+    int level_idc;
+} test_sizes[] = {
+    // First level usable at some standard sizes.
+    // (From H.264 table A-6.)
+    {  176,  144, 10 }, // QCIF
+    {  352,  288, 11 }, // CIF
+    {  640,  480, 22 }, // VGA
+    {  720,  480, 22 }, // NTSC
+    {  720,  576, 22 }, // PAL
+    {  800,  600, 31 }, // SVGA
+    { 1280,  720, 31 }, // 720p
+    { 1280, 1024, 32 }, // SXGA
+    { 1920, 1080, 40 }, // 1080p
+    { 2048, 1080, 42 }, // 2Kx1080
+    { 2048, 1536, 50 }, // 4XGA
+    { 3840, 2160, 51 }, // 4K
+    { 7680, 4320, 60 }, // 8K
+
+    // Overly wide or tall sizes.
+    {    1,  256, 10 },
+    {    1,  512, 11 },
+    {    1, 1024, 21 },
+    {    1, 1808, 22 },
+    {    1, 1824, 31 },
+    {  256,    1, 10 },
+    {  512,    1, 11 },
+    { 1024,    1, 21 },
+    { 1808,    1, 22 },
+    { 1824,    1, 31 },
+    {  512, 4096, 40 },
+    {  256, 4112, 42 },
+    { 8688, 1024, 51 },
+    { 8704,  512, 60 },
+    { 16880,   1, 60 },
+    { 16896,   1,  0 },
+};
+
+static const struct {
+    int width;
+    int height;
+    int dpb_size;
+    int level_idc;
+} test_dpb[] = {
+    // First level usable for some DPB sizes.
+    // (From H.264 table A-7.)
+    {  176,  144,  4, 10 },
+    {  176,  144,  8, 11 },
+    {  176,  144, 16, 12 },
+    { 1280,  720,  1, 31 },
+    { 1280,  720,  5, 31 },
+    { 1280,  720,  9, 40 },
+    { 1280,  720, 10, 50 },
+    { 1920, 1080,  1, 40 },
+    { 1920, 1080,  5, 50 },
+    { 1920, 1080, 13, 50 },
+    { 1920, 1080, 14, 51 },
+    { 3840, 2160,  5, 51 },
+    { 3840, 2160,  6, 60 },
+    { 3840, 2160, 16, 60 },
+    { 7680, 4320,  5, 60 },
+    { 7680, 4320,  6,  0 },
+};
+
+static const struct {
+    int64_t bitrate;
+    int profile_idc;
+    int level_idc;
+} test_bitrate[] = {
+    // Values where profile affects level at a given bitrate.
+    {   2500000,  77, 21 },
+    {   2500000, 100, 20 },
+    {   2500000, 244, 13 },
+    { 100000000,  77, 50 },
+    { 100000000, 100, 50 },
+    { 100000000, 244, 41 },
+    { 999999999,  77,  0 },
+    { 999999999, 100, 62 },
+    // Check level 1b.
+    {  32 * 1200,  66, 10 },
+    {  32 * 1500, 100, 10 },
+    {  96 * 1200,  66, 10 },
+    {  96 * 1500, 100,  9 },
+    { 144 * 1200,  66, 11 },
+    { 144 * 1500, 100, 11 },
+};
+
+static const struct {
+    const char *name;
+    int profile_idc;
+    int64_t bitrate;
+    int width;
+    int height;
+    int dpb_frames;
+    int level_idc;
+} test_all[] = {
+    { "Bluray 1080p 40Mb/s", 100, 40000000, 1920, 1080, 4, 41 },
+    { "Bluray 1080p 24Mb/s", 100, 24000000, 1920, 1080, 4, 40 },
+    { "Bluray 720p 40Mb/s",  100, 40000000, 1280,  720, 6, 41 },
+    { "Bluray 720p 24Mb/s",  100, 24000000, 1280,  720, 6, 40 },
+    { "Bluray PAL 40Mb/s",   100, 40000000,  720,  576, 6, 41 },
+    { "Bluray PAL 24Mb/s",   100, 24000000,  720,  576, 6, 32 },
+    { "Bluray PAL 16Mb/s",   100, 16800000,  720,  576, 6, 31 },
+    { "Bluray PAL 12Mb/s",   100, 12000000,  720,  576, 5, 30 },
+    { "Bluray NTSC 40Mb/s",  100, 40000000,  720,  480, 6, 41 },
+    { "Bluray NTSC 24Mb/s",  100, 24000000,  720,  480, 6, 32 },
+    { "Bluray NTSC 16Mb/s",  100, 16800000,  720,  480, 6, 31 },
+    { "Bluray NTSC 12Mb/s",  100, 12000000,  720,  480, 6, 30 },
+};
+
+int main(void)
+{
+    const H264LevelDescriptor *level;
+    int i;
+
+#define CHECK(expected, format, ...) do { \
+        if (expected ? (!level || level->level_idc != expected) \
+                     : !!level) { \
+            av_log(NULL, AV_LOG_ERROR, "Incorrect level for " \
+                   format ": expected %d, got %d.\n", __VA_ARGS__, \
+                   expected, level ? level->level_idc : -1); \
+            return 1; \
+        } \
+    } while (0)
+
+    for (i = 0; i < FF_ARRAY_ELEMS(test_sizes); i++) {
+        level = ff_h264_guess_level(0, 0, test_sizes[i].width,
+                                    test_sizes[i].height, 0);
+        CHECK(test_sizes[i].level_idc, "size %dx%d",
+              test_sizes[i].width, test_sizes[i].height);
+    }
+
+    for (i = 0; i < FF_ARRAY_ELEMS(test_dpb); i++) {
+        level = ff_h264_guess_level(0, 0, test_dpb[i].width,
+                                    test_dpb[i].height,
+                                    test_dpb[i].dpb_size);
+        CHECK(test_dpb[i].level_idc, "size %dx%d dpb %d",
+              test_dpb[i].width, test_dpb[i].height,
+              test_dpb[i].dpb_size);
+    }
+
+    for (i = 0; i < FF_ARRAY_ELEMS(test_bitrate); i++) {
+        level = ff_h264_guess_level(test_bitrate[i].profile_idc,
+                                    test_bitrate[i].bitrate,
+                                    0, 0, 0);
+        CHECK(test_bitrate[i].level_idc, "bitrate %"PRId64" profile %d",
+              test_bitrate[i].bitrate, test_bitrate[i].profile_idc);
+    }
+
+    for (i = 0; i < FF_ARRAY_ELEMS(test_all); i++) {
+        level = ff_h264_guess_level(test_all[i].profile_idc,
+                                    test_all[i].bitrate,
+                                    test_all[i].width,
+                                    test_all[i].height,
+                                    test_all[i].dpb_frames);
+        CHECK(test_all[i].level_idc, "%s", test_all[i].name);
+    }
+
+    return 0;
+}
diff --git a/tests/fate/libavcodec.mak b/tests/fate/libavcodec.mak
index d3b2dd874e..aa4c36b112 100644
--- a/tests/fate/libavcodec.mak
+++ b/tests/fate/libavcodec.mak
@@ -46,6 +46,11 @@  fate-dct8x8: libavcodec/tests/dct$(EXESUF)
 fate-dct8x8: CMD = run libavcodec/tests/dct
 fate-dct8x8: CMP = null
 
+FATE_LIBAVCODEC-$(CONFIG_H264_VAAPI_ENCODER) += fate-h264-levels
+fate-h264-levels: libavcodec/tests/h264_levels$(EXESUF)
+fate-h264-levels: CMD = run libavcodec/tests/h264_levels
+fate-h264-levels: REF = /dev/null
+
 FATE_LIBAVCODEC-$(CONFIG_IIRFILTER) += fate-iirfilter
 fate-iirfilter: libavcodec/tests/iirfilter$(EXESUF)
 fate-iirfilter: CMD = run libavcodec/tests/iirfilter