]> git.sesse.net Git - ffmpeg/blob - libavfilter/vf_idet.c
Merge remote-tracking branch 'qatar/master'
[ffmpeg] / libavfilter / vf_idet.c
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 "libavutil/cpu.h"
22 #include "libavutil/common.h"
23 #include "libavutil/pixdesc.h"
24 #include "avfilter.h"
25
26 #undef NDEBUG
27 #include <assert.h>
28
29 #define HIST_SIZE 4
30
31 typedef enum {
32     TFF,
33     BFF,
34     PROGRSSIVE,
35     UNDETERMINED,
36 } Type;
37
38 typedef struct {
39     float interlace_threshold;
40     float progressive_threshold;
41
42     Type last_type;
43     Type prestat[4];
44     Type poststat[4];
45
46     uint8_t history[HIST_SIZE];
47
48     AVFilterBufferRef *cur;
49     AVFilterBufferRef *next;
50     AVFilterBufferRef *prev;
51     AVFilterBufferRef *out;
52     int (*filter_line)(const uint8_t *prev, const uint8_t *cur, const uint8_t *next, int w);
53
54     const AVPixFmtDescriptor *csp;
55 } IDETContext;
56
57 static const char *type2str(Type type)
58 {
59     switch(type) {
60         case TFF         : return "Top Field First   ";
61         case BFF         : return "Bottom Field First";
62         case PROGRSSIVE  : return "Progressive       ";
63         case UNDETERMINED: return "Undetermined      ";
64     }
65     return NULL;
66 }
67
68 static int filter_line_c(const uint8_t *a, const uint8_t *b, const uint8_t *c, int w)
69 {
70     int x;
71     int ret=0;
72
73     for(x=0; x<w; x++){
74         ret += FFABS((*a++ + *c++) - 2 * *b++);
75     }
76
77     return ret;
78 }
79
80 static int filter_line_c_16bit(const uint16_t *a, const uint16_t *b, const uint16_t *c, int w)
81 {
82     int x;
83     int ret=0;
84
85     for(x=0; x<w; x++){
86         ret += FFABS((*a++ + *c++) - 2 * *b++);
87     }
88
89     return ret;
90 }
91
92 static void filter(AVFilterContext *ctx)
93 {
94     IDETContext *idet = ctx->priv;
95     int y, i;
96     int64_t alpha[2]={0};
97     int64_t delta=0;
98     Type type, best_type;
99     int match = 0;
100
101     for (i = 0; i < idet->csp->nb_components; i++) {
102         int w = idet->cur->video->w;
103         int h = idet->cur->video->h;
104         int refs = idet->cur->linesize[i];
105
106         if (i && i<3) {
107             w >>= idet->csp->log2_chroma_w;
108             h >>= idet->csp->log2_chroma_h;
109         }
110
111         for (y = 2; y < h - 2; y++) {
112             uint8_t *prev = &idet->prev->data[i][y*refs];
113             uint8_t *cur  = &idet->cur ->data[i][y*refs];
114             uint8_t *next = &idet->next->data[i][y*refs];
115             alpha[ y   &1] += idet->filter_line(cur-refs, prev, cur+refs, w);
116             alpha[(y^1)&1] += idet->filter_line(cur-refs, next, cur+refs, w);
117             delta          += idet->filter_line(cur-refs,  cur, cur+refs, w);
118         }
119     }
120 #if HAVE_MMX
121     __asm__ volatile("emms \n\t" : : : "memory");
122 #endif
123
124     if      (alpha[0] / (float)alpha[1] > idet->interlace_threshold){
125         type = TFF;
126     }else if(alpha[1] / (float)alpha[0] > idet->interlace_threshold){
127         type = BFF;
128     }else if(alpha[1] / (float)delta    > idet->progressive_threshold){
129         type = PROGRSSIVE;
130     }else{
131         type = UNDETERMINED;
132     }
133
134     memmove(idet->history+1, idet->history, HIST_SIZE-1);
135     idet->history[0] = type;
136     best_type = UNDETERMINED;
137     for(i=0; i<HIST_SIZE; i++){
138         if(idet->history[i] != UNDETERMINED){
139             if(best_type == UNDETERMINED)
140                 best_type = idet->history[i];
141
142             if(idet->history[i] == best_type) {
143                 match++;
144             }else{
145                 match=0;
146                 break;
147             }
148         }
149     }
150     if(idet->last_type == UNDETERMINED){
151         if(match  ) idet->last_type = best_type;
152     }else{
153         if(match>2) idet->last_type = best_type;
154     }
155
156     if      (idet->last_type == TFF){
157         idet->cur->video->top_field_first = 1;
158         idet->cur->video->interlaced = 1;
159     }else if(idet->last_type == BFF){
160         idet->cur->video->top_field_first = 0;
161         idet->cur->video->interlaced = 1;
162     }else if(idet->last_type == PROGRSSIVE){
163         idet->cur->video->interlaced = 0;
164     }
165
166     idet->prestat [           type] ++;
167     idet->poststat[idet->last_type] ++;
168     av_log(ctx, AV_LOG_DEBUG, "Single frame:%s, Multi frame:%s\n", type2str(type), type2str(idet->last_type));
169 }
170
171 static void start_frame(AVFilterLink *link, AVFilterBufferRef *picref)
172 {
173     AVFilterContext *ctx = link->dst;
174     IDETContext *idet = ctx->priv;
175
176     if (idet->prev)
177         avfilter_unref_buffer(idet->prev);
178     idet->prev = idet->cur;
179     idet->cur  = idet->next;
180     idet->next = picref;
181
182     if (!idet->cur)
183         return;
184
185     if (!idet->prev)
186         idet->prev = avfilter_ref_buffer(idet->cur, AV_PERM_READ);
187
188     avfilter_start_frame(ctx->outputs[0], avfilter_ref_buffer(idet->cur, AV_PERM_READ));
189 }
190
191 static void end_frame(AVFilterLink *link)
192 {
193     AVFilterContext *ctx = link->dst;
194     IDETContext *idet = ctx->priv;
195
196     if (!idet->cur)
197         return;
198
199     if (!idet->csp)
200         idet->csp = &av_pix_fmt_descriptors[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     avfilter_draw_slice(ctx->outputs[0], 0, link->h, 1);
207     avfilter_end_frame(ctx->outputs[0]);
208 }
209
210 static int request_frame(AVFilterLink *link)
211 {
212     AVFilterContext *ctx = link->src;
213     IDETContext *idet = ctx->priv;
214
215     do {
216         int ret;
217
218         if ((ret = avfilter_request_frame(link->src->inputs[0])))
219             return ret;
220     } while (!idet->cur);
221
222     return 0;
223 }
224
225 static int poll_frame(AVFilterLink *link)
226 {
227     IDETContext *idet = link->src->priv;
228     int ret, val;
229
230     val = avfilter_poll_frame(link->src->inputs[0]);
231
232     if (val >= 1 && !idet->next) { //FIXME change API to not requre this red tape
233         if ((ret = avfilter_request_frame(link->src->inputs[0])) < 0)
234             return ret;
235         val = avfilter_poll_frame(link->src->inputs[0]);
236     }
237     assert(idet->next || !val);
238
239     return val;
240 }
241
242 static av_cold void uninit(AVFilterContext *ctx)
243 {
244     IDETContext *idet = ctx->priv;
245
246     av_log(ctx, AV_LOG_INFO, "Single frame detection: TFF:%d BFF:%d Progressive:%d Undetermined:%d\n",
247            idet->prestat[TFF],
248            idet->prestat[BFF],
249            idet->prestat[PROGRSSIVE],
250            idet->prestat[UNDETERMINED]
251     );
252     av_log(ctx, AV_LOG_INFO, "Multi frame detection: TFF:%d BFF:%d Progressive:%d Undetermined:%d\n",
253            idet->poststat[TFF],
254            idet->poststat[BFF],
255            idet->poststat[PROGRSSIVE],
256            idet->poststat[UNDETERMINED]
257     );
258
259     if (idet->prev) avfilter_unref_buffer(idet->prev);
260     if (idet->cur ) avfilter_unref_buffer(idet->cur );
261     if (idet->next) avfilter_unref_buffer(idet->next);
262 }
263
264 static int query_formats(AVFilterContext *ctx)
265 {
266     static const enum PixelFormat pix_fmts[] = {
267         PIX_FMT_YUV420P,
268         PIX_FMT_YUV422P,
269         PIX_FMT_YUV444P,
270         PIX_FMT_YUV410P,
271         PIX_FMT_YUV411P,
272         PIX_FMT_GRAY8,
273         PIX_FMT_YUVJ420P,
274         PIX_FMT_YUVJ422P,
275         PIX_FMT_YUVJ444P,
276         AV_NE( PIX_FMT_GRAY16BE, PIX_FMT_GRAY16LE ),
277         PIX_FMT_YUV440P,
278         PIX_FMT_YUVJ440P,
279         AV_NE( PIX_FMT_YUV420P10BE, PIX_FMT_YUV420P10LE ),
280         AV_NE( PIX_FMT_YUV422P10BE, PIX_FMT_YUV422P10LE ),
281         AV_NE( PIX_FMT_YUV444P10BE, PIX_FMT_YUV444P10LE ),
282         AV_NE( PIX_FMT_YUV420P16BE, PIX_FMT_YUV420P16LE ),
283         AV_NE( PIX_FMT_YUV422P16BE, PIX_FMT_YUV422P16LE ),
284         AV_NE( PIX_FMT_YUV444P16BE, PIX_FMT_YUV444P16LE ),
285         PIX_FMT_YUVA420P,
286         PIX_FMT_NONE
287     };
288
289     avfilter_set_common_pixel_formats(ctx, avfilter_make_format_list(pix_fmts));
290
291     return 0;
292 }
293
294 static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
295 {
296     IDETContext *idet = ctx->priv;
297
298     idet->csp = NULL;
299
300     idet->interlace_threshold   = 1.01;
301     idet->progressive_threshold = 2.5;
302
303     if (args) sscanf(args, "%f:%f", &idet->interlace_threshold, &idet->progressive_threshold);
304
305     idet->last_type = UNDETERMINED;
306     memset(idet->history, UNDETERMINED, HIST_SIZE);
307
308     idet->filter_line = filter_line_c;
309
310     return 0;
311 }
312
313 static void null_draw_slice(AVFilterLink *link, int y, int h, int slice_dir) { }
314
315 AVFilter avfilter_vf_idet = {
316     .name          = "idet",
317     .description   = NULL_IF_CONFIG_SMALL("Interlace detect Filter."),
318
319     .priv_size     = sizeof(IDETContext),
320     .init          = init,
321     .uninit        = uninit,
322     .query_formats = query_formats,
323
324     .inputs    = (const AVFilterPad[]) {{ .name       = "default",
325                                           .type             = AVMEDIA_TYPE_VIDEO,
326                                           .start_frame      = start_frame,
327                                           .draw_slice       = null_draw_slice,
328                                           .end_frame        = end_frame,
329                                           .rej_perms        = AV_PERM_REUSE2, },
330                                         { .name = NULL}},
331
332     .outputs   = (const AVFilterPad[]) {{ .name       = "default",
333                                           .type             = AVMEDIA_TYPE_VIDEO,
334                                           .poll_frame       = poll_frame,
335                                           .request_frame    = request_frame, },
336                                         { .name = NULL}},
337 };