libavfilter/vf_idet.c
e3e89b6d
 /*
  * Copyright (C) 2012 Michael Niedermayer <michaelni@gmx.at>
  *
  * This file is part of FFmpeg.
  *
  * FFmpeg is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * License as published by the Free Software Foundation; either
  * version 2.1 of the License, or (at your option) any later version.
  *
  * FFmpeg is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with FFmpeg; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
 #include "libavutil/cpu.h"
 #include "libavutil/common.h"
 #include "libavutil/pixdesc.h"
 #include "avfilter.h"
 
 #undef NDEBUG
 #include <assert.h>
 
fa1f92a4
 #define HIST_SIZE 4
 
ca2b450c
 typedef enum {
     TFF,
     BFF,
     PROGRSSIVE,
     UNDETERMINED,
 } Type;
 
e3e89b6d
 typedef struct {
     float interlace_threshold;
     float progressive_threshold;
 
1a5c08ee
     Type last_type;
ca2b450c
     Type prestat[4];
4d4df310
     Type poststat[4];
5d9cfd87
 
fa1f92a4
     uint8_t history[HIST_SIZE];
 
e3e89b6d
     AVFilterBufferRef *cur;
     AVFilterBufferRef *next;
     AVFilterBufferRef *prev;
     AVFilterBufferRef *out;
648dbae5
     int (*filter_line)(const uint8_t *prev, const uint8_t *cur, const uint8_t *next, int w);
e3e89b6d
 
     const AVPixFmtDescriptor *csp;
 } IDETContext;
 
0477254d
 static const char *type2str(Type type)
 {
     switch(type) {
         case TFF         : return "Top Field First   ";
         case BFF         : return "Bottom Field First";
         case PROGRSSIVE  : return "Progressive       ";
         case UNDETERMINED: return "Undetermined      ";
     }
     return NULL;
 }
e3e89b6d
 
 static int filter_line_c(const uint8_t *a, const uint8_t *b, const uint8_t *c, int w)
 {
     int x;
     int ret=0;
 
     for(x=0; x<w; x++){
         ret += FFABS((*a++ + *c++) - 2 * *b++);
     }
 
     return ret;
 }
 
 static int filter_line_c_16bit(const uint16_t *a, const uint16_t *b, const uint16_t *c, int w)
 {
     int x;
     int ret=0;
 
     for(x=0; x<w; x++){
         ret += FFABS((*a++ + *c++) - 2 * *b++);
     }
 
     return ret;
 }
 
 static void filter(AVFilterContext *ctx)
 {
     IDETContext *idet = ctx->priv;
     int y, i;
     int64_t alpha[2]={0};
     int64_t delta=0;
fa1f92a4
     Type type, best_type;
     int match = 0;
e3e89b6d
 
     for (i = 0; i < idet->csp->nb_components; i++) {
         int w = idet->cur->video->w;
         int h = idet->cur->video->h;
         int refs = idet->cur->linesize[i];
 
         if (i && i<3) {
             w >>= idet->csp->log2_chroma_w;
             h >>= idet->csp->log2_chroma_h;
         }
 
         for (y = 2; y < h - 2; y++) {
             uint8_t *prev = &idet->prev->data[i][y*refs];
             uint8_t *cur  = &idet->cur ->data[i][y*refs];
             uint8_t *next = &idet->next->data[i][y*refs];
             alpha[ y   &1] += idet->filter_line(cur-refs, prev, cur+refs, w);
             alpha[(y^1)&1] += idet->filter_line(cur-refs, next, cur+refs, w);
             delta          += idet->filter_line(cur-refs,  cur, cur+refs, w);
         }
     }
 #if HAVE_MMX
     __asm__ volatile("emms \n\t" : : : "memory");
 #endif
 
     if      (alpha[0] / (float)alpha[1] > idet->interlace_threshold){
ca2b450c
         type = TFF;
     }else if(alpha[1] / (float)alpha[0] > idet->interlace_threshold){
         type = BFF;
     }else if(alpha[1] / (float)delta    > idet->progressive_threshold){
         type = PROGRSSIVE;
     }else{
         type = UNDETERMINED;
     }
 
fa1f92a4
     memmove(idet->history+1, idet->history, HIST_SIZE-1);
     idet->history[0] = type;
     best_type = UNDETERMINED;
     for(i=0; i<HIST_SIZE; i++){
         if(idet->history[i] != UNDETERMINED){
             if(best_type == UNDETERMINED)
                 best_type = idet->history[i];
 
             if(idet->history[i] == best_type) {
                 match++;
             }else{
                 match=0;
                 break;
             }
         }
     }
     if(idet->last_type == UNDETERMINED){
         if(match  ) idet->last_type = best_type;
     }else{
         if(match>2) idet->last_type = best_type;
     }
1a5c08ee
 
     if      (idet->last_type == TFF){
baf0c79a
         idet->cur->video->top_field_first = 1;
         idet->cur->video->interlaced = 1;
1a5c08ee
     }else if(idet->last_type == BFF){
baf0c79a
         idet->cur->video->top_field_first = 0;
         idet->cur->video->interlaced = 1;
1a5c08ee
     }else if(idet->last_type == PROGRSSIVE){
baf0c79a
         idet->cur->video->interlaced = 0;
e3e89b6d
     }
0477254d
 
4d4df310
     idet->prestat [           type] ++;
     idet->poststat[idet->last_type] ++;
c59e73d2
     av_log(ctx, AV_LOG_DEBUG, "Single frame:%s, Multi frame:%s\n", type2str(type), type2str(idet->last_type));
e3e89b6d
 }
 
 static void start_frame(AVFilterLink *link, AVFilterBufferRef *picref)
 {
     AVFilterContext *ctx = link->dst;
     IDETContext *idet = ctx->priv;
 
     if (idet->prev)
         avfilter_unref_buffer(idet->prev);
     idet->prev = idet->cur;
     idet->cur  = idet->next;
     idet->next = picref;
 
     if (!idet->cur)
         return;
 
     if (!idet->prev)
         idet->prev = avfilter_ref_buffer(idet->cur, AV_PERM_READ);
 
     avfilter_start_frame(ctx->outputs[0], avfilter_ref_buffer(idet->cur, AV_PERM_READ));
 }
 
 static void end_frame(AVFilterLink *link)
 {
     AVFilterContext *ctx = link->dst;
     IDETContext *idet = ctx->priv;
 
     if (!idet->cur)
         return;
 
     if (!idet->csp)
         idet->csp = &av_pix_fmt_descriptors[link->format];
     if (idet->csp->comp[0].depth_minus1 / 8 == 1)
         idet->filter_line = (void*)filter_line_c_16bit;
 
     filter(ctx);
 
     avfilter_draw_slice(ctx->outputs[0], 0, link->h, 1);
     avfilter_end_frame(ctx->outputs[0]);
 }
 
 static int request_frame(AVFilterLink *link)
 {
     AVFilterContext *ctx = link->src;
     IDETContext *idet = ctx->priv;
 
     do {
         int ret;
 
         if ((ret = avfilter_request_frame(link->src->inputs[0])))
             return ret;
     } while (!idet->cur);
 
     return 0;
 }
 
 static int poll_frame(AVFilterLink *link)
 {
     IDETContext *idet = link->src->priv;
     int ret, val;
 
     val = avfilter_poll_frame(link->src->inputs[0]);
 
     if (val >= 1 && !idet->next) { //FIXME change API to not requre this red tape
         if ((ret = avfilter_request_frame(link->src->inputs[0])) < 0)
             return ret;
         val = avfilter_poll_frame(link->src->inputs[0]);
     }
     assert(idet->next || !val);
 
     return val;
 }
 
 static av_cold void uninit(AVFilterContext *ctx)
 {
     IDETContext *idet = ctx->priv;
 
4d4df310
     av_log(ctx, AV_LOG_INFO, "Single frame detection: TFF:%d BFF:%d Progressive:%d Undetermined:%d\n",
ca2b450c
            idet->prestat[TFF],
            idet->prestat[BFF],
            idet->prestat[PROGRSSIVE],
            idet->prestat[UNDETERMINED]
6fb35dba
     );
4d4df310
     av_log(ctx, AV_LOG_INFO, "Multi frame detection: TFF:%d BFF:%d Progressive:%d Undetermined:%d\n",
            idet->poststat[TFF],
            idet->poststat[BFF],
            idet->poststat[PROGRSSIVE],
            idet->poststat[UNDETERMINED]
     );
6fb35dba
 
e3e89b6d
     if (idet->prev) avfilter_unref_buffer(idet->prev);
     if (idet->cur ) avfilter_unref_buffer(idet->cur );
     if (idet->next) avfilter_unref_buffer(idet->next);
 }
 
 static int query_formats(AVFilterContext *ctx)
 {
     static const enum PixelFormat pix_fmts[] = {
         PIX_FMT_YUV420P,
         PIX_FMT_YUV422P,
         PIX_FMT_YUV444P,
         PIX_FMT_YUV410P,
         PIX_FMT_YUV411P,
         PIX_FMT_GRAY8,
         PIX_FMT_YUVJ420P,
         PIX_FMT_YUVJ422P,
         PIX_FMT_YUVJ444P,
         AV_NE( PIX_FMT_GRAY16BE, PIX_FMT_GRAY16LE ),
         PIX_FMT_YUV440P,
         PIX_FMT_YUVJ440P,
         AV_NE( PIX_FMT_YUV420P10BE, PIX_FMT_YUV420P10LE ),
         AV_NE( PIX_FMT_YUV422P10BE, PIX_FMT_YUV422P10LE ),
         AV_NE( PIX_FMT_YUV444P10BE, PIX_FMT_YUV444P10LE ),
         AV_NE( PIX_FMT_YUV420P16BE, PIX_FMT_YUV420P16LE ),
         AV_NE( PIX_FMT_YUV422P16BE, PIX_FMT_YUV422P16LE ),
         AV_NE( PIX_FMT_YUV444P16BE, PIX_FMT_YUV444P16LE ),
         PIX_FMT_YUVA420P,
         PIX_FMT_NONE
     };
 
     avfilter_set_common_pixel_formats(ctx, avfilter_make_format_list(pix_fmts));
 
     return 0;
 }
 
 static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
 {
     IDETContext *idet = ctx->priv;
 
     idet->csp = NULL;
 
     idet->interlace_threshold   = 1.01;
     idet->progressive_threshold = 2.5;
 
     if (args) sscanf(args, "%f:%f", &idet->interlace_threshold, &idet->progressive_threshold);
 
1a5c08ee
     idet->last_type = UNDETERMINED;
fa1f92a4
     memset(idet->history, UNDETERMINED, HIST_SIZE);
1a5c08ee
 
e3e89b6d
     idet->filter_line = filter_line_c;
 
     return 0;
 }
 
 static void null_draw_slice(AVFilterLink *link, int y, int h, int slice_dir) { }
 
 AVFilter avfilter_vf_idet = {
     .name          = "idet",
     .description   = NULL_IF_CONFIG_SMALL("Interlace detect Filter."),
 
     .priv_size     = sizeof(IDETContext),
     .init          = init,
     .uninit        = uninit,
     .query_formats = query_formats,
 
     .inputs    = (const AVFilterPad[]) {{ .name       = "default",
2941a937
                                           .type             = AVMEDIA_TYPE_VIDEO,
                                           .start_frame      = start_frame,
                                           .draw_slice       = null_draw_slice,
                                           .end_frame        = end_frame,
                                           .rej_perms        = AV_PERM_REUSE2, },
                                         { .name = NULL}},
e3e89b6d
 
     .outputs   = (const AVFilterPad[]) {{ .name       = "default",
2941a937
                                           .type             = AVMEDIA_TYPE_VIDEO,
                                           .poll_frame       = poll_frame,
                                           .request_frame    = request_frame, },
                                         { .name = NULL}},
e3e89b6d
 };