lavfi: add haldclut filter.

Fixes Ticket #2517.
This commit is contained in:
Clément Bœsch 2013-05-25 01:07:46 +02:00
parent 4328602890
commit 158d96e3f0
6 changed files with 309 additions and 16 deletions

View File

@ -55,6 +55,7 @@ version <next>:
- 3D LUT filter (lut3d)
- SMPTE 302M audio encoder
- support for slice multithreading in libavfilter
- Hald CLUT support (generation and filtering)
version 1.2:

View File

@ -4177,6 +4177,79 @@ gradfun=radius=8
@end itemize
@anchor{haldclut}
@section haldclut
Apply a Hald CLUT to a video stream.
First input is the video stream to process, and second one is the Hald CLUT.
The Hald CLUT input can be a simple picture or a complete video stream.
The filter accepts the following options:
@table @option
@item shortest
Force termination when the shortest input terminates. Default is @code{0}.
@item repeatlast
Continue applying the last CLUT after the end of the stream. A value of
@code{0} disable the filter after the last frame of the CLUT is reached.
Default is @code{1}.
@end table
@code{haldclut} also has the same interpolation options as @ref{lut3d} (both
filters share the same internals).
More information about the Hald CLUT can be found on Eskil Steenberg's website
(Hald CLUT author) at @url{http://www.quelsolaar.com/technology/clut.html}.
@subsection Workflow examples
@subsubsection Hald CLUT video stream
Generate an identity Hald CLUT stream altered with various effects:
@example
ffmpeg -f lavfi -i @ref{haldclutsrc}=8 -vf "hue=H=2*PI*t:s=sin(2*PI*t)+1, curves=cross_process" -t 10 -c:v ffv1 clut.nut
@end example
Note: make sure you use a lossless codec.
Then use it with @code{haldclut} to apply it on some random stream:
@example
ffmpeg -f lavfi -i mandelbrot -i clut.nut -filter_complex '[0][1] haldclut' -t 20 mandelclut.mkv
@end example
The Hald CLUT will be applied to the 10 first seconds (duration of
@file{clut.nut}), then the latest picture of that CLUT stream will be applied
to the remaining frames of the @code{mandelbrot} stream.
@subsubsection Hald CLUT with preview
A Hald CLUT is supposed to be a squared image of @code{Level*Level*Level} by
@code{Level*Level*Level} pixels. For a given Hald CLUT, FFmpeg will select the
biggest possible square starting at the top left of the picture. The remaining
padding pixels (bottom or right) will be ignored. This area can be used to add
a preview of the Hald CLUT.
Typically, the following generated Hald CLUT will be supported by the
@code{haldclut} filter:
@example
ffmpeg -f lavfi -i @ref{haldclutsrc}=8 -vf "
pad=iw+320 [padded_clut];
smptebars=s=320x256, split [a][b];
[padded_clut][a] overlay=W-320:h, curves=color_negative [main];
[main][b] overlay=W-320" -frames:v 1 clut.png
@end example
It contains the original and a preview of the effect of the CLUT: SMPTE color
bars are displayed on the right-top, and below the same color bars processed by
the color changes.
Then, the effect of this Hald CLUT can be visualized with:
@example
ffplay input.mkv -vf "movie=clut.png, [in] haldclut"
@end example
@section hflip
Flip the input video horizontally.
@ -4603,6 +4676,7 @@ kerndeint=map=1
@end example
@end itemize
@anchor{lut3d}
@section lut3d
Apply a 3D LUT to an input video.
@ -7401,11 +7475,19 @@ ffplay -f lavfi life=s=300x200:mold=10:r=60:ratio=0.1:death_color=#C83232:life_c
@end example
@end itemize
@anchor{color}
@anchor{haldclutsrc}
@anchor{nullsrc}
@anchor{rgbtestsrc}
@anchor{smptebars}
@anchor{smptehdbars}
@anchor{testsrc}
@section color, haldclutsrc, nullsrc, rgbtestsrc, smptebars, smptehdbars, testsrc
The @code{color} source provides an uniformly colored input.
The @code{haldclutsrc} source provides an identity Hald CLUT.
The @code{haldclutsrc} source provides an identity Hald CLUT. See also
@ref{haldclut} filter.
The @code{nullsrc} source returns unprocessed video frames. It is
mainly useful to be employed in analysis / debugging tools, or as the

View File

@ -135,6 +135,7 @@ OBJS-$(CONFIG_FPS_FILTER) += vf_fps.o
OBJS-$(CONFIG_FREI0R_FILTER) += vf_frei0r.o
OBJS-$(CONFIG_GEQ_FILTER) += vf_geq.o
OBJS-$(CONFIG_GRADFUN_FILTER) += vf_gradfun.o
OBJS-$(CONFIG_HALDCLUT_FILTER) += vf_lut3d.o dualinput.o
OBJS-$(CONFIG_HFLIP_FILTER) += vf_hflip.o
OBJS-$(CONFIG_HISTEQ_FILTER) += vf_histeq.o
OBJS-$(CONFIG_HISTOGRAM_FILTER) += vf_histogram.o

View File

@ -133,6 +133,7 @@ void avfilter_register_all(void)
REGISTER_FILTER(FREI0R, frei0r, vf);
REGISTER_FILTER(GEQ, geq, vf);
REGISTER_FILTER(GRADFUN, gradfun, vf);
REGISTER_FILTER(HALDCLUT, haldclut, vf);
REGISTER_FILTER(HFLIP, hflip, vf);
REGISTER_FILTER(HISTEQ, histeq, vf);
REGISTER_FILTER(HISTOGRAM, histogram, vf);

View File

@ -30,7 +30,7 @@
#include "libavutil/avutil.h"
#define LIBAVFILTER_VERSION_MAJOR 3
#define LIBAVFILTER_VERSION_MINOR 71
#define LIBAVFILTER_VERSION_MINOR 72
#define LIBAVFILTER_VERSION_MICRO 100
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \

View File

@ -31,6 +31,7 @@
#include "libavutil/avstring.h"
#include "avfilter.h"
#include "drawutils.h"
#include "dualinput.h"
#include "formats.h"
#include "internal.h"
#include "video.h"
@ -51,7 +52,9 @@ struct rgbvec {
float r, g, b;
};
#define MAX_LEVEL 36
/* 3D LUT don't often go up to level 32, but it is common to have a Hald CLUT
* of 512x512 (64x64x64) */
#define MAX_LEVEL 64
typedef struct LUT3DContext {
const AVClass *class;
@ -64,20 +67,23 @@ typedef struct LUT3DContext {
struct rgbvec (*interp_16)(const struct LUT3DContext*, uint16_t, uint16_t, uint16_t);
struct rgbvec lut[MAX_LEVEL][MAX_LEVEL][MAX_LEVEL];
int lutsize;
#if CONFIG_HALDCLUT_FILTER
uint8_t clut_rgba_map[4];
int clut_step;
int clut_is16bit;
int clut_width;
FFDualInputContext dinput;
#endif
} LUT3DContext;
#define OFFSET(x) offsetof(LUT3DContext, x)
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
static const AVOption lut3d_options[] = {
{ "file", "set 3D LUT file name", OFFSET(file), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
{ "interp", "select interpolation mode", OFFSET(interpolation), AV_OPT_TYPE_INT, {.i64=INTERPOLATE_TETRAHEDRAL}, 0, NB_INTERP_MODE-1, FLAGS, "interp_mode" },
{ "nearest", "use values from the nearest defined points", 0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_NEAREST}, INT_MIN, INT_MAX, FLAGS, "interp_mode" },
{ "trilinear", "interpolate values using the 8 points defining a cube", 0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_TRILINEAR}, INT_MIN, INT_MAX, FLAGS, "interp_mode" },
{ "tetrahedral", "interpolate values using a tetrahedron", 0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_TETRAHEDRAL}, INT_MIN, INT_MAX, FLAGS, "interp_mode" },
#define COMMON_OPTIONS \
{ "interp", "select interpolation mode", OFFSET(interpolation), AV_OPT_TYPE_INT, {.i64=INTERPOLATE_TETRAHEDRAL}, 0, NB_INTERP_MODE-1, FLAGS, "interp_mode" }, \
{ "nearest", "use values from the nearest defined points", 0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_NEAREST}, INT_MIN, INT_MAX, FLAGS, "interp_mode" }, \
{ "trilinear", "interpolate values using the 8 points defining a cube", 0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_TRILINEAR}, INT_MIN, INT_MAX, FLAGS, "interp_mode" }, \
{ "tetrahedral", "interpolate values using a tetrahedron", 0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_TETRAHEDRAL}, INT_MIN, INT_MAX, FLAGS, "interp_mode" }, \
{ NULL }
};
AVFILTER_DEFINE_CLASS(lut3d);
static inline float lerpf(float v0, float v1, float f)
{
@ -404,7 +410,9 @@ static void set_identity_matrix(LUT3DContext *lut3d, int size)
}
}
static av_cold int init(AVFilterContext *ctx)
#if CONFIG_LUT3D_FILTER
/* TODO: move to the CONFIG_LUT3D_FILTER definition scope at the bottom */
static av_cold int lut3d_init(AVFilterContext *ctx)
{
int ret;
FILE *f;
@ -454,6 +462,7 @@ end:
fclose(f);
return ret;
}
#endif
static int query_formats(AVFilterContext *ctx)
{
@ -523,7 +532,7 @@ static int config_input(AVFilterLink *inlink)
} \
} while (0)
static int filter_frame(AVFilterLink *inlink, AVFrame *in)
static AVFrame *apply_lut(AVFilterLink *inlink, AVFrame *in)
{
int x, y, direct = 0;
AVFilterContext *ctx = inlink->dst;
@ -543,7 +552,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
if (!out) {
av_frame_free(&in);
return AVERROR(ENOMEM);
return NULL;
}
av_frame_copy_props(out, in);
}
@ -554,9 +563,26 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
if (!direct)
av_frame_free(&in);
return out;
}
static int filter_frame(AVFilterLink *inlink, AVFrame *in)
{
AVFilterLink *outlink = inlink->dst->outputs[0];
AVFrame *out = apply_lut(inlink, in);
if (!out)
return AVERROR(ENOMEM);
return ff_filter_frame(outlink, out);
}
#if CONFIG_LUT3D_FILTER
static const AVOption lut3d_options[] = {
{ "file", "set 3D LUT file name", OFFSET(file), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
COMMON_OPTIONS
};
AVFILTER_DEFINE_CLASS(lut3d);
static const AVFilterPad lut3d_inputs[] = {
{
.name = "default",
@ -579,10 +605,192 @@ AVFilter avfilter_vf_lut3d = {
.name = "lut3d",
.description = NULL_IF_CONFIG_SMALL("Adjust colors using a 3D LUT."),
.priv_size = sizeof(LUT3DContext),
.init = init,
.init = lut3d_init,
.query_formats = query_formats,
.inputs = lut3d_inputs,
.outputs = lut3d_outputs,
.priv_class = &lut3d_class,
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
};
#endif
#if CONFIG_HALDCLUT_FILTER
static void update_clut(LUT3DContext *lut3d, const AVFrame *frame)
{
const uint8_t *data = frame->data[0];
const int linesize = frame->linesize[0];
const int w = lut3d->clut_width;
const int step = lut3d->clut_step;
const uint8_t *rgba_map = lut3d->clut_rgba_map;
const int level = lut3d->lutsize;
#define LOAD_CLUT(nbits) do { \
int i, j, k, x = 0, y = 0; \
\
for (k = 0; k < level; k++) { \
for (j = 0; j < level; j++) { \
for (i = 0; i < level; i++) { \
const uint##nbits##_t *src = (const uint##nbits##_t *) \
(data + y*linesize + x*step); \
struct rgbvec *vec = &lut3d->lut[k][j][i]; \
vec->r = src[rgba_map[0]] / (float)((1<<(nbits)) - 1); \
vec->g = src[rgba_map[1]] / (float)((1<<(nbits)) - 1); \
vec->b = src[rgba_map[2]] / (float)((1<<(nbits)) - 1); \
if (++x == w) { \
x = 0; \
y++; \
} \
} \
} \
} \
} while (0)
if (!lut3d->clut_is16bit) LOAD_CLUT(8);
else LOAD_CLUT(16);
}
static int config_output(AVFilterLink *outlink)
{
AVFilterContext *ctx = outlink->src;
outlink->w = ctx->inputs[0]->w;
outlink->h = ctx->inputs[0]->h;
outlink->time_base = ctx->inputs[0]->time_base;
return 0;
}
static int filter_frame_main(AVFilterLink *inlink, AVFrame *inpicref)
{
LUT3DContext *s = inlink->dst->priv;
return ff_dualinput_filter_frame_main(&s->dinput, inlink, inpicref);
}
static int filter_frame_clut(AVFilterLink *inlink, AVFrame *inpicref)
{
LUT3DContext *s = inlink->dst->priv;
return ff_dualinput_filter_frame_second(&s->dinput, inlink, inpicref);
}
static int request_frame(AVFilterLink *outlink)
{
LUT3DContext *s = outlink->src->priv;
return ff_dualinput_request_frame(&s->dinput, outlink);
}
static int config_clut(AVFilterLink *inlink)
{
int size, level, w, h;
AVFilterContext *ctx = inlink->dst;
LUT3DContext *lut3d = ctx->priv;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
lut3d->clut_is16bit = 0;
switch (inlink->format) {
case AV_PIX_FMT_RGB48:
case AV_PIX_FMT_BGR48:
case AV_PIX_FMT_RGBA64:
case AV_PIX_FMT_BGRA64:
lut3d->clut_is16bit = 1;
}
lut3d->clut_step = av_get_padded_bits_per_pixel(desc) >> 3;
ff_fill_rgba_map(lut3d->clut_rgba_map, inlink->format);
if (inlink->w > inlink->h)
av_log(ctx, AV_LOG_INFO, "Padding on the right (%dpx) of the "
"Hald CLUT will be ignored\n", inlink->w - inlink->h);
else if (inlink->w < inlink->h)
av_log(ctx, AV_LOG_INFO, "Padding at the bottom (%dpx) of the "
"Hald CLUT will be ignored\n", inlink->h - inlink->w);
lut3d->clut_width = w = h = FFMIN(inlink->w, inlink->h);
for (level = 1; level*level*level < w; level++);
size = level*level*level;
if (size != w) {
av_log(ctx, AV_LOG_WARNING, "The Hald CLUT width does not match the level\n");
return AVERROR_INVALIDDATA;
}
av_assert0(w == h && w == size);
level *= level;
if (level > MAX_LEVEL) {
const int max_clut_level = sqrt(MAX_LEVEL);
const int max_clut_size = max_clut_level*max_clut_level*max_clut_level;
av_log(ctx, AV_LOG_ERROR, "Too large Hald CLUT "
"(maximum level is %d, or %dx%d CLUT)\n",
max_clut_level, max_clut_size, max_clut_size);
return AVERROR(EINVAL);
}
lut3d->lutsize = level;
return 0;
}
static AVFrame *update_apply_clut(AVFilterContext *ctx, AVFrame *main,
const AVFrame *second)
{
AVFilterLink *inlink = ctx->inputs[0];
update_clut(ctx->priv, second);
return apply_lut(inlink, main);
}
static av_cold int haldclut_init(AVFilterContext *ctx)
{
LUT3DContext *lut3d = ctx->priv;
lut3d->dinput.process = update_apply_clut;
return 0;
}
static av_cold void haldclut_uninit(AVFilterContext *ctx)
{
LUT3DContext *lut3d = ctx->priv;
ff_dualinput_uninit(&lut3d->dinput);
}
static const AVOption haldclut_options[] = {
{ "shortest", "force termination when the shortest input terminates", OFFSET(dinput.shortest), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS },
{ "repeatlast", "continue applying the last clut after eos", OFFSET(dinput.repeatlast), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, FLAGS },
COMMON_OPTIONS
};
AVFILTER_DEFINE_CLASS(haldclut);
static const AVFilterPad haldclut_inputs[] = {
{
.name = "main",
.type = AVMEDIA_TYPE_VIDEO,
.filter_frame = filter_frame_main,
.config_props = config_input,
},{
.name = "clut",
.type = AVMEDIA_TYPE_VIDEO,
.filter_frame = filter_frame_clut,
.config_props = config_clut,
},
{ NULL }
};
static const AVFilterPad haldclut_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.request_frame = request_frame,
.config_props = config_output,
},
{ NULL }
};
AVFilter avfilter_vf_haldclut = {
.name = "haldclut",
.description = NULL_IF_CONFIG_SMALL("Adjust colors using a Hald CLUT."),
.priv_size = sizeof(LUT3DContext),
.init = haldclut_init,
.uninit = haldclut_uninit,
.query_formats = query_formats,
.inputs = haldclut_inputs,
.outputs = haldclut_outputs,
.priv_class = &haldclut_class,
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL,
};
#endif