mirror of
https://gitee.com/openharmony/third_party_ffmpeg
synced 2024-11-23 11:19:55 +00:00
parent
4328602890
commit
158d96e3f0
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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, \
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user