1
0
mirror of https://github.com/FFmpeg/FFmpeg.git synced 2024-11-26 19:01:44 +02:00
FFmpeg/libavfilter/vf_pullup.c
Andreas Rheinhardt b4f5201967 avfilter: Replace query_formats callback with union of list and callback
If one looks at the many query_formats callbacks in existence,
one will immediately recognize that there is one type of default
callback for video and a slightly different default callback for
audio: It is "return ff_set_common_formats_from_list(ctx, pix_fmts);"
for video with a filter-specific pix_fmts list. For audio, it is
the same with a filter-specific sample_fmts list together with
ff_set_common_all_samplerates() and ff_set_common_all_channel_counts().

This commit allows to remove the boilerplate query_formats callbacks
by replacing said callback with a union consisting the old callback
and pointers for pixel and sample format arrays. For the not uncommon
case in which these lists only contain a single entry (besides the
sentinel) enum AVPixelFormat and enum AVSampleFormat fields are also
added to the union to store them directly in the AVFilter,
thereby avoiding a relocation.

The state of said union will be contained in a new, dedicated AVFilter
field (the nb_inputs and nb_outputs fields have been shrunk to uint8_t
in order to create a hole for this new field; this is no problem, as
the maximum of all the nb_inputs is four; for nb_outputs it is only
two).

The state's default value coincides with the earlier default of
query_formats being unset, namely that the filter accepts all formats
(and also sample rates and channel counts/layouts for audio)
provided that these properties agree coincide for all inputs and
outputs.

By using different union members for audio and video filters
the type-unsafety of using the same functions for audio and video
lists will furthermore be more confined to formats.c than before.

When the new fields are used, they will also avoid allocations:
Currently something nearly equivalent to ff_default_query_formats()
is called after every successful call to a query_formats callback;
yet in the common case that the newly allocated AVFilterFormats
are not used at all (namely if there are no free links) these newly
allocated AVFilterFormats are freed again without ever being used.
Filters no longer using the callback will not exhibit this any more.

Reviewed-by: Paul B Mahol <onemda@gmail.com>
Reviewed-by: Nicolas George <george@nsup.org>
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
2021-10-05 17:48:25 +02:00

772 lines
20 KiB
C

/*
* Copyright (c) 2003 Rich Felker
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU 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 "libavutil/avassert.h"
#include "libavutil/imgutils.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "avfilter.h"
#include "formats.h"
#include "internal.h"
#include "video.h"
#include "vf_pullup.h"
#define F_HAVE_BREAKS 1
#define F_HAVE_AFFINITY 2
#define BREAK_LEFT 1
#define BREAK_RIGHT 2
#define OFFSET(x) offsetof(PullupContext, x)
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
static const AVOption pullup_options[] = {
{ "jl", "set left junk size", OFFSET(junk_left), AV_OPT_TYPE_INT, {.i64=1}, 0, INT_MAX, FLAGS },
{ "jr", "set right junk size", OFFSET(junk_right), AV_OPT_TYPE_INT, {.i64=1}, 0, INT_MAX, FLAGS },
{ "jt", "set top junk size", OFFSET(junk_top), AV_OPT_TYPE_INT, {.i64=4}, 1, INT_MAX, FLAGS },
{ "jb", "set bottom junk size", OFFSET(junk_bottom), AV_OPT_TYPE_INT, {.i64=4}, 1, INT_MAX, FLAGS },
{ "sb", "set strict breaks", OFFSET(strict_breaks), AV_OPT_TYPE_BOOL,{.i64=0},-1, 1, FLAGS },
{ "mp", "set metric plane", OFFSET(metric_plane), AV_OPT_TYPE_INT, {.i64=0}, 0, 2, FLAGS, "mp" },
{ "y", "luma", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "mp" },
{ "u", "chroma blue", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "mp" },
{ "v", "chroma red", 0, AV_OPT_TYPE_CONST, {.i64=2}, 0, 0, FLAGS, "mp" },
{ NULL }
};
AVFILTER_DEFINE_CLASS(pullup);
static int query_formats(AVFilterContext *ctx)
{
static const enum AVPixelFormat pix_fmts[] = {
AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P,
AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P,
AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P,
AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P,
AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P,
AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_GRAY8,
AV_PIX_FMT_NONE
};
return ff_set_common_formats_from_list(ctx, pix_fmts);
}
#define ABS(a) (((a) ^ ((a) >> 31)) - ((a) >> 31))
static int diff_c(const uint8_t *a, const uint8_t *b, ptrdiff_t s)
{
int i, j, diff = 0;
for (i = 0; i < 4; i++) {
for (j = 0; j < 8; j++)
diff += ABS(a[j] - b[j]);
a += s;
b += s;
}
return diff;
}
static int comb_c(const uint8_t *a, const uint8_t *b, ptrdiff_t s)
{
int i, j, comb = 0;
for (i = 0; i < 4; i++) {
for (j = 0; j < 8; j++)
comb += ABS((a[j] << 1) - b[j - s] - b[j ]) +
ABS((b[j] << 1) - a[j ] - a[j + s]);
a += s;
b += s;
}
return comb;
}
static int var_c(const uint8_t *a, const uint8_t *b, ptrdiff_t s)
{
int i, j, var = 0;
for (i = 0; i < 3; i++) {
for (j = 0; j < 8; j++)
var += ABS(a[j] - a[j + s]);
a += s;
}
return 4 * var; /* match comb scaling */
}
static int alloc_metrics(PullupContext *s, PullupField *f)
{
f->diffs = av_calloc(FFALIGN(s->metric_length, 16), sizeof(*f->diffs));
f->combs = av_calloc(FFALIGN(s->metric_length, 16), sizeof(*f->combs));
f->vars = av_calloc(FFALIGN(s->metric_length, 16), sizeof(*f->vars));
if (!f->diffs || !f->combs || !f->vars) {
av_freep(&f->diffs);
av_freep(&f->combs);
av_freep(&f->vars);
return AVERROR(ENOMEM);
}
return 0;
}
static void free_field_queue(PullupField *head)
{
PullupField *f = head;
do {
PullupField *next;
if (!f)
break;
av_free(f->diffs);
av_free(f->combs);
av_free(f->vars);
next = f->next;
memset(f, 0, sizeof(*f)); // clear all pointers to avoid stale ones
av_free(f);
f = next;
} while (f != head);
}
static PullupField *make_field_queue(PullupContext *s, int len)
{
PullupField *head, *f;
f = head = av_mallocz(sizeof(*head));
if (!f)
return NULL;
if (alloc_metrics(s, f) < 0) {
av_free(f);
return NULL;
}
for (; len > 0; len--) {
f->next = av_mallocz(sizeof(*f->next));
if (!f->next) {
free_field_queue(head);
return NULL;
}
f->next->prev = f;
f = f->next;
if (alloc_metrics(s, f) < 0) {
free_field_queue(head);
return NULL;
}
}
f->next = head;
head->prev = f;
return head;
}
static int config_input(AVFilterLink *inlink)
{
AVFilterContext *ctx = inlink->dst;
PullupContext *s = ctx->priv;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
int mp = s->metric_plane;
s->nb_planes = av_pix_fmt_count_planes(inlink->format);
if (mp + 1 > s->nb_planes) {
av_log(ctx, AV_LOG_ERROR, "input format does not have such plane\n");
return AVERROR(EINVAL);
}
s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
s->planeheight[0] = s->planeheight[3] = inlink->h;
s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
s->planewidth[0] = s->planewidth[3] = inlink->w;
s->metric_w = (s->planewidth[mp] - ((s->junk_left + s->junk_right) << 3)) >> 3;
s->metric_h = (s->planeheight[mp] - ((s->junk_top + s->junk_bottom) << 1)) >> 3;
s->metric_offset = (s->junk_left << 3) + (s->junk_top << 1) * s->planewidth[mp];
s->metric_length = s->metric_w * s->metric_h;
av_log(ctx, AV_LOG_DEBUG, "w: %d h: %d\n", s->metric_w, s->metric_h);
av_log(ctx, AV_LOG_DEBUG, "offset: %d length: %d\n", s->metric_offset, s->metric_length);
s->head = make_field_queue(s, 8);
if (!s->head)
return AVERROR(ENOMEM);
s->diff = diff_c;
s->comb = comb_c;
s->var = var_c;
if (ARCH_X86)
ff_pullup_init_x86(s);
return 0;
}
static PullupBuffer *pullup_lock_buffer(PullupBuffer *b, int parity)
{
if (!b)
return NULL;
if ((parity + 1) & 1)
b->lock[0]++;
if ((parity + 1) & 2)
b->lock[1]++;
return b;
}
static void pullup_release_buffer(PullupBuffer *b, int parity)
{
if (!b)
return;
if ((parity + 1) & 1)
b->lock[0]--;
if ((parity + 1) & 2)
b->lock[1]--;
}
static int alloc_buffer(PullupContext *s, PullupBuffer *b)
{
int i;
if (b->planes[0])
return 0;
for (i = 0; i < s->nb_planes; i++) {
b->planes[i] = av_malloc(s->planeheight[i] * s->planewidth[i]);
}
if (s->nb_planes == 1)
b->planes[1] = av_malloc(4*256);
return 0;
}
static PullupBuffer *pullup_get_buffer(PullupContext *s, int parity)
{
int i;
/* Try first to get the sister buffer for the previous field */
if (parity < 2 && s->last && parity != s->last->parity
&& !s->last->buffer->lock[parity]) {
alloc_buffer(s, s->last->buffer);
return pullup_lock_buffer(s->last->buffer, parity);
}
/* Prefer a buffer with both fields open */
for (i = 0; i < FF_ARRAY_ELEMS(s->buffers); i++) {
if (s->buffers[i].lock[0])
continue;
if (s->buffers[i].lock[1])
continue;
alloc_buffer(s, &s->buffers[i]);
return pullup_lock_buffer(&s->buffers[i], parity);
}
if (parity == 2)
return 0;
/* Search for any half-free buffer */
for (i = 0; i < FF_ARRAY_ELEMS(s->buffers); i++) {
if (((parity + 1) & 1) && s->buffers[i].lock[0])
continue;
if (((parity + 1) & 2) && s->buffers[i].lock[1])
continue;
alloc_buffer(s, &s->buffers[i]);
return pullup_lock_buffer(&s->buffers[i], parity);
}
return NULL;
}
static int queue_length(PullupField *begin, PullupField *end)
{
PullupField *f;
int count = 1;
if (!begin || !end)
return 0;
for (f = begin; f != end; f = f->next)
count++;
return count;
}
static int find_first_break(PullupField *f, int max)
{
int i;
for (i = 0; i < max; i++) {
if (f->breaks & BREAK_RIGHT || f->next->breaks & BREAK_LEFT)
return i + 1;
f = f->next;
}
return 0;
}
static void compute_breaks(PullupContext *s, PullupField *f0)
{
PullupField *f1 = f0->next;
PullupField *f2 = f1->next;
PullupField *f3 = f2->next;
int i, l, max_l = 0, max_r = 0;
if (f0->flags & F_HAVE_BREAKS)
return;
f0->flags |= F_HAVE_BREAKS;
/* Special case when fields are 100% identical */
if (f0->buffer == f2->buffer && f1->buffer != f3->buffer) {
f2->breaks |= BREAK_RIGHT;
return;
}
if (f0->buffer != f2->buffer && f1->buffer == f3->buffer) {
f1->breaks |= BREAK_LEFT;
return;
}
for (i = 0; i < s->metric_length; i++) {
l = f2->diffs[i] - f3->diffs[i];
if ( l > max_l)
max_l = l;
if (-l > max_r)
max_r = -l;
}
/* Don't get tripped up when differences are mostly quant error */
if (max_l + max_r < 128)
return;
if (max_l > 4 * max_r)
f1->breaks |= BREAK_LEFT;
if (max_r > 4 * max_l)
f2->breaks |= BREAK_RIGHT;
}
static void compute_affinity(PullupContext *s, PullupField *f)
{
int i, max_l = 0, max_r = 0, l;
if (f->flags & F_HAVE_AFFINITY)
return;
f->flags |= F_HAVE_AFFINITY;
if (f->buffer == f->next->next->buffer) {
f->affinity = 1;
f->next->affinity = 0;
f->next->next->affinity = -1;
f->next->flags |= F_HAVE_AFFINITY;
f->next->next->flags |= F_HAVE_AFFINITY;
return;
}
for (i = 0; i < s->metric_length; i++) {
int v = f->vars[i];
int lv = f->prev->vars[i];
int rv = f->next->vars[i];
int lc = f-> combs[i] - 2*(v < lv ? v : lv);
int rc = f->next->combs[i] - 2*(v < rv ? v : rv);
lc = FFMAX(lc, 0);
rc = FFMAX(rc, 0);
l = lc - rc;
if ( l > max_l)
max_l = l;
if (-l > max_r)
max_r = -l;
}
if (max_l + max_r < 64)
return;
if (max_r > 6 * max_l)
f->affinity = -1;
else if (max_l > 6 * max_r)
f->affinity = 1;
}
static int decide_frame_length(PullupContext *s)
{
PullupField *f0 = s->first;
PullupField *f1 = f0->next;
PullupField *f2 = f1->next;
PullupField *f;
int i, l, n;
if (queue_length(s->first, s->last) < 4)
return 0;
f = s->first;
n = queue_length(f, s->last);
for (i = 0; i < n - 1; i++) {
if (i < n - 3)
compute_breaks(s, f);
compute_affinity(s, f);
f = f->next;
}
if (f0->affinity == -1)
return 1;
l = find_first_break(f0, 3);
if (l == 1 && s->strict_breaks < 0)
l = 0;
switch (l) {
case 1:
return 1 + (s->strict_breaks < 1 && f0->affinity == 1 && f1->affinity == -1);
case 2:
/* FIXME: strictly speaking, f0->prev is no longer valid... :) */
if (s->strict_pairs
&& (f0->prev->breaks & BREAK_RIGHT) && (f2->breaks & BREAK_LEFT)
&& (f0->affinity != 1 || f1->affinity != -1) )
return 1;
return 1 + (f1->affinity != 1);
case 3:
return 2 + (f2->affinity != 1);
default:
/* 9 possibilities covered before switch */
if (f1->affinity == 1)
return 1; /* covers 6 */
else if (f1->affinity == -1)
return 2; /* covers 6 */
else if (f2->affinity == -1) { /* covers 2 */
return (f0->affinity == 1) ? 3 : 1;
} else {
return 2; /* the remaining 6 */
}
}
}
static PullupFrame *pullup_get_frame(PullupContext *s)
{
PullupFrame *fr = &s->frame;
int i, n = decide_frame_length(s);
int aff = s->first->next->affinity;
av_assert1(n < FF_ARRAY_ELEMS(fr->ifields));
if (!n || fr->lock)
return NULL;
fr->lock++;
fr->length = n;
fr->parity = s->first->parity;
fr->buffer = 0;
for (i = 0; i < n; i++) {
/* We cheat and steal the buffer without release+relock */
fr->ifields[i] = s->first->buffer;
s->first->buffer = 0;
s->first = s->first->next;
}
if (n == 1) {
fr->ofields[fr->parity ] = fr->ifields[0];
fr->ofields[fr->parity ^ 1] = 0;
} else if (n == 2) {
fr->ofields[fr->parity ] = fr->ifields[0];
fr->ofields[fr->parity ^ 1] = fr->ifields[1];
} else if (n == 3) {
if (!aff)
aff = (fr->ifields[0] == fr->ifields[1]) ? -1 : 1;
fr->ofields[fr->parity ] = fr->ifields[1 + aff];
fr->ofields[fr->parity ^ 1] = fr->ifields[1 ];
}
pullup_lock_buffer(fr->ofields[0], 0);
pullup_lock_buffer(fr->ofields[1], 1);
if (fr->ofields[0] == fr->ofields[1]) {
fr->buffer = fr->ofields[0];
pullup_lock_buffer(fr->buffer, 2);
return fr;
}
return fr;
}
static void pullup_release_frame(PullupFrame *f)
{
int i;
for (i = 0; i < f->length; i++)
pullup_release_buffer(f->ifields[i], f->parity ^ (i & 1));
pullup_release_buffer(f->ofields[0], 0);
pullup_release_buffer(f->ofields[1], 1);
if (f->buffer)
pullup_release_buffer(f->buffer, 2);
f->lock--;
}
static void compute_metric(PullupContext *s, int *dest,
PullupField *fa, int pa, PullupField *fb, int pb,
int (*func)(const uint8_t *, const uint8_t *, ptrdiff_t))
{
int mp = s->metric_plane;
int xstep = 8;
int ystep = s->planewidth[mp] << 3;
int stride = s->planewidth[mp] << 1; /* field stride */
int w = s->metric_w * xstep;
uint8_t *a, *b;
int x, y;
if (!fa->buffer || !fb->buffer)
return;
/* Shortcut for duplicate fields (e.g. from RFF flag) */
if (fa->buffer == fb->buffer && pa == pb) {
memset(dest, 0, s->metric_length * sizeof(*dest));
return;
}
a = fa->buffer->planes[mp] + pa * s->planewidth[mp] + s->metric_offset;
b = fb->buffer->planes[mp] + pb * s->planewidth[mp] + s->metric_offset;
for (y = 0; y < s->metric_h; y++) {
for (x = 0; x < w; x += xstep)
*dest++ = func(a + x, b + x, stride);
a += ystep; b += ystep;
}
}
static int check_field_queue(PullupContext *s)
{
int ret;
if (s->head->next == s->first) {
PullupField *f = av_mallocz(sizeof(*f));
if (!f)
return AVERROR(ENOMEM);
if ((ret = alloc_metrics(s, f)) < 0) {
av_free(f);
return ret;
}
f->prev = s->head;
f->next = s->first;
s->head->next = f;
s->first->prev = f;
}
return 0;
}
static void pullup_submit_field(PullupContext *s, PullupBuffer *b, int parity)
{
PullupField *f;
/* Grow the circular list if needed */
if (check_field_queue(s) < 0)
return;
/* Cannot have two fields of same parity in a row; drop the new one */
if (s->last && s->last->parity == parity)
return;
f = s->head;
f->parity = parity;
f->buffer = pullup_lock_buffer(b, parity);
f->flags = 0;
f->breaks = 0;
f->affinity = 0;
compute_metric(s, f->diffs, f, parity, f->prev->prev, parity, s->diff);
compute_metric(s, f->combs, parity ? f->prev : f, 0, parity ? f : f->prev, 1, s->comb);
compute_metric(s, f->vars, f, parity, f, -1, s->var);
emms_c();
/* Advance the circular list */
if (!s->first)
s->first = s->head;
s->last = s->head;
s->head = s->head->next;
}
static void copy_field(PullupContext *s,
PullupBuffer *dst, PullupBuffer *src, int parity)
{
uint8_t *dd, *ss;
int i;
for (i = 0; i < s->nb_planes; i++) {
ss = src->planes[i] + parity * s->planewidth[i];
dd = dst->planes[i] + parity * s->planewidth[i];
av_image_copy_plane(dd, s->planewidth[i] << 1,
ss, s->planewidth[i] << 1,
s->planewidth[i], s->planeheight[i] >> 1);
}
}
static void pullup_pack_frame(PullupContext *s, PullupFrame *fr)
{
int i;
if (fr->buffer)
return;
if (fr->length < 2)
return; /* FIXME: deal with this */
for (i = 0; i < 2; i++) {
if (fr->ofields[i]->lock[i^1])
continue;
fr->buffer = fr->ofields[i];
pullup_lock_buffer(fr->buffer, 2);
copy_field(s, fr->buffer, fr->ofields[i^1], i^1);
return;
}
fr->buffer = pullup_get_buffer(s, 2);
copy_field(s, fr->buffer, fr->ofields[0], 0);
copy_field(s, fr->buffer, fr->ofields[1], 1);
}
static int filter_frame(AVFilterLink *inlink, AVFrame *in)
{
AVFilterContext *ctx = inlink->dst;
AVFilterLink *outlink = ctx->outputs[0];
PullupContext *s = ctx->priv;
PullupBuffer *b;
PullupFrame *f;
AVFrame *out;
int p, ret = 0;
b = pullup_get_buffer(s, 2);
if (!b) {
av_log(ctx, AV_LOG_WARNING, "Could not get buffer!\n");
f = pullup_get_frame(s);
pullup_release_frame(f);
goto end;
}
av_image_copy(b->planes, s->planewidth,
(const uint8_t**)in->data, in->linesize,
inlink->format, inlink->w, inlink->h);
p = in->interlaced_frame ? !in->top_field_first : 0;
pullup_submit_field(s, b, p );
pullup_submit_field(s, b, p^1);
if (in->repeat_pict)
pullup_submit_field(s, b, p);
pullup_release_buffer(b, 2);
f = pullup_get_frame(s);
if (!f)
goto end;
if (f->length < 2) {
pullup_release_frame(f);
f = pullup_get_frame(s);
if (!f)
goto end;
if (f->length < 2) {
pullup_release_frame(f);
if (!in->repeat_pict)
goto end;
f = pullup_get_frame(s);
if (!f)
goto end;
if (f->length < 2) {
pullup_release_frame(f);
goto end;
}
}
}
/* If the frame isn't already exportable... */
if (!f->buffer)
pullup_pack_frame(s, f);
out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
if (!out) {
ret = AVERROR(ENOMEM);
goto end;
}
av_frame_copy_props(out, in);
av_image_copy(out->data, out->linesize,
(const uint8_t**)f->buffer->planes, s->planewidth,
inlink->format, inlink->w, inlink->h);
ret = ff_filter_frame(outlink, out);
pullup_release_frame(f);
end:
av_frame_free(&in);
return ret;
}
static av_cold void uninit(AVFilterContext *ctx)
{
PullupContext *s = ctx->priv;
int i;
free_field_queue(s->head);
s->last = NULL;
for (i = 0; i < FF_ARRAY_ELEMS(s->buffers); i++) {
av_freep(&s->buffers[i].planes[0]);
av_freep(&s->buffers[i].planes[1]);
av_freep(&s->buffers[i].planes[2]);
}
}
static const AVFilterPad pullup_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.filter_frame = filter_frame,
.config_props = config_input,
},
};
static const AVFilterPad pullup_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
},
};
const AVFilter ff_vf_pullup = {
.name = "pullup",
.description = NULL_IF_CONFIG_SMALL("Pullup from field sequence to frames."),
.priv_size = sizeof(PullupContext),
.priv_class = &pullup_class,
.uninit = uninit,
FILTER_INPUTS(pullup_inputs),
FILTER_OUTPUTS(pullup_outputs),
FILTER_QUERY_FUNC(query_formats),
};