Well, I've made a lot of changes since I last posted in this thread.
I've moved the logic out of deinterlace.c, and added a new --decomb filter located in libhb/decomb.c. This is a highly modified version of deinterlace.c.
Comb detection can only get so good -- it is going to end up being called on progressive frames by mistake. This will happen especially on thin detail lines in animation. And when yadif jumps into action on stuff when it shouldn't, it's *obvious*.
INTERPOLATION
Deinterlacing is largely concerned with interpolation. You've got Line A, you've got line C. Line B is from another time and can't help you, you have to ignore it. You've got to interpolate between A and C to guess what B *should* be.
Now, if you've got 2 points, and you need to guess a point exactly in between, the answer seems obvious -- add them together, and divide by 2 to get the average. And so that's what yadif does. To decide on each pixel for line B, it does a bunch of checks and chooses, in the end, a pixel on Line A and a pixel on Line C it thinks are the closest. It averages them together, and that's the pixel for line B. Sometimes it's the ones directly above and below, sometimes they're off to the sides, sometimes they're from the previous and next frames...whatever. It's always the average of two pixels.
The downside of this is jaggies.
What makes yadif special is its intelligent spatio-temporal checks instead of always using the pixels directly above and below. However, there are plenty of other deinterlacers out there. And jaggies are problems that even the simple linear filters face. So it's worth looking at them.
FIR TREES
If you read through the MPlayer documentation you'll see stuff like:
Which is um...cryptic, no?fd/ffmpegdeint
FFmpeg deinterlacing filter that deinterlaces the given block by filtering every second line with a (−1 4 2 4 −1) filter.
Well it turns out that kind of filter is called a Finite Impulse Response, or FIR filter. These used to be really popular in hardware filters, because they had limited memory and calculation time. There's a bunch of math behind it, involving words like "sinc" and "polyphasic" and "coefficient," which I don't really care enough about to figure out, because in the end it all works pretty simple for this kind of thing. They're weighted averages of pixels above and below.
That one above is the ffmpeg deinterlacer (what we call "Fast" and MPlayer/Transcode calls "pp=fd"). It's not, in fact, as I had long though, just a simple line-doubler. Let's say you're currently interpolating Line D. It would weigh things like so:
B * -1
C * 4
D * 2
E * 4
F * -1
Those are added up, and then divided by 8. These predictions are used to replace half the lines in each frame. Note how it actually gives some weight to the line it's replacing, but nowhere near as much as is given to the ones above and below. And it also includes the lines from the alternate field it should be ignoring, with negative weight. This all can, at times, create a smoother picture. Most of the time, it just looks worse, in my opinion.
BLENDING
There are, of course, many options besides spatial interpolation for deinterlacing. One simple option is blending. You can just blend the two fields together instead of dropping one. Then, you don't have to guess what those lines should be. It does away with the dancing pixels and stair-stepping and jaggies that are so distracting with linear interpolation.
The best blender I could find is lowpass5. This is a FIR filter that runs on *every* line instead of half of them, and looks like this, running on Line C.
A * -1
B * 2
C * 6
D * 2
E * -1
What's everyone's opinion of blending? I'm tempted to use it for progressive sequences, since that way when the comb detection gets a false positive, it's barely noticeable. I'm toying with the notion of using blend when job->interlaced is false ( when most of the 10 preview scans don't show combing). Thoughts?
CUBIC INTERPOLATION
And then there is bicubic interpolation. If you have 2 points, you can draw a straight line. But if you have 4 points, you can generate an equation that plots a curve over all of them and get a better result:
Code: Select all
int CubicInterpolate(
int y0,int y1,
int y2,int y3,
double mu)
{
double a0,a1,a2,a3,mu2;
mu2 = mu*mu;
a0 = y3 - y2 - y0 + y1;
a1 = y0 - y1 - a0;
a2 = y2 - y0;
a3 = y1;
double result = (a0*mu*mu2+a1*mu2+a2*mu+a3);
if( result > 255)
{
result = 255;
}
if( result < 0 )
{
result = 0;
}
return (int)result;
}
Code: Select all
int EfficientCubicInterpolate( int y0, int y1, int y2, int y3 )
{
int result = ( y0 * -3 ) + ( y1 * 23 ) + ( y2 * 23 ) + ( y3 * -3 );
result /= 40;
if( result > 255 )
{
result = 255;
}
else if( result < 0 )
{
result = 0;
}
return result;
}
YADIF AND CUBIC INTERPOLATION
How do we find 4 points to feed that cubic interpolator? Well, we want to only look at pixels on the "good" half the lines we're keeping intact from the source. So in addition to the pixels directly above and below, we also skip up past the next "bad" line to the pixel 3 above and down to the pixel 3 below. Easy. But what about if yadif doesn't stick with its initial vertical prediction? What if it senses less difference in another direction and chooses pixels on a diagonal?
Well, we've got two pixels from yadif for sure. We can look at that as line. If we continue to draw these lines out, pixel choices for all of yadif's possible spatial decisions become apparent. We just look for the next pixels the lines hit on "good" fields.
Code: Select all
case -1:\
spatial_pred = EfficientCubicInterpolate(cur[-3 * refs - 3], cur[-refs -1], cur[+refs + 1], cur[3* refs + 3] );\
break;\
case -2:\
spatial_pred = EfficientCubicInterpolate(cur[-3 * refs -5], cur[-refs -2], cur[+refs + 2], cur[3* refs + 5] );\
break;\
case 1:\
spatial_pred = EfficientCubicInterpolate(cur[-3 * refs +3], cur[-refs +1], cur[+refs - 1], cur[3* refs -3] );\
break;\
case 2:\
spatial_pred = EfficientCubicInterpolate(cur[-3 * refs +5], cur[-refs +2], cur[+refs - 2], cur[3*refs - 5] );\
break;\
This improves curves somewhat.
PATCH
So here's the current patch, with selective deinterlacing based on comb detection, using yadif tweaked to do cubic interpolation:
http://handbrake.pastebin.ca/1007122
Code: Select all
Index: test/test.c
===================================================================
--- test/test.c (revision 1452)
+++ test/test.c (working copy)
@@ -40,6 +40,8 @@
static char * denoise_opt = 0;
static int detelecine = 0;
static char * detelecine_opt = 0;
+static int decomb = 0;
+static char * decomb_opt = 0;
static int grayscale = 0;
static int vcodec = HB_VCODEC_FFMPEG;
static int h264_13 = 0;
@@ -826,7 +828,12 @@
hb_filter_denoise.settings = denoise_opt;
hb_list_add( job->filters, &hb_filter_denoise );
}
-
+ if( decomb )
+ {
+ hb_filter_decomb.settings = decomb_opt;
+ hb_list_add( job->filters, &hb_filter_decomb );
+ }
+
if( width && height )
{
job->width = width;
@@ -1455,6 +1462,8 @@
" <weak/medium/strong>\n"
" -9, --detelecine Detelecine video with pullup filter\n"
" <L:R:T:B:SB:MP> (default 1:1:4:4:0:0)\n"
+ " -5, --decomb Selectively deinterlaces when it detects combing\n"
+ " <YM:FD:EQ:DF:TR> (default: 0:-1:10:30:9)\n"
" -g, --grayscale Grayscale encoding\n"
" -p, --pixelratio Store pixel aspect ratio in video stream\n"
" -P, --loosePixelratio Store pixel aspect ratio with specified width\n"
@@ -1592,6 +1601,7 @@
{ "deblock", optional_argument, NULL, '7' },
{ "denoise", optional_argument, NULL, '8' },
{ "detelecine", optional_argument, NULL, '9' },
+ { "decomb", optional_argument, NULL, '5' },
{ "grayscale", no_argument, NULL, 'g' },
{ "pixelratio", no_argument, NULL, 'p' },
{ "loosePixelratio", optional_argument, NULL, 'P' },
@@ -1621,7 +1631,7 @@
int c;
c = getopt_long( argc, argv,
- "hvuC:f:4i:Io:t:Lc:m::a:6:s:UFN:e:E:2dD:789gpOP::w:l:n:b:q:S:B:r:R:Qx:TY:X:VZ:z",
+ "hvuC:f:4i:Io:t:Lc:m::a:6:s:UFN:e:E:2dD:7895gpOP::w:l:n:b:q:S:B:r:R:Qx:TY:X:VZ:z",
long_options, &option_index );
if( c < 0 )
{
@@ -1854,6 +1864,13 @@
}
detelecine = 1;
break;
+ case '5':
+ if( optarg != NULL )
+ {
+ decomb_opt = strdup( optarg );
+ }
+ decomb = 1;
+ break;
case 'g':
grayscale = 1;
break;
Index: libhb/Jamfile
===================================================================
--- libhb/Jamfile (revision 1452)
+++ libhb/Jamfile (working copy)
@@ -11,7 +11,7 @@
demuxmpeg.c fifo.c render.c reader.c muxcommon.c muxmp4.c sync.c stream.c
decsub.c deca52.c decdca.c encfaac.c declpcm.c encx264.c decavcodec.c encxvid.c
muxavi.c enclame.c muxogm.c encvorbis.c dvd.c muxmkv.c deblock.c deinterlace.c
-denoise.c detelecine.c lang.c enctheora.c ;
+denoise.c detelecine.c decomb.c lang.c enctheora.c ;
Library libhb : $(LIBHB_SRC) ;
Index: libhb/hb.c
===================================================================
--- libhb/hb.c (revision 1452)
+++ libhb/hb.c (working copy)
@@ -510,30 +510,48 @@
}
// compare results
- /* The final metric seems to be doing some kind of bits per pixel style calculation
- to decide whether or not enough lines showed alternating colors for the frame size. */
+ /* The final cc score for a plane is the percentage of combed pixels it contains.
+ Because sensitivity goes down to hundreths of a percent, multiply by 1000
+ so it will be easy to compare against the threhold value which is an integer. */
cc[k] = (int)( ( cc_1 + cc_2 ) * 1000.0 / ( width * height ) );
- /* If the plane's cc score meets the threshold, flag it as combed. */
+ /* If the plane's cc score surpasses the threshold (by default 0.09%), flag it as combed.
+ That means, with default settings, a frame is flagged if 1% or more of it is combed. */
flag[k] = 0;
if ( cc[k] > threshold )
{
+// hb_log("Threhold %i: %i/%i | PTS: %lld (%fs)", k, cc[k], threshold, buf->start, (float)buf->start / 90000 );
flag[k] = 1;
}
}
#if 0
/* Debugging info */
-// if(flag)
+ if(flag[0] || flag[1] || flag[2])
hb_log("flags: %i/%i/%i | cc0: %i | cc1: %i | cc2: %i", flag[0], flag[1], flag[2], cc[0], cc[1], cc[2]);
#endif
- /* When more than one plane shows combing, tell the caller. */
- if (flag[0] || flag[1] || flag[2] )
+ if( buf->flags & 16 )
{
- return 1;
+ /* It's a progressive frame.*/
+ if( flag[0] && ( flag[1] || flag[2] ) )
+ {
+ /* So only mark as combed if luma and one chroma show combing. */
+ return 1;
+ }
}
-
+ else
+ {
+ /* It's not a progressive frame */
+ if( flag[0] || flag[1] || flag[2] )
+ {
+ /* So mark as framed if any plane shows combing. */
+ return 1;
+
+ }
+ }
+
+ /* Reaching this point means no combing detected. */
return 0;
}
Index: libhb/Makefile
===================================================================
--- libhb/Makefile (revision 1452)
+++ libhb/Makefile (working copy)
@@ -25,7 +25,7 @@
update.c demuxmpeg.c fifo.c render.c reader.c muxcommon.c stream.c \
muxmp4.c sync.c decsub.c deca52.c decdca.c encfaac.c declpcm.c encx264.c \
decavcodec.c encxvid.c muxmkv.c muxavi.c enclame.c muxogm.c encvorbis.c \
- dvd.c ipodutil.cpp deblock.c deinterlace.c denoise.c detelecine.c lang.c
+ dvd.c ipodutil.cpp deblock.c deinterlace.c denoise.c detelecine.c decomb.c lang.c
OTMP = $(SRCS:%.c=%.o)
OBJS = $(OTMP:%.cpp=%.o)
Index: libhb/internal.h
===================================================================
--- libhb/internal.h (revision 1452)
+++ libhb/internal.h (working copy)
@@ -224,7 +224,8 @@
FILTER_DEINTERLACE = 1,
FILTER_DEBLOCK,
FILTER_DENOISE,
- FILTER_DETELECINE
+ FILTER_DETELECINE,
+ FILTER_DECOMB
};
extern hb_work_object_t * hb_objects;
Index: libhb/decomb.c
===================================================================
--- libhb/decomb.c (revision 0)
+++ libhb/decomb.c (revision 0)
@@ -0,0 +1,770 @@
+/* $Id: decomb.c,v 1.14 2008/04/25 5:00:00 jbrjake Exp $
+
+ This file is part of the HandBrake source code.
+ Homepage: <http://handbrake.fr/>.
+ It may be used under the terms of the GNU General Public License.
+
+ The yadif algorithm was created by Michael Niedermayer. */
+#include "hb.h"
+#include "ffmpeg/avcodec.h"
+#include "mpeg2dec/mpeg2.h"
+
+#define SUPPRESS_AV_LOG
+
+#define MODE_DEFAULT 0
+#define PARITY_DEFAULT -1
+
+#define MCDEINT_MODE_DEFAULT -1
+#define MCDEINT_QP_DEFAULT 1
+
+#define ABS(a) ((a) > 0 ? (a) : (-(a)))
+#define MIN3(a,b,c) MIN(MIN(a,b),c)
+#define MAX3(a,b,c) MAX(MAX(a,b),c)
+
+struct hb_filter_private_s
+{
+ int pix_fmt;
+ int width[3];
+ int height[3];
+
+ int mode;
+ int parity;
+
+ int yadif_ready;
+
+ int mcdeint_mode;
+ int mcdeint_qp;
+
+ int mcdeint_outbuf_size;
+ uint8_t * mcdeint_outbuf;
+ AVCodecContext * mcdeint_avctx_enc;
+ AVFrame * mcdeint_frame;
+ AVFrame * mcdeint_frame_dec;
+
+ int comb;
+ int color_equal;
+ int color_diff;
+ int threshold;
+ int deinterlaced_frames;
+ int passed_frames;
+
+ uint8_t * ref[4][3];
+ int ref_stride[3];
+
+ AVPicture pic_in;
+ AVPicture pic_out;
+ hb_buffer_t * buf_out[2];
+ hb_buffer_t * buf_settings;
+};
+
+hb_filter_private_t * hb_decomb_init( int pix_fmt,
+ int width,
+ int height,
+ char * settings );
+
+int hb_decomb_work( hb_buffer_t * buf_in,
+ hb_buffer_t ** buf_out,
+ int pix_fmt,
+ int width,
+ int height,
+ hb_filter_private_t * pv );
+
+void hb_decomb_close( hb_filter_private_t * pv );
+
+hb_filter_object_t hb_filter_decomb =
+{
+ FILTER_DECOMB,
+ "Decombs selectively with (ffmpeg or yadif/mcdeint)",
+ NULL,
+ hb_decomb_init,
+ hb_decomb_work,
+ hb_decomb_close,
+};
+
+int EfficientCubicInterpolate( int y0, int y1, int y2, int y3 )
+{
+ int result = ( y0 * -3 ) + ( y1 * 23 ) + ( y2 * 23 ) + ( y3 * -3 );
+ result /= 40;
+
+ if( result > 255 )
+ {
+ result = 255;
+ }
+ else if( result < 0 )
+ {
+ result = 0;
+ }
+
+ return result;
+}
+
+static void store_ref( const uint8_t ** pic,
+ hb_filter_private_t * pv )
+{
+ memcpy( pv->ref[3],
+ pv->ref[0],
+ sizeof(uint8_t *)*3 );
+
+ memmove( pv->ref[0],
+ pv->ref[1],
+ sizeof(uint8_t *)*3*3 );
+
+ int i;
+ for( i = 0; i < 3; i++ )
+ {
+ const uint8_t * src = pic[i];
+ uint8_t * ref = pv->ref[2][i];
+
+ int w = pv->width[i];
+ int h = pv->height[i];
+ int ref_stride = pv->ref_stride[i];
+
+ int y;
+ for( y = 0; y < pv->height[i]; y++ )
+ {
+ memcpy(ref, src, w);
+ src = (uint8_t*)src + w;
+ ref = (uint8_t*)ref + ref_stride;
+ }
+ }
+}
+
+static void get_ref( uint8_t ** pic, hb_filter_private_t * pv, int frm )
+{
+ int i;
+ for( i = 0; i < 3; i++ )
+ {
+ uint8_t * dst = pic[i];
+ const uint8_t * ref = pv->ref[frm][i];
+ int w = pv->width[i];
+ int ref_stride = pv->ref_stride[i];
+
+ int y;
+ for( y = 0; y < pv->height[i]; y++ )
+ {
+ memcpy(dst, ref, w);
+ dst += w;
+ ref += ref_stride;
+ }
+ }
+}
+
+static void blend_filter_line( uint8_t *dst,
+ uint8_t *prev,
+ uint8_t *cur,
+ uint8_t *next,
+ int plane,
+ int parity,
+ int y,
+ hb_filter_private_t * pv )
+{
+ uint8_t *prev2 = parity ? prev : cur ;
+ uint8_t *next2 = parity ? cur : next;
+
+ int w = pv->width[plane];
+ int refs = pv->ref_stride[plane];
+ int x;
+
+ for( x = 0; x < w; x++)
+ {
+
+ int result = 0;
+ result += -cur[-2*refs];
+ result += cur[-refs] * 2;
+ result += cur[0] * 6;
+ result += cur[+refs] * 2;
+ result += -cur[2*refs];
+ result /= 8;
+
+ if( result > 255 )
+ {
+ result = 255;
+ }
+ if( result < 0 )
+ {
+ result = 0;
+ }
+
+ dst[0] = result;
+
+ dst++;
+ cur++;
+ prev++;
+ next++;
+ prev2++;
+ next2++;
+ }
+}
+
+static void yadif_filter_line( uint8_t *dst,
+ uint8_t *prev,
+ uint8_t *cur,
+ uint8_t *next,
+ int plane,
+ int parity,
+ int y,
+ hb_filter_private_t * pv )
+{
+ uint8_t *prev2 = parity ? prev : cur ;
+ uint8_t *next2 = parity ? cur : next;
+
+ int w = pv->width[plane];
+ int refs = pv->ref_stride[plane];
+ int x;
+
+ for( x = 0; x < w; x++)
+ {
+ int c = cur[-refs];
+ int d = (prev2[0] + next2[0])>>1;
+ int e = cur[+refs];
+ int temporal_diff0 = ABS(prev2[0] - next2[0]);
+ int temporal_diff1 = ( ABS(prev[-refs] - cur[-refs]) + ABS(prev[+refs] - cur[+refs]) ) >> 1;
+ int temporal_diff2 = ( ABS(next[-refs] - cur[-refs]) + ABS(next[+refs] - cur[+refs]) ) >> 1;
+ int diff = MAX3(temporal_diff0>>1, temporal_diff1, temporal_diff2);
+
+ int spatial_score = ABS(cur[-refs-1] - cur[+refs-1]) + ABS(cur[-refs]-cur[+refs]) +
+ ABS(cur[-refs+1] - cur[+refs+1]) - 1;
+ int spatial_pred;
+
+ if( pv->mode >= 4 )
+ {
+ spatial_pred = EfficientCubicInterpolate( cur[-3*refs], cur[-refs], cur[+refs], cur[3*refs] );
+ }
+ else
+ {
+ spatial_pred = (c+e)>>1;
+ }
+
+#define YADIF_CHECK(j)\
+ { int score = ABS(cur[-refs-1+j] - cur[+refs-1-j])\
+ + ABS(cur[-refs +j] - cur[+refs -j])\
+ + ABS(cur[-refs+1+j] - cur[+refs+1-j]);\
+ if( score < spatial_score ){\
+ spatial_score = score;\
+ if( pv->mode >= 4)\
+ {\
+ switch(j)\
+ {\
+ case -1:\
+ spatial_pred = EfficientCubicInterpolate(cur[-3 * refs - 3], cur[-refs -1], cur[+refs + 1], cur[3* refs + 3] );\
+ break;\
+ case -2:\
+ spatial_pred = EfficientCubicInterpolate(cur[-3 * refs -5], cur[-refs -2], cur[+refs + 2], cur[3* refs + 5] );\
+ break;\
+ case 1:\
+ spatial_pred = EfficientCubicInterpolate(cur[-3 * refs +3], cur[-refs +1], cur[+refs - 1], cur[3* refs -3] );\
+ break;\
+ case 2:\
+ spatial_pred = EfficientCubicInterpolate(cur[-3 * refs +5], cur[-refs +2], cur[+refs - 2], cur[3*refs - 5] );\
+ break;\
+ }\
+ }\
+ else\
+ {\
+ spatial_pred = (cur[-refs +j] + cur[+refs -j])>>1;\
+ }\
+
+ YADIF_CHECK(-1) YADIF_CHECK(-2) }} }}
+ YADIF_CHECK( 1) YADIF_CHECK( 2) }} }}
+
+ /* Temporally adjust the spatial prediction by comparing against the
+ alternate (associated) fields in the previous and next frames. */
+ int b = (prev2[-2*refs] + next2[-2*refs])>>1;
+ int f = (prev2[+2*refs] + next2[+2*refs])>>1;
+
+ int max = MAX3(d-e, d-c, MIN(b-c, f-e));
+ int min = MIN3(d-e, d-c, MAX(b-c, f-e));
+
+ diff = MAX3( diff, min, -max );
+
+ if( spatial_pred > d + diff )
+ {
+ spatial_pred = d + diff;
+ }
+ else if( spatial_pred < d - diff )
+ {
+ spatial_pred = d - diff;
+ }
+
+ dst[0] = spatial_pred;
+
+end_of_yadif_filter_pixel:
+ dst++;
+ cur++;
+ prev++;
+ next++;
+ prev2++;
+ next2++;
+ }
+}
+
+static void yadif_filter( uint8_t ** dst,
+ int parity,
+ int tff,
+ hb_filter_private_t * pv )
+{
+ int i;
+ for( i = 0; i < 3; i++ )
+ {
+ int w = pv->width[i];
+ int h = pv->height[i];
+ int ref_stride = pv->ref_stride[i];
+
+ int y;
+ for( y = 0; y < h; y++ )
+ {
+ if( pv->mode == 3)
+ {
+ uint8_t *prev = &pv->ref[0][i][y*ref_stride];
+ uint8_t *cur = &pv->ref[1][i][y*ref_stride];
+ uint8_t *next = &pv->ref[2][i][y*ref_stride];
+ uint8_t *dst2 = &dst[i][y*w];
+
+ blend_filter_line( dst2, prev, cur, next, i, parity ^ tff, y, pv );
+
+ }
+ else if( (y ^ parity) & 1 )
+ {
+ uint8_t *prev = &pv->ref[0][i][y*ref_stride];
+ uint8_t *cur = &pv->ref[1][i][y*ref_stride];
+ uint8_t *next = &pv->ref[2][i][y*ref_stride];
+ uint8_t *dst2 = &dst[i][y*w];
+
+ yadif_filter_line( dst2, prev, cur, next, i, parity ^ tff, y, pv );
+ }
+ else
+ {
+ memcpy( &dst[i][y*w],
+ &pv->ref[1][i][y*ref_stride],
+ w * sizeof(uint8_t) );
+ }
+ }
+ }
+}
+
+static void mcdeint_filter( uint8_t ** dst,
+ uint8_t ** src,
+ int parity,
+ hb_filter_private_t * pv )
+{
+ int x, y, i;
+ int out_size;
+
+#ifdef SUPPRESS_AV_LOG
+ /* TODO: temporarily change log level to suppress obnoxious debug output */
+ int loglevel = av_log_get_level();
+ av_log_set_level( AV_LOG_QUIET );
+#endif
+
+ for( i=0; i<3; i++ )
+ {
+ pv->mcdeint_frame->data[i] = src[i];
+ pv->mcdeint_frame->linesize[i] = pv->width[i];
+ }
+ pv->mcdeint_avctx_enc->me_cmp = FF_CMP_SAD;
+ pv->mcdeint_avctx_enc->me_sub_cmp = FF_CMP_SAD;
+ pv->mcdeint_frame->quality = pv->mcdeint_qp * FF_QP2LAMBDA;
+
+ out_size = avcodec_encode_video( pv->mcdeint_avctx_enc,
+ pv->mcdeint_outbuf,
+ pv->mcdeint_outbuf_size,
+ pv->mcdeint_frame );
+
+ pv->mcdeint_frame_dec = pv->mcdeint_avctx_enc->coded_frame;
+
+ for( i = 0; i < 3; i++ )
+ {
+ int w = pv->width[i];
+ int h = pv->height[i];
+ int fils = pv->mcdeint_frame_dec->linesize[i];
+ int srcs = pv->width[i];
+
+ for( y = 0; y < h; y++ )
+ {
+ if( (y ^ parity) & 1 )
+ {
+ for( x = 0; x < w; x++ )
+ {
+ if( (x-2)+(y-1)*w >= 0 && (x+2)+(y+1)*w < w*h )
+ {
+ uint8_t * filp =
+ &pv->mcdeint_frame_dec->data[i][x + y*fils];
+ uint8_t * srcp = &src[i][x + y*srcs];
+
+ int diff0 = filp[-fils] - srcp[-srcs];
+ int diff1 = filp[+fils] - srcp[+srcs];
+
+ int spatial_score =
+ ABS(srcp[-srcs-1] - srcp[+srcs-1])
+ + ABS(srcp[-srcs ] - srcp[+srcs ])
+ + ABS(srcp[-srcs+1] - srcp[+srcs+1]) - 1;
+
+ int temp = filp[0];
+
+#define MCDEINT_CHECK(j)\
+ { int score = ABS(srcp[-srcs-1+j] - srcp[+srcs-1-j])\
+ + ABS(srcp[-srcs +j] - srcp[+srcs -j])\
+ + ABS(srcp[-srcs+1+j] - srcp[+srcs+1-j]);\
+ if( score < spatial_score ) {\
+ spatial_score = score;\
+ diff0 = filp[-fils+j] - srcp[-srcs+j];\
+ diff1 = filp[+fils-j] - srcp[+srcs-j];
+
+ MCDEINT_CHECK(-1) MCDEINT_CHECK(-2) }} }}
+ MCDEINT_CHECK( 1) MCDEINT_CHECK( 2) }} }}
+
+ if(diff0 + diff1 > 0)
+ {
+ temp -= (diff0 + diff1 -
+ ABS( ABS(diff0) - ABS(diff1) ) / 2) / 2;
+ }
+ else
+ {
+ temp -= (diff0 + diff1 +
+ ABS( ABS(diff0) - ABS(diff1) ) / 2) / 2;
+ }
+
+ filp[0] = dst[i][x + y*w] =
+ temp > 255U ? ~(temp>>31) : temp;
+ }
+ else
+ {
+ dst[i][x + y*w] =
+ pv->mcdeint_frame_dec->data[i][x + y*fils];
+ }
+ }
+ }
+ }
+
+ for( y = 0; y < h; y++ )
+ {
+ if( !((y ^ parity) & 1) )
+ {
+ for( x = 0; x < w; x++ )
+ {
+ pv->mcdeint_frame_dec->data[i][x + y*fils] =
+ dst[i][x + y*w]= src[i][x + y*srcs];
+ }
+ }
+ }
+ }
+
+#ifdef SUPPRESS_AV_LOG
+ /* TODO: restore previous log level */
+ av_log_set_level(loglevel);
+#endif
+}
+
+hb_filter_private_t * hb_decomb_init( int pix_fmt,
+ int width,
+ int height,
+ char * settings )
+{
+ if( pix_fmt != PIX_FMT_YUV420P )
+ {
+ return 0;
+ }
+
+ hb_filter_private_t * pv = calloc( 1, sizeof(struct hb_filter_private_s) );
+
+ pv->pix_fmt = pix_fmt;
+
+ pv->width[0] = width;
+ pv->height[0] = height;
+ pv->width[1] = pv->width[2] = width >> 1;
+ pv->height[1] = pv->height[2] = height >> 1;
+
+ int buf_size = 3 * width * height / 2;
+ pv->buf_out[0] = hb_buffer_init( buf_size );
+ pv->buf_out[1] = hb_buffer_init( buf_size );
+ pv->buf_settings = hb_buffer_init( 0 );
+
+ pv->deinterlaced_frames = 0;
+ pv->passed_frames = 0;
+ pv->color_equal = 10;
+ pv->color_diff = 30;
+ pv->threshold = 9;
+
+ pv->yadif_ready = 0;
+ pv->mode = MODE_DEFAULT;
+ pv->parity = PARITY_DEFAULT;
+
+ pv->mcdeint_mode = MCDEINT_MODE_DEFAULT;
+ pv->mcdeint_qp = MCDEINT_QP_DEFAULT;
+
+ if( settings )
+ {
+ sscanf( settings, "%d:%d:%d:%d",
+ &pv->mode,
+ &pv->color_equal,
+ &pv->color_diff,
+ &pv->threshold );
+ }
+
+ if( pv->mode == 2 || pv->mode == 5 )
+ {
+ pv->mcdeint_mode = 0;
+ }
+
+ /* Allocate yadif specific buffers */
+ if( pv->mode > 0 )
+ {
+ int i, j;
+ for( i = 0; i < 3; i++ )
+ {
+ int is_chroma = !!i;
+ int w = ((width + 31) & (~31))>>is_chroma;
+ int h = ((height+6+ 31) & (~31))>>is_chroma;
+
+ pv->ref_stride[i] = w;
+
+ for( j = 0; j < 3; j++ )
+ {
+ pv->ref[j][i] = malloc( w*h*sizeof(uint8_t) ) + 3*w;
+ }
+ }
+ }
+
+ /* Allocate mcdeint specific buffers */
+ if( pv->mcdeint_mode >= 0 )
+ {
+ avcodec_init();
+ avcodec_register_all();
+
+ AVCodec * enc = avcodec_find_encoder( CODEC_ID_SNOW );
+
+ int i;
+ for (i = 0; i < 3; i++ )
+ {
+ AVCodecContext * avctx_enc;
+
+ avctx_enc = pv->mcdeint_avctx_enc = avcodec_alloc_context();
+
+ avctx_enc->width = width;
+ avctx_enc->height = height;
+ avctx_enc->time_base = (AVRational){1,25}; // meaningless
+ avctx_enc->gop_size = 300;
+ avctx_enc->max_b_frames = 0;
+ avctx_enc->pix_fmt = PIX_FMT_YUV420P;
+ avctx_enc->flags = CODEC_FLAG_QSCALE | CODEC_FLAG_LOW_DELAY;
+ avctx_enc->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
+ avctx_enc->global_quality = 1;
+ avctx_enc->flags2 = CODEC_FLAG2_MEMC_ONLY;
+ avctx_enc->me_cmp = FF_CMP_SAD; //SSE;
+ avctx_enc->me_sub_cmp = FF_CMP_SAD; //SSE;
+ avctx_enc->mb_cmp = FF_CMP_SSE;
+
+ switch( pv->mcdeint_mode )
+ {
+ case 3:
+ avctx_enc->refs = 3;
+ case 2:
+ avctx_enc->me_method = ME_UMH;
+ case 1:
+ avctx_enc->flags |= CODEC_FLAG_4MV;
+ avctx_enc->dia_size =2;
+ case 0:
+ avctx_enc->flags |= CODEC_FLAG_QPEL;
+ }
+
+ avcodec_open(avctx_enc, enc);
+ }
+
+ pv->mcdeint_frame = avcodec_alloc_frame();
+ pv->mcdeint_outbuf_size = width * height * 10;
+ pv->mcdeint_outbuf = malloc( pv->mcdeint_outbuf_size );
+ }
+
+ return pv;
+}
+
+void hb_decomb_close( hb_filter_private_t * pv )
+{
+ if( !pv )
+ {
+ return;
+ }
+
+ hb_log("deinterlacer: filtered %i | unfiltered %i | total %i", pv->deinterlaced_frames, pv->passed_frames, pv->deinterlaced_frames + pv->passed_frames);
+
+ /* Cleanup frame buffers */
+ if( pv->buf_out[0] )
+ {
+ hb_buffer_close( &pv->buf_out[0] );
+ }
+ if( pv->buf_out[1] )
+ {
+ hb_buffer_close( &pv->buf_out[1] );
+ }
+ if (pv->buf_settings )
+ {
+ hb_buffer_close( &pv->buf_settings );
+ }
+
+ /* Cleanup yadif specific buffers */
+ if( pv->mode > 0 )
+ {
+ int i;
+ for( i = 0; i<3*3; i++ )
+ {
+ uint8_t **p = &pv->ref[i%3][i/3];
+ if (*p)
+ {
+ free( *p - 3*pv->ref_stride[i/3] );
+ *p = NULL;
+ }
+ }
+ }
+
+ /* Cleanup mcdeint specific buffers */
+ if( pv->mcdeint_mode >= 0 )
+ {
+ if( pv->mcdeint_avctx_enc )
+ {
+ avcodec_close( pv->mcdeint_avctx_enc );
+ av_freep( &pv->mcdeint_avctx_enc );
+ }
+ if( pv->mcdeint_outbuf )
+ {
+ free( pv->mcdeint_outbuf );
+ }
+ }
+
+ free( pv );
+}
+
+int hb_decomb_work( hb_buffer_t * buf_in,
+ hb_buffer_t ** buf_out,
+ int pix_fmt,
+ int width,
+ int height,
+ hb_filter_private_t * pv )
+{
+ if( !pv ||
+ pix_fmt != pv->pix_fmt ||
+ width != pv->width[0] ||
+ height != pv->height[0] )
+ {
+ return FILTER_FAILED;
+ }
+
+ avpicture_fill( &pv->pic_in, buf_in->data,
+ pix_fmt, width, height );
+
+ /* Use libavcodec deinterlace if mode < 0 */
+ if( pv->mode == 0 )
+ {
+ avpicture_fill( &pv->pic_out, pv->buf_out[0]->data,
+ pix_fmt, width, height );
+
+ /* Check for combing on the input frame */
+ int interlaced = hb_detect_comb(buf_in, width, height, pv->color_equal, pv->color_diff, pv->threshold);
+
+ if(interlaced)
+ {
+ avpicture_deinterlace( &pv->pic_out, &pv->pic_in,
+ pix_fmt, width, height );
+
+ pv->deinterlaced_frames++;
+ //hb_log("Frame %i is combed (Progressive: %s )", pv->deinterlaced_frames + pv->passed_frames, (buf_in->flags & 16) ? "Y" : "N");
+
+ hb_buffer_copy_settings( pv->buf_out[0], buf_in );
+ *buf_out = pv->buf_out[0];
+ }
+ else
+ {
+ /* No combing detected, pass input frame through unmolested.*/
+
+ pv->passed_frames++;
+
+ hb_buffer_copy_settings( pv->buf_out[0], buf_in );
+ *buf_out = buf_in;
+
+ }
+
+ return FILTER_OK;
+ }
+
+ /* Determine if top-field first layout */
+ int tff;
+ if( pv->parity < 0 )
+ {
+ tff = !!(buf_in->flags & PIC_FLAG_TOP_FIELD_FIRST);
+ }
+ else
+ {
+ tff = (pv->parity & 1) ^ 1;
+ }
+
+ /* Store current frame in yadif cache */
+ store_ref( (const uint8_t**)pv->pic_in.data, pv );
+
+ /* Note down if the input frame is combed */
+ pv->comb = (pv->comb << 1) | hb_detect_comb(buf_in, width, height, pv->color_equal, pv->color_diff, pv->threshold);
+
+ /* If yadif is not ready, store another ref and return FILTER_DELAY */
+ if( pv->yadif_ready == 0 )
+ {
+ store_ref( (const uint8_t**)pv->pic_in.data, pv );
+
+ hb_buffer_copy_settings( pv->buf_settings, buf_in );
+
+ /* don't let 'work_loop' send a chapter mark upstream */
+ buf_in->new_chap = 0;
+
+ pv->yadif_ready = 1;
+
+ return FILTER_DELAY;
+ }
+
+ /* yadif works one frame behind so if the previous frame
+ * had combing, deinterlace it otherwise just output it. */
+ if( (pv->comb & 2 ) == 0 )
+ {
+ /* previous frame not interlaced - copy cached input frame to buf_out */
+
+ pv->passed_frames++;
+
+ avpicture_fill( &pv->pic_out, pv->buf_out[0]->data, pix_fmt, width, height );
+ get_ref( (uint8_t**)pv->pic_out.data, pv, 1 );
+ *buf_out = pv->buf_out[0];
+ }
+ else
+ {
+ /* Perform yadif filtering */
+
+ pv->deinterlaced_frames++;
+ int frame;
+ for( frame = 0; frame <= ( ( pv->mode == 2 || pv->mode == 5 )? 1 : 0 ) ; frame++ )
+ {
+ int parity = frame ^ tff ^ 1;
+
+ avpicture_fill( &pv->pic_out, pv->buf_out[!(frame^1)]->data,
+ pix_fmt, width, height );
+
+ yadif_filter( pv->pic_out.data, parity, tff, pv );
+
+ if( pv->mcdeint_mode >= 0 )
+ {
+ /* Perform mcdeint filtering */
+ avpicture_fill( &pv->pic_in, pv->buf_out[(frame^1)]->data,
+ pix_fmt, width, height );
+
+ mcdeint_filter( pv->pic_in.data, pv->pic_out.data, parity, pv );
+ }
+
+ *buf_out = pv->buf_out[!(frame^1)];
+ }
+ }
+
+ /* Copy buffered settings to output buffer settings */
+ hb_buffer_copy_settings( *buf_out, pv->buf_settings );
+
+ /* Replace buffered settings with input buffer settings */
+ hb_buffer_copy_settings( pv->buf_settings, buf_in );
+
+ /* don't let 'work_loop' send a chapter mark upstream */
+ buf_in->new_chap = 0;
+
+ return FILTER_OK;
+}
Index: libhb/common.h
===================================================================
--- libhb/common.h (revision 1452)
+++ libhb/common.h (working copy)
@@ -574,6 +574,7 @@
extern hb_filter_object_t hb_filter_deinterlace;
extern hb_filter_object_t hb_filter_deblock;
extern hb_filter_object_t hb_filter_denoise;
+extern hb_filter_object_t hb_filter_decomb;
typedef void hb_error_handler_t( const char *errmsg );
Index: macosx/HandBrake.xcodeproj/project.pbxproj
===================================================================
--- macosx/HandBrake.xcodeproj/project.pbxproj (revision 1452)
+++ macosx/HandBrake.xcodeproj/project.pbxproj (working copy)
@@ -90,6 +90,8 @@
593034EC0BBA39A100172349 /* ChapterTitles.m in Sources */ = {isa = PBXBuildFile; fileRef = 593034EA0BBA39A100172349 /* ChapterTitles.m */; };
59CBD2370BBB44DA004A3BE3 /* parsecsv.c in Sources */ = {isa = PBXBuildFile; fileRef = 59CBD2360BBB44DA004A3BE3 /* parsecsv.c */; };
59CBD2650BBB4D1B004A3BE3 /* ChapterTitles.m in Sources */ = {isa = PBXBuildFile; fileRef = 593034EA0BBA39A100172349 /* ChapterTitles.m */; };
+ 7497010F0DC281BB009200D8 /* decomb.c in Sources */ = {isa = PBXBuildFile; fileRef = 7497010E0DC281BB009200D8 /* decomb.c */; };
+ 749701100DC281BB009200D8 /* decomb.c in Sources */ = {isa = PBXBuildFile; fileRef = 7497010E0DC281BB009200D8 /* decomb.c */; };
A22C85EC0D05D35000C10E36 /* HBPresets.h in Headers */ = {isa = PBXBuildFile; fileRef = A22C85EA0D05D35000C10E36 /* HBPresets.h */; };
A22C85ED0D05D35100C10E36 /* HBPresets.m in Sources */ = {isa = PBXBuildFile; fileRef = A22C85EB0D05D35000C10E36 /* HBPresets.m */; };
A25289E60D87A27D00461D5B /* enctheora.c in Sources */ = {isa = PBXBuildFile; fileRef = A25289E50D87A27D00461D5B /* enctheora.c */; };
@@ -258,6 +260,7 @@
593034E90BBA39A100172349 /* ChapterTitles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChapterTitles.h; sourceTree = "<group>"; };
593034EA0BBA39A100172349 /* ChapterTitles.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ChapterTitles.m; sourceTree = "<group>"; };
59CBD2360BBB44DA004A3BE3 /* parsecsv.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = parsecsv.c; path = ../test/parsecsv.c; sourceTree = SOURCE_ROOT; };
+ 7497010E0DC281BB009200D8 /* decomb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = decomb.c; path = ../libhb/decomb.c; sourceTree = SOURCE_ROOT; };
A22C85EA0D05D35000C10E36 /* HBPresets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBPresets.h; sourceTree = "<group>"; };
A22C85EB0D05D35000C10E36 /* HBPresets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBPresets.m; sourceTree = "<group>"; };
A25289E50D87A27D00461D5B /* enctheora.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = enctheora.c; path = ../libhb/enctheora.c; sourceTree = SOURCE_ROOT; };
@@ -435,6 +438,7 @@
isa = PBXGroup;
children = (
A25289E50D87A27D00461D5B /* enctheora.c */,
+ 7497010E0DC281BB009200D8 /* decomb.c */,
B48359A70C82960500E04440 /* lang.c */,
EAA526920C3B25D200944FF2 /* stream.c */,
0DFA5C7E0B8DD3B60020BC09 /* declpcm.c */,
@@ -787,6 +791,7 @@
FC8519550C59A02C0073812C /* deinterlace.c in Sources */,
FC8519560C59A02C0073812C /* deblock.c in Sources */,
FC8519570C59A02C0073812C /* detelecine.c in Sources */,
+ 749701100DC281BB009200D8 /* decomb.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -832,6 +837,7 @@
FC8519530C59A02C0073812C /* detelecine.c in Sources */,
B48359A80C82960500E04440 /* lang.c in Sources */,
A25289E60D87A27D00461D5B /* enctheora.c in Sources */,
+ 7497010F0DC281BB009200D8 /* decomb.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Mode is different than the yadif_mode in --deinterlace:
Mode 0: uses "Fast" (ffmpeg, the traditional deinterlacer in HandBrake).
Mode 1: Default, uses yadif, mode 0 (I don't bother yadif mode 2), traditional 2-point interpolation
Mode 2: uses yadif and mcdeint (probably not worth it)
Mode 3: does a lowpass5 blend instead of interpolating
Mode 4: uses yadif, mode 0, with the new 4-point interpolation
Mode 5: yadif with the new 4-point interpolation and mcdeint
The last three options are the same as I've explained in earlier posts -- they're the comb detection parameters. I'm sticking with Transcode's values for them for now, but might increase the sensitivity....oh, and the threshold? Figured out how that works. It's the hundredth of a percent of a plane's pixels that need to show combing for the plane to show combing -- by default just under 1%, (greater than 0.09%).
Default is:
1:10:30:9
Know bug:
It's overreading a bit on the bottom of frames, so there's half a garbage line sometimes on the bottom. I think I can fix that, and I'm not sure it doesn't always happens with yadif in HB (I've read people complaining about it before).
RESOURCES
The page I got the CubicInterpolation() function from:
http://local.wasp.uwa.edu.au/~pbourke/o ... rpolation/
Efficient Cubic Interpolation:
http://www.neuron2.net/library/cubicinterp.html
Gerard de Haan is a video engineer with Philips who writes easy-to-grasp papers explaining deinterlacing algorithms he's studied, which he is gracious enough to put on the web for free as PDFs:
http://www.es.ele.tue.nl/~dehaan/papers.html
FIR FAQ:
http://www.dspguru.com/info/faqs/firfaq.htm
Also, if you wikipedia any of the keywords in this post, you'll find some useful articles.