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