| 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 | #include "tinterlace.h" |
| 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 |
| 38 | #define TINTERLACE_FLAG_EXACT_TB 2 |
| 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" }, |
| 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" }, |
| 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 | |
| 67 | static const AVRational standard_tbs[] = { |
| 68 | {1, 25}, |
| 69 | {1, 30}, |
| 70 | {1001, 30000}, |
| 71 | }; |
| 72 | |
| 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 | |
| 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 | |
| 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; |
| 115 | int i; |
| 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 | } |
| 147 | tinterlace->preout_time_base = inlink->time_base; |
| 148 | if (tinterlace->mode == MODE_INTERLACEX2) { |
| 149 | tinterlace->preout_time_base.den *= 2; |
| 150 | outlink->frame_rate = av_mul_q(inlink->frame_rate, (AVRational){2,1}); |
| 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); |
| 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 |
| 192 | void copy_picture_field(TInterlaceContext *tinterlace, |
| 193 | uint8_t *dst[4], int dst_linesize[4], |
| 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; |
| 202 | int h; |
| 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; |
| 206 | int cols = plane == 1 || plane == 2 ? FF_CEIL_RSHIFT( w, desc->log2_chroma_w) : w; |
| 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 |
| 230 | |
| 231 | tinterlace->lowpass_line(dstp, cols, srcp, srcp_above, srcp_below); |
| 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 */ |
| 272 | copy_picture_field(tinterlace, out->data, out->linesize, |
| 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 */ |
| 277 | copy_picture_field(tinterlace, out->data, out->linesize, |
| 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 */ |
| 302 | copy_picture_field(tinterlace, out->data, out->linesize, |
| 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 */ |
| 307 | copy_picture_field(tinterlace, out->data, out->linesize, |
| 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 */ |
| 326 | copy_picture_field(tinterlace, out->data, out->linesize, |
| 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 */ |
| 332 | copy_picture_field(tinterlace, out->data, out->linesize, |
| 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 | |
| 348 | out->pts = av_rescale_q(out->pts, tinterlace->preout_time_base, outlink->time_base); |
| 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; |
| 359 | out->top_field_first = !tff; |
| 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 */ |
| 366 | copy_picture_field(tinterlace, out->data, out->linesize, |
| 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 */ |
| 372 | copy_picture_field(tinterlace, out->data, out->linesize, |
| 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 | |
| 382 | out->pts = av_rescale_q(out->pts, tinterlace->preout_time_base, outlink->time_base); |
| 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 | }; |