diff mbox series

[FFmpeg-devel,v4,1/1] avcodec: add external enc libvvenc for H266/VVC

Message ID 20240528162629.28698-2-chris10317h5@gmail.com
State New
Headers show
Series Add support for H266/VVC encoding | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Christian Bartnik May 28, 2024, 4:26 p.m. UTC
From: Thomas Siedel <thomas.ff@spin-digital.com>

Add external encoder VVenC for H266/VVC encoding.
Register new encoder libvvenc.
Add libvvenc to wrap the vvenc interface.
libvvenc implements encoder option: preset,qp,qpa,period,
passlogfile,stats,vvenc-params,level,tier.
Enable encoder by adding --enable-libvvenc in configure step.

Co-authored-by: Christian Bartnik chris10317h5@gmail.com
Signed-off-by: Thomas Siedel <thomas.ff@spin-digital.com>
---
 configure                 |   4 +
 doc/encoders.texi         |  64 +++++
 fftools/ffmpeg_mux_init.c |   2 +-
 libavcodec/Makefile       |   1 +
 libavcodec/allcodecs.c    |   1 +
 libavcodec/libvvenc.c     | 507 ++++++++++++++++++++++++++++++++++++++
 6 files changed, 578 insertions(+), 1 deletion(-)
 create mode 100644 libavcodec/libvvenc.c

Comments

Nuo Mi June 3, 2024, 1:23 p.m. UTC | #1
Hi all,

Tested with
./ffmpeg -f lavfi -i testsrc=duration=10:size=1280x720:rate=30 -c:v
libvvenc test.mp4 && ./ffplay -strict -2 -i test.mp4

I will merge in 3 days if there are no objections.
thank you

On Wed, May 29, 2024 at 12:27 AM Christian Bartnik <chris10317h5@gmail.com>
wrote:

> From: Thomas Siedel <thomas.ff@spin-digital.com>
>
> Add external encoder VVenC for H266/VVC encoding.
> Register new encoder libvvenc.
> Add libvvenc to wrap the vvenc interface.
> libvvenc implements encoder option: preset,qp,qpa,period,
> passlogfile,stats,vvenc-params,level,tier.
> Enable encoder by adding --enable-libvvenc in configure step.
>
> Co-authored-by: Christian Bartnik chris10317h5@gmail.com
> Signed-off-by: Thomas Siedel <thomas.ff@spin-digital.com>
> ---
>  configure                 |   4 +
>  doc/encoders.texi         |  64 +++++
>  fftools/ffmpeg_mux_init.c |   2 +-
>  libavcodec/Makefile       |   1 +
>  libavcodec/allcodecs.c    |   1 +
>  libavcodec/libvvenc.c     | 507 ++++++++++++++++++++++++++++++++++++++
>  6 files changed, 578 insertions(+), 1 deletion(-)
>  create mode 100644 libavcodec/libvvenc.c
>
> diff --git a/configure b/configure
> index 96b181fd21..082cbca7bb 100755
> --- a/configure
> +++ b/configure
> @@ -293,6 +293,7 @@ External library support:
>    --enable-libvorbis       enable Vorbis en/decoding via libvorbis,
>                             native implementation exists [no]
>    --enable-libvpx          enable VP8 and VP9 de/encoding via libvpx [no]
> +  --enable-libvvenc        enable H.266/VVC encoding via vvenc [no]
>    --enable-libwebp         enable WebP encoding via libwebp [no]
>    --enable-libx264         enable H.264 encoding via x264 [no]
>    --enable-libx265         enable HEVC encoding via x265 [no]
> @@ -1966,6 +1967,7 @@ EXTERNAL_LIBRARY_LIST="
>      libvmaf
>      libvorbis
>      libvpx
> +    libvvenc
>      libwebp
>      libxevd
>      libxeve
> @@ -3560,6 +3562,7 @@ libvpx_vp8_decoder_deps="libvpx"
>  libvpx_vp8_encoder_deps="libvpx"
>  libvpx_vp9_decoder_deps="libvpx"
>  libvpx_vp9_encoder_deps="libvpx"
> +libvvenc_encoder_deps="libvvenc"
>  libwebp_encoder_deps="libwebp"
>  libwebp_anim_encoder_deps="libwebp"
>  libx262_encoder_deps="libx262"
> @@ -7030,6 +7033,7 @@ enabled libvpx            && {
>      fi
>  }
>
> +enabled libvvenc          && require_pkg_config libvvenc "libvvenc >=
> 1.6.1" "vvenc/vvenc.h" vvenc_get_version
>  enabled libwebp           && {
>      enabled libwebp_encoder      && require_pkg_config libwebp "libwebp
> >= 0.2.0" webp/encode.h WebPGetEncoderVersion
>      enabled libwebp_anim_encoder && check_pkg_config libwebp_anim_encoder
> "libwebpmux >= 0.4.0" webp/mux.h WebPAnimEncoderOptionsInit; }
> diff --git a/doc/encoders.texi b/doc/encoders.texi
> index c82f316f94..496852faeb 100644
> --- a/doc/encoders.texi
> +++ b/doc/encoders.texi
> @@ -2378,6 +2378,70 @@ Indicates frame duration
>  For more information about libvpx see:
>  @url{http://www.webmproject.org/}
>
> +@section libvvenc
> +
> +VVenC H.266/VVC encoder wrapper.
> +
> +This encoder requires the presence of the libvvenc headers and library
> +during configuration. You need to explicitly configure the build with
> +@option{--enable-libvvenc}.
> +
> +The VVenC project website is at
> +@url{https://github.com/fraunhoferhhi/vvenc}.
> +
> +@subsection Supported Pixel Formats
> +
> +VVenC supports only 10-bit color spaces as input. But the internal
> (encoded)
> +bit depth can be set to 8-bit or 10-bit at runtime.
> +
> +@subsection Options
> +
> +@table @option
> +@item b
> +Sets target video bitrate.
> +
> +@item g
> +Set the GOP size. Currently support for g=1 (Intra only) or default.
> +
> +@item preset
> +Set the VVenC preset.
> +
> +@item levelidc
> +Set level idc.
> +
> +@item tier
> +Set vvc tier.
> +
> +@item qp
> +Set constant quantization parameter.
> +
> +@item subopt @var{boolean}
> +Set subjective (perceptually motivated) optimization. Default is 1 (on).
> +
> +@item bitdepth8 @var{boolean}
> +Set 8bit coding mode instead of using 10bit. Default is 0 (off).
> +
> +@item period
> +set (intra) refresh period in seconds.
> +
> +@item vvenc-params
> +Set vvenc options using a list of @var{key}=@var{value} couples separated
> +by ":". See @command{vvencapp --fullhelp} or @command{vvencFFapp
> --fullhelp} for a list of options.
> +
> +For example, the options might be provided as:
> +
> +@example
> +intraperiod=64:decodingrefreshtype=idr:poc0idr=1:internalbitdepth=8
> +@end example
> +
> +For example the encoding options might be provided with
> @option{-vvenc-params}:
> +
> +@example
> +ffmpeg -i input -c:v libvvenc -b 1M -vvenc-params
> intraperiod=64:decodingrefreshtype=idr:poc0idr=1:internalbitdepth=8
> output.mp4
> +@end example
> +
> +@end table
> +
>  @section libwebp
>
>  libwebp WebP Image encoder wrapper
> diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c
> index 8797265145..ef2922854a 100644
> --- a/fftools/ffmpeg_mux_init.c
> +++ b/fftools/ffmpeg_mux_init.c
> @@ -739,7 +739,7 @@ static int new_stream_video(Muxer *mux, const
> OptionsContext *o,
>                       ost->logfile_prefix ? ost->logfile_prefix :
>
> DEFAULT_PASS_LOGFILENAME_PREFIX,
>                       ost_idx);
> -            if (!strcmp(ost->enc_ctx->codec->name, "libx264")) {
> +            if (!strcmp(ost->enc_ctx->codec->name, "libx264") ||
> !strcmp(ost->enc_ctx->codec->name, "libvvenc")) {
>                  av_dict_set(&ost->encoder_opts, "stats", logfilename,
> AV_DICT_DONT_OVERWRITE);
>              } else {
>                  if (video_enc->flags & AV_CODEC_FLAG_PASS2) {
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index 2443d2c6fd..5d7349090e 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -1153,6 +1153,7 @@ OBJS-$(CONFIG_LIBVPX_VP8_DECODER)         +=
> libvpxdec.o
>  OBJS-$(CONFIG_LIBVPX_VP8_ENCODER)         += libvpxenc.o
>  OBJS-$(CONFIG_LIBVPX_VP9_DECODER)         += libvpxdec.o
>  OBJS-$(CONFIG_LIBVPX_VP9_ENCODER)         += libvpxenc.o
> +OBJS-$(CONFIG_LIBVVENC_ENCODER)           += libvvenc.o
>  OBJS-$(CONFIG_LIBWEBP_ENCODER)            += libwebpenc_common.o
> libwebpenc.o
>  OBJS-$(CONFIG_LIBWEBP_ANIM_ENCODER)       += libwebpenc_common.o
> libwebpenc_animencoder.o
>  OBJS-$(CONFIG_LIBX262_ENCODER)            += libx264.o
> diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
> index b102a8069e..59d36dbd56 100644
> --- a/libavcodec/allcodecs.c
> +++ b/libavcodec/allcodecs.c
> @@ -800,6 +800,7 @@ extern const FFCodec ff_libvpx_vp8_encoder;
>  extern const FFCodec ff_libvpx_vp8_decoder;
>  extern FFCodec ff_libvpx_vp9_encoder;
>  extern const FFCodec ff_libvpx_vp9_decoder;
> +extern const FFCodec ff_libvvenc_encoder;
>  /* preferred over libwebp */
>  extern const FFCodec ff_libwebp_anim_encoder;
>  extern const FFCodec ff_libwebp_encoder;
> diff --git a/libavcodec/libvvenc.c b/libavcodec/libvvenc.c
> new file mode 100644
> index 0000000000..9de3d8630f
> --- /dev/null
> +++ b/libavcodec/libvvenc.c
> @@ -0,0 +1,507 @@
> +/*
> + * H.266 encoding using the VVenC library
> + *
> + * Copyright (C) 2022, Thomas Siedel
> + *
> + * 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 <vvenc/vvenc.h>
> +#include <vvenc/vvencCfg.h>
> +#include <vvenc/version.h>
> +
> +#include "avcodec.h"
> +#include "codec_internal.h"
> +#include "encode.h"
> +#include "internal.h"
> +#include "packet_internal.h"
> +#include "profiles.h"
> +
> +#include "libavutil/avutil.h"
> +#include "libavutil/mem.h"
> +#include "libavutil/pixdesc.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/common.h"
> +#include "libavutil/imgutils.h"
> +#include "libavutil/frame.h"
> +#include "libavutil/log.h"
> +#include "libavutil/avstring.h"
> +
> +#define VVENC_VERSION_INT  AV_VERSION_INT(VVENC_VERSION_MAJOR, \
> +                                          VVENC_VERSION_MINOR, \
> +                                          VVENC_VERSION_PATCH)
> +
> +typedef struct VVenCContext {
> +    AVClass         *class;
> +    vvencEncoder    *encoder;
> +    vvencAccessUnit *au;
> +    bool             encode_done;
> +    int   preset;
> +    int   qp;
> +    int   qpa;
> +    int   intra_refresh_sec;
> +    char *level;
> +    int   tier;
> +    char *stats;
> +    AVDictionary *vvenc_opts;
> +} VVenCContext;
> +
> +static void vvenc_log_callback(void *ctx, int level,
> +                               const char *fmt, va_list args)
> +{
> +    vvenc_config params;
> +    vvencEncoder *encoder = (vvencEncoder *)ctx;
> +    if (encoder) {
> +        vvenc_config_default(&params);
> +        vvenc_get_config(encoder, &params);
> +        if ((int)params.m_verbosity >= level)
> +            vfprintf(level == 1 ? stderr : stdout, fmt, args);
> +    }
> +}
> +
> +static void vvenc_set_verbository(vvenc_config *params)
> +{
> +    int loglevel = av_log_get_level();
> +    params->m_verbosity = VVENC_SILENT;
> +    if (loglevel >= AV_LOG_DEBUG)
> +        params->m_verbosity = VVENC_DETAILS;
> +    else if (loglevel >= AV_LOG_VERBOSE)
> +        params->m_verbosity = VVENC_NOTICE;
> +    else if (loglevel >= AV_LOG_INFO)
> +        params->m_verbosity = VVENC_WARNING;
> +}
> +
> +static void vvenc_set_pic_format(AVCodecContext *avctx, vvenc_config
> *params)
> +{
> +    params->m_internChromaFormat = VVENC_CHROMA_420;
> +    params->m_inputBitDepth[0]   = 10;
> +}
> +
> +static void vvenc_set_color_format(AVCodecContext *avctx, vvenc_config
> *params)
> +{
> +    if (avctx->color_primaries != AVCOL_PRI_UNSPECIFIED)
> +        params->m_colourPrimaries = (int) avctx->color_primaries;
> +    if (avctx->colorspace != AVCOL_SPC_UNSPECIFIED)
> +        params->m_matrixCoefficients = (int) avctx->colorspace;
> +    if (avctx->color_trc != AVCOL_TRC_UNSPECIFIED) {
> +        params->m_transferCharacteristics = (int) avctx->color_trc;
> +
> +        if (avctx->color_trc == AVCOL_TRC_SMPTE2084)
> +            params->m_HdrMode = (avctx->color_primaries ==
> AVCOL_PRI_BT2020) ?
> +                                VVENC_HDR_PQ_BT2020 : VVENC_HDR_PQ;
> +        else if (avctx->color_trc == AVCOL_TRC_BT2020_10 ||
> avctx->color_trc == AVCOL_TRC_ARIB_STD_B67)
> +            params->m_HdrMode = (avctx->color_trc == AVCOL_TRC_BT2020_10
> ||
> +                                 avctx->color_primaries ==
> AVCOL_PRI_BT2020 ||
> +                                 avctx->colorspace ==
> AVCOL_SPC_BT2020_NCL ||
> +                                 avctx->colorspace ==
> AVCOL_SPC_BT2020_CL) ?
> +                                VVENC_HDR_HLG_BT2020 : VVENC_HDR_HLG;
> +    }
> +
> +    if (params->m_HdrMode == VVENC_HDR_OFF &&
> +        (avctx->color_primaries != AVCOL_PRI_UNSPECIFIED ||
> avctx->colorspace != AVCOL_SPC_UNSPECIFIED)) {
> +        params->m_vuiParametersPresent = 1;
> +        params->m_colourDescriptionPresent = true;
> +    }
> +}
> +
> +static void vvenc_set_framerate(AVCodecContext *avctx, vvenc_config
> *params)
> +{
> +    if (avctx->framerate.num > 0 && avctx->framerate.den > 0) {
> +        params->m_FrameRate = avctx->framerate.num;
> +        params->m_FrameScale = avctx->framerate.den;
> +    } else {
> +        params->m_FrameRate = avctx->time_base.den;
> +        params->m_FrameScale = avctx->time_base.num;
> +    }
> +
> +FF_DISABLE_DEPRECATION_WARNINGS
> +
> +#if FF_API_TICKS_PER_FRAME
> +    if (avctx->ticks_per_frame == 1) {
> +#endif
> +        params->m_TicksPerSecond = -1;   /* auto mode for ticks per frame
> = 1 */
> +#if FF_API_TICKS_PER_FRAME
> +    } else {
> +        params->m_TicksPerSecond =
> +            ceil((avctx->time_base.den / (double) avctx->time_base.num) *
> +                 (double) avctx->ticks_per_frame);
> +    }
> +#endif
> +FF_ENABLE_DEPRECATION_WARNINGS
> +}
> +
> +static int vvenc_parse_vvenc_params(AVCodecContext *avctx, vvenc_config
> *params)
> +{
> +    int parse_ret, ret;
> +    VVenCContext *s;
> +    const AVDictionaryEntry *en = NULL;
> +    s = avctx->priv_data;
> +    ret = 0;
> +
> +    while ((en = av_dict_iterate(s->vvenc_opts, en))) {
> +        av_log(avctx, AV_LOG_DEBUG, "vvenc_set_param: '%s:%s'\n", en->key,
> +               en->value);
> +        parse_ret = vvenc_set_param(params, en->key, en->value);
> +        switch (parse_ret) {
> +        case VVENC_PARAM_BAD_NAME:
> +            av_log(avctx, AV_LOG_ERROR, "Unknown vvenc option: %s.\n",
> en->key);
> +            ret = AVERROR(EINVAL);
> +            break;
> +        case VVENC_PARAM_BAD_VALUE:
> +            av_log(avctx, AV_LOG_ERROR, "Invalid vvenc value for %s:
> %s.\n", en->key, en->value);
> +            ret = AVERROR(EINVAL);
> +            break;
> +        default:
> +            break;
> +        }
> +
> +        if (!av_strcasecmp(en->key, "rcstatsfile")) {
> +            av_log(avctx, AV_LOG_ERROR, "vvenc-params 2pass option
> 'rcstatsfile' "
> +                   "not available. Use option 'passlogfile'\n");
> +            ret = AVERROR(EINVAL);
> +        }
> +        if (!av_strcasecmp(en->key, "passes") || !av_strcasecmp(en->key,
> "pass")) {
> +            av_log(avctx, AV_LOG_ERROR, "vvenc-params 2pass option '%s' "
> +                   "not available. Use option 'pass'\n", en->key);
> +            ret = AVERROR(EINVAL);
> +        }
> +    }
> +    return ret;
> +}
> +
> +static int vvenc_set_rc_mode(AVCodecContext *avctx, vvenc_config *params)
> +{
> +    params->m_RCNumPasses = 1;
> +    if ((avctx->flags & AV_CODEC_FLAG_PASS1 || avctx->flags &
> AV_CODEC_FLAG_PASS2)) {
> +        if (!avctx->bit_rate) {
> +            av_log(avctx, AV_LOG_ERROR, "A bitrate must be set to use two
> pass mode.\n");
> +            return AVERROR(EINVAL);
> +        }
> +        params->m_RCNumPasses = 2;
> +        if (avctx->flags & AV_CODEC_FLAG_PASS1)
> +            params->m_RCPass = 1;
> +        else
> +            params->m_RCPass = 2;
> +    }
> +
> +    if (avctx->rc_max_rate) {
> +#if VVENC_VERSION_INT >= AV_VERSION_INT(1,8,0)
> +        params->m_RCMaxBitrate = avctx->rc_max_rate;
> +#endif
> +
> +#if VVENC_VERSION_INT < AV_VERSION_INT(1,11,0)
> +        /* rc_max_rate without a bit_rate enables capped CQF mode.
> +        (QP + subj. optimization + max. bitrate) */
> +        if (!avctx->bit_rate) {
> +            av_log(avctx, AV_LOG_ERROR, "Capped Constant Quality Factor
> mode (capped CQF) "
> +                   "needs at least vvenc version >= 1.11.0 (current
> version %s)\n", vvenc_get_version());
> +            return AVERROR(EINVAL);
> +        }
> +#endif
> +    }
> +    return 0;
> +}
> +
> +static int vvenc_init_extradata(AVCodecContext *avctx, VVenCContext *s)
> +{
> +    int ret;
> +    if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) {
> +        ret = vvenc_get_headers(s->encoder, s->au);
> +        if (0 != ret) {
> +            av_log(avctx, AV_LOG_ERROR, "cannot get (SPS,PPS) headers:
> %s\n",
> +                   vvenc_get_last_error(s->encoder));
> +            return AVERROR(EINVAL);
> +        }
> +
> +        if (s->au->payloadUsedSize <= 0) {
> +            return AVERROR_INVALIDDATA;
> +        }
> +
> +        avctx->extradata_size = s->au->payloadUsedSize;
> +        avctx->extradata = av_mallocz(avctx->extradata_size +
> AV_INPUT_BUFFER_PADDING_SIZE);
> +        if (!avctx->extradata) {
> +            return AVERROR(ENOMEM);
> +        }
> +
> +        memcpy(avctx->extradata, s->au->payload, avctx->extradata_size);
> +    }
> +    return 0;
> +}
> +
> +static av_cold int vvenc_init(AVCodecContext *avctx)
> +{
> +    int ret;
> +    int framerate;
> +    VVenCContext *s;
> +    vvenc_config params;
> +    vvencPresetMode preset;
> +
> +    s = avctx->priv_data;
> +    preset = (vvencPresetMode) s->preset;
> +
> +    if (avctx->flags & AV_CODEC_FLAG_INTERLACED_DCT) {
> +        av_log(avctx, AV_LOG_ERROR, "interlaced not supported\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    vvenc_config_default(&params);
> +
> +    if (avctx->framerate.num > 0 && avctx->framerate.den > 0)
> +        framerate = avctx->framerate.num / avctx->framerate.den;
> +    else
> +        framerate = avctx->time_base.den / avctx->time_base.num;
> +
> +    vvenc_init_default(&params, avctx->width, avctx->height, framerate,
> +                       avctx->bit_rate, s->qp, preset);
> +
> +    vvenc_set_verbository(&params);
> +
> +    if (avctx->thread_count > 0)
> +        params.m_numThreads = avctx->thread_count;
> +
> +    /* GOP settings (IDR/CRA) */
> +    if (avctx->flags & AV_CODEC_FLAG_CLOSED_GOP)
> +        params.m_DecodingRefreshType = VVENC_DRT_IDR;
> +
> +    if (avctx->gop_size == 1) {
> +        params.m_GOPSize = 1;
> +        params.m_IntraPeriod = 1;
> +    } else
> +        params.m_IntraPeriodSec = s->intra_refresh_sec;
> +
> +    params.m_AccessUnitDelimiter = true;
> +    params.m_usePerceptQPA = s->qpa;
> +    params.m_levelTier     = (vvencTier) s->tier;
> +
> +    if (avctx->level > 0)
> +        params.m_level = (vvencLevel)avctx->level;
> +
> +    if (s->level) {
> +        if (VVENC_PARAM_BAD_VALUE == vvenc_set_param(&params, "level",
> s->level)) {
> +            av_log(avctx, AV_LOG_ERROR, "Invalid level_idc: %s.\n",
> s->level);
> +            return AVERROR(EINVAL);
> +        }
> +    }
> +
> +    vvenc_set_framerate(avctx, &params);
> +
> +    vvenc_set_pic_format(avctx, &params);
> +
> +    vvenc_set_color_format(avctx, &params);
> +
> +    if ((ret = vvenc_parse_vvenc_params(avctx, &params)) != 0)
> +        return ret;
> +
> +    if ((ret = vvenc_set_rc_mode(avctx, &params)) != 0)
> +        return ret;
> +
> +    s->encoder = vvenc_encoder_create();
> +    if (!s->encoder) {
> +        av_log(avctx, AV_LOG_ERROR, "cannot create libvvenc encoder\n");
> +        return AVERROR(ENOMEM);
> +    }
> +
> +    vvenc_set_msg_callback(&params, s->encoder, vvenc_log_callback);
> +    if ((ret = vvenc_encoder_open(s->encoder, &params)) != 0) {
> +        av_log(avctx, AV_LOG_ERROR, "cannot open libvvenc encoder: %s\n",
> +               vvenc_get_last_error(s->encoder));
> +        return AVERROR_EXTERNAL;
> +    }
> +
> +    vvenc_get_config(s->encoder, &params);     /* get the adapted config
> */
> +
> +    av_log(avctx, AV_LOG_INFO, "libvvenc version: %s\n",
> vvenc_get_version());
> +    if (av_log_get_level() >= AV_LOG_VERBOSE)
> +        av_log(avctx, AV_LOG_INFO, "%s\n",
> vvenc_get_config_as_string(&params, params.m_verbosity));
> +
> +    if (params.m_RCNumPasses == 2) {
> +        if ((ret = vvenc_init_pass(s->encoder, params.m_RCPass - 1,
> s->stats)) != 0) {
> +            av_log(avctx, AV_LOG_ERROR, "cannot init pass %d: %s\n",
> params.m_RCPass,
> +                   vvenc_get_last_error(s->encoder));
> +            return AVERROR_EXTERNAL;
> +        }
> +    }
> +
> +    s->au = vvenc_accessUnit_alloc();
> +    if (!s->au) {
> +        av_log(avctx, AV_LOG_FATAL, "cannot allocate memory for AU
> payload\n");
> +        return AVERROR(ENOMEM);
> +    }
> +    vvenc_accessUnit_alloc_payload(s->au, avctx->width * avctx->height);
> +    if (!s->au->payload) {
> +        av_log(avctx, AV_LOG_FATAL, "cannot allocate payload memory of
> size %d\n",
> +               avctx->width * avctx->height);
> +        return AVERROR(ENOMEM);
> +    }
> +
> +    if ((ret = vvenc_init_extradata(avctx, s)) != 0)
> +        return ret;
> +
> +    s->encode_done = false;
> +    return 0;
> +}
> +
> +static av_cold int vvenc_close(AVCodecContext *avctx)
> +{
> +    VVenCContext *s = avctx->priv_data;
> +
> +    if (s->au)
> +        vvenc_accessUnit_free(s->au, true);
> +
> +    if (s->encoder) {
> +        vvenc_print_summary(s->encoder);
> +
> +        if (0 != vvenc_encoder_close(s->encoder))
> +            return AVERROR_EXTERNAL;
> +    }
> +
> +    return 0;
> +}
> +
> +static av_cold int vvenc_frame(AVCodecContext *avctx, AVPacket *pkt,
> const AVFrame *frame,
> +                               int *got_packet)
> +{
> +    VVenCContext *s = avctx->priv_data;
> +    vvencYUVBuffer *pyuvbuf;
> +    vvencYUVBuffer yuvbuf;
> +    int pict_type;
> +    int ret;
> +
> +    pyuvbuf = NULL;
> +    if (frame && avctx->pix_fmt == AV_PIX_FMT_YUV420P10) {
> +        vvenc_YUVBuffer_default(&yuvbuf);
> +        yuvbuf.planes[0].ptr = (int16_t *) frame->data[0];
> +        yuvbuf.planes[1].ptr = (int16_t *) frame->data[1];
> +        yuvbuf.planes[2].ptr = (int16_t *) frame->data[2];
> +
> +        yuvbuf.planes[0].width  = frame->width;
> +        yuvbuf.planes[0].height = frame->height;
> +        yuvbuf.planes[0].stride = frame->linesize[0] >> 1; /* stride is
> used in 16bit samples in vvenc */
> +
> +        yuvbuf.planes[1].width  = frame->width >> 1;
> +        yuvbuf.planes[1].height = frame->height >> 1;
> +        yuvbuf.planes[1].stride = frame->linesize[1] >> 1;
> +
> +        yuvbuf.planes[2].width  = frame->width >> 1;
> +        yuvbuf.planes[2].height = frame->height >> 1;
> +        yuvbuf.planes[2].stride = frame->linesize[2] >> 1;
> +
> +        yuvbuf.cts = frame->pts;
> +        yuvbuf.ctsValid = true;
> +        pyuvbuf = &yuvbuf;
> +    }
> +
> +    if (!s->encode_done) {
> +        if (vvenc_encode(s->encoder, pyuvbuf, s->au, &s->encode_done) !=
> 0)
> +            return AVERROR_EXTERNAL;
> +    } else
> +        return 0;
> +
> +    if (s->au->payloadUsedSize > 0) {
> +        if ((ret = ff_get_encode_buffer(avctx, pkt,
> s->au->payloadUsedSize, 0)) < 0)
> +            return ret;
> +
> +        memcpy(pkt->data, s->au->payload, s->au->payloadUsedSize);
> +
> +        if (s->au->ctsValid)
> +            pkt->pts = s->au->cts;
> +        if (s->au->dtsValid)
> +            pkt->dts = s->au->dts;
> +        pkt->flags |= AV_PKT_FLAG_KEY * s->au->rap;
> +
> +        switch (s->au->sliceType) {
> +        case VVENC_I_SLICE:
> +            pict_type = AV_PICTURE_TYPE_I;
> +            break;
> +        case VVENC_P_SLICE:
> +            pict_type = AV_PICTURE_TYPE_P;
> +            break;
> +        case VVENC_B_SLICE:
> +            pict_type = AV_PICTURE_TYPE_B;
> +            break;
> +        default:
> +            av_log(avctx, AV_LOG_ERROR, "Unknown picture type
> encountered.\n");
> +            return AVERROR_EXTERNAL;
> +        }
> +
> +        ff_side_data_set_encoder_stats(pkt, 0, NULL, 0, pict_type);
> +
> +        *got_packet = 1;
> +        return 0;
> +    }
> +
> +    return 0;
> +}
> +
> +static const enum AVPixelFormat pix_fmts_vvenc[] = {
> +    AV_PIX_FMT_YUV420P10,
> +    AV_PIX_FMT_NONE
> +};
> +
> +#define OFFSET(x) offsetof(VVenCContext, x)
> +#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
> +static const AVOption options[] = {
> +    { "preset",       "set encoding preset", OFFSET(preset),
> AV_OPT_TYPE_INT, {.i64 = 2}, 0, 4, VE, "preset"},
> +    { "faster",       "0", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_FASTER},
> INT_MIN, INT_MAX, VE, "preset" },
> +    { "fast",         "1", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_FAST},
>  INT_MIN, INT_MAX, VE, "preset" },
> +    { "medium",       "2", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_MEDIUM},
> INT_MIN, INT_MAX, VE, "preset" },
> +    { "slow",         "3", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_SLOW},
>  INT_MIN, INT_MAX, VE, "preset" },
> +    { "slower",       "4", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_SLOWER},
> INT_MIN, INT_MAX, VE, "preset" },
> +    { "qp",           "set quantization",          OFFSET(qp),
> AV_OPT_TYPE_INT,  {.i64 = 32}, -1, 63, VE },
> +    { "qpa",          "set subjective (perceptually motivated)
> optimization", OFFSET(qpa), AV_OPT_TYPE_BOOL, {.i64 = 1},  0, 1, VE},
> +    { "passlogfile",  "Filename for 2 pass stats", OFFSET(stats),
> AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, VE},
> +    { "stats",        "Filename for 2 pass stats", OFFSET(stats),
> AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, VE},
> +    { "period",       "set (intra) refresh period in seconds",
> OFFSET(intra_refresh_sec), AV_OPT_TYPE_INT,  {.i64 = 1},  1, INT_MAX, VE },
> +    { "vvenc-params", "set the vvenc configuration using a :-separated
> list of key=value parameters", OFFSET(vvenc_opts), AV_OPT_TYPE_DICT, { 0 },
> 0, 0, VE },
> +    { "level",        "Specify level (as defined by Annex A)",
> OFFSET(level), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, VE},
> +    { "tier",         "set vvc tier", OFFSET(tier), AV_OPT_TYPE_INT,
> {.i64 = 0},  0, 1, VE, "tier"},
> +    { "main",         "main", 0, AV_OPT_TYPE_CONST, {.i64 = 0}, INT_MIN,
> INT_MAX, VE, "tier"},
> +    { "high",         "high", 0, AV_OPT_TYPE_CONST, {.i64 = 1}, INT_MIN,
> INT_MAX, VE, "tier"},
> +    {NULL}
> +};
> +
> +static const AVClass class = {
> +    .class_name = "libvvenc",
> +    .item_name  = av_default_item_name,
> +    .option     = options,
> +    .version    = LIBAVUTIL_VERSION_INT,
> +};
> +
> +static const FFCodecDefault vvenc_defaults[] = {
> +    { "b", "0" },
> +    { "g", "-1" },
> +    { NULL },
> +};
> +
> +const FFCodec ff_libvvenc_encoder = {
> +    .p.name         = "libvvenc",
> +    CODEC_LONG_NAME("libvvenc H.266 / VVC"),
> +    .p.type         = AVMEDIA_TYPE_VIDEO,
> +    .p.id           = AV_CODEC_ID_VVC,
> +    .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY |
> +                      AV_CODEC_CAP_OTHER_THREADS,
> +    .p.profiles     = NULL_IF_CONFIG_SMALL(ff_vvc_profiles),
> +    .p.priv_class   = &class,
> +    .p.wrapper_name = "libvvenc",
> +    .priv_data_size = sizeof(VVenCContext),
> +    .p.pix_fmts     = pix_fmts_vvenc,
> +    .init           = vvenc_init,
> +    FF_CODEC_ENCODE_CB(vvenc_frame),
> +    .close          = vvenc_close,
> +    .defaults       = vvenc_defaults,
> +    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP |
> FF_CODEC_CAP_AUTO_THREADS
> +};
> --
> 2.34.1
>
> _______________________________________________
> 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".
>
Andreas Rheinhardt June 3, 2024, 1:54 p.m. UTC | #2
Christian Bartnik:
> From: Thomas Siedel <thomas.ff@spin-digital.com>
> 
> Add external encoder VVenC for H266/VVC encoding.
> Register new encoder libvvenc.
> Add libvvenc to wrap the vvenc interface.
> libvvenc implements encoder option: preset,qp,qpa,period,
> passlogfile,stats,vvenc-params,level,tier.
> Enable encoder by adding --enable-libvvenc in configure step.
> 
> Co-authored-by: Christian Bartnik chris10317h5@gmail.com
> Signed-off-by: Thomas Siedel <thomas.ff@spin-digital.com>
> ---
>  configure                 |   4 +
>  doc/encoders.texi         |  64 +++++
>  fftools/ffmpeg_mux_init.c |   2 +-
>  libavcodec/Makefile       |   1 +
>  libavcodec/allcodecs.c    |   1 +
>  libavcodec/libvvenc.c     | 507 ++++++++++++++++++++++++++++++++++++++
>  6 files changed, 578 insertions(+), 1 deletion(-)
>  create mode 100644 libavcodec/libvvenc.c
> 
> diff --git a/configure b/configure
> index 96b181fd21..082cbca7bb 100755
> --- a/configure
> +++ b/configure
> @@ -293,6 +293,7 @@ External library support:
>    --enable-libvorbis       enable Vorbis en/decoding via libvorbis,
>                             native implementation exists [no]
>    --enable-libvpx          enable VP8 and VP9 de/encoding via libvpx [no]
> +  --enable-libvvenc        enable H.266/VVC encoding via vvenc [no]
>    --enable-libwebp         enable WebP encoding via libwebp [no]
>    --enable-libx264         enable H.264 encoding via x264 [no]
>    --enable-libx265         enable HEVC encoding via x265 [no]
> @@ -1966,6 +1967,7 @@ EXTERNAL_LIBRARY_LIST="
>      libvmaf
>      libvorbis
>      libvpx
> +    libvvenc
>      libwebp
>      libxevd
>      libxeve
> @@ -3560,6 +3562,7 @@ libvpx_vp8_decoder_deps="libvpx"
>  libvpx_vp8_encoder_deps="libvpx"
>  libvpx_vp9_decoder_deps="libvpx"
>  libvpx_vp9_encoder_deps="libvpx"
> +libvvenc_encoder_deps="libvvenc"
>  libwebp_encoder_deps="libwebp"
>  libwebp_anim_encoder_deps="libwebp"
>  libx262_encoder_deps="libx262"
> @@ -7030,6 +7033,7 @@ enabled libvpx            && {
>      fi
>  }
>  
> +enabled libvvenc          && require_pkg_config libvvenc "libvvenc >= 1.6.1" "vvenc/vvenc.h" vvenc_get_version
>  enabled libwebp           && {
>      enabled libwebp_encoder      && require_pkg_config libwebp "libwebp >= 0.2.0" webp/encode.h WebPGetEncoderVersion
>      enabled libwebp_anim_encoder && check_pkg_config libwebp_anim_encoder "libwebpmux >= 0.4.0" webp/mux.h WebPAnimEncoderOptionsInit; }
> diff --git a/doc/encoders.texi b/doc/encoders.texi
> index c82f316f94..496852faeb 100644
> --- a/doc/encoders.texi
> +++ b/doc/encoders.texi
> @@ -2378,6 +2378,70 @@ Indicates frame duration
>  For more information about libvpx see:
>  @url{http://www.webmproject.org/}
>  
> +@section libvvenc
> +
> +VVenC H.266/VVC encoder wrapper.
> +
> +This encoder requires the presence of the libvvenc headers and library
> +during configuration. You need to explicitly configure the build with
> +@option{--enable-libvvenc}.
> +
> +The VVenC project website is at
> +@url{https://github.com/fraunhoferhhi/vvenc}.
> +
> +@subsection Supported Pixel Formats
> +
> +VVenC supports only 10-bit color spaces as input. But the internal (encoded)
> +bit depth can be set to 8-bit or 10-bit at runtime.
> +
> +@subsection Options
> +
> +@table @option
> +@item b
> +Sets target video bitrate.
> +
> +@item g
> +Set the GOP size. Currently support for g=1 (Intra only) or default.
> +
> +@item preset
> +Set the VVenC preset.
> +
> +@item levelidc
> +Set level idc.
> +
> +@item tier
> +Set vvc tier.
> +
> +@item qp
> +Set constant quantization parameter.
> +
> +@item subopt @var{boolean}
> +Set subjective (perceptually motivated) optimization. Default is 1 (on).
> +
> +@item bitdepth8 @var{boolean}
> +Set 8bit coding mode instead of using 10bit. Default is 0 (off).
> +
> +@item period
> +set (intra) refresh period in seconds.
> +
> +@item vvenc-params
> +Set vvenc options using a list of @var{key}=@var{value} couples separated
> +by ":". See @command{vvencapp --fullhelp} or @command{vvencFFapp --fullhelp} for a list of options.
> +
> +For example, the options might be provided as:
> +
> +@example
> +intraperiod=64:decodingrefreshtype=idr:poc0idr=1:internalbitdepth=8
> +@end example
> +
> +For example the encoding options might be provided with @option{-vvenc-params}:
> +
> +@example
> +ffmpeg -i input -c:v libvvenc -b 1M -vvenc-params intraperiod=64:decodingrefreshtype=idr:poc0idr=1:internalbitdepth=8 output.mp4
> +@end example
> +
> +@end table
> +
>  @section libwebp
>  
>  libwebp WebP Image encoder wrapper
> diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c
> index 8797265145..ef2922854a 100644
> --- a/fftools/ffmpeg_mux_init.c
> +++ b/fftools/ffmpeg_mux_init.c
> @@ -739,7 +739,7 @@ static int new_stream_video(Muxer *mux, const OptionsContext *o,
>                       ost->logfile_prefix ? ost->logfile_prefix :
>                                             DEFAULT_PASS_LOGFILENAME_PREFIX,
>                       ost_idx);
> -            if (!strcmp(ost->enc_ctx->codec->name, "libx264")) {
> +            if (!strcmp(ost->enc_ctx->codec->name, "libx264") || !strcmp(ost->enc_ctx->codec->name, "libvvenc")) {
>                  av_dict_set(&ost->encoder_opts, "stats", logfilename, AV_DICT_DONT_OVERWRITE);
>              } else {
>                  if (video_enc->flags & AV_CODEC_FLAG_PASS2) {
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index 2443d2c6fd..5d7349090e 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -1153,6 +1153,7 @@ OBJS-$(CONFIG_LIBVPX_VP8_DECODER)         += libvpxdec.o
>  OBJS-$(CONFIG_LIBVPX_VP8_ENCODER)         += libvpxenc.o
>  OBJS-$(CONFIG_LIBVPX_VP9_DECODER)         += libvpxdec.o
>  OBJS-$(CONFIG_LIBVPX_VP9_ENCODER)         += libvpxenc.o
> +OBJS-$(CONFIG_LIBVVENC_ENCODER)           += libvvenc.o
>  OBJS-$(CONFIG_LIBWEBP_ENCODER)            += libwebpenc_common.o libwebpenc.o
>  OBJS-$(CONFIG_LIBWEBP_ANIM_ENCODER)       += libwebpenc_common.o libwebpenc_animencoder.o
>  OBJS-$(CONFIG_LIBX262_ENCODER)            += libx264.o
> diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
> index b102a8069e..59d36dbd56 100644
> --- a/libavcodec/allcodecs.c
> +++ b/libavcodec/allcodecs.c
> @@ -800,6 +800,7 @@ extern const FFCodec ff_libvpx_vp8_encoder;
>  extern const FFCodec ff_libvpx_vp8_decoder;
>  extern FFCodec ff_libvpx_vp9_encoder;
>  extern const FFCodec ff_libvpx_vp9_decoder;
> +extern const FFCodec ff_libvvenc_encoder;
>  /* preferred over libwebp */
>  extern const FFCodec ff_libwebp_anim_encoder;
>  extern const FFCodec ff_libwebp_encoder;
> diff --git a/libavcodec/libvvenc.c b/libavcodec/libvvenc.c
> new file mode 100644
> index 0000000000..9de3d8630f
> --- /dev/null
> +++ b/libavcodec/libvvenc.c
> @@ -0,0 +1,507 @@
> +/*
> + * H.266 encoding using the VVenC library
> + *
> + * Copyright (C) 2022, Thomas Siedel
> + *
> + * 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 <vvenc/vvenc.h>
> +#include <vvenc/vvencCfg.h>
> +#include <vvenc/version.h>
> +
> +#include "avcodec.h"
> +#include "codec_internal.h"
> +#include "encode.h"
> +#include "internal.h"
> +#include "packet_internal.h"
> +#include "profiles.h"
> +
> +#include "libavutil/avutil.h"
> +#include "libavutil/mem.h"
> +#include "libavutil/pixdesc.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/common.h"
> +#include "libavutil/imgutils.h"
> +#include "libavutil/frame.h"
> +#include "libavutil/log.h"
> +#include "libavutil/avstring.h"

Should be alphabetical

> +
> +#define VVENC_VERSION_INT  AV_VERSION_INT(VVENC_VERSION_MAJOR, \
> +                                          VVENC_VERSION_MINOR, \
> +                                          VVENC_VERSION_PATCH)
> +
> +typedef struct VVenCContext {
> +    AVClass         *class;
> +    vvencEncoder    *encoder;
> +    vvencAccessUnit *au;
> +    bool             encode_done;
> +    int   preset;
> +    int   qp;
> +    int   qpa;
> +    int   intra_refresh_sec;
> +    char *level;
> +    int   tier;
> +    char *stats;
> +    AVDictionary *vvenc_opts;
> +} VVenCContext;
> +
> +static void vvenc_log_callback(void *ctx, int level,
> +                               const char *fmt, va_list args)
> +{
> +    vvenc_config params;
> +    vvencEncoder *encoder = (vvencEncoder *)ctx;

Unnecessary cast

> +    if (encoder) {
> +        vvenc_config_default(&params);
> +        vvenc_get_config(encoder, &params);
> +        if ((int)params.m_verbosity >= level)
> +            vfprintf(level == 1 ? stderr : stdout, fmt, args);
> +    }
> +}
> +
> +static void vvenc_set_verbository(vvenc_config *params)
> +{
> +    int loglevel = av_log_get_level();
> +    params->m_verbosity = VVENC_SILENT;
> +    if (loglevel >= AV_LOG_DEBUG)
> +        params->m_verbosity = VVENC_DETAILS;
> +    else if (loglevel >= AV_LOG_VERBOSE)
> +        params->m_verbosity = VVENC_NOTICE;
> +    else if (loglevel >= AV_LOG_INFO)
> +        params->m_verbosity = VVENC_WARNING;
> +}
> +
> +static void vvenc_set_pic_format(AVCodecContext *avctx, vvenc_config *params)
> +{
> +    params->m_internChromaFormat = VVENC_CHROMA_420;
> +    params->m_inputBitDepth[0]   = 10;
> +}
> +
> +static void vvenc_set_color_format(AVCodecContext *avctx, vvenc_config *params)
> +{
> +    if (avctx->color_primaries != AVCOL_PRI_UNSPECIFIED)
> +        params->m_colourPrimaries = (int) avctx->color_primaries;
> +    if (avctx->colorspace != AVCOL_SPC_UNSPECIFIED)
> +        params->m_matrixCoefficients = (int) avctx->colorspace;
> +    if (avctx->color_trc != AVCOL_TRC_UNSPECIFIED) {
> +        params->m_transferCharacteristics = (int) avctx->color_trc;
> +
> +        if (avctx->color_trc == AVCOL_TRC_SMPTE2084)
> +            params->m_HdrMode = (avctx->color_primaries == AVCOL_PRI_BT2020) ?
> +                                VVENC_HDR_PQ_BT2020 : VVENC_HDR_PQ;
> +        else if (avctx->color_trc == AVCOL_TRC_BT2020_10 || avctx->color_trc == AVCOL_TRC_ARIB_STD_B67)
> +            params->m_HdrMode = (avctx->color_trc == AVCOL_TRC_BT2020_10 ||
> +                                 avctx->color_primaries == AVCOL_PRI_BT2020 ||
> +                                 avctx->colorspace == AVCOL_SPC_BT2020_NCL ||
> +                                 avctx->colorspace == AVCOL_SPC_BT2020_CL) ?
> +                                VVENC_HDR_HLG_BT2020 : VVENC_HDR_HLG;
> +    }
> +
> +    if (params->m_HdrMode == VVENC_HDR_OFF &&
> +        (avctx->color_primaries != AVCOL_PRI_UNSPECIFIED || avctx->colorspace != AVCOL_SPC_UNSPECIFIED)) {
> +        params->m_vuiParametersPresent = 1;
> +        params->m_colourDescriptionPresent = true;
> +    }
> +}
> +
> +static void vvenc_set_framerate(AVCodecContext *avctx, vvenc_config *params)
> +{
> +    if (avctx->framerate.num > 0 && avctx->framerate.den > 0) {
> +        params->m_FrameRate = avctx->framerate.num;
> +        params->m_FrameScale = avctx->framerate.den;
> +    } else {
> +        params->m_FrameRate = avctx->time_base.den;
> +        params->m_FrameScale = avctx->time_base.num;
> +    }
> +
> +FF_DISABLE_DEPRECATION_WARNINGS
> +
> +#if FF_API_TICKS_PER_FRAME
> +    if (avctx->ticks_per_frame == 1) {
> +#endif
> +        params->m_TicksPerSecond = -1;   /* auto mode for ticks per frame = 1 */
> +#if FF_API_TICKS_PER_FRAME
> +    } else {
> +        params->m_TicksPerSecond =
> +            ceil((avctx->time_base.den / (double) avctx->time_base.num) *
> +                 (double) avctx->ticks_per_frame);
> +    }
> +#endif
> +FF_ENABLE_DEPRECATION_WARNINGS
> +}
> +
> +static int vvenc_parse_vvenc_params(AVCodecContext *avctx, vvenc_config *params)
> +{
> +    int parse_ret, ret;
> +    VVenCContext *s;
> +    const AVDictionaryEntry *en = NULL;
> +    s = avctx->priv_data;

Should be directly initialized

> +    ret = 0;
> +
> +    while ((en = av_dict_iterate(s->vvenc_opts, en))) {
> +        av_log(avctx, AV_LOG_DEBUG, "vvenc_set_param: '%s:%s'\n", en->key,
> +               en->value);
> +        parse_ret = vvenc_set_param(params, en->key, en->value);
> +        switch (parse_ret) {
> +        case VVENC_PARAM_BAD_NAME:
> +            av_log(avctx, AV_LOG_ERROR, "Unknown vvenc option: %s.\n", en->key);
> +            ret = AVERROR(EINVAL);
> +            break;
> +        case VVENC_PARAM_BAD_VALUE:
> +            av_log(avctx, AV_LOG_ERROR, "Invalid vvenc value for %s: %s.\n", en->key, en->value);
> +            ret = AVERROR(EINVAL);
> +            break;
> +        default:
> +            break;
> +        }
> +
> +        if (!av_strcasecmp(en->key, "rcstatsfile")) {
> +            av_log(avctx, AV_LOG_ERROR, "vvenc-params 2pass option 'rcstatsfile' "
> +                   "not available. Use option 'passlogfile'\n");
> +            ret = AVERROR(EINVAL);
> +        }
> +        if (!av_strcasecmp(en->key, "passes") || !av_strcasecmp(en->key, "pass")) {
> +            av_log(avctx, AV_LOG_ERROR, "vvenc-params 2pass option '%s' "
> +                   "not available. Use option 'pass'\n", en->key);
> +            ret = AVERROR(EINVAL);
> +        }
> +    }
> +    return ret;
> +}
> +
> +static int vvenc_set_rc_mode(AVCodecContext *avctx, vvenc_config *params)
> +{
> +    params->m_RCNumPasses = 1;
> +    if ((avctx->flags & AV_CODEC_FLAG_PASS1 || avctx->flags & AV_CODEC_FLAG_PASS2)) {
> +        if (!avctx->bit_rate) {
> +            av_log(avctx, AV_LOG_ERROR, "A bitrate must be set to use two pass mode.\n");
> +            return AVERROR(EINVAL);
> +        }
> +        params->m_RCNumPasses = 2;
> +        if (avctx->flags & AV_CODEC_FLAG_PASS1)
> +            params->m_RCPass = 1;
> +        else
> +            params->m_RCPass = 2;
> +    }
> +
> +    if (avctx->rc_max_rate) {
> +#if VVENC_VERSION_INT >= AV_VERSION_INT(1,8,0)
> +        params->m_RCMaxBitrate = avctx->rc_max_rate;
> +#endif
> +
> +#if VVENC_VERSION_INT < AV_VERSION_INT(1,11,0)
> +        /* rc_max_rate without a bit_rate enables capped CQF mode.
> +        (QP + subj. optimization + max. bitrate) */
> +        if (!avctx->bit_rate) {
> +            av_log(avctx, AV_LOG_ERROR, "Capped Constant Quality Factor mode (capped CQF) "
> +                   "needs at least vvenc version >= 1.11.0 (current version %s)\n", vvenc_get_version());
> +            return AVERROR(EINVAL);
> +        }
> +#endif
> +    }
> +    return 0;
> +}
> +
> +static int vvenc_init_extradata(AVCodecContext *avctx, VVenCContext *s)
> +{
> +    int ret;
> +    if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) {
> +        ret = vvenc_get_headers(s->encoder, s->au);
> +        if (0 != ret) {
> +            av_log(avctx, AV_LOG_ERROR, "cannot get (SPS,PPS) headers: %s\n",
> +                   vvenc_get_last_error(s->encoder));
> +            return AVERROR(EINVAL);
> +        }
> +
> +        if (s->au->payloadUsedSize <= 0) {
> +            return AVERROR_INVALIDDATA;
> +        }
> +
> +        avctx->extradata_size = s->au->payloadUsedSize;
> +        avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
> +        if (!avctx->extradata) {
> +            return AVERROR(ENOMEM);
> +        }
> +
> +        memcpy(avctx->extradata, s->au->payload, avctx->extradata_size);
> +    }
> +    return 0;
> +}
> +
> +static av_cold int vvenc_init(AVCodecContext *avctx)
> +{
> +    int ret;
> +    int framerate;
> +    VVenCContext *s;
> +    vvenc_config params;
> +    vvencPresetMode preset;
> +
> +    s = avctx->priv_data;
> +    preset = (vvencPresetMode) s->preset;

These can be directly initialized

> +
> +    if (avctx->flags & AV_CODEC_FLAG_INTERLACED_DCT) {
> +        av_log(avctx, AV_LOG_ERROR, "interlaced not supported\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    vvenc_config_default(&params);
> +
> +    if (avctx->framerate.num > 0 && avctx->framerate.den > 0)
> +        framerate = avctx->framerate.num / avctx->framerate.den;
> +    else
> +        framerate = avctx->time_base.den / avctx->time_base.num;
> +
> +    vvenc_init_default(&params, avctx->width, avctx->height, framerate,
> +                       avctx->bit_rate, s->qp, preset);
> +
> +    vvenc_set_verbository(&params);
> +
> +    if (avctx->thread_count > 0)
> +        params.m_numThreads = avctx->thread_count;
> +
> +    /* GOP settings (IDR/CRA) */
> +    if (avctx->flags & AV_CODEC_FLAG_CLOSED_GOP)
> +        params.m_DecodingRefreshType = VVENC_DRT_IDR;
> +
> +    if (avctx->gop_size == 1) {
> +        params.m_GOPSize = 1;
> +        params.m_IntraPeriod = 1;
> +    } else
> +        params.m_IntraPeriodSec = s->intra_refresh_sec;
> +
> +    params.m_AccessUnitDelimiter = true;
> +    params.m_usePerceptQPA = s->qpa;
> +    params.m_levelTier     = (vvencTier) s->tier;
> +
> +    if (avctx->level > 0)
> +        params.m_level = (vvencLevel)avctx->level;
> +
> +    if (s->level) {
> +        if (VVENC_PARAM_BAD_VALUE == vvenc_set_param(&params, "level", s->level)) {
> +            av_log(avctx, AV_LOG_ERROR, "Invalid level_idc: %s.\n", s->level);
> +            return AVERROR(EINVAL);
> +        }
> +    }
> +
> +    vvenc_set_framerate(avctx, &params);
> +
> +    vvenc_set_pic_format(avctx, &params);
> +
> +    vvenc_set_color_format(avctx, &params);
> +
> +    if ((ret = vvenc_parse_vvenc_params(avctx, &params)) != 0)
> +        return ret;
> +
> +    if ((ret = vvenc_set_rc_mode(avctx, &params)) != 0)
> +        return ret;
> +
> +    s->encoder = vvenc_encoder_create();
> +    if (!s->encoder) {
> +        av_log(avctx, AV_LOG_ERROR, "cannot create libvvenc encoder\n");
> +        return AVERROR(ENOMEM);
> +    }
> +
> +    vvenc_set_msg_callback(&params, s->encoder, vvenc_log_callback);
> +    if ((ret = vvenc_encoder_open(s->encoder, &params)) != 0) {
> +        av_log(avctx, AV_LOG_ERROR, "cannot open libvvenc encoder: %s\n",
> +               vvenc_get_last_error(s->encoder));
> +        return AVERROR_EXTERNAL;
> +    }
> +
> +    vvenc_get_config(s->encoder, &params);     /* get the adapted config */
> +
> +    av_log(avctx, AV_LOG_INFO, "libvvenc version: %s\n", vvenc_get_version());
> +    if (av_log_get_level() >= AV_LOG_VERBOSE)
> +        av_log(avctx, AV_LOG_INFO, "%s\n", vvenc_get_config_as_string(&params, params.m_verbosity));
> +
> +    if (params.m_RCNumPasses == 2) {
> +        if ((ret = vvenc_init_pass(s->encoder, params.m_RCPass - 1, s->stats)) != 0) {
> +            av_log(avctx, AV_LOG_ERROR, "cannot init pass %d: %s\n",  params.m_RCPass,
> +                   vvenc_get_last_error(s->encoder));
> +            return AVERROR_EXTERNAL;
> +        }
> +    }
> +
> +    s->au = vvenc_accessUnit_alloc();
> +    if (!s->au) {
> +        av_log(avctx, AV_LOG_FATAL, "cannot allocate memory for AU payload\n");
> +        return AVERROR(ENOMEM);
> +    }
> +    vvenc_accessUnit_alloc_payload(s->au, avctx->width * avctx->height);
> +    if (!s->au->payload) {
> +        av_log(avctx, AV_LOG_FATAL, "cannot allocate payload memory of size %d\n",
> +               avctx->width * avctx->height);
> +        return AVERROR(ENOMEM);
> +    }
> +
> +    if ((ret = vvenc_init_extradata(avctx, s)) != 0)
> +        return ret;
> +
> +    s->encode_done = false;
> +    return 0;
> +}
> +
> +static av_cold int vvenc_close(AVCodecContext *avctx)
> +{
> +    VVenCContext *s = avctx->priv_data;
> +
> +    if (s->au)
> +        vvenc_accessUnit_free(s->au, true);
> +
> +    if (s->encoder) {
> +        vvenc_print_summary(s->encoder);
> +
> +        if (0 != vvenc_encoder_close(s->encoder))
> +            return AVERROR_EXTERNAL;
> +    }
> +
> +    return 0;
> +}
> +
> +static av_cold int vvenc_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *frame,
> +                               int *got_packet)
> +{
> +    VVenCContext *s = avctx->priv_data;
> +    vvencYUVBuffer *pyuvbuf;
> +    vvencYUVBuffer yuvbuf;
> +    int pict_type;
> +    int ret;
> +
> +    pyuvbuf = NULL;
> +    if (frame && avctx->pix_fmt == AV_PIX_FMT_YUV420P10) {

The pix_fmt check is redundant.

> +        vvenc_YUVBuffer_default(&yuvbuf);
> +        yuvbuf.planes[0].ptr = (int16_t *) frame->data[0];
> +        yuvbuf.planes[1].ptr = (int16_t *) frame->data[1];
> +        yuvbuf.planes[2].ptr = (int16_t *) frame->data[2];
> +
> +        yuvbuf.planes[0].width  = frame->width;
> +        yuvbuf.planes[0].height = frame->height;
> +        yuvbuf.planes[0].stride = frame->linesize[0] >> 1; /* stride is used in 16bit samples in vvenc */
> +
> +        yuvbuf.planes[1].width  = frame->width >> 1;
> +        yuvbuf.planes[1].height = frame->height >> 1;
> +        yuvbuf.planes[1].stride = frame->linesize[1] >> 1;
> +
> +        yuvbuf.planes[2].width  = frame->width >> 1;
> +        yuvbuf.planes[2].height = frame->height >> 1;
> +        yuvbuf.planes[2].stride = frame->linesize[2] >> 1;
> +
> +        yuvbuf.cts = frame->pts;
> +        yuvbuf.ctsValid = true;
> +        pyuvbuf = &yuvbuf;
> +    }
> +
> +    if (!s->encode_done) {
> +        if (vvenc_encode(s->encoder, pyuvbuf, s->au, &s->encode_done) != 0)
> +            return AVERROR_EXTERNAL;
> +    } else
> +        return 0;
> +
> +    if (s->au->payloadUsedSize > 0) {
> +        if ((ret = ff_get_encode_buffer(avctx, pkt, s->au->payloadUsedSize, 0)) < 0)

ret = ff_get_encode_buffer();
if (ret < 0)
    return ret;
Improves readability and is less error-prone.

> +            return ret;
> +
> +        memcpy(pkt->data, s->au->payload, s->au->payloadUsedSize);
> +
> +        if (s->au->ctsValid)
> +            pkt->pts = s->au->cts;
> +        if (s->au->dtsValid)
> +            pkt->dts = s->au->dts;
> +        pkt->flags |= AV_PKT_FLAG_KEY * s->au->rap;
> +
> +        switch (s->au->sliceType) {
> +        case VVENC_I_SLICE:
> +            pict_type = AV_PICTURE_TYPE_I;
> +            break;
> +        case VVENC_P_SLICE:
> +            pict_type = AV_PICTURE_TYPE_P;
> +            break;
> +        case VVENC_B_SLICE:
> +            pict_type = AV_PICTURE_TYPE_B;
> +            break;
> +        default:
> +            av_log(avctx, AV_LOG_ERROR, "Unknown picture type encountered.\n");
> +            return AVERROR_EXTERNAL;
> +        }
> +
> +        ff_side_data_set_encoder_stats(pkt, 0, NULL, 0, pict_type);

As has already been said: You do not have meaningful stats. The only
thing you attach to this packet is the slice type, something which could
also be recovered from the packet by parsing it (in contrast to proper
encoder stats for which this is not true).

> +
> +        *got_packet = 1;
> +        return 0;
> +    }
> +
> +    return 0;
> +}
> +
> +static const enum AVPixelFormat pix_fmts_vvenc[] = {
> +    AV_PIX_FMT_YUV420P10,
> +    AV_PIX_FMT_NONE
> +};
> +
> +#define OFFSET(x) offsetof(VVenCContext, x)
> +#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
> +static const AVOption options[] = {
> +    { "preset",       "set encoding preset", OFFSET(preset), AV_OPT_TYPE_INT, {.i64 = 2}, 0, 4, VE, "preset"},
> +    { "faster",       "0", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_FASTER}, INT_MIN, INT_MAX, VE, "preset" },
> +    { "fast",         "1", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_FAST},   INT_MIN, INT_MAX, VE, "preset" },
> +    { "medium",       "2", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_MEDIUM}, INT_MIN, INT_MAX, VE, "preset" },
> +    { "slow",         "3", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_SLOW},   INT_MIN, INT_MAX, VE, "preset" },
> +    { "slower",       "4", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_SLOWER}, INT_MIN, INT_MAX, VE, "preset" },
> +    { "qp",           "set quantization",          OFFSET(qp), AV_OPT_TYPE_INT,  {.i64 = 32}, -1, 63, VE },
> +    { "qpa",          "set subjective (perceptually motivated) optimization", OFFSET(qpa), AV_OPT_TYPE_BOOL, {.i64 = 1},  0, 1, VE},
> +    { "passlogfile",  "Filename for 2 pass stats", OFFSET(stats), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, VE},
> +    { "stats",        "Filename for 2 pass stats", OFFSET(stats), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, VE},
> +    { "period",       "set (intra) refresh period in seconds", OFFSET(intra_refresh_sec), AV_OPT_TYPE_INT,  {.i64 = 1},  1, INT_MAX, VE },
> +    { "vvenc-params", "set the vvenc configuration using a :-separated list of key=value parameters", OFFSET(vvenc_opts), AV_OPT_TYPE_DICT, { 0 }, 0, 0, VE },
> +    { "level",        "Specify level (as defined by Annex A)", OFFSET(level), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, VE},
> +    { "tier",         "set vvc tier", OFFSET(tier), AV_OPT_TYPE_INT, {.i64 = 0},  0, 1, VE, "tier"},
> +    { "main",         "main", 0, AV_OPT_TYPE_CONST, {.i64 = 0}, INT_MIN, INT_MAX, VE, "tier"},
> +    { "high",         "high", 0, AV_OPT_TYPE_CONST, {.i64 = 1}, INT_MIN, INT_MAX, VE, "tier"},
> +    {NULL}
> +};
> +
> +static const AVClass class = {
> +    .class_name = "libvvenc",
> +    .item_name  = av_default_item_name,
> +    .option     = options,
> +    .version    = LIBAVUTIL_VERSION_INT,
> +};
> +
> +static const FFCodecDefault vvenc_defaults[] = {
> +    { "b", "0" },
> +    { "g", "-1" },
> +    { NULL },
> +};
> +
> +const FFCodec ff_libvvenc_encoder = {
> +    .p.name         = "libvvenc",
> +    CODEC_LONG_NAME("libvvenc H.266 / VVC"),
> +    .p.type         = AVMEDIA_TYPE_VIDEO,
> +    .p.id           = AV_CODEC_ID_VVC,
> +    .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY |
> +                      AV_CODEC_CAP_OTHER_THREADS,
> +    .p.profiles     = NULL_IF_CONFIG_SMALL(ff_vvc_profiles),
> +    .p.priv_class   = &class,
> +    .p.wrapper_name = "libvvenc",
> +    .priv_data_size = sizeof(VVenCContext),
> +    .p.pix_fmts     = pix_fmts_vvenc,
> +    .init           = vvenc_init,
> +    FF_CODEC_ENCODE_CB(vvenc_frame),
> +    .close          = vvenc_close,
> +    .defaults       = vvenc_defaults,
> +    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP | FF_CODEC_CAP_AUTO_THREADS
> +};
Anton Khirnov June 4, 2024, 9:21 a.m. UTC | #3
Quoting Christian Bartnik (2024-05-28 18:26:29)
> +static const AVOption options[] = {
> +    { "passlogfile",  "Filename for 2 pass stats", OFFSET(stats), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, VE},
> +    { "stats",        "Filename for 2 pass stats", OFFSET(stats), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, VE},

These option names conflict with ffmpeg CLI options.
Christian Bartnik June 4, 2024, 2:33 p.m. UTC | #4
> On 4. Jun 2024, at 11:21, Anton Khirnov <anton@khirnov.net> wrote:
> 
> Quoting Christian Bartnik (2024-05-28 18:26:29)
>> +static const AVOption options[] = {
>> +    { "passlogfile",  "Filename for 2 pass stats", OFFSET(stats), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, VE},
>> +    { "stats",        "Filename for 2 pass stats", OFFSET(stats), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, VE},
> 
> These option names conflict with ffmpeg CLI options.

I´m aware of this and I´m using exactly the same approach as libx264 does. 

passlogfile is overwriting the CLI option to make the generated passfile filename from the CLI availalbe to the codec.

In the review of my latest version of my patch I was told to use the 'passlogfile' option.

Please let me know how I should pass the passlogfile filename from the CLI to the codec implementation.

> 
> -- 
> Anton Khirnov
> _______________________________________________
> 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".
diff mbox series

Patch

diff --git a/configure b/configure
index 96b181fd21..082cbca7bb 100755
--- a/configure
+++ b/configure
@@ -293,6 +293,7 @@  External library support:
   --enable-libvorbis       enable Vorbis en/decoding via libvorbis,
                            native implementation exists [no]
   --enable-libvpx          enable VP8 and VP9 de/encoding via libvpx [no]
+  --enable-libvvenc        enable H.266/VVC encoding via vvenc [no]
   --enable-libwebp         enable WebP encoding via libwebp [no]
   --enable-libx264         enable H.264 encoding via x264 [no]
   --enable-libx265         enable HEVC encoding via x265 [no]
@@ -1966,6 +1967,7 @@  EXTERNAL_LIBRARY_LIST="
     libvmaf
     libvorbis
     libvpx
+    libvvenc
     libwebp
     libxevd
     libxeve
@@ -3560,6 +3562,7 @@  libvpx_vp8_decoder_deps="libvpx"
 libvpx_vp8_encoder_deps="libvpx"
 libvpx_vp9_decoder_deps="libvpx"
 libvpx_vp9_encoder_deps="libvpx"
+libvvenc_encoder_deps="libvvenc"
 libwebp_encoder_deps="libwebp"
 libwebp_anim_encoder_deps="libwebp"
 libx262_encoder_deps="libx262"
@@ -7030,6 +7033,7 @@  enabled libvpx            && {
     fi
 }
 
+enabled libvvenc          && require_pkg_config libvvenc "libvvenc >= 1.6.1" "vvenc/vvenc.h" vvenc_get_version
 enabled libwebp           && {
     enabled libwebp_encoder      && require_pkg_config libwebp "libwebp >= 0.2.0" webp/encode.h WebPGetEncoderVersion
     enabled libwebp_anim_encoder && check_pkg_config libwebp_anim_encoder "libwebpmux >= 0.4.0" webp/mux.h WebPAnimEncoderOptionsInit; }
diff --git a/doc/encoders.texi b/doc/encoders.texi
index c82f316f94..496852faeb 100644
--- a/doc/encoders.texi
+++ b/doc/encoders.texi
@@ -2378,6 +2378,70 @@  Indicates frame duration
 For more information about libvpx see:
 @url{http://www.webmproject.org/}
 
+@section libvvenc
+
+VVenC H.266/VVC encoder wrapper.
+
+This encoder requires the presence of the libvvenc headers and library
+during configuration. You need to explicitly configure the build with
+@option{--enable-libvvenc}.
+
+The VVenC project website is at
+@url{https://github.com/fraunhoferhhi/vvenc}.
+
+@subsection Supported Pixel Formats
+
+VVenC supports only 10-bit color spaces as input. But the internal (encoded)
+bit depth can be set to 8-bit or 10-bit at runtime.
+
+@subsection Options
+
+@table @option
+@item b
+Sets target video bitrate.
+
+@item g
+Set the GOP size. Currently support for g=1 (Intra only) or default.
+
+@item preset
+Set the VVenC preset.
+
+@item levelidc
+Set level idc.
+
+@item tier
+Set vvc tier.
+
+@item qp
+Set constant quantization parameter.
+
+@item subopt @var{boolean}
+Set subjective (perceptually motivated) optimization. Default is 1 (on).
+
+@item bitdepth8 @var{boolean}
+Set 8bit coding mode instead of using 10bit. Default is 0 (off).
+
+@item period
+set (intra) refresh period in seconds.
+
+@item vvenc-params
+Set vvenc options using a list of @var{key}=@var{value} couples separated
+by ":". See @command{vvencapp --fullhelp} or @command{vvencFFapp --fullhelp} for a list of options.
+
+For example, the options might be provided as:
+
+@example
+intraperiod=64:decodingrefreshtype=idr:poc0idr=1:internalbitdepth=8
+@end example
+
+For example the encoding options might be provided with @option{-vvenc-params}:
+
+@example
+ffmpeg -i input -c:v libvvenc -b 1M -vvenc-params intraperiod=64:decodingrefreshtype=idr:poc0idr=1:internalbitdepth=8 output.mp4
+@end example
+
+@end table
+
 @section libwebp
 
 libwebp WebP Image encoder wrapper
diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c
index 8797265145..ef2922854a 100644
--- a/fftools/ffmpeg_mux_init.c
+++ b/fftools/ffmpeg_mux_init.c
@@ -739,7 +739,7 @@  static int new_stream_video(Muxer *mux, const OptionsContext *o,
                      ost->logfile_prefix ? ost->logfile_prefix :
                                            DEFAULT_PASS_LOGFILENAME_PREFIX,
                      ost_idx);
-            if (!strcmp(ost->enc_ctx->codec->name, "libx264")) {
+            if (!strcmp(ost->enc_ctx->codec->name, "libx264") || !strcmp(ost->enc_ctx->codec->name, "libvvenc")) {
                 av_dict_set(&ost->encoder_opts, "stats", logfilename, AV_DICT_DONT_OVERWRITE);
             } else {
                 if (video_enc->flags & AV_CODEC_FLAG_PASS2) {
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 2443d2c6fd..5d7349090e 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -1153,6 +1153,7 @@  OBJS-$(CONFIG_LIBVPX_VP8_DECODER)         += libvpxdec.o
 OBJS-$(CONFIG_LIBVPX_VP8_ENCODER)         += libvpxenc.o
 OBJS-$(CONFIG_LIBVPX_VP9_DECODER)         += libvpxdec.o
 OBJS-$(CONFIG_LIBVPX_VP9_ENCODER)         += libvpxenc.o
+OBJS-$(CONFIG_LIBVVENC_ENCODER)           += libvvenc.o
 OBJS-$(CONFIG_LIBWEBP_ENCODER)            += libwebpenc_common.o libwebpenc.o
 OBJS-$(CONFIG_LIBWEBP_ANIM_ENCODER)       += libwebpenc_common.o libwebpenc_animencoder.o
 OBJS-$(CONFIG_LIBX262_ENCODER)            += libx264.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index b102a8069e..59d36dbd56 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -800,6 +800,7 @@  extern const FFCodec ff_libvpx_vp8_encoder;
 extern const FFCodec ff_libvpx_vp8_decoder;
 extern FFCodec ff_libvpx_vp9_encoder;
 extern const FFCodec ff_libvpx_vp9_decoder;
+extern const FFCodec ff_libvvenc_encoder;
 /* preferred over libwebp */
 extern const FFCodec ff_libwebp_anim_encoder;
 extern const FFCodec ff_libwebp_encoder;
diff --git a/libavcodec/libvvenc.c b/libavcodec/libvvenc.c
new file mode 100644
index 0000000000..9de3d8630f
--- /dev/null
+++ b/libavcodec/libvvenc.c
@@ -0,0 +1,507 @@ 
+/*
+ * H.266 encoding using the VVenC library
+ *
+ * Copyright (C) 2022, Thomas Siedel
+ *
+ * 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 <vvenc/vvenc.h>
+#include <vvenc/vvencCfg.h>
+#include <vvenc/version.h>
+
+#include "avcodec.h"
+#include "codec_internal.h"
+#include "encode.h"
+#include "internal.h"
+#include "packet_internal.h"
+#include "profiles.h"
+
+#include "libavutil/avutil.h"
+#include "libavutil/mem.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/opt.h"
+#include "libavutil/common.h"
+#include "libavutil/imgutils.h"
+#include "libavutil/frame.h"
+#include "libavutil/log.h"
+#include "libavutil/avstring.h"
+
+#define VVENC_VERSION_INT  AV_VERSION_INT(VVENC_VERSION_MAJOR, \
+                                          VVENC_VERSION_MINOR, \
+                                          VVENC_VERSION_PATCH)
+
+typedef struct VVenCContext {
+    AVClass         *class;
+    vvencEncoder    *encoder;
+    vvencAccessUnit *au;
+    bool             encode_done;
+    int   preset;
+    int   qp;
+    int   qpa;
+    int   intra_refresh_sec;
+    char *level;
+    int   tier;
+    char *stats;
+    AVDictionary *vvenc_opts;
+} VVenCContext;
+
+static void vvenc_log_callback(void *ctx, int level,
+                               const char *fmt, va_list args)
+{
+    vvenc_config params;
+    vvencEncoder *encoder = (vvencEncoder *)ctx;
+    if (encoder) {
+        vvenc_config_default(&params);
+        vvenc_get_config(encoder, &params);
+        if ((int)params.m_verbosity >= level)
+            vfprintf(level == 1 ? stderr : stdout, fmt, args);
+    }
+}
+
+static void vvenc_set_verbository(vvenc_config *params)
+{
+    int loglevel = av_log_get_level();
+    params->m_verbosity = VVENC_SILENT;
+    if (loglevel >= AV_LOG_DEBUG)
+        params->m_verbosity = VVENC_DETAILS;
+    else if (loglevel >= AV_LOG_VERBOSE)
+        params->m_verbosity = VVENC_NOTICE;
+    else if (loglevel >= AV_LOG_INFO)
+        params->m_verbosity = VVENC_WARNING;
+}
+
+static void vvenc_set_pic_format(AVCodecContext *avctx, vvenc_config *params)
+{
+    params->m_internChromaFormat = VVENC_CHROMA_420;
+    params->m_inputBitDepth[0]   = 10;
+}
+
+static void vvenc_set_color_format(AVCodecContext *avctx, vvenc_config *params)
+{
+    if (avctx->color_primaries != AVCOL_PRI_UNSPECIFIED)
+        params->m_colourPrimaries = (int) avctx->color_primaries;
+    if (avctx->colorspace != AVCOL_SPC_UNSPECIFIED)
+        params->m_matrixCoefficients = (int) avctx->colorspace;
+    if (avctx->color_trc != AVCOL_TRC_UNSPECIFIED) {
+        params->m_transferCharacteristics = (int) avctx->color_trc;
+
+        if (avctx->color_trc == AVCOL_TRC_SMPTE2084)
+            params->m_HdrMode = (avctx->color_primaries == AVCOL_PRI_BT2020) ?
+                                VVENC_HDR_PQ_BT2020 : VVENC_HDR_PQ;
+        else if (avctx->color_trc == AVCOL_TRC_BT2020_10 || avctx->color_trc == AVCOL_TRC_ARIB_STD_B67)
+            params->m_HdrMode = (avctx->color_trc == AVCOL_TRC_BT2020_10 ||
+                                 avctx->color_primaries == AVCOL_PRI_BT2020 ||
+                                 avctx->colorspace == AVCOL_SPC_BT2020_NCL ||
+                                 avctx->colorspace == AVCOL_SPC_BT2020_CL) ?
+                                VVENC_HDR_HLG_BT2020 : VVENC_HDR_HLG;
+    }
+
+    if (params->m_HdrMode == VVENC_HDR_OFF &&
+        (avctx->color_primaries != AVCOL_PRI_UNSPECIFIED || avctx->colorspace != AVCOL_SPC_UNSPECIFIED)) {
+        params->m_vuiParametersPresent = 1;
+        params->m_colourDescriptionPresent = true;
+    }
+}
+
+static void vvenc_set_framerate(AVCodecContext *avctx, vvenc_config *params)
+{
+    if (avctx->framerate.num > 0 && avctx->framerate.den > 0) {
+        params->m_FrameRate = avctx->framerate.num;
+        params->m_FrameScale = avctx->framerate.den;
+    } else {
+        params->m_FrameRate = avctx->time_base.den;
+        params->m_FrameScale = avctx->time_base.num;
+    }
+
+FF_DISABLE_DEPRECATION_WARNINGS
+
+#if FF_API_TICKS_PER_FRAME
+    if (avctx->ticks_per_frame == 1) {
+#endif
+        params->m_TicksPerSecond = -1;   /* auto mode for ticks per frame = 1 */
+#if FF_API_TICKS_PER_FRAME
+    } else {
+        params->m_TicksPerSecond =
+            ceil((avctx->time_base.den / (double) avctx->time_base.num) *
+                 (double) avctx->ticks_per_frame);
+    }
+#endif
+FF_ENABLE_DEPRECATION_WARNINGS
+}
+
+static int vvenc_parse_vvenc_params(AVCodecContext *avctx, vvenc_config *params)
+{
+    int parse_ret, ret;
+    VVenCContext *s;
+    const AVDictionaryEntry *en = NULL;
+    s = avctx->priv_data;
+    ret = 0;
+
+    while ((en = av_dict_iterate(s->vvenc_opts, en))) {
+        av_log(avctx, AV_LOG_DEBUG, "vvenc_set_param: '%s:%s'\n", en->key,
+               en->value);
+        parse_ret = vvenc_set_param(params, en->key, en->value);
+        switch (parse_ret) {
+        case VVENC_PARAM_BAD_NAME:
+            av_log(avctx, AV_LOG_ERROR, "Unknown vvenc option: %s.\n", en->key);
+            ret = AVERROR(EINVAL);
+            break;
+        case VVENC_PARAM_BAD_VALUE:
+            av_log(avctx, AV_LOG_ERROR, "Invalid vvenc value for %s: %s.\n", en->key, en->value);
+            ret = AVERROR(EINVAL);
+            break;
+        default:
+            break;
+        }
+
+        if (!av_strcasecmp(en->key, "rcstatsfile")) {
+            av_log(avctx, AV_LOG_ERROR, "vvenc-params 2pass option 'rcstatsfile' "
+                   "not available. Use option 'passlogfile'\n");
+            ret = AVERROR(EINVAL);
+        }
+        if (!av_strcasecmp(en->key, "passes") || !av_strcasecmp(en->key, "pass")) {
+            av_log(avctx, AV_LOG_ERROR, "vvenc-params 2pass option '%s' "
+                   "not available. Use option 'pass'\n", en->key);
+            ret = AVERROR(EINVAL);
+        }
+    }
+    return ret;
+}
+
+static int vvenc_set_rc_mode(AVCodecContext *avctx, vvenc_config *params)
+{
+    params->m_RCNumPasses = 1;
+    if ((avctx->flags & AV_CODEC_FLAG_PASS1 || avctx->flags & AV_CODEC_FLAG_PASS2)) {
+        if (!avctx->bit_rate) {
+            av_log(avctx, AV_LOG_ERROR, "A bitrate must be set to use two pass mode.\n");
+            return AVERROR(EINVAL);
+        }
+        params->m_RCNumPasses = 2;
+        if (avctx->flags & AV_CODEC_FLAG_PASS1)
+            params->m_RCPass = 1;
+        else
+            params->m_RCPass = 2;
+    }
+
+    if (avctx->rc_max_rate) {
+#if VVENC_VERSION_INT >= AV_VERSION_INT(1,8,0)
+        params->m_RCMaxBitrate = avctx->rc_max_rate;
+#endif
+
+#if VVENC_VERSION_INT < AV_VERSION_INT(1,11,0)
+        /* rc_max_rate without a bit_rate enables capped CQF mode.
+        (QP + subj. optimization + max. bitrate) */
+        if (!avctx->bit_rate) {
+            av_log(avctx, AV_LOG_ERROR, "Capped Constant Quality Factor mode (capped CQF) "
+                   "needs at least vvenc version >= 1.11.0 (current version %s)\n", vvenc_get_version());
+            return AVERROR(EINVAL);
+        }
+#endif
+    }
+    return 0;
+}
+
+static int vvenc_init_extradata(AVCodecContext *avctx, VVenCContext *s)
+{
+    int ret;
+    if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) {
+        ret = vvenc_get_headers(s->encoder, s->au);
+        if (0 != ret) {
+            av_log(avctx, AV_LOG_ERROR, "cannot get (SPS,PPS) headers: %s\n",
+                   vvenc_get_last_error(s->encoder));
+            return AVERROR(EINVAL);
+        }
+
+        if (s->au->payloadUsedSize <= 0) {
+            return AVERROR_INVALIDDATA;
+        }
+
+        avctx->extradata_size = s->au->payloadUsedSize;
+        avctx->extradata = av_mallocz(avctx->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
+        if (!avctx->extradata) {
+            return AVERROR(ENOMEM);
+        }
+
+        memcpy(avctx->extradata, s->au->payload, avctx->extradata_size);
+    }
+    return 0;
+}
+
+static av_cold int vvenc_init(AVCodecContext *avctx)
+{
+    int ret;
+    int framerate;
+    VVenCContext *s;
+    vvenc_config params;
+    vvencPresetMode preset;
+
+    s = avctx->priv_data;
+    preset = (vvencPresetMode) s->preset;
+
+    if (avctx->flags & AV_CODEC_FLAG_INTERLACED_DCT) {
+        av_log(avctx, AV_LOG_ERROR, "interlaced not supported\n");
+        return AVERROR(EINVAL);
+    }
+
+    vvenc_config_default(&params);
+
+    if (avctx->framerate.num > 0 && avctx->framerate.den > 0)
+        framerate = avctx->framerate.num / avctx->framerate.den;
+    else
+        framerate = avctx->time_base.den / avctx->time_base.num;
+
+    vvenc_init_default(&params, avctx->width, avctx->height, framerate,
+                       avctx->bit_rate, s->qp, preset);
+
+    vvenc_set_verbository(&params);
+
+    if (avctx->thread_count > 0)
+        params.m_numThreads = avctx->thread_count;
+
+    /* GOP settings (IDR/CRA) */
+    if (avctx->flags & AV_CODEC_FLAG_CLOSED_GOP)
+        params.m_DecodingRefreshType = VVENC_DRT_IDR;
+
+    if (avctx->gop_size == 1) {
+        params.m_GOPSize = 1;
+        params.m_IntraPeriod = 1;
+    } else
+        params.m_IntraPeriodSec = s->intra_refresh_sec;
+
+    params.m_AccessUnitDelimiter = true;
+    params.m_usePerceptQPA = s->qpa;
+    params.m_levelTier     = (vvencTier) s->tier;
+
+    if (avctx->level > 0)
+        params.m_level = (vvencLevel)avctx->level;
+
+    if (s->level) {
+        if (VVENC_PARAM_BAD_VALUE == vvenc_set_param(&params, "level", s->level)) {
+            av_log(avctx, AV_LOG_ERROR, "Invalid level_idc: %s.\n", s->level);
+            return AVERROR(EINVAL);
+        }
+    }
+
+    vvenc_set_framerate(avctx, &params);
+
+    vvenc_set_pic_format(avctx, &params);
+
+    vvenc_set_color_format(avctx, &params);
+
+    if ((ret = vvenc_parse_vvenc_params(avctx, &params)) != 0)
+        return ret;
+
+    if ((ret = vvenc_set_rc_mode(avctx, &params)) != 0)
+        return ret;
+
+    s->encoder = vvenc_encoder_create();
+    if (!s->encoder) {
+        av_log(avctx, AV_LOG_ERROR, "cannot create libvvenc encoder\n");
+        return AVERROR(ENOMEM);
+    }
+
+    vvenc_set_msg_callback(&params, s->encoder, vvenc_log_callback);
+    if ((ret = vvenc_encoder_open(s->encoder, &params)) != 0) {
+        av_log(avctx, AV_LOG_ERROR, "cannot open libvvenc encoder: %s\n",
+               vvenc_get_last_error(s->encoder));
+        return AVERROR_EXTERNAL;
+    }
+
+    vvenc_get_config(s->encoder, &params);     /* get the adapted config */
+
+    av_log(avctx, AV_LOG_INFO, "libvvenc version: %s\n", vvenc_get_version());
+    if (av_log_get_level() >= AV_LOG_VERBOSE)
+        av_log(avctx, AV_LOG_INFO, "%s\n", vvenc_get_config_as_string(&params, params.m_verbosity));
+
+    if (params.m_RCNumPasses == 2) {
+        if ((ret = vvenc_init_pass(s->encoder, params.m_RCPass - 1, s->stats)) != 0) {
+            av_log(avctx, AV_LOG_ERROR, "cannot init pass %d: %s\n",  params.m_RCPass,
+                   vvenc_get_last_error(s->encoder));
+            return AVERROR_EXTERNAL;
+        }
+    }
+
+    s->au = vvenc_accessUnit_alloc();
+    if (!s->au) {
+        av_log(avctx, AV_LOG_FATAL, "cannot allocate memory for AU payload\n");
+        return AVERROR(ENOMEM);
+    }
+    vvenc_accessUnit_alloc_payload(s->au, avctx->width * avctx->height);
+    if (!s->au->payload) {
+        av_log(avctx, AV_LOG_FATAL, "cannot allocate payload memory of size %d\n",
+               avctx->width * avctx->height);
+        return AVERROR(ENOMEM);
+    }
+
+    if ((ret = vvenc_init_extradata(avctx, s)) != 0)
+        return ret;
+
+    s->encode_done = false;
+    return 0;
+}
+
+static av_cold int vvenc_close(AVCodecContext *avctx)
+{
+    VVenCContext *s = avctx->priv_data;
+
+    if (s->au)
+        vvenc_accessUnit_free(s->au, true);
+
+    if (s->encoder) {
+        vvenc_print_summary(s->encoder);
+
+        if (0 != vvenc_encoder_close(s->encoder))
+            return AVERROR_EXTERNAL;
+    }
+
+    return 0;
+}
+
+static av_cold int vvenc_frame(AVCodecContext *avctx, AVPacket *pkt, const AVFrame *frame,
+                               int *got_packet)
+{
+    VVenCContext *s = avctx->priv_data;
+    vvencYUVBuffer *pyuvbuf;
+    vvencYUVBuffer yuvbuf;
+    int pict_type;
+    int ret;
+
+    pyuvbuf = NULL;
+    if (frame && avctx->pix_fmt == AV_PIX_FMT_YUV420P10) {
+        vvenc_YUVBuffer_default(&yuvbuf);
+        yuvbuf.planes[0].ptr = (int16_t *) frame->data[0];
+        yuvbuf.planes[1].ptr = (int16_t *) frame->data[1];
+        yuvbuf.planes[2].ptr = (int16_t *) frame->data[2];
+
+        yuvbuf.planes[0].width  = frame->width;
+        yuvbuf.planes[0].height = frame->height;
+        yuvbuf.planes[0].stride = frame->linesize[0] >> 1; /* stride is used in 16bit samples in vvenc */
+
+        yuvbuf.planes[1].width  = frame->width >> 1;
+        yuvbuf.planes[1].height = frame->height >> 1;
+        yuvbuf.planes[1].stride = frame->linesize[1] >> 1;
+
+        yuvbuf.planes[2].width  = frame->width >> 1;
+        yuvbuf.planes[2].height = frame->height >> 1;
+        yuvbuf.planes[2].stride = frame->linesize[2] >> 1;
+
+        yuvbuf.cts = frame->pts;
+        yuvbuf.ctsValid = true;
+        pyuvbuf = &yuvbuf;
+    }
+
+    if (!s->encode_done) {
+        if (vvenc_encode(s->encoder, pyuvbuf, s->au, &s->encode_done) != 0)
+            return AVERROR_EXTERNAL;
+    } else
+        return 0;
+
+    if (s->au->payloadUsedSize > 0) {
+        if ((ret = ff_get_encode_buffer(avctx, pkt, s->au->payloadUsedSize, 0)) < 0)
+            return ret;
+
+        memcpy(pkt->data, s->au->payload, s->au->payloadUsedSize);
+
+        if (s->au->ctsValid)
+            pkt->pts = s->au->cts;
+        if (s->au->dtsValid)
+            pkt->dts = s->au->dts;
+        pkt->flags |= AV_PKT_FLAG_KEY * s->au->rap;
+
+        switch (s->au->sliceType) {
+        case VVENC_I_SLICE:
+            pict_type = AV_PICTURE_TYPE_I;
+            break;
+        case VVENC_P_SLICE:
+            pict_type = AV_PICTURE_TYPE_P;
+            break;
+        case VVENC_B_SLICE:
+            pict_type = AV_PICTURE_TYPE_B;
+            break;
+        default:
+            av_log(avctx, AV_LOG_ERROR, "Unknown picture type encountered.\n");
+            return AVERROR_EXTERNAL;
+        }
+
+        ff_side_data_set_encoder_stats(pkt, 0, NULL, 0, pict_type);
+
+        *got_packet = 1;
+        return 0;
+    }
+
+    return 0;
+}
+
+static const enum AVPixelFormat pix_fmts_vvenc[] = {
+    AV_PIX_FMT_YUV420P10,
+    AV_PIX_FMT_NONE
+};
+
+#define OFFSET(x) offsetof(VVenCContext, x)
+#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
+static const AVOption options[] = {
+    { "preset",       "set encoding preset", OFFSET(preset), AV_OPT_TYPE_INT, {.i64 = 2}, 0, 4, VE, "preset"},
+    { "faster",       "0", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_FASTER}, INT_MIN, INT_MAX, VE, "preset" },
+    { "fast",         "1", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_FAST},   INT_MIN, INT_MAX, VE, "preset" },
+    { "medium",       "2", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_MEDIUM}, INT_MIN, INT_MAX, VE, "preset" },
+    { "slow",         "3", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_SLOW},   INT_MIN, INT_MAX, VE, "preset" },
+    { "slower",       "4", 0, AV_OPT_TYPE_CONST, {.i64 = VVENC_SLOWER}, INT_MIN, INT_MAX, VE, "preset" },
+    { "qp",           "set quantization",          OFFSET(qp), AV_OPT_TYPE_INT,  {.i64 = 32}, -1, 63, VE },
+    { "qpa",          "set subjective (perceptually motivated) optimization", OFFSET(qpa), AV_OPT_TYPE_BOOL, {.i64 = 1},  0, 1, VE},
+    { "passlogfile",  "Filename for 2 pass stats", OFFSET(stats), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, VE},
+    { "stats",        "Filename for 2 pass stats", OFFSET(stats), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, VE},
+    { "period",       "set (intra) refresh period in seconds", OFFSET(intra_refresh_sec), AV_OPT_TYPE_INT,  {.i64 = 1},  1, INT_MAX, VE },
+    { "vvenc-params", "set the vvenc configuration using a :-separated list of key=value parameters", OFFSET(vvenc_opts), AV_OPT_TYPE_DICT, { 0 }, 0, 0, VE },
+    { "level",        "Specify level (as defined by Annex A)", OFFSET(level), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, VE},
+    { "tier",         "set vvc tier", OFFSET(tier), AV_OPT_TYPE_INT, {.i64 = 0},  0, 1, VE, "tier"},
+    { "main",         "main", 0, AV_OPT_TYPE_CONST, {.i64 = 0}, INT_MIN, INT_MAX, VE, "tier"},
+    { "high",         "high", 0, AV_OPT_TYPE_CONST, {.i64 = 1}, INT_MIN, INT_MAX, VE, "tier"},
+    {NULL}
+};
+
+static const AVClass class = {
+    .class_name = "libvvenc",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+static const FFCodecDefault vvenc_defaults[] = {
+    { "b", "0" },
+    { "g", "-1" },
+    { NULL },
+};
+
+const FFCodec ff_libvvenc_encoder = {
+    .p.name         = "libvvenc",
+    CODEC_LONG_NAME("libvvenc H.266 / VVC"),
+    .p.type         = AVMEDIA_TYPE_VIDEO,
+    .p.id           = AV_CODEC_ID_VVC,
+    .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY |
+                      AV_CODEC_CAP_OTHER_THREADS,
+    .p.profiles     = NULL_IF_CONFIG_SMALL(ff_vvc_profiles),
+    .p.priv_class   = &class,
+    .p.wrapper_name = "libvvenc",
+    .priv_data_size = sizeof(VVenCContext),
+    .p.pix_fmts     = pix_fmts_vvenc,
+    .init           = vvenc_init,
+    FF_CODEC_ENCODE_CB(vvenc_frame),
+    .close          = vvenc_close,
+    .defaults       = vvenc_defaults,
+    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP | FF_CODEC_CAP_AUTO_THREADS
+};