[FFmpeg-devel] [PATCH v6 1/2] lavc, doc, configure: add libxavs2 video encoder wrapper

Mark Thompson sw at jkqxz.net
Thu Sep 6 03:43:05 EEST 2018


On 05/09/18 14:38, hwren wrote:
> Signed-off-by: hwren <hwrenx at 126.com>
> ---
>  Changelog              |   1 +
>  configure              |   4 +
>  doc/encoders.texi      |  34 ++++++
>  doc/general.texi       |  14 +++
>  libavcodec/Makefile    |   1 +
>  libavcodec/allcodecs.c |   1 +
>  libavcodec/libxavs2.c  | 290 +++++++++++++++++++++++++++++++++++++++++++++++++
>  libavcodec/version.h   |   4 +-
>  8 files changed, 347 insertions(+), 2 deletions(-)
>  create mode 100644 libavcodec/libxavs2.c
> 
> diff --git a/Changelog b/Changelog
> index 0975fee..8377956 100644
> ...
> diff --git a/doc/encoders.texi b/doc/encoders.texi
> index 7b09575..6998493 100644
> --- a/doc/encoders.texi
> +++ b/doc/encoders.texi
> @@ -2726,6 +2726,40 @@ Reduces detail but attempts to preserve color at extremely low bitrates.
>  
>  @end table
>  
> + at section libxavs2
> +
> +xavs2 AVS2-P2/IEEE1857.4 encoder wrapper.
> +
> +This encoder requires the presence of the libxavs2 headers and library
> +during configuration. You need to explicitly configure the build with
> + at option{--enable-libxavs2}.
> +
> + at subsection Options
> +
> + at table @option
> + at item i_lcurow_threads
> +Set the number of parallel threads for rows from 1 to 8 (default 5).
> +
> + at item i_initial_qp
> +Set the xavs2 quantization parameter from 1 to 63 (default 34).
> +
> + at item speed_level
> +Set the Speed level from 0 to 9 (default 0).

I think mention that larger numbers mean slower / higher quality here as well.

> +
> + at item hierarchical_ref
> +Set the hierarchical reference or not (default true).
> +
> + at item xavs2-params
> +Set xavs2 options using a list of @var{key}=@var{value} couples separated
> +by ":".
> +
> +For example to specify libxavs2 encoding options with @option{-xavs2-params}:
> +
> + at example
> +ffmpeg -i input -c:v libxavs2 -xavs2-params preset_level=5 output.avs2
> + at end example
> + at end table
> +
>  @c man end VIDEO ENCODERS
>  
>  @chapter Subtitles Encoders
> ...
> diff --git a/libavcodec/libxavs2.c b/libavcodec/libxavs2.c
> new file mode 100644
> index 0000000..ce92efb
> --- /dev/null
> +++ b/libavcodec/libxavs2.c
> @@ -0,0 +1,290 @@
> +/*
> + * AVS2 encoding using the xavs2 library
> + *
> + * Copyright (C) 2018 Yiqun Xu,   <yiqun.xu at vipl.ict.ac.cn>
> + *                    Falei Luo,  <falei.luo at gmail.com>
> + *                    Huiwen Ren, <hwrenx at gmail.com>
> + *
> + * 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 <ctype.h>
> +
> +#include "xavs2.h"
> +#include "avcodec.h"
> +#include "mpeg12.h"
> +#include "internal.h"
> +#include "libavutil/internal.h"
> +#include "libavutil/mem.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/imgutils.h"
> +#include "libavutil/avassert.h"
> +#include "libavutil/avstring.h"
> +#include "libavutil/common.h"
> +#include "libavutil/avutil.h"
> +
> +#define xavs2_opt_set2(name, format, ...) do{ \
> +    char opt_str[16] = {0}; \
> +    av_strlcatf(opt_str, sizeof(opt_str), format, __VA_ARGS__); \
> +    cae->api->opt_set2(cae->param, name, opt_str); \
> +} while(0);
> +
> +typedef struct XAVS2EContext {
> +    AVClass *class;
> +
> +    int i_lcurow_threads;
> +    int i_initial_qp;
> +    int preset_level;
> +    int intra_period;

This field isn't used any more.

> +
> +    void *encoder;
> +    char *xavs2_opts;
> +
> +    int b_hierarchical_reference;
> +
> +    xavs2_outpacket_t packet;
> +    xavs2_param_t *param;
> +
> +    const xavs2_api_t *api;
> +
> +} XAVS2EContext;
> +
> +static av_cold int xavs2_init(AVCodecContext *avctx)
> +{
> +    XAVS2EContext *cae= avctx->priv_data;
> +    int bit_depth, code;
> +
> +    bit_depth = avctx->pix_fmt == AV_PIX_FMT_YUV420P ? 8 : 10;
> +
> +    /* get API handler */
> +    cae->api = xavs2_api_get(bit_depth);
> +
> +    if (!cae->api) {
> +        av_log(avctx, AV_LOG_ERROR, "api get failed\n");
> +        return AVERROR(EINVAL);

AVERROR_EXTERNAL?  I don't think this indicates that the user did something invalid.

> +    }
> +
> +    cae->param = cae->api->opt_alloc();
> +
> +    if (!cae->param) {
> +        av_log(avctx, AV_LOG_ERROR, "param alloc failed\n");
> +        return AVERROR(EINVAL);

Maybe AVERROR(ENOMEM)?

> +    }
> +
> +    xavs2_opt_set2("rec",   "%d", 0);
> +    xavs2_opt_set2("log",   "%d", 2);
> +
> +    xavs2_opt_set2("width",     "%d", avctx->width);
> +    xavs2_opt_set2("height",    "%d", avctx->height);
> +    xavs2_opt_set2("bframes",   "%d", avctx->max_b_frames);
> +    xavs2_opt_set2("bitdepth",  "%d", bit_depth);
> +    xavs2_opt_set2("preset",    "%d", cae->preset_level);
> +
> +    xavs2_opt_set2("thread_frames",     "%d", avctx->thread_count);
> +    xavs2_opt_set2("intraperiod",       "%d", avctx->gop_size);
> +    xavs2_opt_set2("thread_rows",       "%d", cae->i_lcurow_threads);
> +    xavs2_opt_set2("initial_qp",        "%d", cae->i_initial_qp);
> +    xavs2_opt_set2("hierarchical_ref",  "%d", cae->b_hierarchical_reference);

This code looks much nicer, thank you!

> +
> +    if (cae->xavs2_opts) {
> +        AVDictionary *dict    = NULL;
> +        AVDictionaryEntry *en = NULL;
> +
> +        if (!av_dict_parse_string(&dict, cae->xavs2_opts, "=", ":", 0)) {
> +            while ((en = av_dict_get(dict, "", en, AV_DICT_IGNORE_SUFFIX))) {
> +                xavs2_opt_set2(en->key, "%s", en->value);

Should you check the result of this one?  The user might pass soemthing completely invalid, which probably wants a warning at least.

> +            }
> +            av_dict_free(&dict);
> +        }
> +    }
> +
> +    /* Rate control */
> +    if (avctx->bit_rate > 0) {
> +        xavs2_opt_set2("RateControl",   "%d", 1);
> +        xavs2_opt_set2("TargetBitRate", "%d", avctx->bit_rate);
> +    }
> +
> +
> +    ff_mpeg12_find_best_frame_rate(avctx->framerate, &code, NULL, NULL, 0);
> +
> +    xavs2_opt_set2("FrameRate",   "%d", code);
> +
> +    cae->encoder = cae->api->encoder_create(cae->param);
> +
> +    if (!cae->encoder) {
> +        av_log(avctx,AV_LOG_ERROR, "Can not create encoder. Null pointer returned\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    return 0;
> +}
> +
> ...
> +
> +static int xavs2_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
> +                      const AVFrame *frame, int *got_packet)
> +{
> +    XAVS2EContext *cae = avctx->priv_data;
> +    xavs2_picture_t pic;
> +    int ret;
> +
> +    /* create the XAVS2 video encoder */
> +    /* read frame data and send to the XAVS2 video encoder */
> +    if (cae->api->encoder_get_buffer(cae->encoder, &pic) < 0) {
> +        av_log(avctx,AV_LOG_ERROR, "failed to get frame buffer\n");
> +        return AVERROR(AVERROR_EXTERNAL);

Just AVERROR_EXTERNAL - AVERROR(EFOO) is only used for the errno.h EFOO codes.

> +    }
> +    if (frame) {
> +        switch (frame->format) {
> +            case AV_PIX_FMT_YUV420P:
> +                if (pic.img.in_sample_size == pic.img.enc_sample_size) {
> +                    xavs2_copy_frame(&pic, frame);
> +                } else {
> +                    const int shift_in = atoi(cae->api->opt_get(cae->param, "SampleShift"));
> +                    xavs2_copy_frame_with_shift(&pic, frame, shift_in);
> +                }
> +            break;
> +            case AV_PIX_FMT_YUV420P10:
> +                if (pic.img.in_sample_size == pic.img.enc_sample_size) {
> +                    xavs2_copy_frame(&pic, frame);
> +                    break;
> +                }
> +            default:
> +                av_log(avctx, AV_LOG_ERROR, "Unsupported pixel format\n");
> +                return AVERROR(EINVAL);
> +            break;
> +        }
> +
> +        pic.i_state = 0;
> +        pic.i_pts  = frame->pts;
> +        pic.i_type = XAVS2_TYPE_AUTO;
> +
> +        ret = cae->api->encoder_encode(cae->encoder, &pic, &cae->packet);
> +
> +        if (ret) {
> +            av_log(avctx, AV_LOG_ERROR, "encode failed\n");
> +            return AVERROR(AVERROR_EXTERNAL);

Just AVERROR_EXTERNAL.

> +        }
> +
> +    } else {
> +        cae->api->encoder_encode(cae->encoder, NULL, &cae->packet);
> +    }
> +
> +    if ((cae->packet.len) && (cae->packet.state != XAVS2_STATE_FLUSH_END)){
> +
> +        if (av_new_packet(pkt, cae->packet.len) < 0){
> +            av_log(avctx, AV_LOG_ERROR, "packet alloc failed\n");
> +            cae->api->encoder_packet_unref(cae->encoder, &cae->packet);
> +            return AVERROR(ENOMEM);
> +        }
> +
> +        pkt->pts = cae->packet.pts;
> +        pkt->dts = cae->packet.dts;
> +
> +        memcpy(pkt->data, cae->packet.stream, cae->packet.len);
> +        pkt->size = cae->packet.len;
> +
> +        cae->api->encoder_packet_unref(cae->encoder, &cae->packet);
> +
> +        *got_packet = 1;
> +    } else {
> +        *got_packet = 0;
> +    }
> +
> +    return 0;
> +}
> +
> ...
> +
> +#define OFFSET(x) offsetof(XAVS2EContext, x)
> +#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
> +
> +static const AVOption options[] = {
> +    { "i_lcurow_threads",   "number of parallel threads for rows" ,     OFFSET(i_lcurow_threads), AV_OPT_TYPE_INT, {.i64 =  5 }, 1,   8,  VE },

This name could probably be clearer - "row_threads" might be enough, or if the fact that they are LCU rows is important then "lcu_row_threads".

Where does the default of 5 and the upper limit of 8 come from?  Is it desirable that this matches the number of CPUs, or are there some codec constraints to take into account as well?

> +    { "i_initial_qp"    ,   "Quantization parameter" ,                  OFFSET(i_initial_qp)    , AV_OPT_TYPE_INT, {.i64 = 34 }, 1,  63,  VE },

If I understand what you said previously correctly, this is only used in constant-QP mode, and there it is used as the QP for every frame (not just the initial one)?

If that's the case then it should probably not say "initial" - I would read "initial_qp" as meaning the QP used for the first frame only, so probably in modes with a bitrate target.  Maybe change it to just be "qp"?  That name is used by several other encoders, including libx264 and libxavs.

> +    { "speed_level"     ,   "Speed level, higher is slower and better", OFFSET(preset_level)    , AV_OPT_TYPE_INT, {.i64 =  0 }, 0,   9,  VE },
> +    { "hierarchical_ref",   "hierarchical reference" ,                  OFFSET(b_hierarchical_reference)    , AV_OPT_TYPE_BOOL,    {.i64 =  1 }, 0, 1,  VE },
> +    { "xavs2-params"    ,   "set the xavs2 configuration using a :-separated list of key=value parameters", OFFSET(xavs2_opts), AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE },
> +    { NULL },
> +};
> +
> +static const AVClass libxavs2_class = {
> +    .class_name = "XAVS2EContext",

Use the class name "libxavs2", to match other encoders.

> +    .item_name  = av_default_item_name,
> +    .option     = options,
> +    .version    = LIBAVUTIL_VERSION_INT,
> +};
> +
> +static const AVCodecDefault xavs2_defaults[] = {
> +    { "b",                "0" },

You might want consider setting a default gop_size ("g") here as well - the global default of 12 is generally a bit low for most applications.

Similarly, you might want a value for max_b_frames ("bf"), since that defaults to zero.

> +    { NULL },
> +};
> +
> +AVCodec ff_libxavs2_encoder = {
> +    .name           = "libxavs2",
> +    .long_name      = NULL_IF_CONFIG_SMALL("libxavs2 AVS2-P2/IEEE1857.4"),
> +    .type           = AVMEDIA_TYPE_VIDEO,
> +    .id             = AV_CODEC_ID_AVS2,
> +    .priv_data_size = sizeof(XAVS2EContext),
> +    .init           = xavs2_init,
> +    .encode2        = xavs2_encode_frame,
> +    .close          = xavs2_close,
> +    .capabilities   = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AUTO_THREADS,
> +    .pix_fmts       = (const enum AVPixelFormat[]) { AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10, AV_PIX_FMT_NONE },
> +    .priv_class     = &libxavs2_class,
> +    .defaults       = xavs2_defaults,
> +    .wrapper_name   = "libxavs2",
> +} ;
Thanks,

- Mark


More information about the ffmpeg-devel mailing list