From patchwork Thu Sep 12 17:07:53 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Almer X-Patchwork-Id: 51553 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:612c:14c:b0:48e:c0f8:d0de with SMTP id h12csp1229709vqi; Thu, 12 Sep 2024 16:04:11 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCVzT3naIAuCKThdZmxQNYhkMbqejV8tSWLC4XiTV7la2f8wM2VPpgGp0aV4rZ57NofAMCE6ZD/WVXy3VKInZ1YI@gmail.com X-Google-Smtp-Source: AGHT+IFh7SE6DME1aPquzeuRndF69X/L7ChzOvnwFntfgjwmuezUJVPu2AqG+dL2CK/UUWOk3rZY X-Received: by 2002:a05:6512:224f:b0:52f:c13f:23d2 with SMTP id 2adb3069b0e04-53678fbf051mr2951372e87.25.1726182251416; Thu, 12 Sep 2024 16:04:11 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1726182251; cv=none; d=google.com; s=arc-20240605; b=k8s5/lEz8Vn81FGlD+buiKzLYfHA83I7zQE9XaaALVhamzCdPMbnl3wYeckzh6Ypuh cfslVueUjQ+1lnFy99+2NEgNGNOHNrwe2d8F67u6xaJPTYorxewwmcMSVq0AM6/d9HLN sxD3WMxtL7sg/YFiCm5nY6LBr1Zz6VEMXNMwq66AkA2aIPEigDd5w9Bv3+bbHcP/qfCE GuV7L74YQ4DJOSi+PEU3jTNDZYez/NtNVzjdUTqwxYM8jAr2PgzEwG98YDzoMPbXtjcl mUmLsl7pn9AYTxoaUBhTEL5qsFvS15BOCOsrWcqKxcmCtVa321b721e/CiBhrQ149yDw JTvQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:references:in-reply-to:message-id :date:to:from:dkim-signature:delivered-to; bh=0m3QMFtefE2QEDm1mNfJN1Kc8tIlSNQDm5lEWZALkm8=; fh=YOA8vD9MJZuwZ71F/05pj6KdCjf6jQRmzLS+CATXUQk=; b=hsdqErIcKTlHc1ELUXHAjyDUzcjKVwrRyVTEQ2VKPtc+GBoumfsnVJMFmeMApTjMaf BOXQJbaXSS3npbwJMYSfRc9a/KZJa5GmBQw0D5tDEQh4xJJjaiB4YPcBbq4BXjdlybU7 Cfijqmw0gnSbED8cTD5gFmlfUTWlkK3PnOW2wkslqehsxKJtx9jKge7K+FPwZSZSqFTB yponzJZoXadaMKYnR3AKedurSoRG3LJnfxhYRJ4e0eJcCL6IpJvNNLXUn6m17u+C+0ZX urDGYtR6QnJ9QNbFvwD3Wk1MzfFUVLUeVjRGWY569sTWua+N9FXOLXsmK5vuhZXhgHGO x3uw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20230601 header.b=AFnH6dvO; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com; dara=fail header.i=@gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id 2adb3069b0e04-5365f86ecd8si4249778e87.152.2024.09.12.16.04.10; Thu, 12 Sep 2024 16:04:11 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20230601 header.b=AFnH6dvO; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com; dara=fail header.i=@gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 444B668D90F; Thu, 12 Sep 2024 20:08:24 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-pl1-f170.google.com (mail-pl1-f170.google.com [209.85.214.170]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 1B39468D90F for ; Thu, 12 Sep 2024 20:08:17 +0300 (EEST) Received: by mail-pl1-f170.google.com with SMTP id d9443c01a7336-20573eb852aso15853795ad.1 for ; Thu, 12 Sep 2024 10:08:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1726160894; x=1726765694; darn=ffmpeg.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to; bh=Ye2R+UR32u9bpw4QvVcBcZeM2MbezvhiJk8pWWleon8=; b=AFnH6dvO91vhIPukUlnyxuABaWUlZ0i8LAd7VLkPmi+8qbLR15QSyUJKn91MsdQQAH 4c3ds0bom20IFfRfmNB0SERAoHcjAEfWWS7ycZDNU2SL2mUGLbhY1aDOPO/MRYI50oqR ymHNqSbgBLqm7uoVfMRQHcb/iFv5D2meVs1pDbqJBfMMFEuCQYw7+SHzUf85Rs4zA1yW AOnDCKw/x4rWBhu6qioh7kTaSijdMomngb3Ehl1zLG2tx9CN7MUCf3QDnTBnQ5FpYtRj ke/0sA8ulkJSlRZ6Mvg9xi2iq0IfEaiPE0P+mRrYCvn0bIKORtCFv5+T31al3I2EXrp0 OFMA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1726160894; x=1726765694; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Ye2R+UR32u9bpw4QvVcBcZeM2MbezvhiJk8pWWleon8=; b=SlWcJ26OjtvQTd7zYHIlkn24qdydLQfbEsUNIdh4AW8jNu2G2bpaQSn8qPqVOMXdro wMHIi1N06E47whOWKFNJrOb4fSo3eGVnJrwN/nLfRHrRrC3Cjz9sgN7eggkwU61AKDCY S+oxdZjnPY22baVEuu2XPmhJlswzK6uAjuRFWqWYY0AvAOcxH53IObGZSt12Ih3DSvwe BrWyKaPzdzc3otBp0LqYBnp7hEslKHlgVdjDYIDkzlrN0DaFtUSWhWCKiGVtF+rmX6c9 aAwMn63TkbE17YEK5EWTyWi6QRA2Tpc78SmX50gcrPwi2mndhPO4m2yiPH+PbZoS+ePA OrlQ== X-Gm-Message-State: AOJu0YyrJx6/8kezDau3M9mj9GFDtz/EW6FFdDWt8CNVad89ZJtW9SfW kbj2OfMrMzneQ+/5w25iST3UdtDfr8NA6KUhUcEfTTDwCNjJbwahGILv+Q== X-Received: by 2002:a17:902:db05:b0:205:5c06:39e6 with SMTP id d9443c01a7336-2074c3b43b4mr136041035ad.0.1726160893650; Thu, 12 Sep 2024 10:08:13 -0700 (PDT) Received: from localhost.localdomain ([181.92.233.116]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2076afe9198sm16379125ad.198.2024.09.12.10.08.12 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 12 Sep 2024 10:08:13 -0700 (PDT) From: James Almer To: ffmpeg-devel@ffmpeg.org Date: Thu, 12 Sep 2024 14:07:53 -0300 Message-ID: <20240912170753.1961-1-jamrial@gmail.com> X-Mailer: git-send-email 2.46.0 In-Reply-To: <20240912005925.10151-1-jamrial@gmail.com> References: <20240912005925.10151-1-jamrial@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 11/11] avfilter: add an LCEVC decoding filter via LCEVCdec X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: TrN3vCQ9vl6F Signed-off-by: James Almer --- configure | 1 + libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/version.h | 4 +- libavfilter/vf_lcevc.c | 434 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 439 insertions(+), 2 deletions(-) create mode 100644 libavfilter/vf_lcevc.c diff --git a/configure b/configure index e528714735..e88f4490b4 100755 --- a/configure +++ b/configure @@ -3891,6 +3891,7 @@ identity_filter_select="scene_sad" 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" diff --git a/libavfilter/Makefile b/libavfilter/Makefile index ac6a8d5783..91487afb21 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -360,6 +360,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 8e7d912c9f..9819f0f95b 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/version.h b/libavfilter/version.h index d8cd8a2cfb..7e0eb9af97 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -31,8 +31,8 @@ #include "version_major.h" -#define LIBAVFILTER_VERSION_MINOR 2 -#define LIBAVFILTER_VERSION_MICRO 102 +#define LIBAVFILTER_VERSION_MINOR 3 +#define LIBAVFILTER_VERSION_MICRO 100 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ diff --git a/libavfilter/vf_lcevc.c b/libavfilter/vf_lcevc.c new file mode 100644 index 0000000000..843692cf40 --- /dev/null +++ b/libavfilter/vf_lcevc.c @@ -0,0 +1,434 @@ +/* + * This file is part of FFmpeg. + * + * Copyright (c) 2024 James Almer + * + * 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 + +#include + +#include "libavutil/internal.h" +#include "libavutil/opt.h" +#include "filters.h" +#include "video.h" + +typedef struct LCEVCContext { + LCEVC_DecoderHandle decoder; + LCEVC_PictureHandle base; + int w, h; +} LCEVCContext; + +static LCEVC_ColorFormat map_format(int format) +{ + switch (format) { + case AV_PIX_FMT_YUV420P: + return LCEVC_I420_8; + case AV_PIX_FMT_YUV420P10: + return LCEVC_I420_10_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; + } + + 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, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + LCEVCContext *lcevc = ctx->priv; + const AVFrameSideData *sd = av_frame_get_side_data(in, AV_FRAME_DATA_LCEVC); + LCEVC_ReturnCode res; + int ret; + + ret = alloc_base_frame(inlink, in, &lcevc->base); + if (ret < 0) + return ret; + + if (sd) { + res = LCEVC_SendDecoderEnhancementData(lcevc->decoder, 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, in->pts, 0, lcevc->base, -1, in); + memset(&lcevc->base, 0, sizeof(lcevc->base)); + if (res != LCEVC_Success) { + 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; + int status; + if (ff_inlink_acknowledge_status(inlink, &status, &pts)) { + av_frame_free(&out); + ff_outlink_set_status(outlink, status, pts); + return 0; + } + // this shouldn't be reachable, but instead of asserting, just error out + return AVERROR_BUG; + } 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); + LCEVC_FreePicture(lcevc->decoder, picture); + if (res != LCEVC_Success) { + av_log(ctx, AV_LOG_ERROR, "LCEVC_GetPictureDesc 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_VERBOSE, "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 *in, *out; + int status, ret; + + FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink); + + ret = ff_inlink_consume_frame(inlink, &in); + if (ret < 0) + return ret; + if (!ret) { + int64_t pts; + if (ff_inlink_acknowledge_status(inlink, &status, &pts)) { + if (!status) + ff_outlink_set_status(outlink, status, pts); + } + if (!status) + FF_FILTER_FORWARD_WANTED(outlink, inlink); + } + + if (in) { + if (in->width != inlink->w || + in->height != inlink->h || + in->sample_aspect_ratio.den != inlink->sample_aspect_ratio.den || + in->sample_aspect_ratio.num != inlink->sample_aspect_ratio.num) { + inlink->dst->inputs[0]->w = in->width; + inlink->dst->inputs[0]->h = in->height; + inlink->dst->inputs[0]->sample_aspect_ratio.den = in->sample_aspect_ratio.den; + inlink->dst->inputs[0]->sample_aspect_ratio.num = in->sample_aspect_ratio.num; + + config_props(outlink); + } + + ret = send_frame(inlink, in); + if (ret < 0) + 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_ConfigureDecoderInt(lcevc->decoder, "log_level", 4); + if (res != LCEVC_Success) { + av_log(ctx, AV_LOG_ERROR, "LCEVC_ConfigureDecoderInt failed to set \"log_level\"\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_NV12, AV_PIX_FMT_NV21, + AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY10LE, + 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, +};