Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * Copyright (c) 2011 Stefano Sabatini | |
3 | * Copyright (c) 2010 Baptiste Coudurier | |
4 | * Copyright (c) 2003 Michael Zucchi <notzed@ximian.com> | |
5 | * | |
6 | * This file is part of FFmpeg. | |
7 | * | |
8 | * FFmpeg is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | * | |
13 | * FFmpeg is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License along | |
19 | * with FFmpeg if not, write to the Free Software Foundation, Inc., | |
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
21 | */ | |
22 | ||
23 | /** | |
24 | * @file | |
25 | * temporal field interlace filter, ported from MPlayer/libmpcodecs | |
26 | */ | |
27 | ||
28 | #include "libavutil/opt.h" | |
29 | #include "libavutil/imgutils.h" | |
30 | #include "libavutil/avassert.h" | |
31 | #include "avfilter.h" | |
32 | #include "internal.h" | |
33 | ||
34 | enum TInterlaceMode { | |
35 | MODE_MERGE = 0, | |
36 | MODE_DROP_EVEN, | |
37 | MODE_DROP_ODD, | |
38 | MODE_PAD, | |
39 | MODE_INTERLEAVE_TOP, | |
40 | MODE_INTERLEAVE_BOTTOM, | |
41 | MODE_INTERLACEX2, | |
42 | MODE_NB, | |
43 | }; | |
44 | ||
45 | typedef struct { | |
46 | const AVClass *class; | |
47 | enum TInterlaceMode mode; ///< interlace mode selected | |
48 | int flags; ///< flags affecting interlacing algorithm | |
49 | int frame; ///< number of the output frame | |
50 | int vsub; ///< chroma vertical subsampling | |
51 | AVFrame *cur; | |
52 | AVFrame *next; | |
53 | uint8_t *black_data[4]; ///< buffer used to fill padded lines | |
54 | int black_linesize[4]; | |
55 | } TInterlaceContext; | |
56 | ||
57 | #define OFFSET(x) offsetof(TInterlaceContext, x) | |
58 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM | |
59 | #define TINTERLACE_FLAG_VLPF 01 | |
60 | ||
61 | static const AVOption tinterlace_options[] = { | |
62 | {"mode", "select interlace mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=MODE_MERGE}, 0, MODE_NB-1, FLAGS, "mode"}, | |
63 | {"merge", "merge fields", 0, AV_OPT_TYPE_CONST, {.i64=MODE_MERGE}, INT_MIN, INT_MAX, FLAGS, "mode"}, | |
64 | {"drop_even", "drop even fields", 0, AV_OPT_TYPE_CONST, {.i64=MODE_DROP_EVEN}, INT_MIN, INT_MAX, FLAGS, "mode"}, | |
65 | {"drop_odd", "drop odd fields", 0, AV_OPT_TYPE_CONST, {.i64=MODE_DROP_ODD}, INT_MIN, INT_MAX, FLAGS, "mode"}, | |
66 | {"pad", "pad alternate lines with black", 0, AV_OPT_TYPE_CONST, {.i64=MODE_PAD}, INT_MIN, INT_MAX, FLAGS, "mode"}, | |
67 | {"interleave_top", "interleave top and bottom fields", 0, AV_OPT_TYPE_CONST, {.i64=MODE_INTERLEAVE_TOP}, INT_MIN, INT_MAX, FLAGS, "mode"}, | |
68 | {"interleave_bottom", "interleave bottom and top fields", 0, AV_OPT_TYPE_CONST, {.i64=MODE_INTERLEAVE_BOTTOM}, INT_MIN, INT_MAX, FLAGS, "mode"}, | |
69 | {"interlacex2", "interlace fields from two consecutive frames", 0, AV_OPT_TYPE_CONST, {.i64=MODE_INTERLACEX2}, INT_MIN, INT_MAX, FLAGS, "mode"}, | |
70 | ||
71 | {"flags", "set flags", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0}, 0, INT_MAX, 0, "flags" }, | |
72 | {"low_pass_filter", "enable vertical low-pass filter", 0, AV_OPT_TYPE_CONST, {.i64 = TINTERLACE_FLAG_VLPF}, INT_MIN, INT_MAX, FLAGS, "flags" }, | |
73 | {"vlpf", "enable vertical low-pass filter", 0, AV_OPT_TYPE_CONST, {.i64 = TINTERLACE_FLAG_VLPF}, INT_MIN, INT_MAX, FLAGS, "flags" }, | |
74 | ||
75 | {NULL} | |
76 | }; | |
77 | ||
78 | AVFILTER_DEFINE_CLASS(tinterlace); | |
79 | ||
80 | #define FULL_SCALE_YUVJ_FORMATS \ | |
81 | AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P | |
82 | ||
83 | static const enum AVPixelFormat full_scale_yuvj_pix_fmts[] = { | |
84 | FULL_SCALE_YUVJ_FORMATS, AV_PIX_FMT_NONE | |
85 | }; | |
86 | ||
87 | static int query_formats(AVFilterContext *ctx) | |
88 | { | |
89 | static const enum AVPixelFormat pix_fmts[] = { | |
90 | AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P, | |
91 | AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, | |
92 | AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV444P, | |
93 | AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA444P, | |
94 | AV_PIX_FMT_GRAY8, FULL_SCALE_YUVJ_FORMATS, | |
95 | AV_PIX_FMT_NONE | |
96 | }; | |
97 | ||
98 | ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); | |
99 | return 0; | |
100 | } | |
101 | ||
102 | static av_cold void uninit(AVFilterContext *ctx) | |
103 | { | |
104 | TInterlaceContext *tinterlace = ctx->priv; | |
105 | ||
106 | av_frame_free(&tinterlace->cur ); | |
107 | av_frame_free(&tinterlace->next); | |
108 | av_freep(&tinterlace->black_data[0]); | |
109 | } | |
110 | ||
111 | static int config_out_props(AVFilterLink *outlink) | |
112 | { | |
113 | AVFilterContext *ctx = outlink->src; | |
114 | AVFilterLink *inlink = outlink->src->inputs[0]; | |
115 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(outlink->format); | |
116 | TInterlaceContext *tinterlace = ctx->priv; | |
117 | ||
118 | tinterlace->vsub = desc->log2_chroma_h; | |
119 | outlink->flags |= FF_LINK_FLAG_REQUEST_LOOP; | |
120 | outlink->w = inlink->w; | |
121 | outlink->h = tinterlace->mode == MODE_MERGE || tinterlace->mode == MODE_PAD ? | |
122 | inlink->h*2 : inlink->h; | |
123 | ||
124 | if (tinterlace->mode == MODE_PAD) { | |
125 | uint8_t black[4] = { 16, 128, 128, 16 }; | |
126 | int i, ret; | |
127 | if (ff_fmt_is_in(outlink->format, full_scale_yuvj_pix_fmts)) | |
128 | black[0] = black[3] = 0; | |
129 | ret = av_image_alloc(tinterlace->black_data, tinterlace->black_linesize, | |
130 | outlink->w, outlink->h, outlink->format, 1); | |
131 | if (ret < 0) | |
132 | return ret; | |
133 | ||
134 | /* fill black picture with black */ | |
135 | for (i = 0; i < 4 && tinterlace->black_data[i]; i++) { | |
136 | int h = i == 1 || i == 2 ? FF_CEIL_RSHIFT(outlink->h, desc->log2_chroma_h) : outlink->h; | |
137 | memset(tinterlace->black_data[i], black[i], | |
138 | tinterlace->black_linesize[i] * h); | |
139 | } | |
140 | } | |
141 | if ((tinterlace->flags & TINTERLACE_FLAG_VLPF) | |
142 | && !(tinterlace->mode == MODE_INTERLEAVE_TOP | |
143 | || tinterlace->mode == MODE_INTERLEAVE_BOTTOM)) { | |
144 | av_log(ctx, AV_LOG_WARNING, "low_pass_filter flag ignored with mode %d\n", | |
145 | tinterlace->mode); | |
146 | tinterlace->flags &= ~TINTERLACE_FLAG_VLPF; | |
147 | } | |
148 | if (tinterlace->mode == MODE_INTERLACEX2) { | |
149 | outlink->time_base.num = inlink->time_base.num; | |
150 | outlink->time_base.den = inlink->time_base.den * 2; | |
151 | outlink->frame_rate = av_mul_q(inlink->frame_rate, (AVRational){2,1}); | |
152 | } | |
153 | ||
154 | av_log(ctx, AV_LOG_VERBOSE, "mode:%d filter:%s h:%d -> h:%d\n", | |
155 | tinterlace->mode, (tinterlace->flags & TINTERLACE_FLAG_VLPF) ? "on" : "off", | |
156 | inlink->h, outlink->h); | |
157 | ||
158 | return 0; | |
159 | } | |
160 | ||
161 | #define FIELD_UPPER 0 | |
162 | #define FIELD_LOWER 1 | |
163 | #define FIELD_UPPER_AND_LOWER 2 | |
164 | ||
165 | /** | |
166 | * Copy picture field from src to dst. | |
167 | * | |
168 | * @param src_field copy from upper, lower field or both | |
169 | * @param interleave leave a padding line between each copied line | |
170 | * @param dst_field copy to upper or lower field, | |
171 | * only meaningful when interleave is selected | |
172 | * @param flags context flags | |
173 | */ | |
174 | static inline | |
175 | void copy_picture_field(uint8_t *dst[4], int dst_linesize[4], | |
176 | const uint8_t *src[4], int src_linesize[4], | |
177 | enum AVPixelFormat format, int w, int src_h, | |
178 | int src_field, int interleave, int dst_field, | |
179 | int flags) | |
180 | { | |
181 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(format); | |
182 | int plane, vsub = desc->log2_chroma_h; | |
183 | int k = src_field == FIELD_UPPER_AND_LOWER ? 1 : 2; | |
184 | int h, i; | |
185 | ||
186 | for (plane = 0; plane < desc->nb_components; plane++) { | |
187 | int lines = plane == 1 || plane == 2 ? FF_CEIL_RSHIFT(src_h, vsub) : src_h; | |
188 | int linesize = av_image_get_linesize(format, w, plane); | |
189 | uint8_t *dstp = dst[plane]; | |
190 | const uint8_t *srcp = src[plane]; | |
191 | ||
192 | if (linesize < 0) | |
193 | return; | |
194 | ||
195 | lines = (lines + (src_field == FIELD_UPPER)) / k; | |
196 | if (src_field == FIELD_LOWER) | |
197 | srcp += src_linesize[plane]; | |
198 | if (interleave && dst_field == FIELD_LOWER) | |
199 | dstp += dst_linesize[plane]; | |
200 | if (flags & TINTERLACE_FLAG_VLPF) { | |
201 | // Low-pass filtering is required when creating an interlaced destination from | |
202 | // a progressive source which contains high-frequency vertical detail. | |
203 | // Filtering will reduce interlace 'twitter' and Moire patterning. | |
204 | int srcp_linesize = src_linesize[plane] * k; | |
205 | int dstp_linesize = dst_linesize[plane] * (interleave ? 2 : 1); | |
206 | for (h = lines; h > 0; h--) { | |
207 | const uint8_t *srcp_above = srcp - src_linesize[plane]; | |
208 | const uint8_t *srcp_below = srcp + src_linesize[plane]; | |
209 | if (h == lines) srcp_above = srcp; // there is no line above | |
210 | if (h == 1) srcp_below = srcp; // there is no line below | |
211 | for (i = 0; i < linesize; i++) { | |
212 | // this calculation is an integer representation of | |
213 | // '0.5 * current + 0.25 * above + 0.25 * below' | |
214 | // '1 +' is for rounding. */ | |
215 | dstp[i] = (1 + srcp[i] + srcp[i] + srcp_above[i] + srcp_below[i]) >> 2; | |
216 | } | |
217 | dstp += dstp_linesize; | |
218 | srcp += srcp_linesize; | |
219 | } | |
220 | } else { | |
221 | av_image_copy_plane(dstp, dst_linesize[plane] * (interleave ? 2 : 1), | |
222 | srcp, src_linesize[plane]*k, linesize, lines); | |
223 | } | |
224 | } | |
225 | } | |
226 | ||
227 | static int filter_frame(AVFilterLink *inlink, AVFrame *picref) | |
228 | { | |
229 | AVFilterContext *ctx = inlink->dst; | |
230 | AVFilterLink *outlink = ctx->outputs[0]; | |
231 | TInterlaceContext *tinterlace = ctx->priv; | |
232 | AVFrame *cur, *next, *out; | |
233 | int field, tff, ret; | |
234 | ||
235 | av_frame_free(&tinterlace->cur); | |
236 | tinterlace->cur = tinterlace->next; | |
237 | tinterlace->next = picref; | |
238 | ||
239 | cur = tinterlace->cur; | |
240 | next = tinterlace->next; | |
241 | /* we need at least two frames */ | |
242 | if (!tinterlace->cur) | |
243 | return 0; | |
244 | ||
245 | switch (tinterlace->mode) { | |
246 | case MODE_MERGE: /* move the odd frame into the upper field of the new image, even into | |
247 | * the lower field, generating a double-height video at half framerate */ | |
248 | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
249 | if (!out) | |
250 | return AVERROR(ENOMEM); | |
251 | av_frame_copy_props(out, cur); | |
252 | out->height = outlink->h; | |
253 | out->interlaced_frame = 1; | |
254 | out->top_field_first = 1; | |
255 | ||
256 | /* write odd frame lines into the upper field of the new frame */ | |
257 | copy_picture_field(out->data, out->linesize, | |
258 | (const uint8_t **)cur->data, cur->linesize, | |
259 | inlink->format, inlink->w, inlink->h, | |
260 | FIELD_UPPER_AND_LOWER, 1, FIELD_UPPER, tinterlace->flags); | |
261 | /* write even frame lines into the lower field of the new frame */ | |
262 | copy_picture_field(out->data, out->linesize, | |
263 | (const uint8_t **)next->data, next->linesize, | |
264 | inlink->format, inlink->w, inlink->h, | |
265 | FIELD_UPPER_AND_LOWER, 1, FIELD_LOWER, tinterlace->flags); | |
266 | av_frame_free(&tinterlace->next); | |
267 | break; | |
268 | ||
269 | case MODE_DROP_ODD: /* only output even frames, odd frames are dropped; height unchanged, half framerate */ | |
270 | case MODE_DROP_EVEN: /* only output odd frames, even frames are dropped; height unchanged, half framerate */ | |
271 | out = av_frame_clone(tinterlace->mode == MODE_DROP_EVEN ? cur : next); | |
272 | if (!out) | |
273 | return AVERROR(ENOMEM); | |
274 | av_frame_free(&tinterlace->next); | |
275 | break; | |
276 | ||
277 | case MODE_PAD: /* expand each frame to double height, but pad alternate | |
278 | * lines with black; framerate unchanged */ | |
279 | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
280 | if (!out) | |
281 | return AVERROR(ENOMEM); | |
282 | av_frame_copy_props(out, cur); | |
283 | out->height = outlink->h; | |
284 | ||
285 | field = (1 + tinterlace->frame) & 1 ? FIELD_UPPER : FIELD_LOWER; | |
286 | /* copy upper and lower fields */ | |
287 | copy_picture_field(out->data, out->linesize, | |
288 | (const uint8_t **)cur->data, cur->linesize, | |
289 | inlink->format, inlink->w, inlink->h, | |
290 | FIELD_UPPER_AND_LOWER, 1, field, tinterlace->flags); | |
291 | /* pad with black the other field */ | |
292 | copy_picture_field(out->data, out->linesize, | |
293 | (const uint8_t **)tinterlace->black_data, tinterlace->black_linesize, | |
294 | inlink->format, inlink->w, inlink->h, | |
295 | FIELD_UPPER_AND_LOWER, 1, !field, tinterlace->flags); | |
296 | break; | |
297 | ||
298 | /* interleave upper/lower lines from odd frames with lower/upper lines from even frames, | |
299 | * halving the frame rate and preserving image height */ | |
300 | case MODE_INTERLEAVE_TOP: /* top field first */ | |
301 | case MODE_INTERLEAVE_BOTTOM: /* bottom field first */ | |
302 | tff = tinterlace->mode == MODE_INTERLEAVE_TOP; | |
303 | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
304 | if (!out) | |
305 | return AVERROR(ENOMEM); | |
306 | av_frame_copy_props(out, cur); | |
307 | out->interlaced_frame = 1; | |
308 | out->top_field_first = tff; | |
309 | ||
310 | /* copy upper/lower field from cur */ | |
311 | copy_picture_field(out->data, out->linesize, | |
312 | (const uint8_t **)cur->data, cur->linesize, | |
313 | inlink->format, inlink->w, inlink->h, | |
314 | tff ? FIELD_UPPER : FIELD_LOWER, 1, tff ? FIELD_UPPER : FIELD_LOWER, | |
315 | tinterlace->flags); | |
316 | /* copy lower/upper field from next */ | |
317 | copy_picture_field(out->data, out->linesize, | |
318 | (const uint8_t **)next->data, next->linesize, | |
319 | inlink->format, inlink->w, inlink->h, | |
320 | tff ? FIELD_LOWER : FIELD_UPPER, 1, tff ? FIELD_LOWER : FIELD_UPPER, | |
321 | tinterlace->flags); | |
322 | av_frame_free(&tinterlace->next); | |
323 | break; | |
324 | case MODE_INTERLACEX2: /* re-interlace preserving image height, double frame rate */ | |
325 | /* output current frame first */ | |
326 | out = av_frame_clone(cur); | |
327 | if (!out) | |
328 | return AVERROR(ENOMEM); | |
329 | out->interlaced_frame = 1; | |
330 | if (cur->pts != AV_NOPTS_VALUE) | |
331 | out->pts = cur->pts*2; | |
332 | ||
333 | if ((ret = ff_filter_frame(outlink, out)) < 0) | |
334 | return ret; | |
335 | ||
336 | /* output mix of current and next frame */ | |
337 | tff = next->top_field_first; | |
338 | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
339 | if (!out) | |
340 | return AVERROR(ENOMEM); | |
341 | av_frame_copy_props(out, next); | |
342 | out->interlaced_frame = 1; | |
343 | ||
344 | if (next->pts != AV_NOPTS_VALUE && cur->pts != AV_NOPTS_VALUE) | |
345 | out->pts = cur->pts + next->pts; | |
346 | else | |
347 | out->pts = AV_NOPTS_VALUE; | |
348 | /* write current frame second field lines into the second field of the new frame */ | |
349 | copy_picture_field(out->data, out->linesize, | |
350 | (const uint8_t **)cur->data, cur->linesize, | |
351 | inlink->format, inlink->w, inlink->h, | |
352 | tff ? FIELD_LOWER : FIELD_UPPER, 1, tff ? FIELD_LOWER : FIELD_UPPER, | |
353 | tinterlace->flags); | |
354 | /* write next frame first field lines into the first field of the new frame */ | |
355 | copy_picture_field(out->data, out->linesize, | |
356 | (const uint8_t **)next->data, next->linesize, | |
357 | inlink->format, inlink->w, inlink->h, | |
358 | tff ? FIELD_UPPER : FIELD_LOWER, 1, tff ? FIELD_UPPER : FIELD_LOWER, | |
359 | tinterlace->flags); | |
360 | break; | |
361 | default: | |
362 | av_assert0(0); | |
363 | } | |
364 | ||
365 | ret = ff_filter_frame(outlink, out); | |
366 | tinterlace->frame++; | |
367 | ||
368 | return ret; | |
369 | } | |
370 | ||
371 | static const AVFilterPad tinterlace_inputs[] = { | |
372 | { | |
373 | .name = "default", | |
374 | .type = AVMEDIA_TYPE_VIDEO, | |
375 | .filter_frame = filter_frame, | |
376 | }, | |
377 | { NULL } | |
378 | }; | |
379 | ||
380 | static const AVFilterPad tinterlace_outputs[] = { | |
381 | { | |
382 | .name = "default", | |
383 | .type = AVMEDIA_TYPE_VIDEO, | |
384 | .config_props = config_out_props, | |
385 | }, | |
386 | { NULL } | |
387 | }; | |
388 | ||
389 | AVFilter ff_vf_tinterlace = { | |
390 | .name = "tinterlace", | |
391 | .description = NULL_IF_CONFIG_SMALL("Perform temporal field interlacing."), | |
392 | .priv_size = sizeof(TInterlaceContext), | |
393 | .uninit = uninit, | |
394 | .query_formats = query_formats, | |
395 | .inputs = tinterlace_inputs, | |
396 | .outputs = tinterlace_outputs, | |
397 | .priv_class = &tinterlace_class, | |
398 | }; |