diff mbox series

[FFmpeg-devel,2/3] avfilter: add an LCEVC decoding filter

Message ID 20240319140636.4323-2-jamrial@gmail.com
State New
Headers show
Series [FFmpeg-devel,1/3] libavcodec/h2645_sei: export raw LCEVC metadata | 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

James Almer March 19, 2024, 2:06 p.m. UTC
Signed-off-by: James Almer <jamrial@gmail.com>
---
 configure                |   4 +
 libavfilter/Makefile     |   1 +
 libavfilter/allfilters.c |   1 +
 libavfilter/vf_lcevc.c   | 466 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 472 insertions(+)
 create mode 100644 libavfilter/vf_lcevc.c

Comments

Andreas Rheinhardt March 19, 2024, 2:56 p.m. UTC | #1
James Almer:
> Signed-off-by: James Almer <jamrial@gmail.com>
> ---
>  configure                |   4 +
>  libavfilter/Makefile     |   1 +
>  libavfilter/allfilters.c |   1 +
>  libavfilter/vf_lcevc.c   | 466 +++++++++++++++++++++++++++++++++++++++
>  4 files changed, 472 insertions(+)
>  create mode 100644 libavfilter/vf_lcevc.c
> 
> diff --git a/configure b/configure
> index e019d1b996..4022d13f76 100755
> --- a/configure
> +++ b/configure
> @@ -224,6 +224,7 @@ External library support:
>    --enable-libcdio         enable audio CD grabbing with libcdio [no]
>    --enable-libcodec2       enable codec2 en/decoding using libcodec2 [no]
>    --enable-libdav1d        enable AV1 decoding via libdav1d [no]
> +  --enable-liblcevc_dec    enable AV1 decoding via liblcevc_dec [no]

How is this filter supposed to decode AV1 if it does not even get
AVPackets? It seems to be for decoding an EVC enhancment layer instead.

>    --enable-libdavs2        enable AVS2 decoding via libdavs2 [no]
>    --enable-libdc1394       enable IIDC-1394 grabbing using libdc1394
>                             and libraw1394 [no]
> @@ -1861,6 +1862,7 @@ EXTERNAL_LIBRARY_LIST="
>      libcelt
>      libcodec2
>      libdav1d
> +    liblcevc_dec
>      libdc1394
>      libflite
>      libfontconfig
> @@ -3789,6 +3791,7 @@ iccgen_filter_deps="lcms2"
>  interlace_filter_deps="gpl"
>  kerndeint_filter_deps="gpl"
>  ladspa_filter_deps="ladspa libdl"
> +lcevc_filter_deps="liblcevc_dec"
>  lensfun_filter_deps="liblensfun version3"
>  libplacebo_filter_deps="libplacebo vulkan"
>  lv2_filter_deps="lv2"
> @@ -6801,6 +6804,7 @@ enabled libjxl            && require_pkg_config libjxl "libjxl >= 0.7.0" jxl/dec
>  enabled libklvanc         && require libklvanc libklvanc/vanc.h klvanc_context_create -lklvanc
>  enabled libkvazaar        && require_pkg_config libkvazaar "kvazaar >= 2.0.0" kvazaar.h kvz_api_get
>  enabled liblensfun        && require_pkg_config liblensfun lensfun lensfun.h lf_db_create
> +enabled liblcevc_dec      && require_pkg_config liblcevc_dec "lcevc_dec >= 2.0.0" "LCEVC/lcevc_dec.h" LCEVC_CreateDecoder
>  
>  if enabled libmfx && enabled libvpl; then
>     die "ERROR: can not use libmfx and libvpl together"
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 994d9773ba..57f27a1221 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -359,6 +359,7 @@ OBJS-$(CONFIG_INTERLEAVE_FILTER)             += f_interleave.o
>  OBJS-$(CONFIG_KERNDEINT_FILTER)              += vf_kerndeint.o
>  OBJS-$(CONFIG_KIRSCH_FILTER)                 += vf_convolution.o
>  OBJS-$(CONFIG_LAGFUN_FILTER)                 += vf_lagfun.o
> +OBJS-$(CONFIG_LCEVC_FILTER)                  += vf_lcevc.o
>  OBJS-$(CONFIG_LATENCY_FILTER)                += f_latency.o
>  OBJS-$(CONFIG_LENSCORRECTION_FILTER)         += vf_lenscorrection.o
>  OBJS-$(CONFIG_LENSFUN_FILTER)                += vf_lensfun.o
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 149bf50997..54d6ea1043 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -337,6 +337,7 @@ extern const AVFilter ff_vf_kerndeint;
>  extern const AVFilter ff_vf_kirsch;
>  extern const AVFilter ff_vf_lagfun;
>  extern const AVFilter ff_vf_latency;
> +extern const AVFilter ff_vf_lcevc;
>  extern const AVFilter ff_vf_lenscorrection;
>  extern const AVFilter ff_vf_lensfun;
>  extern const AVFilter ff_vf_libplacebo;
> diff --git a/libavfilter/vf_lcevc.c b/libavfilter/vf_lcevc.c
> new file mode 100644
> index 0000000000..e18f0945f9
> --- /dev/null
> +++ b/libavfilter/vf_lcevc.c
> @@ -0,0 +1,466 @@
> +/*
> + * This file is part of FFmpeg.
> + *
> + * Copyright (c) 2024 James Almer <jamrial@gmail.com>
> + *
> + * 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 <stdint.h>
> +
> +#include <LCEVC/lcevc_dec.h>
> +
> +#include "libavutil/internal.h"
> +#include "libavutil/opt.h"
> +#include "filters.h"
> +#include "internal.h"
> +#include "video.h"
> +
> +typedef struct LCEVCContext {
> +    LCEVC_DecoderHandle decoder;
> +    LCEVC_PictureHandle base;
> +    AVFrame *in;
> +    int w, h;
> +    int status;
> +} LCEVCContext;
> +
> +static LCEVC_ColorFormat map_format(int format)
> +{
> +    switch (format) {
> +    case AV_PIX_FMT_YUV420P:
> +        return LCEVC_I420_8;
> +    case AV_PIX_FMT_YUV420P10LE:
> +        return LCEVC_I420_10_LE;
> +    case AV_PIX_FMT_YUV420P12LE:
> +        return LCEVC_I420_12_LE;
> +    case AV_PIX_FMT_NV12:
> +        return LCEVC_NV12_8;
> +    case AV_PIX_FMT_NV21:
> +        return LCEVC_NV21_8;
> +    case AV_PIX_FMT_GRAY8:
> +        return LCEVC_GRAY_8;
> +    case AV_PIX_FMT_GRAY10LE:
> +        return LCEVC_GRAY_10_LE;
> +    case AV_PIX_FMT_GRAY12LE:
> +        return LCEVC_GRAY_12_LE;
> +    case AV_PIX_FMT_RGB24:
> +        return LCEVC_RGB_8;
> +    case AV_PIX_FMT_BGR24:
> +        return LCEVC_BGR_8;
> +    case AV_PIX_FMT_RGBA:
> +        return LCEVC_RGBA_8;
> +    case AV_PIX_FMT_ARGB:
> +        return LCEVC_ARGB_8;
> +    case AV_PIX_FMT_ABGR:
> +        return LCEVC_ABGR_8;
> +    case AV_PIX_FMT_BGRA:
> +        return LCEVC_BGRA_8;
> +    }
> +
> +    return LCEVC_ColorFormat_Unknown;
> +}
> +
> +static inline LCEVC_ColorRange map_range(int range)
> +{
> +    switch (range) {
> +    case AVCOL_RANGE_MPEG:
> +        return LCEVC_ColorRange_Limited;
> +    case AVCOL_RANGE_JPEG:
> +        return LCEVC_ColorRange_Full;
> +    }
> +
> +    return LCEVC_ColorRange_Unknown;
> +}
> +
> +static inline enum AVColorRange map_av_range(int range)
> +{
> +    switch (range) {
> +    case LCEVC_ColorRange_Limited:
> +        return AVCOL_RANGE_MPEG;
> +    case LCEVC_ColorRange_Full:
> +        return AVCOL_RANGE_JPEG;
> +    }
> +
> +    return AVCOL_RANGE_UNSPECIFIED;
> +}
> +
> +static int alloc_base_frame(AVFilterLink *inlink, const AVFrame *in,
> +                            LCEVC_PictureHandle *picture)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    LCEVCContext *lcevc = ctx->priv;
> +    LCEVC_PictureDesc desc;
> +    LCEVC_PicturePlaneDesc planes[AV_VIDEO_MAX_PLANES] = { 0 };
> +    LCEVC_ColorFormat fmt = map_format(in->format);
> +    int width = in->width - in->crop_left - in->crop_right;
> +    int height = in->height - in->crop_top - in->crop_bottom;
> +    LCEVC_ReturnCode res;
> +
> +    res = LCEVC_DefaultPictureDesc(&desc, fmt, width, height);
> +    if (res != LCEVC_Success) {
> +        av_log(ctx, AV_LOG_ERROR, "LCEVC_DefaultPictureDesc failed\n");
> +		return AVERROR_EXTERNAL;
> +    }
> +
> +    for (int i = 0; i < AV_VIDEO_MAX_PLANES; i++) {
> +        planes[i].firstSample = in->data[i];
> +        planes[i].rowByteStride = in->linesize[i];
> +    }
> +
> +    desc.cropTop    = in->crop_top;
> +    desc.cropBottom = in->crop_bottom;
> +    desc.cropLeft   = in->crop_left;
> +    desc.cropRight  = in->crop_right;
> +    desc.sampleAspectRatioNum = in->sample_aspect_ratio.num;
> +    desc.sampleAspectRatioDen = in->sample_aspect_ratio.den;
> +    desc.colorRange = map_range(in->color_range);
> +    desc.colorPrimaries = (LCEVC_ColorPrimaries)in->color_primaries;
> +    desc.matrixCoefficients = (LCEVC_MatrixCoefficients)in->colorspace;
> +    desc.transferCharacteristics = (LCEVC_TransferCharacteristics)in->color_trc;
> +    av_log(ctx, AV_LOG_DEBUG, "in  PTS %"PRId64", %dx%d, "
> +                              "%"SIZE_SPECIFIER"/%"SIZE_SPECIFIER"/%"SIZE_SPECIFIER"/%"SIZE_SPECIFIER", "
> +                              "SAR %d:%d\n",
> +           in->pts, in->width, in->height,
> +           in->crop_top, in->crop_bottom, in->crop_left, in->crop_right,
> +           in->sample_aspect_ratio.num, in->sample_aspect_ratio.den);
> +
> +    res = LCEVC_AllocPictureExternal(lcevc->decoder, &desc, NULL, planes, picture);
> +    if (res != LCEVC_Success) {
> +        av_log(ctx, AV_LOG_ERROR, "LCEVC_AllocPictureExternal to allocate a buffer for a base frame\n");
> +        return AVERROR_EXTERNAL;
> +    }
> +
> +    return 0;
> +}
> +
> +static int send_frame(AVFilterLink *inlink)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    LCEVCContext *lcevc = ctx->priv;
> +    const AVFrameSideData *sd = av_frame_get_side_data(lcevc->in, AV_FRAME_DATA_LCEVC);
> +    LCEVC_ReturnCode res;
> +
> +    // lcevc->base will be set only if a previous LCEVC_SendDecoderBase() call failed with LCEVC_Again,
> +    // in which case we'll try sending the picture again after having fetched at least one enhanced frame.
> +    if (!lcevc->base.hdl) {
> +        int ret = alloc_base_frame(inlink, lcevc->in, &lcevc->base);
> +        if (ret < 0)
> +            return ret;
> +    }
> +
> +    if (sd) {
> +        res = LCEVC_SendDecoderEnhancementData(lcevc->decoder, lcevc->in->pts, 0, sd->data, sd->size);
> +        if (res == LCEVC_Again)
> +            return AVERROR(EAGAIN);
> +        else if (res != LCEVC_Success) {
> +            av_log(ctx, AV_LOG_ERROR, "LCEVC_SendDecoderEnhancementData failed\n");
> +            return AVERROR_EXTERNAL;
> +        }
> +    }
> +
> +    res = LCEVC_SendDecoderBase(lcevc->decoder, lcevc->in->pts, 0, lcevc->base, -1, lcevc->in);
> +    if (res == LCEVC_Success) {
> +        lcevc->in = NULL;
> +        memset(&lcevc->base, 0, sizeof(lcevc->base));
> +    } else if (res == LCEVC_Again)
> +        return AVERROR(EAGAIN);
> +    else {
> +        av_log(ctx, AV_LOG_ERROR, "LCEVC_SendDecoderBase failed\n");
> +        return AVERROR_EXTERNAL;
> +    }
> +
> +    return 0;
> +}
> +
> +static int alloc_enhanced_frame(AVFilterLink *inlink, const AVFrame *out,
> +                                LCEVC_PictureHandle *picture)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    LCEVCContext *lcevc = ctx->priv;
> +    LCEVC_PictureDesc desc;
> +    LCEVC_PicturePlaneDesc planes[AV_VIDEO_MAX_PLANES] = { 0 };
> +    LCEVC_ColorFormat fmt = map_format(out->format);
> +    LCEVC_ReturnCode res;
> +
> +    res = LCEVC_DefaultPictureDesc(&desc, fmt, out->width, out->height);
> +    if (res != LCEVC_Success)
> +        return AVERROR_EXTERNAL;
> +
> +    for (int i = 0; i < AV_VIDEO_MAX_PLANES; i++) {
> +        planes[i].firstSample = out->data[i];
> +        planes[i].rowByteStride = out->linesize[i];
> +    }
> +
> +    res = LCEVC_AllocPictureExternal(lcevc->decoder, &desc, NULL, planes, picture);
> +    if (res != LCEVC_Success) {
> +        av_log(ctx, AV_LOG_ERROR, "LCEVC_AllocPictureExternal to allocate a buffer for an enhanced frame\n");
> +        return AVERROR_EXTERNAL;
> +    }
> +
> +    return 0;
> +}
> +
> +static int generate_output(AVFilterLink *inlink, AVFrame *out)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    AVFilterLink *outlink = ctx->outputs[0];
> +    LCEVCContext *lcevc = ctx->priv;
> +    LCEVC_PictureDesc desc;
> +    LCEVC_DecodeInformation info;
> +    LCEVC_PictureHandle picture;
> +    LCEVC_ReturnCode res;
> +
> +    res = LCEVC_ReceiveDecoderPicture(lcevc->decoder, &picture, &info);
> +    if (res == LCEVC_Again) {
> +        int64_t pts;
> +        if (ff_inlink_acknowledge_status(inlink, &lcevc->status, &pts)) {
> +            av_frame_free(&out);
> +            ff_outlink_set_status(outlink, lcevc->status, pts);
> +            return 0;
> +        }
> +        // this shouldn't be reachable, but instead of asserting, just error out
> +        return AVERROR_EXTERNAL;
> +    } else if (res != LCEVC_Success) {
> +        av_log(ctx, AV_LOG_ERROR, "LCEVC_ReceiveDecoderPicture failed\n");
> +        return AVERROR_EXTERNAL;
> +    }
> +
> +    av_frame_copy_props(out, (AVFrame *)info.baseUserData);
> +    av_frame_remove_side_data(out, AV_FRAME_DATA_LCEVC);
> +
> +    av_frame_free((AVFrame **)&info.baseUserData);
> +
> +    res = LCEVC_GetPictureDesc(lcevc->decoder, picture, &desc);
> +    if (res != LCEVC_Success) {
> +        av_log(ctx, AV_LOG_ERROR, "LCEVC_GetPictureDesc failed\n");
> +        return AVERROR_EXTERNAL;
> +    }
> +    res = LCEVC_FreePicture(lcevc->decoder, picture);
> +    if (res != LCEVC_Success) {
> +        av_log(ctx, AV_LOG_ERROR, "LCEVC_FreePicture enhanced failed\n");
> +        return AVERROR_EXTERNAL;
> +    }
> +
> +    out->crop_top = desc.cropTop;
> +    out->crop_bottom = desc.cropBottom;
> +    out->crop_left = desc.cropLeft;
> +    out->crop_right = desc.cropRight;
> +    out->sample_aspect_ratio.num = outlink->sample_aspect_ratio.num = desc.sampleAspectRatioNum;
> +    out->sample_aspect_ratio.den = outlink->sample_aspect_ratio.den = desc.sampleAspectRatioDen;
> +    out->color_range = map_range(desc.colorRange);
> +    out->color_primaries = (enum AVColorPrimaries)desc.colorPrimaries;
> +    out->colorspace = (enum AVColorSpace)desc.matrixCoefficients;
> +    out->color_trc = (enum AVColorTransferCharacteristic)desc.transferCharacteristics;
> +    out->width = outlink->w = desc.width + out->crop_left + out->crop_right;
> +    out->height = outlink->h = desc.height + out->crop_top + out->crop_bottom;
> +
> +    av_log(ctx, AV_LOG_DEBUG, "out PTS %"PRId64", %dx%d, "
> +                              "%"SIZE_SPECIFIER"/%"SIZE_SPECIFIER"/%"SIZE_SPECIFIER"/%"SIZE_SPECIFIER", "
> +                              "SAR %d:%d, "
> +                              "hasEnhancement %d, enhanced %d\n",
> +           out->pts, out->width, out->height,
> +           out->crop_top, out->crop_bottom, out->crop_left, out->crop_right,
> +           out->sample_aspect_ratio.num, out->sample_aspect_ratio.den,
> +           info.hasEnhancement, info.enhanced);
> +
> +    return ff_filter_frame(outlink, out);
> +}
> +
> +static int receive_frame(AVFilterLink *inlink, AVFrame *out)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    LCEVCContext *lcevc = ctx->priv;
> +    LCEVC_PictureHandle picture;
> +    LCEVC_ReturnCode res;
> +    int ret;
> +
> +    ret = alloc_enhanced_frame(inlink, out, &picture);
> +    if (ret < 0)
> +        return ret;
> +
> +    res = LCEVC_SendDecoderPicture(lcevc->decoder, picture);
> +    if (res != LCEVC_Success) {
> +        av_log(ctx, AV_LOG_ERROR, "LCEVC_SendDecoderPicture failed\n");
> +        return AVERROR_EXTERNAL;
> +    }
> +
> +    return generate_output(inlink, out);
> +}
> +
> +static int config_props(AVFilterLink *outlink)
> +{
> +    AVFilterContext *ctx = outlink->src;
> +    AVFilterLink *inlink = ctx->inputs[0];
> +    LCEVCContext *lcevc = ctx->priv;
> +
> +    outlink->w = lcevc->w = inlink->w * 2 / FFMAX(inlink->sample_aspect_ratio.den, 1);
> +    outlink->h = lcevc->h = inlink->h * 2 / FFMAX(inlink->sample_aspect_ratio.den, 1);
> +    outlink->sample_aspect_ratio = (AVRational) { 0, 1 };
> +
> +    return 0;
> +}
> +
> +static void flush_bases(AVFilterContext *ctx)
> +{
> +    LCEVCContext *lcevc = ctx->priv;
> +    LCEVC_PictureHandle picture;
> +
> +    while (LCEVC_ReceiveDecoderBase(lcevc->decoder, &picture) == LCEVC_Success)
> +        LCEVC_FreePicture(lcevc->decoder, picture);
> +}
> +
> +static int activate(AVFilterContext *ctx)
> +{
> +    LCEVCContext *lcevc   = ctx->priv;
> +    AVFilterLink *inlink  = ctx->inputs[0];
> +    AVFilterLink *outlink = ctx->outputs[0];
> +    AVFrame *out;
> +    int ret;
> +
> +    FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink);
> +
> +    if (!lcevc->in) {
> +        ret = ff_inlink_consume_frame(inlink, &lcevc->in);
> +        if (ret < 0)
> +            return ret;
> +        if (!ret) {
> +            int64_t pts;
> +            if (ff_inlink_acknowledge_status(inlink, &lcevc->status, &pts)) {
> +                if (!lcevc->status)
> +                    ff_outlink_set_status(outlink, lcevc->status, pts);
> +            }
> +            if (!lcevc->status)
> +                FF_FILTER_FORWARD_WANTED(outlink, inlink);
> +        }
> +    }
> +
> +    if (lcevc->in) {
> +       if (lcevc->in->width  != inlink->w ||
> +            lcevc->in->height != inlink->h ||
> +            lcevc->in->sample_aspect_ratio.den != inlink->sample_aspect_ratio.den ||
> +            lcevc->in->sample_aspect_ratio.num != inlink->sample_aspect_ratio.num) {
> +            inlink->dst->inputs[0]->w                       = lcevc->in->width;
> +            inlink->dst->inputs[0]->h                       = lcevc->in->height;
> +            inlink->dst->inputs[0]->sample_aspect_ratio.den = lcevc->in->sample_aspect_ratio.den;
> +            inlink->dst->inputs[0]->sample_aspect_ratio.num = lcevc->in->sample_aspect_ratio.num;
> +
> +            config_props(outlink);
> +        }
> +
> +        ret = send_frame(inlink);
> +        // Buffer as many base frames as the decoder allows
> +        if (!ret) {
> +            FF_FILTER_FORWARD_WANTED(outlink, inlink);
> +            return FFERROR_NOT_READY;
> +        } else if (ret < 0 && ret != AVERROR(EAGAIN))
> +            return ret;
> +    }
> +
> +    out = ff_get_video_buffer(outlink, lcevc->w, lcevc->h);
> +    if (!out)
> +        return AVERROR(ENOMEM);
> +
> +    ret = receive_frame(inlink, out);
> +    if (ret < 0) {
> +        av_frame_free(&out);
> +        return ret;
> +    }
> +
> +    flush_bases(ctx);
> +
> +    return ret;
> +}
> +
> +static void log_callback(LCEVC_DecoderHandle dec, LCEVC_Event event,
> +                         LCEVC_PictureHandle pic, const LCEVC_DecodeInformation *info,
> +                         const uint8_t *data, uint32_t size, void *logctx)
> +{
> +    if (event != LCEVC_Log) // shouldn't happen
> +        return;
> +
> +    if (strlen(data) != size) // sanitize input
> +        return;
> +
> +    av_log(logctx, AV_LOG_INFO, "LCEVC Log: %s\n", data);
> +}
> +
> +static av_cold int init(AVFilterContext *ctx)
> +{
> +    LCEVCContext *lcevc = ctx->priv;
> +    LCEVC_AccelContextHandle dummy = { 0 };
> +    const int32_t event = LCEVC_Log;
> +    LCEVC_ReturnCode res;
> +
> +    res = LCEVC_CreateDecoder(&lcevc->decoder, dummy);
> +    if (res != LCEVC_Success) {
> +        av_log(ctx, AV_LOG_ERROR, "LCEVC_CreateDecoder failed\n");
> +        return AVERROR_EXTERNAL;
> +    }
> +
> +    res = LCEVC_ConfigureDecoderIntArray(lcevc->decoder, "events", 1, &event);
> +    if (res != LCEVC_Success) {
> +        av_log(ctx, AV_LOG_ERROR, "LCEVC_ConfigureDecoderIntArray failed to set \"events\"\n");
> +        return AVERROR_EXTERNAL;
> +    }
> +    res = LCEVC_SetDecoderEventCallback(lcevc->decoder, log_callback, ctx);
> +    if (res != LCEVC_Success) {
> +        av_log(ctx, AV_LOG_ERROR, "LCEVC_SetDecoderEventCallback failed\n");
> +        return AVERROR_EXTERNAL;
> +    }
> +
> +    res = LCEVC_InitializeDecoder(lcevc->decoder);
> +    if (res != LCEVC_Success) {
> +        av_log(ctx, AV_LOG_ERROR, "LCEVC_InitializeDecoder failed\n");
> +        return AVERROR_EXTERNAL;
> +    }
> +
> +    return 0;
> +}
> +
> +static av_cold void uninit(AVFilterContext *ctx)
> +{
> +    LCEVCContext *lcevc = ctx->priv;
> +
> +    LCEVC_DestroyDecoder(lcevc->decoder);
> +}
> +
> +static const AVFilterPad lcevc_outputs[] = {
> +    {
> +        .name         = "default",
> +        .type         = AVMEDIA_TYPE_VIDEO,
> +        .config_props = config_props,
> +    },
> +};
> +
> +static const enum AVPixelFormat pix_fmts[] = {
> +    AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10LE, AV_PIX_FMT_YUV420P12LE,
> +    AV_PIX_FMT_NV12, AV_PIX_FMT_NV21,
> +    AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY10LE, AV_PIX_FMT_GRAY12LE,
> +    AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24,
> +    AV_PIX_FMT_RGBA, AV_PIX_FMT_ARGB, AV_PIX_FMT_ABGR, AV_PIX_FMT_BGRA,
> +    AV_PIX_FMT_NONE
> +};
> +
> +const AVFilter ff_vf_lcevc = {
> +    .name          = "lcevc",
> +    .description   = NULL_IF_CONFIG_SMALL("LCEVC"),
> +    .activate      = activate,
> +    FILTER_INPUTS(ff_video_default_filterpad),
> +    FILTER_OUTPUTS(lcevc_outputs),
> +    FILTER_PIXFMTS_ARRAY(pix_fmts),
> +    .priv_size     = sizeof(LCEVCContext),
> +    .init          = init,
> +    .uninit        = uninit,
> +};
Stefano Sabatini March 19, 2024, 3 p.m. UTC | #2
On date Tuesday 2024-03-19 11:06:35 -0300, James Almer wrote:
> Signed-off-by: James Almer <jamrial@gmail.com>
> ---
>  configure                |   4 +
>  libavfilter/Makefile     |   1 +
>  libavfilter/allfilters.c |   1 +
>  libavfilter/vf_lcevc.c   | 466 +++++++++++++++++++++++++++++++++++++++

missing docs, this is also needed to clarify the use case
James Almer March 19, 2024, 3:05 p.m. UTC | #3
On 3/19/2024 11:56 AM, Andreas Rheinhardt wrote:
> James Almer:
>> Signed-off-by: James Almer <jamrial@gmail.com>
>> ---
>>   configure                |   4 +
>>   libavfilter/Makefile     |   1 +
>>   libavfilter/allfilters.c |   1 +
>>   libavfilter/vf_lcevc.c   | 466 +++++++++++++++++++++++++++++++++++++++
>>   4 files changed, 472 insertions(+)
>>   create mode 100644 libavfilter/vf_lcevc.c
>>
>> diff --git a/configure b/configure
>> index e019d1b996..4022d13f76 100755
>> --- a/configure
>> +++ b/configure
>> @@ -224,6 +224,7 @@ External library support:
>>     --enable-libcdio         enable audio CD grabbing with libcdio [no]
>>     --enable-libcodec2       enable codec2 en/decoding using libcodec2 [no]
>>     --enable-libdav1d        enable AV1 decoding via libdav1d [no]
>> +  --enable-liblcevc_dec    enable AV1 decoding via liblcevc_dec [no]
> 
> How is this filter supposed to decode AV1 if it does not even get
> AVPackets? It seems to be for decoding an EVC enhancment layer instead.

Simple, it doesn't. It's a copy-paste fail from my part.
Fixed locally.
Kieran Kunhya March 19, 2024, 3:20 p.m. UTC | #4
On Tue, 19 Mar 2024 at 15:05, James Almer <jamrial@gmail.com> wrote:

> On 3/19/2024 11:56 AM, Andreas Rheinhardt wrote:
> > James Almer:
> >> Signed-off-by: James Almer <jamrial@gmail.com>
> >> ---
> >>   configure                |   4 +
> >>   libavfilter/Makefile     |   1 +
> >>   libavfilter/allfilters.c |   1 +
> >>   libavfilter/vf_lcevc.c   | 466 +++++++++++++++++++++++++++++++++++++++
> >>   4 files changed, 472 insertions(+)
> >>   create mode 100644 libavfilter/vf_lcevc.c
> >>
> >> diff --git a/configure b/configure
> >> index e019d1b996..4022d13f76 100755
> >> --- a/configure
> >> +++ b/configure
> >> @@ -224,6 +224,7 @@ External library support:
> >>     --enable-libcdio         enable audio CD grabbing with libcdio [no]
> >>     --enable-libcodec2       enable codec2 en/decoding using libcodec2
> [no]
> >>     --enable-libdav1d        enable AV1 decoding via libdav1d [no]
> >> +  --enable-liblcevc_dec    enable AV1 decoding via liblcevc_dec [no]
> >
> > How is this filter supposed to decode AV1 if it does not even get
> > AVPackets? It seems to be for decoding an EVC enhancment layer instead.
>
> Simple, it doesn't. It's a copy-paste fail from my part.
> Fixed locally.
>
>
From https://github.com/v-novaltd/LCEVCdec
" This software is protected by copyrights and other intellectual property
rights and no license is granted to any such rights. If you would like to
obtain a license to compile, distribute, or make any other use of this
software, please contact V-Nova Limited info@v-nova.com."

So you want to include a library that requires ffmpeg users to obtain a
licence to compile?

Kieran
James Almer March 19, 2024, 3:28 p.m. UTC | #5
On 3/19/2024 12:20 PM, Kieran Kunhya wrote:
> On Tue, 19 Mar 2024 at 15:05, James Almer <jamrial@gmail.com> wrote:
> 
>> On 3/19/2024 11:56 AM, Andreas Rheinhardt wrote:
>>> James Almer:
>>>> Signed-off-by: James Almer <jamrial@gmail.com>
>>>> ---
>>>>    configure                |   4 +
>>>>    libavfilter/Makefile     |   1 +
>>>>    libavfilter/allfilters.c |   1 +
>>>>    libavfilter/vf_lcevc.c   | 466 +++++++++++++++++++++++++++++++++++++++
>>>>    4 files changed, 472 insertions(+)
>>>>    create mode 100644 libavfilter/vf_lcevc.c
>>>>
>>>> diff --git a/configure b/configure
>>>> index e019d1b996..4022d13f76 100755
>>>> --- a/configure
>>>> +++ b/configure
>>>> @@ -224,6 +224,7 @@ External library support:
>>>>      --enable-libcdio         enable audio CD grabbing with libcdio [no]
>>>>      --enable-libcodec2       enable codec2 en/decoding using libcodec2
>> [no]
>>>>      --enable-libdav1d        enable AV1 decoding via libdav1d [no]
>>>> +  --enable-liblcevc_dec    enable AV1 decoding via liblcevc_dec [no]
>>>
>>> How is this filter supposed to decode AV1 if it does not even get
>>> AVPackets? It seems to be for decoding an EVC enhancment layer instead.
>>
>> Simple, it doesn't. It's a copy-paste fail from my part.
>> Fixed locally.
>>
>>
>  From https://github.com/v-novaltd/LCEVCdec
> " This software is protected by copyrights and other intellectual property
> rights and no license is granted to any such rights. If you would like to
> obtain a license to compile, distribute, or make any other use of this
> software, please contact V-Nova Limited info@v-nova.com."
> 
> So you want to include a library that requires ffmpeg users to obtain a
> licence to compile?

Would adding it to the nonfree list be enough? But it's true that most 
users would not be aware of the aforementioned limitations.
Kieran Kunhya March 19, 2024, 5:21 p.m. UTC | #6
On Tue, 19 Mar 2024 at 15:27, James Almer <jamrial@gmail.com> wrote:

> On 3/19/2024 12:20 PM, Kieran Kunhya wrote:
> > On Tue, 19 Mar 2024 at 15:05, James Almer <jamrial@gmail.com> wrote:
> >
> >> On 3/19/2024 11:56 AM, Andreas Rheinhardt wrote:
> >>> James Almer:
> >>>> Signed-off-by: James Almer <jamrial@gmail.com>
> >>>> ---
> >>>>    configure                |   4 +
> >>>>    libavfilter/Makefile     |   1 +
> >>>>    libavfilter/allfilters.c |   1 +
> >>>>    libavfilter/vf_lcevc.c   | 466
> +++++++++++++++++++++++++++++++++++++++
> >>>>    4 files changed, 472 insertions(+)
> >>>>    create mode 100644 libavfilter/vf_lcevc.c
> >>>>
> >>>> diff --git a/configure b/configure
> >>>> index e019d1b996..4022d13f76 100755
> >>>> --- a/configure
> >>>> +++ b/configure
> >>>> @@ -224,6 +224,7 @@ External library support:
> >>>>      --enable-libcdio         enable audio CD grabbing with libcdio
> [no]
> >>>>      --enable-libcodec2       enable codec2 en/decoding using
> libcodec2
> >> [no]
> >>>>      --enable-libdav1d        enable AV1 decoding via libdav1d [no]
> >>>> +  --enable-liblcevc_dec    enable AV1 decoding via liblcevc_dec [no]
> >>>
> >>> How is this filter supposed to decode AV1 if it does not even get
> >>> AVPackets? It seems to be for decoding an EVC enhancment layer instead.
> >>
> >> Simple, it doesn't. It's a copy-paste fail from my part.
> >> Fixed locally.
> >>
> >>
> >  From https://github.com/v-novaltd/LCEVCdec
> > " This software is protected by copyrights and other intellectual
> property
> > rights and no license is granted to any such rights. If you would like to
> > obtain a license to compile, distribute, or make any other use of this
> > software, please contact V-Nova Limited info@v-nova.com."
> >
> > So you want to include a library that requires ffmpeg users to obtain a
> > licence to compile?
>
> Would adding it to the nonfree list be enough? But it's true that most
> users would not be aware of the aforementioned limitations.
>
>
This is a slippery slope to adding whatever binary blob. As the decoder is
de-facto a binary blob.

Kieran
Cosmin Stejerean March 19, 2024, 7:07 p.m. UTC | #7
On Mar 19, 2024, at 10:21 AM, Kieran Kunhya <kierank@obe.tv> wrote:

On Tue, 19 Mar 2024 at 15:27, James Almer <jamrial@gmail.com <mailto:jamrial@gmail.com> > wrote:

On 3/19/2024 12:20 PM, Kieran Kunhya wrote:From https://github.com/v-novaltd/LCEVCdec
" This software is protected by copyrights and other intellectual
property
rights and no license is granted to any such rights. If you would like to
obtain a license to compile, distribute, or make any other use of this
software, please contact V-Nova Limited info@v-nova.com."

So you want to include a library that requires ffmpeg users to obtain a
licence to compile?

Would adding it to the nonfree list be enough? But it's true that most

users would not be aware of the aforementioned limitations.


This is a slippery slope to adding whatever binary blob. As the decoder is
de-facto a binary blob.


Yeah, source available but with a prohibition on compilation might as well be a binary blob. Even to be on non-free I'd expect something along the lines of the FDK-AAC license at a minimum.

- Cosmin
diff mbox series

Patch

diff --git a/configure b/configure
index e019d1b996..4022d13f76 100755
--- a/configure
+++ b/configure
@@ -224,6 +224,7 @@  External library support:
   --enable-libcdio         enable audio CD grabbing with libcdio [no]
   --enable-libcodec2       enable codec2 en/decoding using libcodec2 [no]
   --enable-libdav1d        enable AV1 decoding via libdav1d [no]
+  --enable-liblcevc_dec    enable AV1 decoding via liblcevc_dec [no]
   --enable-libdavs2        enable AVS2 decoding via libdavs2 [no]
   --enable-libdc1394       enable IIDC-1394 grabbing using libdc1394
                            and libraw1394 [no]
@@ -1861,6 +1862,7 @@  EXTERNAL_LIBRARY_LIST="
     libcelt
     libcodec2
     libdav1d
+    liblcevc_dec
     libdc1394
     libflite
     libfontconfig
@@ -3789,6 +3791,7 @@  iccgen_filter_deps="lcms2"
 interlace_filter_deps="gpl"
 kerndeint_filter_deps="gpl"
 ladspa_filter_deps="ladspa libdl"
+lcevc_filter_deps="liblcevc_dec"
 lensfun_filter_deps="liblensfun version3"
 libplacebo_filter_deps="libplacebo vulkan"
 lv2_filter_deps="lv2"
@@ -6801,6 +6804,7 @@  enabled libjxl            && require_pkg_config libjxl "libjxl >= 0.7.0" jxl/dec
 enabled libklvanc         && require libklvanc libklvanc/vanc.h klvanc_context_create -lklvanc
 enabled libkvazaar        && require_pkg_config libkvazaar "kvazaar >= 2.0.0" kvazaar.h kvz_api_get
 enabled liblensfun        && require_pkg_config liblensfun lensfun lensfun.h lf_db_create
+enabled liblcevc_dec      && require_pkg_config liblcevc_dec "lcevc_dec >= 2.0.0" "LCEVC/lcevc_dec.h" LCEVC_CreateDecoder
 
 if enabled libmfx && enabled libvpl; then
    die "ERROR: can not use libmfx and libvpl together"
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 994d9773ba..57f27a1221 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -359,6 +359,7 @@  OBJS-$(CONFIG_INTERLEAVE_FILTER)             += f_interleave.o
 OBJS-$(CONFIG_KERNDEINT_FILTER)              += vf_kerndeint.o
 OBJS-$(CONFIG_KIRSCH_FILTER)                 += vf_convolution.o
 OBJS-$(CONFIG_LAGFUN_FILTER)                 += vf_lagfun.o
+OBJS-$(CONFIG_LCEVC_FILTER)                  += vf_lcevc.o
 OBJS-$(CONFIG_LATENCY_FILTER)                += f_latency.o
 OBJS-$(CONFIG_LENSCORRECTION_FILTER)         += vf_lenscorrection.o
 OBJS-$(CONFIG_LENSFUN_FILTER)                += vf_lensfun.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 149bf50997..54d6ea1043 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -337,6 +337,7 @@  extern const AVFilter ff_vf_kerndeint;
 extern const AVFilter ff_vf_kirsch;
 extern const AVFilter ff_vf_lagfun;
 extern const AVFilter ff_vf_latency;
+extern const AVFilter ff_vf_lcevc;
 extern const AVFilter ff_vf_lenscorrection;
 extern const AVFilter ff_vf_lensfun;
 extern const AVFilter ff_vf_libplacebo;
diff --git a/libavfilter/vf_lcevc.c b/libavfilter/vf_lcevc.c
new file mode 100644
index 0000000000..e18f0945f9
--- /dev/null
+++ b/libavfilter/vf_lcevc.c
@@ -0,0 +1,466 @@ 
+/*
+ * This file is part of FFmpeg.
+ *
+ * Copyright (c) 2024 James Almer <jamrial@gmail.com>
+ *
+ * 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 <stdint.h>
+
+#include <LCEVC/lcevc_dec.h>
+
+#include "libavutil/internal.h"
+#include "libavutil/opt.h"
+#include "filters.h"
+#include "internal.h"
+#include "video.h"
+
+typedef struct LCEVCContext {
+    LCEVC_DecoderHandle decoder;
+    LCEVC_PictureHandle base;
+    AVFrame *in;
+    int w, h;
+    int status;
+} LCEVCContext;
+
+static LCEVC_ColorFormat map_format(int format)
+{
+    switch (format) {
+    case AV_PIX_FMT_YUV420P:
+        return LCEVC_I420_8;
+    case AV_PIX_FMT_YUV420P10LE:
+        return LCEVC_I420_10_LE;
+    case AV_PIX_FMT_YUV420P12LE:
+        return LCEVC_I420_12_LE;
+    case AV_PIX_FMT_NV12:
+        return LCEVC_NV12_8;
+    case AV_PIX_FMT_NV21:
+        return LCEVC_NV21_8;
+    case AV_PIX_FMT_GRAY8:
+        return LCEVC_GRAY_8;
+    case AV_PIX_FMT_GRAY10LE:
+        return LCEVC_GRAY_10_LE;
+    case AV_PIX_FMT_GRAY12LE:
+        return LCEVC_GRAY_12_LE;
+    case AV_PIX_FMT_RGB24:
+        return LCEVC_RGB_8;
+    case AV_PIX_FMT_BGR24:
+        return LCEVC_BGR_8;
+    case AV_PIX_FMT_RGBA:
+        return LCEVC_RGBA_8;
+    case AV_PIX_FMT_ARGB:
+        return LCEVC_ARGB_8;
+    case AV_PIX_FMT_ABGR:
+        return LCEVC_ABGR_8;
+    case AV_PIX_FMT_BGRA:
+        return LCEVC_BGRA_8;
+    }
+
+    return LCEVC_ColorFormat_Unknown;
+}
+
+static inline LCEVC_ColorRange map_range(int range)
+{
+    switch (range) {
+    case AVCOL_RANGE_MPEG:
+        return LCEVC_ColorRange_Limited;
+    case AVCOL_RANGE_JPEG:
+        return LCEVC_ColorRange_Full;
+    }
+
+    return LCEVC_ColorRange_Unknown;
+}
+
+static inline enum AVColorRange map_av_range(int range)
+{
+    switch (range) {
+    case LCEVC_ColorRange_Limited:
+        return AVCOL_RANGE_MPEG;
+    case LCEVC_ColorRange_Full:
+        return AVCOL_RANGE_JPEG;
+    }
+
+    return AVCOL_RANGE_UNSPECIFIED;
+}
+
+static int alloc_base_frame(AVFilterLink *inlink, const AVFrame *in,
+                            LCEVC_PictureHandle *picture)
+{
+    AVFilterContext *ctx = inlink->dst;
+    LCEVCContext *lcevc = ctx->priv;
+    LCEVC_PictureDesc desc;
+    LCEVC_PicturePlaneDesc planes[AV_VIDEO_MAX_PLANES] = { 0 };
+    LCEVC_ColorFormat fmt = map_format(in->format);
+    int width = in->width - in->crop_left - in->crop_right;
+    int height = in->height - in->crop_top - in->crop_bottom;
+    LCEVC_ReturnCode res;
+
+    res = LCEVC_DefaultPictureDesc(&desc, fmt, width, height);
+    if (res != LCEVC_Success) {
+        av_log(ctx, AV_LOG_ERROR, "LCEVC_DefaultPictureDesc failed\n");
+		return AVERROR_EXTERNAL;
+    }
+
+    for (int i = 0; i < AV_VIDEO_MAX_PLANES; i++) {
+        planes[i].firstSample = in->data[i];
+        planes[i].rowByteStride = in->linesize[i];
+    }
+
+    desc.cropTop    = in->crop_top;
+    desc.cropBottom = in->crop_bottom;
+    desc.cropLeft   = in->crop_left;
+    desc.cropRight  = in->crop_right;
+    desc.sampleAspectRatioNum = in->sample_aspect_ratio.num;
+    desc.sampleAspectRatioDen = in->sample_aspect_ratio.den;
+    desc.colorRange = map_range(in->color_range);
+    desc.colorPrimaries = (LCEVC_ColorPrimaries)in->color_primaries;
+    desc.matrixCoefficients = (LCEVC_MatrixCoefficients)in->colorspace;
+    desc.transferCharacteristics = (LCEVC_TransferCharacteristics)in->color_trc;
+    av_log(ctx, AV_LOG_DEBUG, "in  PTS %"PRId64", %dx%d, "
+                              "%"SIZE_SPECIFIER"/%"SIZE_SPECIFIER"/%"SIZE_SPECIFIER"/%"SIZE_SPECIFIER", "
+                              "SAR %d:%d\n",
+           in->pts, in->width, in->height,
+           in->crop_top, in->crop_bottom, in->crop_left, in->crop_right,
+           in->sample_aspect_ratio.num, in->sample_aspect_ratio.den);
+
+    res = LCEVC_AllocPictureExternal(lcevc->decoder, &desc, NULL, planes, picture);
+    if (res != LCEVC_Success) {
+        av_log(ctx, AV_LOG_ERROR, "LCEVC_AllocPictureExternal to allocate a buffer for a base frame\n");
+        return AVERROR_EXTERNAL;
+    }
+
+    return 0;
+}
+
+static int send_frame(AVFilterLink *inlink)
+{
+    AVFilterContext *ctx = inlink->dst;
+    LCEVCContext *lcevc = ctx->priv;
+    const AVFrameSideData *sd = av_frame_get_side_data(lcevc->in, AV_FRAME_DATA_LCEVC);
+    LCEVC_ReturnCode res;
+
+    // lcevc->base will be set only if a previous LCEVC_SendDecoderBase() call failed with LCEVC_Again,
+    // in which case we'll try sending the picture again after having fetched at least one enhanced frame.
+    if (!lcevc->base.hdl) {
+        int ret = alloc_base_frame(inlink, lcevc->in, &lcevc->base);
+        if (ret < 0)
+            return ret;
+    }
+
+    if (sd) {
+        res = LCEVC_SendDecoderEnhancementData(lcevc->decoder, lcevc->in->pts, 0, sd->data, sd->size);
+        if (res == LCEVC_Again)
+            return AVERROR(EAGAIN);
+        else if (res != LCEVC_Success) {
+            av_log(ctx, AV_LOG_ERROR, "LCEVC_SendDecoderEnhancementData failed\n");
+            return AVERROR_EXTERNAL;
+        }
+    }
+
+    res = LCEVC_SendDecoderBase(lcevc->decoder, lcevc->in->pts, 0, lcevc->base, -1, lcevc->in);
+    if (res == LCEVC_Success) {
+        lcevc->in = NULL;
+        memset(&lcevc->base, 0, sizeof(lcevc->base));
+    } else if (res == LCEVC_Again)
+        return AVERROR(EAGAIN);
+    else {
+        av_log(ctx, AV_LOG_ERROR, "LCEVC_SendDecoderBase failed\n");
+        return AVERROR_EXTERNAL;
+    }
+
+    return 0;
+}
+
+static int alloc_enhanced_frame(AVFilterLink *inlink, const AVFrame *out,
+                                LCEVC_PictureHandle *picture)
+{
+    AVFilterContext *ctx = inlink->dst;
+    LCEVCContext *lcevc = ctx->priv;
+    LCEVC_PictureDesc desc;
+    LCEVC_PicturePlaneDesc planes[AV_VIDEO_MAX_PLANES] = { 0 };
+    LCEVC_ColorFormat fmt = map_format(out->format);
+    LCEVC_ReturnCode res;
+
+    res = LCEVC_DefaultPictureDesc(&desc, fmt, out->width, out->height);
+    if (res != LCEVC_Success)
+        return AVERROR_EXTERNAL;
+
+    for (int i = 0; i < AV_VIDEO_MAX_PLANES; i++) {
+        planes[i].firstSample = out->data[i];
+        planes[i].rowByteStride = out->linesize[i];
+    }
+
+    res = LCEVC_AllocPictureExternal(lcevc->decoder, &desc, NULL, planes, picture);
+    if (res != LCEVC_Success) {
+        av_log(ctx, AV_LOG_ERROR, "LCEVC_AllocPictureExternal to allocate a buffer for an enhanced frame\n");
+        return AVERROR_EXTERNAL;
+    }
+
+    return 0;
+}
+
+static int generate_output(AVFilterLink *inlink, AVFrame *out)
+{
+    AVFilterContext *ctx = inlink->dst;
+    AVFilterLink *outlink = ctx->outputs[0];
+    LCEVCContext *lcevc = ctx->priv;
+    LCEVC_PictureDesc desc;
+    LCEVC_DecodeInformation info;
+    LCEVC_PictureHandle picture;
+    LCEVC_ReturnCode res;
+
+    res = LCEVC_ReceiveDecoderPicture(lcevc->decoder, &picture, &info);
+    if (res == LCEVC_Again) {
+        int64_t pts;
+        if (ff_inlink_acknowledge_status(inlink, &lcevc->status, &pts)) {
+            av_frame_free(&out);
+            ff_outlink_set_status(outlink, lcevc->status, pts);
+            return 0;
+        }
+        // this shouldn't be reachable, but instead of asserting, just error out
+        return AVERROR_EXTERNAL;
+    } else if (res != LCEVC_Success) {
+        av_log(ctx, AV_LOG_ERROR, "LCEVC_ReceiveDecoderPicture failed\n");
+        return AVERROR_EXTERNAL;
+    }
+
+    av_frame_copy_props(out, (AVFrame *)info.baseUserData);
+    av_frame_remove_side_data(out, AV_FRAME_DATA_LCEVC);
+
+    av_frame_free((AVFrame **)&info.baseUserData);
+
+    res = LCEVC_GetPictureDesc(lcevc->decoder, picture, &desc);
+    if (res != LCEVC_Success) {
+        av_log(ctx, AV_LOG_ERROR, "LCEVC_GetPictureDesc failed\n");
+        return AVERROR_EXTERNAL;
+    }
+    res = LCEVC_FreePicture(lcevc->decoder, picture);
+    if (res != LCEVC_Success) {
+        av_log(ctx, AV_LOG_ERROR, "LCEVC_FreePicture enhanced failed\n");
+        return AVERROR_EXTERNAL;
+    }
+
+    out->crop_top = desc.cropTop;
+    out->crop_bottom = desc.cropBottom;
+    out->crop_left = desc.cropLeft;
+    out->crop_right = desc.cropRight;
+    out->sample_aspect_ratio.num = outlink->sample_aspect_ratio.num = desc.sampleAspectRatioNum;
+    out->sample_aspect_ratio.den = outlink->sample_aspect_ratio.den = desc.sampleAspectRatioDen;
+    out->color_range = map_range(desc.colorRange);
+    out->color_primaries = (enum AVColorPrimaries)desc.colorPrimaries;
+    out->colorspace = (enum AVColorSpace)desc.matrixCoefficients;
+    out->color_trc = (enum AVColorTransferCharacteristic)desc.transferCharacteristics;
+    out->width = outlink->w = desc.width + out->crop_left + out->crop_right;
+    out->height = outlink->h = desc.height + out->crop_top + out->crop_bottom;
+
+    av_log(ctx, AV_LOG_DEBUG, "out PTS %"PRId64", %dx%d, "
+                              "%"SIZE_SPECIFIER"/%"SIZE_SPECIFIER"/%"SIZE_SPECIFIER"/%"SIZE_SPECIFIER", "
+                              "SAR %d:%d, "
+                              "hasEnhancement %d, enhanced %d\n",
+           out->pts, out->width, out->height,
+           out->crop_top, out->crop_bottom, out->crop_left, out->crop_right,
+           out->sample_aspect_ratio.num, out->sample_aspect_ratio.den,
+           info.hasEnhancement, info.enhanced);
+
+    return ff_filter_frame(outlink, out);
+}
+
+static int receive_frame(AVFilterLink *inlink, AVFrame *out)
+{
+    AVFilterContext *ctx = inlink->dst;
+    LCEVCContext *lcevc = ctx->priv;
+    LCEVC_PictureHandle picture;
+    LCEVC_ReturnCode res;
+    int ret;
+
+    ret = alloc_enhanced_frame(inlink, out, &picture);
+    if (ret < 0)
+        return ret;
+
+    res = LCEVC_SendDecoderPicture(lcevc->decoder, picture);
+    if (res != LCEVC_Success) {
+        av_log(ctx, AV_LOG_ERROR, "LCEVC_SendDecoderPicture failed\n");
+        return AVERROR_EXTERNAL;
+    }
+
+    return generate_output(inlink, out);
+}
+
+static int config_props(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    AVFilterLink *inlink = ctx->inputs[0];
+    LCEVCContext *lcevc = ctx->priv;
+
+    outlink->w = lcevc->w = inlink->w * 2 / FFMAX(inlink->sample_aspect_ratio.den, 1);
+    outlink->h = lcevc->h = inlink->h * 2 / FFMAX(inlink->sample_aspect_ratio.den, 1);
+    outlink->sample_aspect_ratio = (AVRational) { 0, 1 };
+
+    return 0;
+}
+
+static void flush_bases(AVFilterContext *ctx)
+{
+    LCEVCContext *lcevc = ctx->priv;
+    LCEVC_PictureHandle picture;
+
+    while (LCEVC_ReceiveDecoderBase(lcevc->decoder, &picture) == LCEVC_Success)
+        LCEVC_FreePicture(lcevc->decoder, picture);
+}
+
+static int activate(AVFilterContext *ctx)
+{
+    LCEVCContext *lcevc   = ctx->priv;
+    AVFilterLink *inlink  = ctx->inputs[0];
+    AVFilterLink *outlink = ctx->outputs[0];
+    AVFrame *out;
+    int ret;
+
+    FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink);
+
+    if (!lcevc->in) {
+        ret = ff_inlink_consume_frame(inlink, &lcevc->in);
+        if (ret < 0)
+            return ret;
+        if (!ret) {
+            int64_t pts;
+            if (ff_inlink_acknowledge_status(inlink, &lcevc->status, &pts)) {
+                if (!lcevc->status)
+                    ff_outlink_set_status(outlink, lcevc->status, pts);
+            }
+            if (!lcevc->status)
+                FF_FILTER_FORWARD_WANTED(outlink, inlink);
+        }
+    }
+
+    if (lcevc->in) {
+       if (lcevc->in->width  != inlink->w ||
+            lcevc->in->height != inlink->h ||
+            lcevc->in->sample_aspect_ratio.den != inlink->sample_aspect_ratio.den ||
+            lcevc->in->sample_aspect_ratio.num != inlink->sample_aspect_ratio.num) {
+            inlink->dst->inputs[0]->w                       = lcevc->in->width;
+            inlink->dst->inputs[0]->h                       = lcevc->in->height;
+            inlink->dst->inputs[0]->sample_aspect_ratio.den = lcevc->in->sample_aspect_ratio.den;
+            inlink->dst->inputs[0]->sample_aspect_ratio.num = lcevc->in->sample_aspect_ratio.num;
+
+            config_props(outlink);
+        }
+
+        ret = send_frame(inlink);
+        // Buffer as many base frames as the decoder allows
+        if (!ret) {
+            FF_FILTER_FORWARD_WANTED(outlink, inlink);
+            return FFERROR_NOT_READY;
+        } else if (ret < 0 && ret != AVERROR(EAGAIN))
+            return ret;
+    }
+
+    out = ff_get_video_buffer(outlink, lcevc->w, lcevc->h);
+    if (!out)
+        return AVERROR(ENOMEM);
+
+    ret = receive_frame(inlink, out);
+    if (ret < 0) {
+        av_frame_free(&out);
+        return ret;
+    }
+
+    flush_bases(ctx);
+
+    return ret;
+}
+
+static void log_callback(LCEVC_DecoderHandle dec, LCEVC_Event event,
+                         LCEVC_PictureHandle pic, const LCEVC_DecodeInformation *info,
+                         const uint8_t *data, uint32_t size, void *logctx)
+{
+    if (event != LCEVC_Log) // shouldn't happen
+        return;
+
+    if (strlen(data) != size) // sanitize input
+        return;
+
+    av_log(logctx, AV_LOG_INFO, "LCEVC Log: %s\n", data);
+}
+
+static av_cold int init(AVFilterContext *ctx)
+{
+    LCEVCContext *lcevc = ctx->priv;
+    LCEVC_AccelContextHandle dummy = { 0 };
+    const int32_t event = LCEVC_Log;
+    LCEVC_ReturnCode res;
+
+    res = LCEVC_CreateDecoder(&lcevc->decoder, dummy);
+    if (res != LCEVC_Success) {
+        av_log(ctx, AV_LOG_ERROR, "LCEVC_CreateDecoder failed\n");
+        return AVERROR_EXTERNAL;
+    }
+
+    res = LCEVC_ConfigureDecoderIntArray(lcevc->decoder, "events", 1, &event);
+    if (res != LCEVC_Success) {
+        av_log(ctx, AV_LOG_ERROR, "LCEVC_ConfigureDecoderIntArray failed to set \"events\"\n");
+        return AVERROR_EXTERNAL;
+    }
+    res = LCEVC_SetDecoderEventCallback(lcevc->decoder, log_callback, ctx);
+    if (res != LCEVC_Success) {
+        av_log(ctx, AV_LOG_ERROR, "LCEVC_SetDecoderEventCallback failed\n");
+        return AVERROR_EXTERNAL;
+    }
+
+    res = LCEVC_InitializeDecoder(lcevc->decoder);
+    if (res != LCEVC_Success) {
+        av_log(ctx, AV_LOG_ERROR, "LCEVC_InitializeDecoder failed\n");
+        return AVERROR_EXTERNAL;
+    }
+
+    return 0;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    LCEVCContext *lcevc = ctx->priv;
+
+    LCEVC_DestroyDecoder(lcevc->decoder);
+}
+
+static const AVFilterPad lcevc_outputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .config_props = config_props,
+    },
+};
+
+static const enum AVPixelFormat pix_fmts[] = {
+    AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10LE, AV_PIX_FMT_YUV420P12LE,
+    AV_PIX_FMT_NV12, AV_PIX_FMT_NV21,
+    AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY10LE, AV_PIX_FMT_GRAY12LE,
+    AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24,
+    AV_PIX_FMT_RGBA, AV_PIX_FMT_ARGB, AV_PIX_FMT_ABGR, AV_PIX_FMT_BGRA,
+    AV_PIX_FMT_NONE
+};
+
+const AVFilter ff_vf_lcevc = {
+    .name          = "lcevc",
+    .description   = NULL_IF_CONFIG_SMALL("LCEVC"),
+    .activate      = activate,
+    FILTER_INPUTS(ff_video_default_filterpad),
+    FILTER_OUTPUTS(lcevc_outputs),
+    FILTER_PIXFMTS_ARRAY(pix_fmts),
+    .priv_size     = sizeof(LCEVCContext),
+    .init          = init,
+    .uninit        = uninit,
+};