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