lavfi/vf_tiltandshift: stop (ab)using AVFrame.opaque

This filter uses AVFrame.opaque to build a linked list of AVFrames. This
is very wrong, as AVFrame.opaque is intended to store caller's private
data and may not be touched by filters. What's worse, the filter leaks
the opaque values to the outside.

Use an AVFifo instead of a linked list to implement the same logic.
This commit is contained in:
Anton Khirnov
2025-11-12 09:56:49 +01:00
committed by James Almer
parent 6ed6815b46
commit dba8c62400

View File

@@ -25,6 +25,8 @@
#include <string.h> #include <string.h>
#include "libavutil/avassert.h"
#include "libavutil/fifo.h"
#include "libavutil/imgutils.h" #include "libavutil/imgutils.h"
#include "libavutil/mem.h" #include "libavutil/mem.h"
#include "libavutil/opt.h" #include "libavutil/opt.h"
@@ -63,38 +65,13 @@ typedef struct TiltandshiftContext {
uint8_t *black_buffers[4]; uint8_t *black_buffers[4];
int black_linesizes[4]; int black_linesizes[4];
/* list containing all input frames */ /* FIFO containing all input frames */
size_t input_size; AVFifo *input;
AVFrame *input;
AVFrame *prev; AVFrame *prev;
const AVPixFmtDescriptor *desc; const AVPixFmtDescriptor *desc;
} TiltandshiftContext; } TiltandshiftContext;
static int list_add_frame(TiltandshiftContext *s, AVFrame *frame)
{
if (s->input == NULL) {
s->input = frame;
} else {
AVFrame *head = s->input;
while (head->opaque)
head = head->opaque;
head->opaque = frame;
}
s->input_size++;
return 0;
}
static void list_remove_head(TiltandshiftContext *s)
{
AVFrame *head = s->input;
if (head) {
s->input = head->opaque;
av_frame_free(&head);
}
s->input_size--;
}
static const enum AVPixelFormat pix_fmts[] = { static const enum AVPixelFormat pix_fmts[] = {
AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV444P,
AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV410P,
@@ -103,11 +80,30 @@ static const enum AVPixelFormat pix_fmts[] = {
AV_PIX_FMT_NONE AV_PIX_FMT_NONE
}; };
static av_cold int init(AVFilterContext *ctx)
{
TiltandshiftContext *s = ctx->priv;
s->input = av_fifo_alloc2(32, sizeof(AVFrame*), AV_FIFO_FLAG_AUTO_GROW);
if (!s->input)
return AVERROR(ENOMEM);
return 0;
}
static av_cold void uninit(AVFilterContext *ctx) static av_cold void uninit(AVFilterContext *ctx)
{ {
TiltandshiftContext *s = ctx->priv; TiltandshiftContext *s = ctx->priv;
while (s->input)
list_remove_head(s); if (s->input) {
AVFrame *frame;
while (av_fifo_read(s->input, &frame, 1) >= 0)
av_frame_free(&frame);
av_fifo_freep2(&s->input);
}
av_freep(&s->black_buffers); av_freep(&s->black_buffers);
} }
@@ -192,6 +188,7 @@ static int output_frame(AVFilterLink *outlink)
TiltandshiftContext *s = outlink->src->priv; TiltandshiftContext *s = outlink->src->priv;
AVFrame *head; AVFrame *head;
int ret; int ret;
int nb_buffered;
int ncol = 0; int ncol = 0;
AVFrame *dst = ff_get_video_buffer(outlink, outlink->w, outlink->h); AVFrame *dst = ff_get_video_buffer(outlink, outlink->w, outlink->h);
@@ -206,20 +203,23 @@ static int output_frame(AVFilterLink *outlink)
ncol, 0); ncol, 0);
} }
head = s->input; nb_buffered = av_fifo_can_read(s->input);
av_assert0(nb_buffered);
// copy a column from each input frame // copy a column from each input frame
for ( ; ncol < s->input_size; ncol++) { for (int in_idx = 0; ncol < nb_buffered; ncol++) {
AVFrame *src = head; AVFrame *src;
av_fifo_peek(s->input, &src, 1, in_idx);
copy_column(outlink, dst->data, dst->linesize, copy_column(outlink, dst->data, dst->linesize,
(const uint8_t **)src->data, src->linesize, (const uint8_t **)src->data, src->linesize,
ncol, s->tilt); ncol, s->tilt);
// keep track of the last known frame in case we need it below // keep track of the last known frame in case we need it below
s->prev = head; s->prev = src;
// advance to the next frame unless we have to hold it // advance to the next frame unless we have to hold it
if (s->hold <= ncol) if (s->hold <= ncol)
head = head->opaque; in_idx++;
} }
// pad any remaining space with black or last frame // pad any remaining space with black or last frame
@@ -236,14 +236,14 @@ static int output_frame(AVFilterLink *outlink)
} }
// set correct timestamps and props as long as there is proper input // set correct timestamps and props as long as there is proper input
ret = av_frame_copy_props(dst, s->input); av_fifo_read(s->input, &head, 1);
ret = av_frame_copy_props(dst, head);
av_frame_free(&head);
if (ret < 0) { if (ret < 0) {
av_frame_free(&dst); av_frame_free(&dst);
return ret; return ret;
} }
// discard frame at the top of the list since it has been fully processed
list_remove_head(s);
// and it is safe to reduce the hold value (even if unused) // and it is safe to reduce the hold value (even if unused)
s->hold--; s->hold--;
@@ -257,16 +257,21 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
AVFilterLink *outlink = inlink->dst->outputs[0]; AVFilterLink *outlink = inlink->dst->outputs[0];
AVFilterContext *ctx = outlink->src; AVFilterContext *ctx = outlink->src;
TiltandshiftContext *s = inlink->dst->priv; TiltandshiftContext *s = inlink->dst->priv;
int ret;
int ret = list_add_frame(s, frame); ret = av_fifo_write(s->input, &frame, 1);
if (ret < 0) { if (ret < 0) {
av_frame_free(&frame);
return ret; return ret;
} }
// load up enough frames to fill a frame and keep the queue filled on subsequent // load up enough frames to fill a frame and keep the queue filled on subsequent
// calls, until we receive EOF, and then we either pad or end // calls, until we receive EOF, and then we either pad or end
if (!s->eof_recv && s->input_size < outlink->w - s->pad) { if (!s->eof_recv &&
av_log(ctx, AV_LOG_DEBUG, "Not enough frames in the list (%zu/%d), waiting for more.\n", s->input_size, outlink->w - s->pad); av_fifo_can_read(s->input) < outlink->w - s->pad) {
av_log(ctx, AV_LOG_DEBUG,
"Not enough frames in the list (%zu/%d), waiting for more.\n",
av_fifo_can_read(s->input), outlink->w - s->pad);
return 0; return 0;
} }
@@ -277,11 +282,13 @@ static int request_frame(AVFilterLink *outlink)
{ {
AVFilterContext *ctx = outlink->src; AVFilterContext *ctx = outlink->src;
TiltandshiftContext *s = ctx->priv; TiltandshiftContext *s = ctx->priv;
size_t nb_buffered = av_fifo_can_read(s->input);
int ret; int ret;
// signal job finished when list is empty or when padding is either // signal job finished when list is empty or when padding is either
// limited or disabled and eof was received // limited or disabled and eof was received
if ((s->input_size <= 0 || s->input_size == outlink->w - s->pad || s->end == TILT_NONE) && s->eof_recv) { if ((nb_buffered <= 0 || nb_buffered == outlink->w - s->pad ||
s->end == TILT_NONE) && s->eof_recv) {
return AVERROR_EOF; return AVERROR_EOF;
} }
@@ -293,8 +300,9 @@ static int request_frame(AVFilterLink *outlink)
} }
if (s->eof_recv) { if (s->eof_recv) {
while (s->input_size) { while (av_fifo_can_read(s->input)) {
av_log(ctx, AV_LOG_DEBUG, "Emptying buffers (%zu/%d).\n", s->input_size, outlink->w - s->pad); av_log(ctx, AV_LOG_DEBUG, "Emptying buffers (%zu/%d).\n",
av_fifo_can_read(s->input), outlink->w - s->pad);
ret = output_frame(outlink); ret = output_frame(outlink);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
@@ -361,6 +369,7 @@ const FFFilter ff_vf_tiltandshift = {
.p.description = NULL_IF_CONFIG_SMALL("Generate a tilt-and-shift'd video."), .p.description = NULL_IF_CONFIG_SMALL("Generate a tilt-and-shift'd video."),
.p.priv_class = &tiltandshift_class, .p.priv_class = &tiltandshift_class,
.priv_size = sizeof(TiltandshiftContext), .priv_size = sizeof(TiltandshiftContext),
.init = init,
.uninit = uninit, .uninit = uninit,
FILTER_INPUTS(tiltandshift_inputs), FILTER_INPUTS(tiltandshift_inputs),
FILTER_OUTPUTS(tiltandshift_outputs), FILTER_OUTPUTS(tiltandshift_outputs),