| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * Copyright (c) 2013 Paul B Mahol | ||
| 3 | * | ||
| 4 | * This file is part of FFmpeg. | ||
| 5 | * | ||
| 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. | ||
| 10 | * | ||
| 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. | ||
| 15 | * | ||
| 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 | ||
| 19 | */ | ||
| 20 | |||
| 21 | #include "libavutil/eval.h" | ||
| 22 | #include "libavutil/opt.h" | ||
| 23 | #include "libavutil/pixdesc.h" | ||
| 24 | #include "avfilter.h" | ||
| 25 | #include "filters.h" | ||
| 26 | #include "video.h" | ||
| 27 | #include "libswscale/swscale.h" | ||
| 28 | |||
| 29 | static const char *const var_names[] = { | ||
| 30 | "in_w", "iw", | ||
| 31 | "in_h", "ih", | ||
| 32 | "out_w", "ow", | ||
| 33 | "out_h", "oh", | ||
| 34 | "in", | ||
| 35 | "on", | ||
| 36 | "duration", | ||
| 37 | "pduration", | ||
| 38 | "in_time", "it", | ||
| 39 | "out_time", "time", "ot", | ||
| 40 | "frame", | ||
| 41 | "zoom", | ||
| 42 | "pzoom", | ||
| 43 | "x", "px", | ||
| 44 | "y", "py", | ||
| 45 | "a", | ||
| 46 | "sar", | ||
| 47 | "dar", | ||
| 48 | "hsub", | ||
| 49 | "vsub", | ||
| 50 | NULL | ||
| 51 | }; | ||
| 52 | |||
| 53 | enum var_name { | ||
| 54 | VAR_IN_W, VAR_IW, | ||
| 55 | VAR_IN_H, VAR_IH, | ||
| 56 | VAR_OUT_W, VAR_OW, | ||
| 57 | VAR_OUT_H, VAR_OH, | ||
| 58 | VAR_IN, | ||
| 59 | VAR_ON, | ||
| 60 | VAR_DURATION, | ||
| 61 | VAR_PDURATION, | ||
| 62 | VAR_IN_TIME, VAR_IT, | ||
| 63 | VAR_TIME, VAR_OUT_TIME, VAR_OT, | ||
| 64 | VAR_FRAME, | ||
| 65 | VAR_ZOOM, | ||
| 66 | VAR_PZOOM, | ||
| 67 | VAR_X, VAR_PX, | ||
| 68 | VAR_Y, VAR_PY, | ||
| 69 | VAR_A, | ||
| 70 | VAR_SAR, | ||
| 71 | VAR_DAR, | ||
| 72 | VAR_HSUB, | ||
| 73 | VAR_VSUB, | ||
| 74 | VARS_NB | ||
| 75 | }; | ||
| 76 | |||
| 77 | typedef struct ZPcontext { | ||
| 78 | const AVClass *class; | ||
| 79 | char *zoom_expr_str; | ||
| 80 | char *x_expr_str; | ||
| 81 | char *y_expr_str; | ||
| 82 | char *duration_expr_str; | ||
| 83 | |||
| 84 | AVExpr *zoom_expr, *x_expr, *y_expr; | ||
| 85 | |||
| 86 | int w, h; | ||
| 87 | double x, y; | ||
| 88 | double prev_zoom; | ||
| 89 | int prev_nb_frames; | ||
| 90 | struct SwsContext *sws; | ||
| 91 | int64_t frame_count; | ||
| 92 | const AVPixFmtDescriptor *desc; | ||
| 93 | AVFrame *in; | ||
| 94 | double var_values[VARS_NB]; | ||
| 95 | int nb_frames; | ||
| 96 | int current_frame; | ||
| 97 | int finished; | ||
| 98 | AVRational framerate; | ||
| 99 | } ZPContext; | ||
| 100 | |||
| 101 | #define OFFSET(x) offsetof(ZPContext, x) | ||
| 102 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM | ||
| 103 | static const AVOption zoompan_options[] = { | ||
| 104 | { "zoom", "set the zoom expression", OFFSET(zoom_expr_str), AV_OPT_TYPE_STRING, {.str = "1" }, .flags = FLAGS }, | ||
| 105 | { "z", "set the zoom expression", OFFSET(zoom_expr_str), AV_OPT_TYPE_STRING, {.str = "1" }, .flags = FLAGS }, | ||
| 106 | { "x", "set the x expression", OFFSET(x_expr_str), AV_OPT_TYPE_STRING, {.str="0"}, .flags = FLAGS }, | ||
| 107 | { "y", "set the y expression", OFFSET(y_expr_str), AV_OPT_TYPE_STRING, {.str="0"}, .flags = FLAGS }, | ||
| 108 | { "d", "set the duration expression", OFFSET(duration_expr_str), AV_OPT_TYPE_STRING, {.str="90"}, .flags = FLAGS }, | ||
| 109 | { "s", "set the output image size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str="hd720"}, .flags = FLAGS }, | ||
| 110 | { "fps", "set the output framerate", OFFSET(framerate), AV_OPT_TYPE_VIDEO_RATE, { .str = "25" }, 0, INT_MAX, .flags = FLAGS }, | ||
| 111 | { NULL } | ||
| 112 | }; | ||
| 113 | |||
| 114 | AVFILTER_DEFINE_CLASS(zoompan); | ||
| 115 | |||
| 116 | ✗ | static av_cold int init(AVFilterContext *ctx) | |
| 117 | { | ||
| 118 | ✗ | ZPContext *s = ctx->priv; | |
| 119 | |||
| 120 | ✗ | s->prev_zoom = 1; | |
| 121 | ✗ | return 0; | |
| 122 | } | ||
| 123 | |||
| 124 | ✗ | static int config_output(AVFilterLink *outlink) | |
| 125 | { | ||
| 126 | ✗ | AVFilterContext *ctx = outlink->src; | |
| 127 | ✗ | FilterLink *l = ff_filter_link(outlink); | |
| 128 | ✗ | ZPContext *s = ctx->priv; | |
| 129 | int ret; | ||
| 130 | |||
| 131 | ✗ | outlink->w = s->w; | |
| 132 | ✗ | outlink->h = s->h; | |
| 133 | ✗ | outlink->time_base = av_inv_q(s->framerate); | |
| 134 | ✗ | l->frame_rate = s->framerate; | |
| 135 | ✗ | s->desc = av_pix_fmt_desc_get(outlink->format); | |
| 136 | ✗ | s->finished = 1; | |
| 137 | |||
| 138 | ✗ | ret = av_expr_parse(&s->zoom_expr, s->zoom_expr_str, var_names, NULL, NULL, NULL, NULL, 0, ctx); | |
| 139 | ✗ | if (ret < 0) | |
| 140 | ✗ | return ret; | |
| 141 | |||
| 142 | ✗ | ret = av_expr_parse(&s->x_expr, s->x_expr_str, var_names, NULL, NULL, NULL, NULL, 0, ctx); | |
| 143 | ✗ | if (ret < 0) | |
| 144 | ✗ | return ret; | |
| 145 | |||
| 146 | ✗ | ret = av_expr_parse(&s->y_expr, s->y_expr_str, var_names, NULL, NULL, NULL, NULL, 0, ctx); | |
| 147 | ✗ | if (ret < 0) | |
| 148 | ✗ | return ret; | |
| 149 | |||
| 150 | ✗ | return 0; | |
| 151 | } | ||
| 152 | |||
| 153 | ✗ | static int output_single_frame(AVFilterContext *ctx, AVFrame *in, double *var_values, int i, | |
| 154 | double *zoom, double *dx, double *dy) | ||
| 155 | { | ||
| 156 | ✗ | ZPContext *s = ctx->priv; | |
| 157 | ✗ | AVFilterLink *outlink = ctx->outputs[0]; | |
| 158 | ✗ | FilterLink *outl = ff_filter_link(outlink); | |
| 159 | ✗ | AVFilterLink *inlink = ctx->inputs[0]; | |
| 160 | ✗ | int64_t pts = s->frame_count; | |
| 161 | ✗ | int k, x, y, w, h, ret = 0; | |
| 162 | uint8_t *input[4]; | ||
| 163 | int px[4], py[4]; | ||
| 164 | AVFrame *out; | ||
| 165 | |||
| 166 | ✗ | var_values[VAR_PX] = s->x; | |
| 167 | ✗ | var_values[VAR_PY] = s->y; | |
| 168 | ✗ | var_values[VAR_PZOOM] = s->prev_zoom; | |
| 169 | ✗ | var_values[VAR_PDURATION] = s->prev_nb_frames; | |
| 170 | ✗ | var_values[VAR_IN_TIME] = var_values[VAR_IT] = in->pts == AV_NOPTS_VALUE ? | |
| 171 | ✗ | NAN : in->pts * av_q2d(inlink->time_base); | |
| 172 | ✗ | var_values[VAR_OUT_TIME] = pts * av_q2d(outlink->time_base); | |
| 173 | ✗ | var_values[VAR_TIME] = var_values[VAR_OT] = var_values[VAR_OUT_TIME]; | |
| 174 | ✗ | var_values[VAR_FRAME] = i; | |
| 175 | ✗ | var_values[VAR_ON] = outl->frame_count_in; | |
| 176 | |||
| 177 | ✗ | *zoom = av_expr_eval(s->zoom_expr, var_values, NULL); | |
| 178 | |||
| 179 | ✗ | *zoom = av_clipd(*zoom, 1, 10); | |
| 180 | ✗ | var_values[VAR_ZOOM] = *zoom; | |
| 181 | ✗ | w = in->width * (1.0 / *zoom); | |
| 182 | ✗ | h = in->height * (1.0 / *zoom); | |
| 183 | |||
| 184 | ✗ | *dx = av_expr_eval(s->x_expr, var_values, NULL); | |
| 185 | |||
| 186 | ✗ | x = *dx = av_clipd(*dx, 0, FFMAX(in->width - w, 0)); | |
| 187 | ✗ | var_values[VAR_X] = *dx; | |
| 188 | ✗ | x &= ~((1 << s->desc->log2_chroma_w) - 1); | |
| 189 | |||
| 190 | ✗ | *dy = av_expr_eval(s->y_expr, var_values, NULL); | |
| 191 | |||
| 192 | ✗ | y = *dy = av_clipd(*dy, 0, FFMAX(in->height - h, 0)); | |
| 193 | ✗ | var_values[VAR_Y] = *dy; | |
| 194 | ✗ | y &= ~((1 << s->desc->log2_chroma_h) - 1); | |
| 195 | |||
| 196 | ✗ | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
| 197 | ✗ | if (!out) { | |
| 198 | ✗ | ret = AVERROR(ENOMEM); | |
| 199 | ✗ | return ret; | |
| 200 | } | ||
| 201 | |||
| 202 | ✗ | px[1] = px[2] = AV_CEIL_RSHIFT(x, s->desc->log2_chroma_w); | |
| 203 | ✗ | px[0] = px[3] = x; | |
| 204 | |||
| 205 | ✗ | py[1] = py[2] = AV_CEIL_RSHIFT(y, s->desc->log2_chroma_h); | |
| 206 | ✗ | py[0] = py[3] = y; | |
| 207 | |||
| 208 | ✗ | s->sws = sws_alloc_context(); | |
| 209 | ✗ | if (!s->sws) { | |
| 210 | ✗ | ret = AVERROR(ENOMEM); | |
| 211 | ✗ | goto error; | |
| 212 | } | ||
| 213 | |||
| 214 | ✗ | for (k = 0; in->data[k]; k++) | |
| 215 | ✗ | input[k] = in->data[k] + py[k] * in->linesize[k] + px[k]; | |
| 216 | |||
| 217 | ✗ | av_opt_set_int(s->sws, "srcw", w, 0); | |
| 218 | ✗ | av_opt_set_int(s->sws, "srch", h, 0); | |
| 219 | ✗ | av_opt_set_int(s->sws, "src_format", in->format, 0); | |
| 220 | ✗ | av_opt_set_int(s->sws, "dstw", outlink->w, 0); | |
| 221 | ✗ | av_opt_set_int(s->sws, "dsth", outlink->h, 0); | |
| 222 | ✗ | av_opt_set_int(s->sws, "dst_format", outlink->format, 0); | |
| 223 | ✗ | av_opt_set_int(s->sws, "sws_flags", SWS_BICUBIC, 0); | |
| 224 | |||
| 225 | ✗ | if ((ret = sws_init_context(s->sws, NULL, NULL)) < 0) | |
| 226 | ✗ | goto error; | |
| 227 | |||
| 228 | ✗ | sws_scale(s->sws, (const uint8_t *const *)&input, in->linesize, 0, h, out->data, out->linesize); | |
| 229 | |||
| 230 | ✗ | out->pts = pts; | |
| 231 | ✗ | s->frame_count++; | |
| 232 | |||
| 233 | ✗ | ret = ff_filter_frame(outlink, out); | |
| 234 | ✗ | sws_freeContext(s->sws); | |
| 235 | ✗ | s->sws = NULL; | |
| 236 | ✗ | s->current_frame++; | |
| 237 | |||
| 238 | ✗ | if (s->current_frame >= s->nb_frames) { | |
| 239 | ✗ | if (*dx != -1) | |
| 240 | ✗ | s->x = *dx; | |
| 241 | ✗ | if (*dy != -1) | |
| 242 | ✗ | s->y = *dy; | |
| 243 | ✗ | if (*zoom != -1) | |
| 244 | ✗ | s->prev_zoom = *zoom; | |
| 245 | ✗ | s->prev_nb_frames = s->nb_frames; | |
| 246 | ✗ | s->nb_frames = 0; | |
| 247 | ✗ | s->current_frame = 0; | |
| 248 | ✗ | av_frame_free(&s->in); | |
| 249 | ✗ | s->finished = 1; | |
| 250 | } | ||
| 251 | ✗ | return ret; | |
| 252 | ✗ | error: | |
| 253 | ✗ | sws_freeContext(s->sws); | |
| 254 | ✗ | s->sws = NULL; | |
| 255 | ✗ | av_frame_free(&out); | |
| 256 | ✗ | return ret; | |
| 257 | } | ||
| 258 | |||
| 259 | ✗ | static int activate(AVFilterContext *ctx) | |
| 260 | { | ||
| 261 | ✗ | ZPContext *s = ctx->priv; | |
| 262 | ✗ | AVFilterLink *inlink = ctx->inputs[0]; | |
| 263 | ✗ | FilterLink *inl = ff_filter_link(inlink); | |
| 264 | ✗ | AVFilterLink *outlink = ctx->outputs[0]; | |
| 265 | ✗ | FilterLink *outl = ff_filter_link(outlink); | |
| 266 | ✗ | int status, ret = 0; | |
| 267 | int64_t pts; | ||
| 268 | |||
| 269 | ✗ | FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink); | |
| 270 | |||
| 271 | ✗ | if (s->in && ff_outlink_frame_wanted(outlink)) { | |
| 272 | ✗ | double zoom = -1, dx = -1, dy = -1; | |
| 273 | |||
| 274 | ✗ | ret = output_single_frame(ctx, s->in, s->var_values, s->current_frame, | |
| 275 | &zoom, &dx, &dy); | ||
| 276 | ✗ | if (ret < 0) | |
| 277 | ✗ | return ret; | |
| 278 | } | ||
| 279 | |||
| 280 | ✗ | if (!s->in && (ret = ff_inlink_consume_frame(inlink, &s->in)) > 0) { | |
| 281 | ✗ | double zoom = -1, dx = -1, dy = -1, nb_frames; | |
| 282 | |||
| 283 | ✗ | s->finished = 0; | |
| 284 | ✗ | s->var_values[VAR_IN_W] = s->var_values[VAR_IW] = s->in->width; | |
| 285 | ✗ | s->var_values[VAR_IN_H] = s->var_values[VAR_IH] = s->in->height; | |
| 286 | ✗ | s->var_values[VAR_OUT_W] = s->var_values[VAR_OW] = s->w; | |
| 287 | ✗ | s->var_values[VAR_OUT_H] = s->var_values[VAR_OH] = s->h; | |
| 288 | ✗ | s->var_values[VAR_IN] = inl->frame_count_out - 1; | |
| 289 | ✗ | s->var_values[VAR_ON] = outl->frame_count_in; | |
| 290 | ✗ | s->var_values[VAR_PX] = s->x; | |
| 291 | ✗ | s->var_values[VAR_PY] = s->y; | |
| 292 | ✗ | s->var_values[VAR_X] = 0; | |
| 293 | ✗ | s->var_values[VAR_Y] = 0; | |
| 294 | ✗ | s->var_values[VAR_PZOOM] = s->prev_zoom; | |
| 295 | ✗ | s->var_values[VAR_ZOOM] = 1; | |
| 296 | ✗ | s->var_values[VAR_PDURATION] = s->prev_nb_frames; | |
| 297 | ✗ | s->var_values[VAR_A] = (double) s->in->width / s->in->height; | |
| 298 | ✗ | s->var_values[VAR_SAR] = inlink->sample_aspect_ratio.num ? | |
| 299 | ✗ | (double) inlink->sample_aspect_ratio.num / inlink->sample_aspect_ratio.den : 1; | |
| 300 | ✗ | s->var_values[VAR_DAR] = s->var_values[VAR_A] * s->var_values[VAR_SAR]; | |
| 301 | ✗ | s->var_values[VAR_HSUB] = 1 << s->desc->log2_chroma_w; | |
| 302 | ✗ | s->var_values[VAR_VSUB] = 1 << s->desc->log2_chroma_h; | |
| 303 | |||
| 304 | ✗ | if ((ret = av_expr_parse_and_eval(&nb_frames, s->duration_expr_str, | |
| 305 | ✗ | var_names, s->var_values, | |
| 306 | NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0) { | ||
| 307 | ✗ | av_frame_free(&s->in); | |
| 308 | ✗ | return ret; | |
| 309 | } | ||
| 310 | |||
| 311 | ✗ | s->var_values[VAR_DURATION] = s->nb_frames = nb_frames; | |
| 312 | |||
| 313 | ✗ | ret = output_single_frame(ctx, s->in, s->var_values, s->current_frame, | |
| 314 | &zoom, &dx, &dy); | ||
| 315 | ✗ | if (ret < 0) | |
| 316 | ✗ | return ret; | |
| 317 | } | ||
| 318 | ✗ | if (ret < 0) { | |
| 319 | ✗ | return ret; | |
| 320 | ✗ | } else if (s->finished && ff_inlink_acknowledge_status(inlink, &status, &pts)) { | |
| 321 | ✗ | ff_outlink_set_status(outlink, status, pts); | |
| 322 | ✗ | return 0; | |
| 323 | } else { | ||
| 324 | ✗ | if (ff_outlink_frame_wanted(outlink) && s->finished) | |
| 325 | ✗ | ff_inlink_request_frame(inlink); | |
| 326 | ✗ | return 0; | |
| 327 | } | ||
| 328 | } | ||
| 329 | |||
| 330 | static const enum AVPixelFormat pix_fmts[] = { | ||
| 331 | AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P, | ||
| 332 | AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV411P, | ||
| 333 | AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV440P, | ||
| 334 | AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA422P, | ||
| 335 | AV_PIX_FMT_YUVA420P, | ||
| 336 | AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, | ||
| 337 | AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P, | ||
| 338 | AV_PIX_FMT_YUVJ411P, | ||
| 339 | AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, | ||
| 340 | AV_PIX_FMT_GRAY8, | ||
| 341 | AV_PIX_FMT_NONE | ||
| 342 | }; | ||
| 343 | |||
| 344 | ✗ | static av_cold void uninit(AVFilterContext *ctx) | |
| 345 | { | ||
| 346 | ✗ | ZPContext *s = ctx->priv; | |
| 347 | |||
| 348 | ✗ | sws_freeContext(s->sws); | |
| 349 | ✗ | s->sws = NULL; | |
| 350 | ✗ | av_expr_free(s->x_expr); | |
| 351 | ✗ | av_expr_free(s->y_expr); | |
| 352 | ✗ | av_expr_free(s->zoom_expr); | |
| 353 | ✗ | av_frame_free(&s->in); | |
| 354 | ✗ | } | |
| 355 | |||
| 356 | static const AVFilterPad outputs[] = { | ||
| 357 | { | ||
| 358 | .name = "default", | ||
| 359 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 360 | .config_props = config_output, | ||
| 361 | }, | ||
| 362 | }; | ||
| 363 | |||
| 364 | const FFFilter ff_vf_zoompan = { | ||
| 365 | .p.name = "zoompan", | ||
| 366 | .p.description = NULL_IF_CONFIG_SMALL("Apply Zoom & Pan effect."), | ||
| 367 | .p.priv_class = &zoompan_class, | ||
| 368 | .priv_size = sizeof(ZPContext), | ||
| 369 | .init = init, | ||
| 370 | .uninit = uninit, | ||
| 371 | .activate = activate, | ||
| 372 | FILTER_INPUTS(ff_video_default_filterpad), | ||
| 373 | FILTER_OUTPUTS(outputs), | ||
| 374 | FILTER_PIXFMTS_ARRAY(pix_fmts), | ||
| 375 | }; | ||
| 376 |