2 * Copyright (c) 2013 Georg Martius <georg dot martius at web dot de>
4 * This file is part of FFmpeg.
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 #define DEFAULT_INPUT_NAME "transforms.trf"
23 #include <vid.stab/libvidstab.h>
25 #include "libavutil/common.h"
26 #include "libavutil/opt.h"
27 #include "libavutil/imgutils.h"
31 #include "vidstabutils.h"
37 VSTransformConfig conf
;
39 VSTransformations trans
; // transformations
40 char *input
; // name of transform file
45 #define OFFSET(x) offsetof(TransformContext, x)
46 #define OFFSETC(x) (offsetof(TransformContext, conf)+offsetof(VSTransformConfig, x))
47 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
49 static const AVOption vidstabtransform_options
[] = {
50 {"input", "set path to the file storing the transforms", OFFSET(input
),
51 AV_OPT_TYPE_STRING
, {.str
= DEFAULT_INPUT_NAME
}, .flags
= FLAGS
},
52 {"smoothing", "set number of frames*2 + 1 used for lowpass filtering", OFFSETC(smoothing
),
53 AV_OPT_TYPE_INT
, {.i64
= 15}, 0, 1000, FLAGS
},
55 {"optalgo", "set camera path optimization algo", OFFSETC(camPathAlgo
),
56 AV_OPT_TYPE_INT
, {.i64
= VSOptimalL1
}, VSOptimalL1
, VSAvg
, FLAGS
, "optalgo"},
57 { "opt", "global optimization", 0, // from version 1.0 on
58 AV_OPT_TYPE_CONST
, {.i64
= VSOptimalL1
}, 0, 0, FLAGS
, "optalgo"},
59 { "gauss", "gaussian kernel", 0,
60 AV_OPT_TYPE_CONST
, {.i64
= VSGaussian
}, 0, 0, FLAGS
, "optalgo"},
61 { "avg", "simple averaging on motion", 0,
62 AV_OPT_TYPE_CONST
, {.i64
= VSAvg
}, 0, 0, FLAGS
, "optalgo"},
64 {"maxshift", "set maximal number of pixels to translate image", OFFSETC(maxShift
),
65 AV_OPT_TYPE_INT
, {.i64
= -1}, -1, 500, FLAGS
},
66 {"maxangle", "set maximal angle in rad to rotate image", OFFSETC(maxAngle
),
67 AV_OPT_TYPE_DOUBLE
, {.dbl
= -1.0}, -1.0, 3.14, FLAGS
},
69 {"crop", "set cropping mode", OFFSETC(crop
),
70 AV_OPT_TYPE_INT
, {.i64
= 0}, 0, 1, FLAGS
, "crop"},
71 { "keep", "keep border", 0,
72 AV_OPT_TYPE_CONST
, {.i64
= VSKeepBorder
}, 0, 0, FLAGS
, "crop"},
73 { "black", "black border", 0,
74 AV_OPT_TYPE_CONST
, {.i64
= VSCropBorder
}, 0, 0, FLAGS
, "crop"},
76 {"invert", "invert transforms", OFFSETC(invert
),
77 AV_OPT_TYPE_INT
, {.i64
= 0}, 0, 1, FLAGS
},
78 {"relative", "consider transforms as relative", OFFSETC(relative
),
79 AV_OPT_TYPE_INT
, {.i64
= 1}, 0, 1, FLAGS
},
80 {"zoom", "set percentage to zoom (>0: zoom in, <0: zoom out", OFFSETC(zoom
),
81 AV_OPT_TYPE_DOUBLE
, {.dbl
= 0}, -100, 100, FLAGS
},
82 {"optzoom", "set optimal zoom (0: nothing, 1: optimal static zoom, 2: optimal dynamic zoom)", OFFSETC(optZoom
),
83 AV_OPT_TYPE_INT
, {.i64
= 1}, 0, 2, FLAGS
},
84 {"zoomspeed", "for adative zoom: percent to zoom maximally each frame", OFFSETC(zoomSpeed
),
85 AV_OPT_TYPE_DOUBLE
, {.dbl
= 0.25}, 0, 5, FLAGS
},
87 {"interpol", "set type of interpolation", OFFSETC(interpolType
),
88 AV_OPT_TYPE_INT
, {.i64
= 2}, 0, 3, FLAGS
, "interpol"},
89 { "no", "no interpolation", 0,
90 AV_OPT_TYPE_CONST
, {.i64
= VS_Zero
}, 0, 0, FLAGS
, "interpol"},
91 { "linear", "linear (horizontal)", 0,
92 AV_OPT_TYPE_CONST
, {.i64
= VS_Linear
}, 0, 0, FLAGS
, "interpol"},
93 { "bilinear","bi-linear", 0,
94 AV_OPT_TYPE_CONST
, {.i64
= VS_BiLinear
},0, 0, FLAGS
, "interpol"},
95 { "bicubic", "bi-cubic", 0,
96 AV_OPT_TYPE_CONST
, {.i64
= VS_BiCubic
},0, 0, FLAGS
, "interpol"},
98 {"tripod", "enable virtual tripod mode (same as relative=0:smoothing=0)", OFFSET(tripod
),
99 AV_OPT_TYPE_INT
, {.i64
= 0}, 0, 1, FLAGS
},
100 {"debug", "enable debug mode and writer global motions information to file", OFFSET(debug
),
101 AV_OPT_TYPE_INT
, {.i64
= 0}, 0, 1, FLAGS
},
105 AVFILTER_DEFINE_CLASS(vidstabtransform
);
107 static av_cold
int init(AVFilterContext
*ctx
)
109 TransformContext
*tc
= ctx
->priv
;
111 tc
->class = &vidstabtransform_class
;
112 av_log(ctx
, AV_LOG_VERBOSE
, "vidstabtransform filter: init %s\n", LIBVIDSTAB_VERSION
);
116 static av_cold
void uninit(AVFilterContext
*ctx
)
118 TransformContext
*tc
= ctx
->priv
;
120 vsTransformDataCleanup(&tc
->td
);
121 vsTransformationsCleanup(&tc
->trans
);
124 static int query_formats(AVFilterContext
*ctx
)
126 // If you add something here also add it in vidstabutils.c
127 static const enum AVPixelFormat pix_fmts
[] = {
128 AV_PIX_FMT_YUV444P
, AV_PIX_FMT_YUV422P
, AV_PIX_FMT_YUV420P
,
129 AV_PIX_FMT_YUV411P
, AV_PIX_FMT_YUV410P
, AV_PIX_FMT_YUVA420P
,
130 AV_PIX_FMT_YUV440P
, AV_PIX_FMT_GRAY8
,
131 AV_PIX_FMT_RGB24
, AV_PIX_FMT_BGR24
, AV_PIX_FMT_RGBA
,
135 ff_set_common_formats(ctx
, ff_make_format_list(pix_fmts
));
140 static int config_input(AVFilterLink
*inlink
)
142 AVFilterContext
*ctx
= inlink
->dst
;
143 TransformContext
*tc
= ctx
->priv
;
146 const AVPixFmtDescriptor
*desc
= av_pix_fmt_desc_get(inlink
->format
);
148 VSTransformData
*td
= &(tc
->td
);
153 if (!vsFrameInfoInit(&fi_src
, inlink
->w
, inlink
->h
,
154 ff_av2vs_pixfmt(ctx
, inlink
->format
)) ||
155 !vsFrameInfoInit(&fi_dest
, inlink
->w
, inlink
->h
,
156 ff_av2vs_pixfmt(ctx
, inlink
->format
))) {
157 av_log(ctx
, AV_LOG_ERROR
, "unknown pixel format: %i (%s)",
158 inlink
->format
, desc
->name
);
159 return AVERROR(EINVAL
);
162 if (fi_src
.bytesPerPixel
!= av_get_bits_per_pixel(desc
)/8 ||
163 fi_src
.log2ChromaW
!= desc
->log2_chroma_w
||
164 fi_src
.log2ChromaH
!= desc
->log2_chroma_h
) {
165 av_log(ctx
, AV_LOG_ERROR
, "pixel-format error: bpp %i<>%i ",
166 fi_src
.bytesPerPixel
, av_get_bits_per_pixel(desc
)/8);
167 av_log(ctx
, AV_LOG_ERROR
, "chroma_subsampl: w: %i<>%i h: %i<>%i\n",
168 fi_src
.log2ChromaW
, desc
->log2_chroma_w
,
169 fi_src
.log2ChromaH
, desc
->log2_chroma_h
);
170 return AVERROR(EINVAL
);
173 // set values that are not initializes by the options
174 tc
->conf
.modName
= "vidstabtransform";
175 tc
->conf
.verbose
= 1 + tc
->debug
;
177 av_log(ctx
, AV_LOG_INFO
, "Virtual tripod mode: relative=0, smoothing=0\n");
178 tc
->conf
.relative
= 0;
179 tc
->conf
.smoothing
= 0;
181 tc
->conf
.simpleMotionCalculation
= 0;
182 tc
->conf
.storeTransforms
= tc
->debug
;
183 tc
->conf
.smoothZoom
= 0;
185 if (vsTransformDataInit(td
, &tc
->conf
, &fi_src
, &fi_dest
) != VS_OK
) {
186 av_log(ctx
, AV_LOG_ERROR
, "initialization of vid.stab transform failed, please report a BUG\n");
187 return AVERROR(EINVAL
);
190 vsTransformGetConfig(&tc
->conf
, td
);
191 av_log(ctx
, AV_LOG_INFO
, "Video transformation/stabilization settings (pass 2/2):\n");
192 av_log(ctx
, AV_LOG_INFO
, " input = %s\n", tc
->input
);
193 av_log(ctx
, AV_LOG_INFO
, " smoothing = %d\n", tc
->conf
.smoothing
);
194 av_log(ctx
, AV_LOG_INFO
, " optalgo = %s\n",
195 tc
->conf
.camPathAlgo
== VSOptimalL1
? "opt" :
196 (tc
->conf
.camPathAlgo
== VSGaussian
? "gauss" : "avg"));
197 av_log(ctx
, AV_LOG_INFO
, " maxshift = %d\n", tc
->conf
.maxShift
);
198 av_log(ctx
, AV_LOG_INFO
, " maxangle = %f\n", tc
->conf
.maxAngle
);
199 av_log(ctx
, AV_LOG_INFO
, " crop = %s\n", tc
->conf
.crop
? "Black" : "Keep");
200 av_log(ctx
, AV_LOG_INFO
, " relative = %s\n", tc
->conf
.relative
? "True": "False");
201 av_log(ctx
, AV_LOG_INFO
, " invert = %s\n", tc
->conf
.invert
? "True" : "False");
202 av_log(ctx
, AV_LOG_INFO
, " zoom = %f\n", tc
->conf
.zoom
);
203 av_log(ctx
, AV_LOG_INFO
, " optzoom = %s\n",
204 tc
->conf
.optZoom
== 1 ? "Static (1)" : (tc
->conf
.optZoom
== 2 ? "Dynamic (2)" : "Off (0)"));
205 if (tc
->conf
.optZoom
== 2)
206 av_log(ctx
, AV_LOG_INFO
, " zoomspeed = %g\n", tc
->conf
.zoomSpeed
);
207 av_log(ctx
, AV_LOG_INFO
, " interpol = %s\n", getInterpolationTypeName(tc
->conf
.interpolType
));
209 f
= fopen(tc
->input
, "r");
211 int ret
= AVERROR(errno
);
212 av_log(ctx
, AV_LOG_ERROR
, "cannot open input file %s\n", tc
->input
);
215 VSManyLocalMotions mlms
;
216 if (vsReadLocalMotionsFile(f
, &mlms
) == VS_OK
) {
217 // calculate the actual transforms from the local motions
218 if (vsLocalmotions2Transforms(td
, &mlms
, &tc
->trans
) != VS_OK
) {
219 av_log(ctx
, AV_LOG_ERROR
, "calculating transformations failed\n");
220 return AVERROR(EINVAL
);
222 } else { // try to read old format
223 if (!vsReadOldTransforms(td
, f
, &tc
->trans
)) { /* read input file */
224 av_log(ctx
, AV_LOG_ERROR
, "error parsing input file %s\n", tc
->input
);
225 return AVERROR(EINVAL
);
231 if (vsPreprocessTransforms(td
, &tc
->trans
) != VS_OK
) {
232 av_log(ctx
, AV_LOG_ERROR
, "error while preprocessing transforms\n");
233 return AVERROR(EINVAL
);
236 // TODO: add sharpening, so far the user needs to call the unsharp filter manually
241 static int filter_frame(AVFilterLink
*inlink
, AVFrame
*in
)
243 AVFilterContext
*ctx
= inlink
->dst
;
244 TransformContext
*tc
= ctx
->priv
;
245 VSTransformData
* td
= &(tc
->td
);
247 AVFilterLink
*outlink
= inlink
->dst
->outputs
[0];
253 if (av_frame_is_writable(in
)) {
257 out
= ff_get_video_buffer(outlink
, outlink
->w
, outlink
->h
);
260 return AVERROR(ENOMEM
);
262 av_frame_copy_props(out
, in
);
265 for (plane
= 0; plane
< vsTransformGetSrcFrameInfo(td
)->planes
; plane
++) {
266 inframe
.data
[plane
] = in
->data
[plane
];
267 inframe
.linesize
[plane
] = in
->linesize
[plane
];
270 vsTransformPrepare(td
, &inframe
, &inframe
);
271 } else { // separate frames
273 for (plane
= 0; plane
< vsTransformGetDestFrameInfo(td
)->planes
; plane
++) {
274 outframe
.data
[plane
] = out
->data
[plane
];
275 outframe
.linesize
[plane
] = out
->linesize
[plane
];
277 vsTransformPrepare(td
, &inframe
, &outframe
);
280 vsDoTransform(td
, vsGetNextTransform(td
, &tc
->trans
));
282 vsTransformFinish(td
);
287 return ff_filter_frame(outlink
, out
);
290 static const AVFilterPad avfilter_vf_vidstabtransform_inputs
[] = {
293 .type
= AVMEDIA_TYPE_VIDEO
,
294 .filter_frame
= filter_frame
,
295 .config_props
= config_input
,
300 static const AVFilterPad avfilter_vf_vidstabtransform_outputs
[] = {
303 .type
= AVMEDIA_TYPE_VIDEO
,
308 AVFilter ff_vf_vidstabtransform
= {
309 .name
= "vidstabtransform",
310 .description
= NULL_IF_CONFIG_SMALL("Transform the frames, "
311 "pass 2 of 2 for stabilization "
312 "(see vidstabdetect for pass 1)."),
313 .priv_size
= sizeof(TransformContext
),
316 .query_formats
= query_formats
,
317 .inputs
= avfilter_vf_vidstabtransform_inputs
,
318 .outputs
= avfilter_vf_vidstabtransform_outputs
,
319 .priv_class
= &vidstabtransform_class
,