1/*
2 * Copyright (C) 2012 Michael Niedermayer <michaelni@gmx.at>
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 <float.h> /* FLT_MAX */
22
23#include "libavutil/cpu.h"
24#include "libavutil/common.h"
25#include "libavutil/opt.h"
26#include "libavutil/pixdesc.h"
27#include "avfilter.h"
28#include "internal.h"
29
30#define HIST_SIZE 4
31
32typedef enum {
33    TFF,
34    BFF,
35    PROGRSSIVE,
36    UNDETERMINED,
37} Type;
38
39typedef struct {
40    const AVClass *class;
41    float interlace_threshold;
42    float progressive_threshold;
43
44    Type last_type;
45    int prestat[4];
46    int poststat[4];
47
48    uint8_t history[HIST_SIZE];
49
50    AVFrame *cur;
51    AVFrame *next;
52    AVFrame *prev;
53    int (*filter_line)(const uint8_t *prev, const uint8_t *cur, const uint8_t *next, int w);
54
55    const AVPixFmtDescriptor *csp;
56} IDETContext;
57
58#define OFFSET(x) offsetof(IDETContext, x)
59#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
60
61static const AVOption idet_options[] = {
62    { "intl_thres", "set interlacing threshold", OFFSET(interlace_threshold),   AV_OPT_TYPE_FLOAT, {.dbl = 1.04}, -1, FLT_MAX, FLAGS },
63    { "prog_thres", "set progressive threshold", OFFSET(progressive_threshold), AV_OPT_TYPE_FLOAT, {.dbl = 1.5},  -1, FLT_MAX, FLAGS },
64    { NULL }
65};
66
67AVFILTER_DEFINE_CLASS(idet);
68
69static const char *type2str(Type type)
70{
71    switch(type) {
72        case TFF         : return "Top Field First   ";
73        case BFF         : return "Bottom Field First";
74        case PROGRSSIVE  : return "Progressive       ";
75        case UNDETERMINED: return "Undetermined      ";
76    }
77    return NULL;
78}
79
80static int filter_line_c(const uint8_t *a, const uint8_t *b, const uint8_t *c, int w)
81{
82    int x;
83    int ret=0;
84
85    for(x=0; x<w; x++){
86        int v = (*a++ + *c++) - 2 * *b++;
87        ret += FFABS(v);
88    }
89
90    return ret;
91}
92
93static int filter_line_c_16bit(const uint16_t *a, const uint16_t *b, const uint16_t *c, int w)
94{
95    int x;
96    int ret=0;
97
98    for(x=0; x<w; x++){
99        int v = (*a++ + *c++) - 2 * *b++;
100        ret += FFABS(v);
101    }
102
103    return ret;
104}
105
106static void filter(AVFilterContext *ctx)
107{
108    IDETContext *idet = ctx->priv;
109    int y, i;
110    int64_t alpha[2]={0};
111    int64_t delta=0;
112    Type type, best_type;
113    int match = 0;
114
115    for (i = 0; i < idet->csp->nb_components; i++) {
116        int w = idet->cur->width;
117        int h = idet->cur->height;
118        int refs = idet->cur->linesize[i];
119
120        if (i && i<3) {
121            w = FF_CEIL_RSHIFT(w, idet->csp->log2_chroma_w);
122            h = FF_CEIL_RSHIFT(h, idet->csp->log2_chroma_h);
123        }
124
125        for (y = 2; y < h - 2; y++) {
126            uint8_t *prev = &idet->prev->data[i][y*refs];
127            uint8_t *cur  = &idet->cur ->data[i][y*refs];
128            uint8_t *next = &idet->next->data[i][y*refs];
129            alpha[ y   &1] += idet->filter_line(cur-refs, prev, cur+refs, w);
130            alpha[(y^1)&1] += idet->filter_line(cur-refs, next, cur+refs, w);
131            delta          += idet->filter_line(cur-refs,  cur, cur+refs, w);
132        }
133    }
134
135    if      (alpha[0] > idet->interlace_threshold * alpha[1]){
136        type = TFF;
137    }else if(alpha[1] > idet->interlace_threshold * alpha[0]){
138        type = BFF;
139    }else if(alpha[1] > idet->progressive_threshold * delta){
140        type = PROGRSSIVE;
141    }else{
142        type = UNDETERMINED;
143    }
144
145    memmove(idet->history+1, idet->history, HIST_SIZE-1);
146    idet->history[0] = type;
147    best_type = UNDETERMINED;
148    for(i=0; i<HIST_SIZE; i++){
149        if(idet->history[i] != UNDETERMINED){
150            if(best_type == UNDETERMINED)
151                best_type = idet->history[i];
152
153            if(idet->history[i] == best_type) {
154                match++;
155            }else{
156                match=0;
157                break;
158            }
159        }
160    }
161    if(idet->last_type == UNDETERMINED){
162        if(match  ) idet->last_type = best_type;
163    }else{
164        if(match>2) idet->last_type = best_type;
165    }
166
167    if      (idet->last_type == TFF){
168        idet->cur->top_field_first = 1;
169        idet->cur->interlaced_frame = 1;
170    }else if(idet->last_type == BFF){
171        idet->cur->top_field_first = 0;
172        idet->cur->interlaced_frame = 1;
173    }else if(idet->last_type == PROGRSSIVE){
174        idet->cur->interlaced_frame = 0;
175    }
176
177    idet->prestat [           type] ++;
178    idet->poststat[idet->last_type] ++;
179    av_log(ctx, AV_LOG_DEBUG, "Single frame:%s, Multi frame:%s\n", type2str(type), type2str(idet->last_type));
180}
181
182static int filter_frame(AVFilterLink *link, AVFrame *picref)
183{
184    AVFilterContext *ctx = link->dst;
185    IDETContext *idet = ctx->priv;
186
187    if (idet->prev)
188        av_frame_free(&idet->prev);
189    idet->prev = idet->cur;
190    idet->cur  = idet->next;
191    idet->next = picref;
192
193    if (!idet->cur)
194        return 0;
195
196    if (!idet->prev)
197        idet->prev = av_frame_clone(idet->cur);
198
199    if (!idet->csp)
200        idet->csp = av_pix_fmt_desc_get(link->format);
201    if (idet->csp->comp[0].depth_minus1 / 8 == 1)
202        idet->filter_line = (void*)filter_line_c_16bit;
203
204    filter(ctx);
205
206    return ff_filter_frame(ctx->outputs[0], av_frame_clone(idet->cur));
207}
208
209static av_cold void uninit(AVFilterContext *ctx)
210{
211    IDETContext *idet = ctx->priv;
212
213    av_log(ctx, AV_LOG_INFO, "Single frame detection: TFF:%d BFF:%d Progressive:%d Undetermined:%d\n",
214           idet->prestat[TFF],
215           idet->prestat[BFF],
216           idet->prestat[PROGRSSIVE],
217           idet->prestat[UNDETERMINED]
218    );
219    av_log(ctx, AV_LOG_INFO, "Multi frame detection: TFF:%d BFF:%d Progressive:%d Undetermined:%d\n",
220           idet->poststat[TFF],
221           idet->poststat[BFF],
222           idet->poststat[PROGRSSIVE],
223           idet->poststat[UNDETERMINED]
224    );
225
226    av_frame_free(&idet->prev);
227    av_frame_free(&idet->cur );
228    av_frame_free(&idet->next);
229}
230
231static int query_formats(AVFilterContext *ctx)
232{
233    static const enum AVPixelFormat pix_fmts[] = {
234        AV_PIX_FMT_YUV420P,
235        AV_PIX_FMT_YUV422P,
236        AV_PIX_FMT_YUV444P,
237        AV_PIX_FMT_YUV410P,
238        AV_PIX_FMT_YUV411P,
239        AV_PIX_FMT_GRAY8,
240        AV_PIX_FMT_YUVJ420P,
241        AV_PIX_FMT_YUVJ422P,
242        AV_PIX_FMT_YUVJ444P,
243        AV_PIX_FMT_GRAY16,
244        AV_PIX_FMT_YUV440P,
245        AV_PIX_FMT_YUVJ440P,
246        AV_PIX_FMT_YUV420P10,
247        AV_PIX_FMT_YUV422P10,
248        AV_PIX_FMT_YUV444P10,
249        AV_PIX_FMT_YUV420P16,
250        AV_PIX_FMT_YUV422P16,
251        AV_PIX_FMT_YUV444P16,
252        AV_PIX_FMT_YUVA420P,
253        AV_PIX_FMT_NONE
254    };
255
256    ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
257
258    return 0;
259}
260
261static int config_output(AVFilterLink *outlink)
262{
263    outlink->flags |= FF_LINK_FLAG_REQUEST_LOOP;
264    return 0;
265}
266
267static av_cold int init(AVFilterContext *ctx)
268{
269    IDETContext *idet = ctx->priv;
270
271    idet->last_type = UNDETERMINED;
272    memset(idet->history, UNDETERMINED, HIST_SIZE);
273
274    idet->filter_line = filter_line_c;
275
276    return 0;
277}
278
279
280static const AVFilterPad idet_inputs[] = {
281    {
282        .name         = "default",
283        .type         = AVMEDIA_TYPE_VIDEO,
284        .filter_frame = filter_frame,
285    },
286    { NULL }
287};
288
289static const AVFilterPad idet_outputs[] = {
290    {
291        .name         = "default",
292        .type         = AVMEDIA_TYPE_VIDEO,
293        .config_props = config_output,
294    },
295    { NULL }
296};
297
298AVFilter ff_vf_idet = {
299    .name          = "idet",
300    .description   = NULL_IF_CONFIG_SMALL("Interlace detect Filter."),
301    .priv_size     = sizeof(IDETContext),
302    .init          = init,
303    .uninit        = uninit,
304    .query_formats = query_formats,
305    .inputs        = idet_inputs,
306    .outputs       = idet_outputs,
307    .priv_class    = &idet_class,
308};
309