summaryrefslogtreecommitdiffstats
path: root/libhb
diff options
context:
space:
mode:
authorJohn Stebbins <[email protected]>2016-01-22 15:46:13 -0700
committerJohn Stebbins <[email protected]>2016-03-11 14:13:33 -0700
commitc3c076a86e8c3a9b97b1ed352c88365728e3879a (patch)
tree192ca09b9b67c5670d7d64fa42bdcb4107240919 /libhb
parent2615c363516a5b29d7d02b73e6b5cf2842584e13 (diff)
decomb: split comb detection out into it's own filter
Diffstat (limited to 'libhb')
-rw-r--r--libhb/avfilter.c11
-rw-r--r--libhb/builtin_presets.h8
-rw-r--r--libhb/comb_detect.c1560
-rw-r--r--libhb/common.c25
-rw-r--r--libhb/common.h2
-rw-r--r--libhb/decomb.c1736
-rw-r--r--libhb/decomb.h24
-rw-r--r--libhb/internal.h1
-rw-r--r--libhb/libhb_presets.list2
-rw-r--r--libhb/param.c26
-rw-r--r--libhb/preset.c185
-rw-r--r--libhb/preset_builtin.json4
-rw-r--r--libhb/preset_template.json2
-rw-r--r--libhb/work.c29
14 files changed, 2033 insertions, 1582 deletions
diff --git a/libhb/avfilter.c b/libhb/avfilter.c
index 2e57f72d6..9aa8a316b 100644
--- a/libhb/avfilter.c
+++ b/libhb/avfilter.c
@@ -13,6 +13,7 @@
#include "libavfilter/avfilter.h"
#include "libavfilter/buffersrc.h"
#include "libavfilter/buffersink.h"
+#include "decomb.h"
/*****
@@ -508,11 +509,6 @@ static int avfilter_work( hb_filter_object_t * filter,
return HB_FILTER_OK;
}
-#define MODE_YADIF_ENABLE 1
-#define MODE_YADIF_SPATIAL 2
-#define MODE_YADIF_BOB 4
-#define MODE_YADIF_AUTO 8
-
/* Deinterlace Settings
* mode:parity
*
@@ -523,12 +519,13 @@ static int avfilter_work( hb_filter_object_t * filter,
* 1 = Enabled
* 2 = Spatial
* 4 = Bob
- * 8 = Auto
+ * 8 = Selective
*
* Parity:
* 0 = Top Field First
* 1 = Bottom Field First
* -1 = Automatic detection of field parity
+ *
*/
static hb_dict_t *
convert_deint_settings(const hb_dict_t * settings)
@@ -542,7 +539,7 @@ convert_deint_settings(const hb_dict_t * settings)
{
return hb_value_null();
}
- int automatic = !!(mode & MODE_YADIF_AUTO);
+ int automatic = !!(mode & MODE_DECOMB_SELECTIVE);
int bob = !!(mode & MODE_YADIF_BOB);
int no_spatial = !(mode & MODE_YADIF_SPATIAL);
mode = bob | (no_spatial << 1);
diff --git a/libhb/builtin_presets.h b/libhb/builtin_presets.h
index bbb6e1335..6fa988f4e 100644
--- a/libhb/builtin_presets.h
+++ b/libhb/builtin_presets.h
@@ -403,10 +403,11 @@ const char hb_builtin_presets_json[] =
" \"Mp4iPodCompatible\": false, \n"
" \"PictureAutoCrop\": true, \n"
" \"PictureBottomCrop\": 0, \n"
+" \"PictureCombDetectPreset\": \"fast\", \n"
" \"PictureDeblock\": 0, \n"
" \"PictureDeinterlaceCustom\": \"\", \n"
" \"PictureDeinterlaceFilter\": \"decomb\", \n"
-" \"PictureDeinterlacePreset\": \"fast\", \n"
+" \"PictureDeinterlacePreset\": \"default\", \n"
" \"PictureDenoiseCustom\": \"\", \n"
" \"PictureDenoiseFilter\": \"off\", \n"
" \"PictureDetelecine\": \"off\", \n"
@@ -708,6 +709,7 @@ const char hb_builtin_presets_json[] =
" \"Mp4iPodCompatible\": false, \n"
" \"PictureAutoCrop\": true, \n"
" \"PictureBottomCrop\": 0, \n"
+" \"PictureCombDetectPreset\": \"default\", \n"
" \"PictureDeblock\": 0, \n"
" \"PictureDeinterlaceCustom\": \"\", \n"
" \"PictureDeinterlaceFilter\": \"decomb\", \n"
@@ -795,6 +797,8 @@ const char hb_builtin_presets_json[] =
" \"Mp4iPodCompatible\": false, \n"
" \"PictureAutoCrop\": true, \n"
" \"PictureBottomCrop\": 0, \n"
+" \"PictureCombDetectCustom\": \"\", \n"
+" \"PictureCombDetectPreset\": \"off\", \n"
" \"PictureDARWidth\": 0, \n"
" \"PictureDeblock\": 0, \n"
" \"PictureDeblockCustom\": \"qp=0:mode=2\", \n"
@@ -857,7 +861,7 @@ const char hb_builtin_presets_json[] =
" \"x264Option\": \"\", \n"
" \"x264UseAdvancedOptions\": false\n"
" }, \n"
-" \"VersionMajor\": 12, \n"
+" \"VersionMajor\": 13, \n"
" \"VersionMicro\": 0, \n"
" \"VersionMinor\": 0\n"
" }\n"
diff --git a/libhb/comb_detect.c b/libhb/comb_detect.c
new file mode 100644
index 000000000..b9f96ee31
--- /dev/null
+++ b/libhb/comb_detect.c
@@ -0,0 +1,1560 @@
+/* comb_detect.c
+
+ Copyright (c) 2003-2016 HandBrake Team
+ 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 v2.
+ For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
+
+*/
+
+/*****
+Parameters:
+ Mode : Spatial metric : Motion thresh : Spatial thresh : Mask Filter Mode :
+ Block thresh : Block width : Block height
+
+Defaults:
+ 3:2:3:3:2:40:16:16
+
+Original "Faster" settings:
+ 0:2:6:9:1:80:16:16
+*****/
+
+#define MODE_GAMMA 1 // Scale gamma when decombing
+#define MODE_FILTER 2 // Filter combing mask
+#define MODE_MASK 4 // Output combing masks instead of pictures
+#define MODE_COMPOSITE 8 // Overlay combing mask onto picture
+
+#define FILTER_CLASSIC 1
+#define FILTER_ERODE_DILATE 2
+
+#include "hb.h"
+#include "taskset.h"
+
+typedef struct decomb_thread_arg_s {
+ hb_filter_private_t *pv;
+ int segment;
+ int segment_start[3];
+ int segment_height[3];
+} decomb_thread_arg_t;
+
+struct hb_filter_private_s
+{
+ // comb detect parameters
+ int mode;
+ int filter_mode;
+ int spatial_metric;
+ int motion_threshold;
+ int spatial_threshold;
+ int block_threshold;
+ int block_width;
+ int block_height;
+ int * block_score;
+ int comb_check_complete;
+ int comb_check_nthreads;
+
+ float gamma_lut[256];
+
+ int comb_detect_ready;
+
+ hb_buffer_t * ref[3];
+ int ref_used[3];
+
+ /* Make buffers to store a comb masks. */
+ hb_buffer_t * mask;
+ hb_buffer_t * mask_filtered;
+ hb_buffer_t * mask_temp;
+ int mask_box_x;
+ int mask_box_y;
+ uint8_t mask_box_color;
+
+ int cpu_count;
+ int segment_height[3];
+
+ taskset_t decomb_filter_taskset; // Threads for comb detection
+ taskset_t decomb_check_taskset; // Threads for comb check
+ taskset_t mask_filter_taskset; // Threads for decomb mask filter
+ taskset_t mask_erode_taskset; // Threads for decomb mask erode
+ taskset_t mask_dilate_taskset; // Threads for decomb mask dilate
+
+ hb_buffer_list_t out_list;
+
+ // Filter statistics
+ int comb_heavy;
+ int comb_light;
+ int comb_none;
+ int frames;
+};
+
+static int comb_detect_init( hb_filter_object_t * filter,
+ hb_filter_init_t * init );
+
+static int comb_detect_work( hb_filter_object_t * filter,
+ hb_buffer_t ** buf_in,
+ hb_buffer_t ** buf_out );
+
+static void comb_detect_close( hb_filter_object_t * filter );
+
+static const char comb_detect_template[] =
+ "mode=^"HB_INT_REG"$:spatial-metric=^([012])$:"
+ "motion-thresh=^"HB_INT_REG"$:spatial-thresh=^"HB_INT_REG"$:"
+ "filter-mode=^([012])$:block-thresh=^"HB_INT_REG"$:"
+ "block-width=^"HB_INT_REG"$:block-height=^"HB_INT_REG"$:"
+ "disable=^"HB_BOOL_REG"$";
+
+hb_filter_object_t hb_filter_comb_detect =
+{
+ .id = HB_FILTER_COMB_DETECT,
+ .enforce_order = 1,
+ .name = "Comb Detect",
+ .settings = NULL,
+ .init = comb_detect_init,
+ .work = comb_detect_work,
+ .close = comb_detect_close,
+ .settings_template = comb_detect_template,
+};
+
+static void draw_mask_box( hb_filter_private_t * pv )
+{
+ int x = pv->mask_box_x;
+ int y = pv->mask_box_y;
+ int box_width = pv->block_width;
+ int box_height = pv->block_height;
+ int stride;
+ uint8_t * mskp;
+
+ if (pv->mode & MODE_FILTER)
+ {
+ mskp = pv->mask_filtered->plane[0].data;
+ stride = pv->mask_filtered->plane[0].stride;
+ }
+ else
+ {
+ mskp = pv->mask->plane[0].data;
+ stride = pv->mask->plane[0].stride;
+ }
+
+
+ int block_x, block_y;
+ for (block_x = 0; block_x < box_width; block_x++)
+ {
+ mskp[ y * stride + x + block_x] = 128;
+ mskp[(y + box_height) * stride + x + block_x] = 128;
+ }
+
+ for (block_y = 0; block_y < box_height; block_y++)
+ {
+ mskp[stride * (y + block_y) + x ] = 128;
+ mskp[stride * (y + block_y) + x + box_width] = 128;
+ }
+}
+
+static void apply_mask_line( uint8_t * srcp,
+ uint8_t * mskp,
+ int width )
+{
+ int x;
+
+ for (x = 0; x < width; x++)
+ {
+ if (mskp[x] == 1)
+ {
+ srcp[x] = 255;
+ }
+ if (mskp[x] == 128)
+ {
+ srcp[x] = 128;
+ }
+ }
+}
+
+static void apply_mask(hb_filter_private_t * pv, hb_buffer_t * b)
+{
+ /* draw_boxes */
+ draw_mask_box( pv );
+
+ int pp, yy;
+ hb_buffer_t * m;
+
+ if (pv->mode & MODE_FILTER)
+ {
+ m = pv->mask_filtered;
+ }
+ else
+ {
+ m = pv->mask;
+ }
+ for (pp = 0; pp < 3; pp++)
+ {
+ uint8_t * dstp = b->plane[pp].data;
+ uint8_t * mskp = m->plane[pp].data;
+
+ for (yy = 0; yy < m->plane[pp].height; yy++)
+ {
+ if (!(pv->mode & MODE_COMPOSITE) && pp == 0)
+ {
+ memcpy(dstp, mskp, m->plane[pp].width);
+ }
+ else if (!(pv->mode & MODE_COMPOSITE))
+ {
+ memset(dstp, 128, m->plane[pp].width);
+ }
+ if (pp == 0)
+ {
+ apply_mask_line(dstp, mskp, m->plane[pp].width);
+ }
+
+ dstp += b->plane[pp].stride;
+ mskp += m->plane[pp].stride;
+ }
+ }
+}
+
+static void store_ref(hb_filter_private_t * pv, hb_buffer_t * b)
+{
+ // Free unused buffer
+ if (!pv->ref_used[0])
+ {
+ hb_buffer_close(&pv->ref[0]);
+ }
+ memmove(&pv->ref[0], &pv->ref[1], sizeof(pv->ref[0]) * 2 );
+ memmove(&pv->ref_used[0], &pv->ref_used[1], sizeof(pv->ref_used[0]) * 2 );
+ pv->ref[2] = b;
+ pv->ref_used[2] = 0;
+}
+
+static void reset_combing_results( hb_filter_private_t * pv )
+{
+ pv->comb_check_complete = 0;
+ int ii;
+ for (ii = 0; ii < pv->comb_check_nthreads; ii++)
+ {
+ pv->block_score[ii] = 0;
+ }
+}
+
+static int check_combing_results( hb_filter_private_t * pv )
+{
+ int combed = HB_COMB_NONE;
+
+ int ii;
+ for (ii = 0; ii < pv->comb_check_nthreads; ii++)
+ {
+ if (pv->block_score[ii] >= ( pv->block_threshold / 2 ))
+ {
+ if (pv->block_score[ii] <= pv->block_threshold)
+ {
+ // Indicate light combing for block_score that is between
+ // ( pv->block_threshold / 2 ) and pv->block_threshold
+ combed = HB_COMB_LIGHT;
+ pv->mask_box_color = 2;
+ }
+ else if (pv->block_score[ii] > pv->block_threshold)
+ {
+ pv->mask_box_color = 1;
+ return HB_COMB_HEAVY;
+ }
+ }
+ }
+
+ return combed;
+}
+
+static void check_filtered_combing_mask( hb_filter_private_t * pv, int segment,
+ int start, int stop )
+{
+ /* Go through the mask in X*Y blocks. If any of these windows
+ have threshold or more combed pixels, consider the whole
+ frame to be combed and send it on to be deinterlaced. */
+
+ /* Block mask threshold -- The number of pixels
+ in a block_width * block_height window of
+ he mask that need to show combing for the
+ whole frame to be seen as such. */
+ int threshold = pv->block_threshold;
+ int block_width = pv->block_width;
+ int block_height = pv->block_height;
+ int block_x, block_y;
+ int block_score = 0;
+ uint8_t * mask_p;
+ int x, y, pp;
+
+ for (pp = 0; pp < 1; pp++)
+ {
+ int stride = pv->mask_filtered->plane[pp].stride;
+ int width = pv->mask_filtered->plane[pp].width;
+
+ pv->mask_box_x = -1;
+ pv->mask_box_y = -1;
+ pv->mask_box_color = 0;
+
+ for (y = start; y < ( stop - block_height + 1 ); y = y + block_height)
+ {
+ for (x = 0; x < ( width - block_width ); x = x + block_width)
+ {
+ block_score = 0;
+
+ for (block_y = 0; block_y < block_height; block_y++)
+ {
+ int my = y + block_y;
+ mask_p = &pv->mask_filtered->plane[pp].data[my*stride + x];
+
+ for (block_x = 0; block_x < block_width; block_x++)
+ {
+ block_score += mask_p[0];
+ mask_p++;
+ }
+ }
+
+ if (pv->comb_check_complete)
+ {
+ // Some other thread found coming before this one
+ return;
+ }
+
+ if (block_score >= ( threshold / 2 ))
+ {
+ pv->mask_box_x = x;
+ pv->mask_box_y = y;
+
+ pv->block_score[segment] = block_score;
+ if (block_score > threshold)
+ {
+ pv->comb_check_complete = 1;
+ return;
+ }
+ }
+ }
+ }
+ }
+}
+
+static void check_combing_mask( hb_filter_private_t * pv, int segment,
+ int start, int stop )
+{
+ /* Go through the mask in X*Y blocks. If any of these windows
+ have threshold or more combed pixels, consider the whole
+ frame to be combed and send it on to be deinterlaced. */
+
+ /* Block mask threshold -- The number of pixels
+ in a block_width * block_height window of
+ he mask that need to show combing for the
+ whole frame to be seen as such. */
+ int threshold = pv->block_threshold;
+ int block_width = pv->block_width;
+ int block_height = pv->block_height;
+ int block_x, block_y;
+ int block_score = 0;
+ uint8_t * mask_p;
+ int x, y, pp;
+
+ for (pp = 0; pp < 1; pp++)
+ {
+ int stride = pv->mask->plane[pp].stride;
+ int width = pv->mask->plane[pp].width;
+
+ for (y = start; y < (stop - block_height + 1); y = y + block_height)
+ {
+ for (x = 0; x < (width - block_width); x = x + block_width)
+ {
+ block_score = 0;
+
+ for (block_y = 0; block_y < block_height; block_y++)
+ {
+ int mask_y = y + block_y;
+ mask_p = &pv->mask->plane[pp].data[mask_y * stride + x];
+
+ for (block_x = 0; block_x < block_width; block_x++)
+ {
+ /* We only want to mark a pixel in a block as combed
+ if the adjacent pixels are as well. Got to
+ handle the sides separately. */
+ if ((x + block_x) == 0)
+ {
+ block_score += mask_p[0] & mask_p[1];
+ }
+ else if ((x + block_x) == (width -1))
+ {
+ block_score += mask_p[-1] & mask_p[0];
+ }
+ else
+ {
+ block_score += mask_p[-1] & mask_p[0] & mask_p[1];
+ }
+
+ mask_p++;
+ }
+ }
+
+ if (pv->comb_check_complete)
+ {
+ // Some other thread found coming before this one
+ return;
+ }
+
+ if (block_score >= ( threshold / 2 ))
+ {
+ pv->mask_box_x = x;
+ pv->mask_box_y = y;
+
+ pv->block_score[segment] = block_score;
+ if (block_score > threshold)
+ {
+ pv->comb_check_complete = 1;
+ return;
+ }
+ }
+ }
+ }
+ }
+}
+
+static void build_gamma_lut( hb_filter_private_t * pv )
+{
+ int i;
+ for (i = 0; i < 256; i++)
+ {
+ pv->gamma_lut[i] = pow( ( (float)i / (float)255 ), 2.2f );
+ }
+}
+
+static void detect_gamma_combed_segment( hb_filter_private_t * pv,
+ int segment_start, int segment_stop )
+{
+ /* A mish-mash of various comb detection tricks
+ picked up from neuron2's Decomb plugin for
+ AviSynth and tritical's IsCombedT and
+ IsCombedTIVTC plugins. */
+
+ /* Comb scoring algorithm */
+ /* Motion threshold */
+ float mthresh = (float)pv->motion_threshold / (float)255;
+ /* Spatial threshold */
+ float athresh = (float)pv->spatial_threshold / (float)255;
+ float athresh6 = 6 *athresh;
+
+ /* One pas for Y, one pass for U, one pass for V */
+ int pp;
+ for (pp = 0; pp < 1; pp++)
+ {
+ int x, y;
+ int stride = pv->ref[0]->plane[pp].stride;
+ int width = pv->ref[0]->plane[pp].width;
+ int height = pv->ref[0]->plane[pp].height;
+
+ /* Comb detection has to start at y = 2 and end at
+ y = height - 2, because it needs to examine
+ 2 pixels above and 2 below the current pixel. */
+ if (segment_start < 2)
+ segment_start = 2;
+ if (segment_stop > height - 2)
+ segment_stop = height - 2;
+
+ for (y = segment_start; y < segment_stop; y++)
+ {
+ /* These are just to make the buffer locations easier to read. */
+ int up_2 = -2 * stride ;
+ int up_1 = -1 * stride;
+ int down_1 = stride;
+ int down_2 = 2 * stride;
+
+ /* We need to examine a column of 5 pixels
+ in the prev, cur, and next frames. */
+ uint8_t * prev = &pv->ref[0]->plane[pp].data[y * stride];
+ uint8_t * cur = &pv->ref[1]->plane[pp].data[y * stride];
+ uint8_t * next = &pv->ref[2]->plane[pp].data[y * stride];
+ uint8_t * mask = &pv->mask->plane[pp].data[y * stride];
+
+ memset(mask, 0, stride);
+
+ for (x = 0; x < width; x++)
+ {
+ float up_diff, down_diff;
+ up_diff = pv->gamma_lut[cur[0]] - pv->gamma_lut[cur[up_1]];
+ down_diff = pv->gamma_lut[cur[0]] - pv->gamma_lut[cur[down_1]];
+
+ if (( up_diff > athresh && down_diff > athresh ) ||
+ ( up_diff < -athresh && down_diff < -athresh ))
+ {
+ /* The pixel above and below are different,
+ and they change in the same "direction" too.*/
+ int motion = 0;
+ if (mthresh > 0)
+ {
+ /* Make sure there's sufficient motion between frame t-1 to frame t+1. */
+ if (fabs(pv->gamma_lut[prev[0]] - pv->gamma_lut[cur[0]] ) > mthresh &&
+ fabs(pv->gamma_lut[cur[up_1]] - pv->gamma_lut[next[up_1]] ) > mthresh &&
+ fabs(pv->gamma_lut[cur[down_1]] - pv->gamma_lut[next[down_1]]) > mthresh)
+ motion++;
+ if (fabs(pv->gamma_lut[next[0]] - pv->gamma_lut[cur[0]] ) > mthresh &&
+ fabs(pv->gamma_lut[prev[up_1]] - pv->gamma_lut[cur[up_1]] ) > mthresh &&
+ fabs(pv->gamma_lut[prev[down_1]] - pv->gamma_lut[cur[down_1]]) > mthresh)
+ motion++;
+
+ }
+ else
+ {
+ /* User doesn't want to check for motion,
+ so move on to the spatial check. */
+ motion = 1;
+ }
+
+ if (motion || pv->frames == 0)
+ {
+ float combing;
+ /* Tritical's noise-resistant combing scorer.
+ The check is done on a bob+blur convolution. */
+ combing = fabs(pv->gamma_lut[cur[up_2]] +
+ (4 * pv->gamma_lut[cur[0]]) +
+ pv->gamma_lut[cur[down_2]] -
+ (3 * (pv->gamma_lut[cur[up_1]] +
+ pv->gamma_lut[cur[down_1]])));
+ /* If the frame is sufficiently combed,
+ then mark it down on the mask as 1. */
+ if (combing > athresh6)
+ {
+ mask[0] = 1;
+ }
+ }
+ }
+
+ cur++;
+ prev++;
+ next++;
+ mask++;
+ }
+ }
+ }
+}
+
+static void detect_combed_segment( hb_filter_private_t * pv,
+ int segment_start, int segment_stop )
+{
+ /* A mish-mash of various comb detection tricks
+ picked up from neuron2's Decomb plugin for
+ AviSynth and tritical's IsCombedT and
+ IsCombedTIVTC plugins. */
+
+
+ /* Comb scoring algorithm */
+ int spatial_metric = pv->spatial_metric;
+ /* Motion threshold */
+ int mthresh = pv->motion_threshold;
+ /* Spatial threshold */
+ int athresh = pv->spatial_threshold;
+ int athresh_squared = athresh * athresh;
+ int athresh6 = 6 * athresh;
+
+ /* One pas for Y, one pass for U, one pass for V */
+ int pp;
+ for (pp = 0; pp < 1; pp++)
+ {
+ int x, y;
+ int stride = pv->ref[0]->plane[pp].stride;
+ int width = pv->ref[0]->plane[pp].width;
+ int height = pv->ref[0]->plane[pp].height;
+
+ /* Comb detection has to start at y = 2 and end at
+ y = height - 2, because it needs to examine
+ 2 pixels above and 2 below the current pixel. */
+ if (segment_start < 2)
+ segment_start = 2;
+ if (segment_stop > height - 2)
+ segment_stop = height - 2;
+
+ for (y = segment_start; y < segment_stop; y++)
+ {
+ /* These are just to make the buffer locations easier to read. */
+ int up_2 = -2 * stride ;
+ int up_1 = -1 * stride;
+ int down_1 = stride;
+ int down_2 = 2 * stride;
+
+ /* We need to examine a column of 5 pixels
+ in the prev, cur, and next frames. */
+ uint8_t * prev = &pv->ref[0]->plane[pp].data[y * stride];
+ uint8_t * cur = &pv->ref[1]->plane[pp].data[y * stride];
+ uint8_t * next = &pv->ref[2]->plane[pp].data[y * stride];
+ uint8_t * mask = &pv->mask->plane[pp].data[y * stride];
+
+ memset(mask, 0, stride);
+
+ for (x = 0; x < width; x++)
+ {
+ int up_diff = cur[0] - cur[up_1];
+ int down_diff = cur[0] - cur[down_1];
+
+ if (( up_diff > athresh && down_diff > athresh ) ||
+ ( up_diff < -athresh && down_diff < -athresh ))
+ {
+ /* The pixel above and below are different,
+ and they change in the same "direction" too.*/
+ int motion = 0;
+ if (mthresh > 0)
+ {
+ /* Make sure there's sufficient motion between frame t-1 to frame t+1. */
+ if (abs(prev[0] - cur[0] ) > mthresh &&
+ abs(cur[up_1] - next[up_1] ) > mthresh &&
+ abs(cur[down_1] - next[down_1]) > mthresh)
+ motion++;
+ if (abs(next[0] - cur[0] ) > mthresh &&
+ abs(prev[up_1] - cur[up_1] ) > mthresh &&
+ abs(prev[down_1] - cur[down_1]) > mthresh)
+ motion++;
+ }
+ else
+ {
+ /* User doesn't want to check for motion,
+ so move on to the spatial check. */
+ motion = 1;
+ }
+
+ // If motion, or we can't measure motion yet...
+ if (motion || pv->frames == 0)
+ {
+ /* That means it's time for the spatial check.
+ We've got several options here. */
+ if (spatial_metric == 0)
+ {
+ /* Simple 32detect style comb detection */
+ if ((abs(cur[0] - cur[down_2]) < 10) &&
+ (abs(cur[0] - cur[down_1]) > 15))
+ {
+ mask[0] = 1;
+ }
+ }
+ else if (spatial_metric == 1)
+ {
+ /* This, for comparison, is what IsCombed uses.
+ It's better, but still noise senstive. */
+ int combing = ( cur[up_1] - cur[0] ) *
+ ( cur[down_1] - cur[0] );
+
+ if (combing > athresh_squared)
+ {
+ mask[0] = 1;
+ }
+ }
+ else if (spatial_metric == 2)
+ {
+ /* Tritical's noise-resistant combing scorer.
+ The check is done on a bob+blur convolution. */
+ int combing = abs( cur[up_2]
+ + ( 4 * cur[0] )
+ + cur[down_2]
+ - ( 3 * ( cur[up_1]
+ + cur[down_1] ) ) );
+
+ /* If the frame is sufficiently combed,
+ then mark it down on the mask as 1. */
+ if (combing > athresh6)
+ {
+ mask[0] = 1;
+ }
+ }
+ }
+ }
+
+ cur++;
+ prev++;
+ next++;
+ mask++;
+ }
+ }
+ }
+}
+
+static void mask_dilate_thread( void *thread_args_v )
+{
+ hb_filter_private_t * pv;
+ int segment, segment_start, segment_stop;
+ decomb_thread_arg_t *thread_args = thread_args_v;
+
+ pv = thread_args->pv;
+ segment = thread_args->segment;
+
+ hb_log("mask dilate thread started for segment %d", segment);
+
+ while (1)
+ {
+ /*
+ * Wait here until there is work to do.
+ */
+ taskset_thread_wait4start( &pv->mask_dilate_taskset, segment );
+
+ if (taskset_thread_stop(&pv->mask_dilate_taskset, segment))
+ {
+ /*
+ * No more work to do, exit this thread.
+ */
+ break;
+ }
+
+ int xx, yy, pp;
+
+ int count;
+ int dilation_threshold = 4;
+
+ for (pp = 0; pp < 1; pp++)
+ {
+ int width = pv->mask_filtered->plane[pp].width;
+ int height = pv->mask_filtered->plane[pp].height;
+ int stride = pv->mask_filtered->plane[pp].stride;
+
+ int start, stop, p, c, n;
+ segment_start = thread_args->segment_start[pp];
+ segment_stop = segment_start + thread_args->segment_height[pp];
+
+ if (segment_start == 0)
+ {
+ start = 1;
+ p = 0;
+ c = 1;
+ n = 2;
+ }
+ else
+ {
+ start = segment_start;
+ p = segment_start - 1;
+ c = segment_start;
+ n = segment_start + 1;
+ }
+
+ if (segment_stop == height)
+ {
+ stop = height -1;
+ }
+ else
+ {
+ stop = segment_stop;
+ }
+
+ uint8_t *curp = &pv->mask_filtered->plane[pp].data[p * stride + 1];
+ uint8_t *cur = &pv->mask_filtered->plane[pp].data[c * stride + 1];
+ uint8_t *curn = &pv->mask_filtered->plane[pp].data[n * stride + 1];
+ uint8_t *dst = &pv->mask_temp->plane[pp].data[c * stride + 1];
+
+ for (yy = start; yy < stop; yy++)
+ {
+ for (xx = 1; xx < width - 1; xx++)
+ {
+ if (cur[xx])
+ {
+ dst[xx] = 1;
+ continue;
+ }
+
+ count = curp[xx-1] + curp[xx] + curp[xx+1] +
+ cur [xx-1] + cur [xx+1] +
+ curn[xx-1] + curn[xx] + curn[xx+1];
+
+ dst[xx] = count >= dilation_threshold;
+ }
+ curp += stride;
+ cur += stride;
+ curn += stride;
+ dst += stride;
+ }
+ }
+
+ taskset_thread_complete( &pv->mask_dilate_taskset, segment );
+ }
+
+ /*
+ * Finished this segment, let everyone know.
+ */
+ taskset_thread_complete( &pv->mask_dilate_taskset, segment );
+}
+
+static void mask_erode_thread( void *thread_args_v )
+{
+ hb_filter_private_t * pv;
+ int segment, segment_start, segment_stop;
+ decomb_thread_arg_t *thread_args = thread_args_v;
+
+ pv = thread_args->pv;
+ segment = thread_args->segment;
+
+ hb_log("mask erode thread started for segment %d", segment);
+
+ while (1)
+ {
+ /*
+ * Wait here until there is work to do.
+ */
+ taskset_thread_wait4start( &pv->mask_erode_taskset, segment );
+
+ if (taskset_thread_stop( &pv->mask_erode_taskset, segment ))
+ {
+ /*
+ * No more work to do, exit this thread.
+ */
+ break;
+ }
+
+ int xx, yy, pp;
+
+ int count;
+ int erosion_threshold = 2;
+
+ for (pp = 0; pp < 1; pp++)
+ {
+ int width = pv->mask_filtered->plane[pp].width;
+ int height = pv->mask_filtered->plane[pp].height;
+ int stride = pv->mask_filtered->plane[pp].stride;
+
+ int start, stop, p, c, n;
+ segment_start = thread_args->segment_start[pp];
+ segment_stop = segment_start + thread_args->segment_height[pp];
+
+ if (segment_start == 0)
+ {
+ start = 1;
+ p = 0;
+ c = 1;
+ n = 2;
+ }
+ else
+ {
+ start = segment_start;
+ p = segment_start - 1;
+ c = segment_start;
+ n = segment_start + 1;
+ }
+
+ if (segment_stop == height)
+ {
+ stop = height -1;
+ }
+ else
+ {
+ stop = segment_stop;
+ }
+
+ uint8_t *curp = &pv->mask_temp->plane[pp].data[p * stride + 1];
+ uint8_t *cur = &pv->mask_temp->plane[pp].data[c * stride + 1];
+ uint8_t *curn = &pv->mask_temp->plane[pp].data[n * stride + 1];
+ uint8_t *dst = &pv->mask_filtered->plane[pp].data[c * stride + 1];
+
+ for (yy = start; yy < stop; yy++)
+ {
+ for (xx = 1; xx < width - 1; xx++)
+ {
+ if (cur[xx] == 0)
+ {
+ dst[xx] = 0;
+ continue;
+ }
+
+ count = curp[xx-1] + curp[xx] + curp[xx+1] +
+ cur [xx-1] + cur [xx+1] +
+ curn[xx-1] + curn[xx] + curn[xx+1];
+
+ dst[xx] = count >= erosion_threshold;
+ }
+ curp += stride;
+ cur += stride;
+ curn += stride;
+ dst += stride;
+ }
+ }
+
+ taskset_thread_complete( &pv->mask_erode_taskset, segment );
+ }
+
+ /*
+ * Finished this segment, let everyone know.
+ */
+ taskset_thread_complete( &pv->mask_erode_taskset, segment );
+}
+
+static void mask_filter_thread( void *thread_args_v )
+{
+ hb_filter_private_t * pv;
+ int segment, segment_start, segment_stop;
+ decomb_thread_arg_t *thread_args = thread_args_v;
+
+ pv = thread_args->pv;
+ segment = thread_args->segment;
+
+ hb_log("mask filter thread started for segment %d", segment);
+
+ while (1)
+ {
+ /*
+ * Wait here until there is work to do.
+ */
+ taskset_thread_wait4start( &pv->mask_filter_taskset, segment );
+
+ if (taskset_thread_stop( &pv->mask_filter_taskset, segment ))
+ {
+ /*
+ * No more work to do, exit this thread.
+ */
+ break;
+ }
+
+ int xx, yy, pp;
+
+ for (pp = 0; pp < 1; pp++)
+ {
+ int width = pv->mask->plane[pp].width;
+ int height = pv->mask->plane[pp].height;
+ int stride = pv->mask->plane[pp].stride;
+
+ int start, stop, p, c, n;
+ segment_start = thread_args->segment_start[pp];
+ segment_stop = segment_start + thread_args->segment_height[pp];
+
+ if (segment_start == 0)
+ {
+ start = 1;
+ p = 0;
+ c = 1;
+ n = 2;
+ }
+ else
+ {
+ start = segment_start;
+ p = segment_start - 1;
+ c = segment_start;
+ n = segment_start + 1;
+ }
+
+ if (segment_stop == height)
+ {
+ stop = height - 1;
+ }
+ else
+ {
+ stop = segment_stop;
+ }
+
+ uint8_t *curp = &pv->mask->plane[pp].data[p * stride + 1];
+ uint8_t *cur = &pv->mask->plane[pp].data[c * stride + 1];
+ uint8_t *curn = &pv->mask->plane[pp].data[n * stride + 1];
+ uint8_t *dst = (pv->filter_mode == FILTER_CLASSIC ) ?
+ &pv->mask_filtered->plane[pp].data[c * stride + 1] :
+ &pv->mask_temp->plane[pp].data[c * stride + 1] ;
+
+ for (yy = start; yy < stop; yy++)
+ {
+ for (xx = 1; xx < width - 1; xx++)
+ {
+ int h_count, v_count;
+
+ h_count = cur[xx-1] & cur[xx] & cur[xx+1];
+ v_count = curp[xx] & cur[xx] & curn[xx];
+
+ if (pv->filter_mode == FILTER_CLASSIC)
+ {
+ dst[xx] = h_count;
+ }
+ else
+ {
+ dst[xx] = h_count & v_count;
+ }
+ }
+ curp += stride;
+ cur += stride;
+ curn += stride;
+ dst += stride;
+ }
+ }
+
+ taskset_thread_complete( &pv->mask_filter_taskset, segment );
+ }
+
+ /*
+ * Finished this segment, let everyone know.
+ */
+ taskset_thread_complete( &pv->mask_filter_taskset, segment );
+}
+
+static void decomb_check_thread( void *thread_args_v )
+{
+ hb_filter_private_t * pv;
+ int segment, segment_start, segment_stop;
+ decomb_thread_arg_t *thread_args = thread_args_v;
+
+ pv = thread_args->pv;
+ segment = thread_args->segment;
+
+ hb_log("decomb check thread started for segment %d", segment);
+
+ while (1)
+ {
+ /*
+ * Wait here until there is work to do.
+ */
+ taskset_thread_wait4start( &pv->decomb_check_taskset, segment );
+
+ if (taskset_thread_stop( &pv->decomb_check_taskset, segment ))
+ {
+ /*
+ * No more work to do, exit this thread.
+ */
+ break;
+ }
+
+ segment_start = thread_args->segment_start[0];
+ segment_stop = segment_start + thread_args->segment_height[0];
+
+ if (pv->mode & MODE_FILTER)
+ {
+ check_filtered_combing_mask(pv, segment, segment_start, segment_stop);
+ }
+ else
+ {
+ check_combing_mask(pv, segment, segment_start, segment_stop);
+ }
+
+ taskset_thread_complete( &pv->decomb_check_taskset, segment );
+ }
+
+ /*
+ * Finished this segment, let everyone know.
+ */
+ taskset_thread_complete( &pv->decomb_check_taskset, segment );
+}
+
+/*
+ * comb detect this segment of all three planes in a single thread.
+ */
+static void decomb_filter_thread( void *thread_args_v )
+{
+ hb_filter_private_t * pv;
+ int segment, segment_start, segment_stop;
+ decomb_thread_arg_t *thread_args = thread_args_v;
+
+ pv = thread_args->pv;
+ segment = thread_args->segment;
+
+ hb_log("decomb filter thread started for segment %d", segment);
+
+ while (1)
+ {
+ /*
+ * Wait here until there is work to do.
+ */
+ taskset_thread_wait4start( &pv->decomb_filter_taskset, segment );
+
+ if (taskset_thread_stop( &pv->decomb_filter_taskset, segment ))
+ {
+ /*
+ * No more work to do, exit this thread.
+ */
+ break;
+ }
+
+ /*
+ * Process segment (for now just from luma)
+ */
+ int pp;
+ for (pp = 0; pp < 1; pp++)
+ {
+ segment_start = thread_args->segment_start[pp];
+ segment_stop = segment_start + thread_args->segment_height[pp];
+
+ if (pv->mode & MODE_GAMMA)
+ {
+ detect_gamma_combed_segment( pv, segment_start, segment_stop );
+ }
+ else
+ {
+ detect_combed_segment( pv, segment_start, segment_stop );
+ }
+ }
+
+ taskset_thread_complete( &pv->decomb_filter_taskset, segment );
+ }
+
+ /*
+ * Finished this segment, let everyone know.
+ */
+ taskset_thread_complete( &pv->decomb_filter_taskset, segment );
+}
+
+static int comb_segmenter( hb_filter_private_t * pv )
+{
+ /*
+ * Now that all data for decomb detection is ready for
+ * our threads, fire them off and wait for their completion.
+ */
+ taskset_cycle( &pv->decomb_filter_taskset );
+
+ if (pv->mode & MODE_FILTER)
+ {
+ taskset_cycle( &pv->mask_filter_taskset );
+ if (pv->filter_mode == FILTER_ERODE_DILATE)
+ {
+ taskset_cycle( &pv->mask_erode_taskset );
+ taskset_cycle( &pv->mask_dilate_taskset );
+ taskset_cycle( &pv->mask_erode_taskset );
+ }
+ }
+ reset_combing_results(pv);
+ taskset_cycle(&pv->decomb_check_taskset);
+ return check_combing_results(pv);
+}
+
+static int comb_detect_init( hb_filter_object_t * filter,
+ hb_filter_init_t * init )
+{
+ filter->private_data = calloc( 1, sizeof(struct hb_filter_private_s) );
+ hb_filter_private_t * pv = filter->private_data;
+
+ hb_buffer_list_clear(&pv->out_list);
+ build_gamma_lut( pv );
+
+ pv->frames = 0;
+ pv->comb_heavy = 0;
+ pv->comb_light = 0;
+ pv->comb_none = 0;
+
+ pv->comb_detect_ready = 0;
+
+ pv->mode = MODE_GAMMA | MODE_FILTER;
+ pv->filter_mode = FILTER_ERODE_DILATE;
+ pv->spatial_metric = 2;
+ pv->motion_threshold = 3;
+ pv->spatial_threshold = 3;
+ pv->block_threshold = 40;
+ pv->block_width = 16;
+ pv->block_height = 16;
+
+ if (filter->settings)
+ {
+ hb_value_t * dict = filter->settings;
+
+ // Get comb detection settings
+ hb_dict_extract_int(&pv->mode, dict, "mode");
+ hb_dict_extract_int(&pv->spatial_metric, dict, "spatial-metric");
+ hb_dict_extract_int(&pv->motion_threshold, dict, "motion-thresh");
+ hb_dict_extract_int(&pv->spatial_threshold, dict, "spatial-thresh");
+ hb_dict_extract_int(&pv->filter_mode, dict, "filter-mode");
+ hb_dict_extract_int(&pv->block_threshold, dict, "block-thresh");
+ hb_dict_extract_int(&pv->block_width, dict, "block-width");
+ hb_dict_extract_int(&pv->block_height, dict, "block-height");
+ }
+
+ pv->cpu_count = hb_get_cpu_count();
+
+ // Make segment sizes an even number of lines
+ int height = hb_image_height(init->pix_fmt, init->geometry.height, 0);
+ // each segment of each plane must begin on an even row.
+ pv->segment_height[0] = (height / pv->cpu_count) & ~3;
+ pv->segment_height[1] = hb_image_height(init->pix_fmt, pv->segment_height[0], 1);
+ pv->segment_height[2] = hb_image_height(init->pix_fmt, pv->segment_height[0], 2);
+
+ /* Allocate buffers to store comb masks. */
+ pv->mask = hb_frame_buffer_init(init->pix_fmt,
+ init->geometry.width, init->geometry.height);
+ pv->mask_filtered = hb_frame_buffer_init(init->pix_fmt,
+ init->geometry.width, init->geometry.height);
+ pv->mask_temp = hb_frame_buffer_init(init->pix_fmt,
+ init->geometry.width, init->geometry.height);
+ memset(pv->mask->data, 0, pv->mask->size);
+ memset(pv->mask_filtered->data, 0, pv->mask_filtered->size);
+ memset(pv->mask_temp->data, 0, pv->mask_temp->size);
+
+ int ii;
+
+ /*
+ * Create comb detection taskset.
+ */
+ if (taskset_init( &pv->decomb_filter_taskset, pv->cpu_count,
+ sizeof( decomb_thread_arg_t ) ) == 0)
+ {
+ hb_error( "decomb could not initialize taskset" );
+ }
+
+ decomb_thread_arg_t *decomb_prev_thread_args = NULL;
+ for (ii = 0; ii < pv->cpu_count; ii++)
+ {
+ decomb_thread_arg_t *thread_args;
+
+ thread_args = taskset_thread_args( &pv->decomb_filter_taskset, ii );
+ thread_args->pv = pv;
+ thread_args->segment = ii;
+
+ int pp;
+ for (pp = 0; pp < 3; pp++)
+ {
+ if (decomb_prev_thread_args != NULL)
+ {
+ thread_args->segment_start[pp] =
+ decomb_prev_thread_args->segment_start[pp] +
+ decomb_prev_thread_args->segment_height[pp];
+ }
+ if (ii == pv->cpu_count - 1)
+ {
+ /*
+ * Final segment
+ */
+ thread_args->segment_height[pp] =
+ hb_image_height(init->pix_fmt, init->geometry.height, pp) -
+ thread_args->segment_start[pp];
+ } else {
+ thread_args->segment_height[pp] = pv->segment_height[pp];
+ }
+ }
+
+ if (taskset_thread_spawn( &pv->decomb_filter_taskset, ii,
+ "decomb_filter_segment",
+ decomb_filter_thread,
+ HB_NORMAL_PRIORITY ) == 0)
+ {
+ hb_error( "decomb could not spawn thread" );
+ }
+
+ decomb_prev_thread_args = thread_args;
+ }
+
+ pv->comb_check_nthreads = init->geometry.height / pv->block_height;
+
+ if (pv->comb_check_nthreads > pv->cpu_count)
+ pv->comb_check_nthreads = pv->cpu_count;
+
+ pv->block_score = calloc(pv->comb_check_nthreads, sizeof(int));
+
+ /*
+ * Create comb check taskset.
+ */
+ if (taskset_init( &pv->decomb_check_taskset, pv->comb_check_nthreads,
+ sizeof( decomb_thread_arg_t ) ) == 0)
+ {
+ hb_error( "decomb check could not initialize taskset" );
+ }
+
+ decomb_prev_thread_args = NULL;
+ for (ii = 0; ii < pv->comb_check_nthreads; ii++)
+ {
+ decomb_thread_arg_t *thread_args;
+
+ thread_args = taskset_thread_args( &pv->decomb_check_taskset, ii);
+ thread_args->pv = pv;
+ thread_args->segment = ii;
+
+ int pp;
+ for (pp = 0; pp < 3; pp++)
+ {
+ if (decomb_prev_thread_args != NULL)
+ {
+ thread_args->segment_start[pp] =
+ decomb_prev_thread_args->segment_start[pp] +
+ decomb_prev_thread_args->segment_height[pp];
+ }
+
+ // Make segment hight a multiple of block_height
+ int h = hb_image_height(init->pix_fmt, init->geometry.height, pp) / pv->comb_check_nthreads;
+ h = h / pv->block_height * pv->block_height;
+ if (h == 0)
+ h = pv->block_height;
+
+ if (ii == pv->comb_check_nthreads - 1)
+ {
+ /*
+ * Final segment
+ */
+ thread_args->segment_height[pp] =
+ hb_image_height(init->pix_fmt, init->geometry.height, pp) -
+ thread_args->segment_start[pp];
+ } else {
+ thread_args->segment_height[pp] = h;
+ }
+ }
+
+ if (taskset_thread_spawn( &pv->decomb_check_taskset, ii,
+ "decomb_check_segment",
+ decomb_check_thread,
+ HB_NORMAL_PRIORITY ) == 0)
+ {
+ hb_error( "decomb check could not spawn thread" );
+ }
+
+ decomb_prev_thread_args = thread_args;
+ }
+
+ if (pv->mode & MODE_FILTER)
+ {
+ if (taskset_init( &pv->mask_filter_taskset, pv->cpu_count,
+ sizeof( decomb_thread_arg_t ) ) == 0)
+ {
+ hb_error( "maske filter could not initialize taskset" );
+ }
+
+ decomb_prev_thread_args = NULL;
+ for (ii = 0; ii < pv->cpu_count; ii++)
+ {
+ decomb_thread_arg_t *thread_args;
+
+ thread_args = taskset_thread_args( &pv->mask_filter_taskset, ii );
+ thread_args->pv = pv;
+ thread_args->segment = ii;
+
+ int pp;
+ for (pp = 0; pp < 3; pp++)
+ {
+ if (decomb_prev_thread_args != NULL)
+ {
+ thread_args->segment_start[pp] =
+ decomb_prev_thread_args->segment_start[pp] +
+ decomb_prev_thread_args->segment_height[pp];
+ }
+
+ if (ii == pv->cpu_count - 1)
+ {
+ /*
+ * Final segment
+ */
+ thread_args->segment_height[pp] =
+ hb_image_height(init->pix_fmt, init->geometry.height, pp) -
+ thread_args->segment_start[pp];
+ } else {
+ thread_args->segment_height[pp] = pv->segment_height[pp];
+ }
+ }
+
+ if (taskset_thread_spawn( &pv->mask_filter_taskset, ii,
+ "mask_filter_segment",
+ mask_filter_thread,
+ HB_NORMAL_PRIORITY ) == 0)
+ {
+ hb_error( "mask filter could not spawn thread" );
+ }
+
+ decomb_prev_thread_args = thread_args;
+ }
+
+ if (pv->filter_mode == FILTER_ERODE_DILATE)
+ {
+ if (taskset_init( &pv->mask_erode_taskset, pv->cpu_count,
+ sizeof( decomb_thread_arg_t ) ) == 0)
+ {
+ hb_error( "mask erode could not initialize taskset" );
+ }
+
+ decomb_prev_thread_args = NULL;
+ for (ii = 0; ii < pv->cpu_count; ii++)
+ {
+ decomb_thread_arg_t *thread_args;
+
+ thread_args = taskset_thread_args( &pv->mask_erode_taskset, ii );
+ thread_args->pv = pv;
+ thread_args->segment = ii;
+
+ int pp;
+ for (pp = 0; pp < 3; pp++)
+ {
+ if (decomb_prev_thread_args != NULL)
+ {
+ thread_args->segment_start[pp] =
+ decomb_prev_thread_args->segment_start[pp] +
+ decomb_prev_thread_args->segment_height[pp];
+ }
+
+ if (ii == pv->cpu_count - 1)
+ {
+ /*
+ * Final segment
+ */
+ thread_args->segment_height[pp] =
+ hb_image_height(init->pix_fmt, init->geometry.height, pp) -
+ thread_args->segment_start[pp];
+ } else {
+ thread_args->segment_height[pp] = pv->segment_height[pp];
+ }
+ }
+
+ if (taskset_thread_spawn( &pv->mask_erode_taskset, ii,
+ "mask_erode_segment",
+ mask_erode_thread,
+ HB_NORMAL_PRIORITY ) == 0)
+ {
+ hb_error( "mask erode could not spawn thread" );
+ }
+
+ decomb_prev_thread_args = thread_args;
+ }
+
+ if (taskset_init( &pv->mask_dilate_taskset, pv->cpu_count,
+ sizeof( decomb_thread_arg_t ) ) == 0)
+ {
+ hb_error( "mask dilate could not initialize taskset" );
+ }
+
+ decomb_prev_thread_args = NULL;
+ for (ii = 0; ii < pv->cpu_count; ii++)
+ {
+ decomb_thread_arg_t *thread_args;
+
+ thread_args = taskset_thread_args( &pv->mask_dilate_taskset, ii );
+ thread_args->pv = pv;
+ thread_args->segment = ii;
+
+ int pp;
+ for (pp = 0; pp < 3; pp++)
+ {
+ if (decomb_prev_thread_args != NULL)
+ {
+ thread_args->segment_start[pp] =
+ decomb_prev_thread_args->segment_start[pp] +
+ decomb_prev_thread_args->segment_height[pp];
+ }
+
+ if (ii == pv->cpu_count - 1)
+ {
+ /*
+ * Final segment
+ */
+ thread_args->segment_height[pp] =
+ hb_image_height(init->pix_fmt, init->geometry.height, pp) -
+ thread_args->segment_start[pp];
+ } else {
+ thread_args->segment_height[pp] = pv->segment_height[pp];
+ }
+ }
+
+ if (taskset_thread_spawn( &pv->mask_dilate_taskset, ii,
+ "mask_dilate_segment",
+ mask_dilate_thread,
+ HB_NORMAL_PRIORITY ) == 0)
+ {
+ hb_error( "mask dilate could not spawn thread" );
+ }
+
+ decomb_prev_thread_args = thread_args;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void comb_detect_close( hb_filter_object_t * filter )
+{
+ hb_filter_private_t * pv = filter->private_data;
+
+ if (pv == NULL)
+ {
+ return;
+ }
+
+ hb_log("comb detect: heavy %i | light %i | uncombed %i | total %i",
+ pv->comb_heavy, pv->comb_light, pv->comb_none, pv->frames);
+
+ taskset_fini( &pv->decomb_filter_taskset );
+ taskset_fini( &pv->decomb_check_taskset );
+
+ if (pv->mode & MODE_FILTER)
+ {
+ taskset_fini( &pv->mask_filter_taskset );
+ if (pv->filter_mode == FILTER_ERODE_DILATE)
+ {
+ taskset_fini( &pv->mask_erode_taskset );
+ taskset_fini( &pv->mask_dilate_taskset );
+ }
+ }
+
+ /* Cleanup reference buffers. */
+ int ii;
+ for (ii = 0; ii < 3; ii++)
+ {
+ if (!pv->ref_used[ii])
+ {
+ hb_buffer_close(&pv->ref[ii]);
+ }
+ }
+
+ /* Cleanup combing masks. */
+ hb_buffer_close(&pv->mask);
+ hb_buffer_close(&pv->mask_filtered);
+ hb_buffer_close(&pv->mask_temp);
+
+ free(pv->block_score);
+ free( pv );
+ filter->private_data = NULL;
+}
+
+static void process_frame( hb_filter_private_t * pv )
+{
+ int combed;
+
+ combed = comb_segmenter(pv);
+ switch (combed)
+ {
+ case HB_COMB_HEAVY:
+ pv->comb_heavy++;
+ break;
+
+ case HB_COMB_LIGHT:
+ pv->comb_light++;
+ break;
+
+ case HB_COMB_NONE:
+ default:
+ pv->comb_none++;
+ break;
+ }
+ pv->frames++;
+ if ((pv->mode & MODE_MASK) && combed)
+ {
+ hb_buffer_t * out;
+ out = hb_buffer_dup(pv->ref[1]);
+ apply_mask(pv, out);
+ out->s.combed = combed;
+ hb_buffer_list_append(&pv->out_list, out);
+ }
+ else
+ {
+ pv->ref_used[1] = 1;
+ pv->ref[1]->s.combed = combed;
+ hb_buffer_list_append(&pv->out_list, pv->ref[1]);
+ }
+}
+
+static int comb_detect_work( hb_filter_object_t * filter,
+ hb_buffer_t ** buf_in,
+ hb_buffer_t ** buf_out )
+{
+ hb_filter_private_t * pv = filter->private_data;
+ hb_buffer_t * in = *buf_in;
+
+ // Input buffer is always consumed.
+ *buf_in = NULL;
+ if (in->s.flags & HB_BUF_FLAG_EOF)
+ {
+ // Duplicate last frame and process refs
+ store_ref(pv, hb_buffer_dup(pv->ref[2]));
+ process_frame(pv);
+ hb_buffer_list_append(&pv->out_list, in);
+ *buf_out = hb_buffer_list_clear(&pv->out_list);
+ return HB_FILTER_DONE;
+ }
+
+ // comb detect requires 3 buffers, prev, cur, and next. For the first
+ // frame, there can be no prev, so we duplicate the first frame.
+ if (!pv->comb_detect_ready)
+ {
+ // If not ready, store duplicate ref and return HB_FILTER_DELAY
+ store_ref(pv, hb_buffer_dup(in));
+ store_ref(pv, in);
+ pv->comb_detect_ready = 1;
+ // Wait for next
+ return HB_FILTER_DELAY;
+ }
+
+ store_ref(pv, in);
+ process_frame(pv);
+
+ // Output buffers may also be in comb detect's internal ref list.
+ // Since buffers are not reference counted, we must wait until
+ // we are certain they are no longer in the ref list before sending
+ // down the pipeline where they will ultimately get closed.
+ if (hb_buffer_list_count(&pv->out_list) > 3)
+ {
+ *buf_out = hb_buffer_list_rem_head(&pv->out_list);
+ }
+ return HB_FILTER_OK;
+}
diff --git a/libhb/common.c b/libhb/common.c
index ca74aeb16..3cb804ce7 100644
--- a/libhb/common.c
+++ b/libhb/common.c
@@ -3718,6 +3718,27 @@ hb_list_t *hb_filter_list_copy(const hb_list_t *src)
return list;
}
+hb_filter_object_t * hb_filter_find(const hb_list_t *list, int filter_id)
+{
+ hb_filter_object_t *filter = NULL;
+ int ii;
+
+ if (list == NULL)
+ {
+ return NULL;
+ }
+ for (ii = 0; ii < hb_list_count(list); ii++)
+ {
+ filter = hb_list_item(list, ii);
+ if (filter->id == filter_id)
+ {
+ return filter;
+ }
+ }
+
+ return NULL;
+}
+
/**
* Gets a filter object with the given type
* @param filter_id The type of filter to get.
@@ -3732,6 +3753,10 @@ hb_filter_object_t * hb_filter_get( int filter_id )
filter = &hb_filter_detelecine;
break;
+ case HB_FILTER_COMB_DETECT:
+ filter = &hb_filter_comb_detect;
+ break;
+
case HB_FILTER_DECOMB:
filter = &hb_filter_decomb;
break;
diff --git a/libhb/common.h b/libhb/common.h
index d09c6e9a0..5d99e2eb8 100644
--- a/libhb/common.h
+++ b/libhb/common.h
@@ -1230,6 +1230,7 @@ enum
// First, filters that may change the framerate (drop or dup frames)
HB_FILTER_DETELECINE,
+ HB_FILTER_COMB_DETECT,
HB_FILTER_DECOMB,
HB_FILTER_DEINTERLACE,
HB_FILTER_VFR,
@@ -1259,6 +1260,7 @@ hb_filter_object_t * hb_filter_get( int filter_id );
hb_filter_object_t * hb_filter_init( int filter_id );
hb_filter_object_t * hb_filter_copy( hb_filter_object_t * filter );
hb_list_t * hb_filter_list_copy(const hb_list_t *src);
+hb_filter_object_t * hb_filter_find(const hb_list_t *list, int filter_id);
void hb_filter_close( hb_filter_object_t ** );
void hb_filter_info_close( hb_filter_info_t ** );
hb_dict_t * hb_parse_filter_settings(const char * settings);
diff --git a/libhb/decomb.c b/libhb/decomb.c
index cf0db0b6e..a91dffc7f 100644
--- a/libhb/decomb.c
+++ b/libhb/decomb.c
@@ -13,8 +13,13 @@
/*****
Parameters:
- Mode : Spatial metric : Motion thresh : Spatial thresh : Mask Filter Mode :
- Block thresh : Block width : Block height
+ Mode:
+ 1 = yadif
+ 2 = blend
+ 4 = cubic interpolation
+ 8 = EEDI2 interpolation
+ 16 = Deinterlace each field to a separate frame
+ 32 = Selectively deinterlace based on comb detection
Appended for EEDI2:
Magnitude thresh : Variance thresh : Laplacian thresh : Dilation thresh :
@@ -24,24 +29,9 @@ Plus:
Parity
Defaults:
- 391:2:3:3:2:40:16:16:10:20:20:4:2:50:24:1:-1
+ 7:10:20:20:4:2:50:24:1:-1
-Original "Faster" settings:
- 7:2:6:9:1:80:16:16:10:20:20:4:2:50:24:1:-1
*****/
-#define MODE_YADIF 1 // Use yadif
-#define MODE_BLEND 2 // Use blending interpolation
-#define MODE_CUBIC 4 // Use cubic interpolation
-#define MODE_EEDI2 8 // Use EEDI2 interpolation
-#define MODE_MASK 32 // Output combing masks instead of pictures
-#define MODE_BOB 64 // Deinterlace each field to a separate frame
-
-#define MODE_GAMMA 128 // Scale gamma when decombing
-#define MODE_FILTER 256 // Filter combing mask
-#define MODE_COMPOSITE 512 // Overlay combing mask onto picture
-
-#define FILTER_CLASSIC 1
-#define FILTER_ERODE_DILATE 2
/*****
These modes can be layered. For example, Yadif (1) + EEDI2 (8) = 9,
@@ -60,8 +50,6 @@ which will feed EEDI2 interpolations to yadif.
10: Switch between EEDI2 and blend
11: Switch between EEDI2->yadif and blend
...okay I'm getting bored now listing all these different modes
-32: Passes through the combing mask for every combed frame (white for combed pixels, otherwise black)
-33+: Overlay the combing mask for every combed frame on top of the filtered output (white for combed pixels)
12-15: EEDI2 will override cubic interpolation
*****/
@@ -70,6 +58,7 @@ which will feed EEDI2 interpolations to yadif.
#include "hbffmpeg.h"
#include "eedi2.h"
#include "taskset.h"
+#include "decomb.h"
#define PARITY_DEFAULT -1
@@ -92,7 +81,7 @@ struct yadif_arguments_s {
hb_buffer_t *dst;
int parity;
int tff;
- int is_combed;
+ int mode;
};
typedef struct yadif_arguments_s yadif_arguments_t;
@@ -102,13 +91,6 @@ typedef struct eedi2_thread_arg_s {
int plane;
} eedi2_thread_arg_t;
-typedef struct decomb_thread_arg_s {
- hb_filter_private_t *pv;
- int segment;
- int segment_start[3];
- int segment_height[3];
-} decomb_thread_arg_t;
-
typedef struct yadif_thread_arg_s {
hb_filter_private_t *pv;
int segment;
@@ -118,23 +100,8 @@ typedef struct yadif_thread_arg_s {
struct hb_filter_private_s
{
- // Decomb detect parameters
+ // Decomb parameters
int mode;
- int filter_mode;
- int spatial_metric;
- int motion_threshold;
- int spatial_threshold;
- int block_threshold;
- int block_width;
- int block_height;
-
- int * block_score;
- int comb_check_complete;
- int comb_check_nthreads;
- int skip_comb_check;
- int is_combed;
-
- float gamma_lut[256];
/* Make buffers to store a comb masks. */
hb_buffer_t * mask;
@@ -144,49 +111,45 @@ struct hb_filter_private_s
int mask_box_y;
uint8_t mask_box_color;
- // Deinterlace parameters
- int parity;
// EEDI2 parameters
- int magnitude_threshold;
- int variance_threshold;
- int laplacian_threshold;
- int dilation_threshold;
- int erosion_threshold;
- int noise_threshold;
- int maximum_search_distance;
- int post_processing;
-
- int tff;
+ int magnitude_threshold;
+ int variance_threshold;
+ int laplacian_threshold;
+ int dilation_threshold;
+ int erosion_threshold;
+ int noise_threshold;
+ int maximum_search_distance;
+ int post_processing;
- int yadif_ready;
+ // Deinterlace parameters
+ int parity;
+ int tff;
- int deinterlaced_frames;
- int blended_frames;
- int unfiltered_frames;
+ int yadif_ready;
- hb_buffer_t * ref[3];
+ int deinterlaced;
+ int blended;
+ int unfiltered;
+ int frames;
+ hb_buffer_t * ref[3];
- hb_buffer_t * eedi_half[4];
- hb_buffer_t * eedi_full[5];
- int * cx2;
- int * cy2;
- int * cxy;
- int * tmpc;
+ hb_buffer_t * eedi_half[4];
+ hb_buffer_t * eedi_full[5];
+ int * cx2;
+ int * cy2;
+ int * cxy;
+ int * tmpc;
- int cpu_count;
- int segment_height[3];
+ int cpu_count;
+ int segment_height[3];
- taskset_t yadif_taskset; // Threads for Yadif - one per CPU
- yadif_arguments_t *yadif_arguments; // Arguments to thread for work
+ taskset_t yadif_taskset; // Threads for Yadif - one per CPU
+ yadif_arguments_t * yadif_arguments; // Arguments to thread for work
- taskset_t decomb_filter_taskset; // Threads for comb detection
- taskset_t decomb_check_taskset; // Threads for comb check
- taskset_t mask_filter_taskset; // Threads for decomb mask filter
- taskset_t mask_erode_taskset; // Threads for decomb mask erode
- taskset_t mask_dilate_taskset; // Threads for decomb mask dilate
+ taskset_t eedi2_taskset; // Threads for eedi2 - one per plane
- taskset_t eedi2_taskset; // Threads for eedi2 - one per plane
+ hb_buffer_list_t out_list;
};
typedef struct
@@ -205,10 +168,7 @@ static int hb_decomb_work( hb_filter_object_t * filter,
static void hb_decomb_close( hb_filter_object_t * filter );
static const char decomb_template[] =
- "mode=^"HB_INT_REG"$:spatial-metric=^([012])$:"
- "motion-thresh=^"HB_INT_REG"$:spatial-thresh=^"HB_INT_REG"$:"
- "filter-mode=^([012])$:block-thresh=^"HB_INT_REG"$:"
- "block-width=^"HB_INT_REG"$:block-height=^"HB_INT_REG"$:"
+ "mode=^"HB_INT_REG"$:"
"magnitude-thresh=^"HB_INT_REG"$:variance-thresh=^"HB_INT_REG"$:"
"laplacian-thresh=^"HB_INT_REG"$:dilation-thresh=^"HB_INT_REG"$:"
"erosion-thresh=^"HB_INT_REG"$:noise-thresh=^"HB_INT_REG"$:"
@@ -321,102 +281,6 @@ static void cubic_interpolate_line(
}
}
-static void draw_mask_box( hb_filter_private_t * pv )
-{
- int x = pv->mask_box_x;
- int y = pv->mask_box_y;
- int box_width = pv->block_width;
- int box_height = pv->block_height;
- int stride;
- uint8_t * mskp;
-
- if (pv->mode & MODE_FILTER)
- {
- mskp = pv->mask_filtered->plane[0].data;
- stride = pv->mask_filtered->plane[0].stride;
- }
- else
- {
- mskp = pv->mask->plane[0].data;
- stride = pv->mask->plane[0].stride;
- }
-
-
- int block_x, block_y;
- for( block_x = 0; block_x < box_width; block_x++)
- {
- mskp[y*stride+x+block_x] = 128;
- mskp[(y+box_height)*stride+x+block_x] = 128;
- }
-
- for( block_y = 0; block_y < box_height; block_y++)
- {
- mskp[stride*(y+block_y)+x] = 128;
- mskp[stride*(y+block_y) + x + box_width] = 128;
- }
-}
-
-static void apply_mask_line( uint8_t * srcp,
- uint8_t * mskp,
- int width )
-{
- int x;
-
- for( x = 0; x < width; x++ )
- {
- if( mskp[x] == 1 )
- {
- srcp[x] = 255;
- }
- if( mskp[x] == 128 )
- {
- srcp[x] = 128;
- }
- }
-}
-
-static void apply_mask(hb_filter_private_t * pv, hb_buffer_t * b)
-{
- /* draw_boxes */
- draw_mask_box( pv );
-
- int pp, yy;
- hb_buffer_t * m;
-
- if (pv->mode & MODE_FILTER)
- {
- m = pv->mask_filtered;
- }
- else
- {
- m = pv->mask;
- }
- for (pp = 0; pp < 3; pp++)
- {
- uint8_t * dstp = b->plane[pp].data;
- uint8_t * mskp = m->plane[pp].data;
-
- for( yy = 0; yy < m->plane[pp].height; yy++ )
- {
- if (!(pv->mode & MODE_COMPOSITE) && pp == 0)
- {
- memcpy(dstp, mskp, m->plane[pp].width);
- }
- else if (!(pv->mode & MODE_COMPOSITE))
- {
- memset(dstp, 128, m->plane[pp].width);
- }
- if (pp == 0)
- {
- apply_mask_line(dstp, mskp, m->plane[pp].width);
- }
-
- dstp += b->plane[pp].stride;
- mskp += m->plane[pp].stride;
- }
- }
-}
-
static void store_ref(hb_filter_private_t * pv, hb_buffer_t * b)
{
hb_buffer_close(&pv->ref[0]);
@@ -503,452 +367,6 @@ static void blend_filter_line(filter_param_t *filter,
}
}
-static void reset_combing_results( hb_filter_private_t * pv )
-{
- pv->comb_check_complete = 0;
- int ii;
- for (ii = 0; ii < pv->comb_check_nthreads; ii++)
- {
- pv->block_score[ii] = 0;
- }
-}
-
-static int check_combing_results( hb_filter_private_t * pv )
-{
- int threshold = pv->block_threshold;
- int send_to_blend = 0;
-
- int ii;
- for (ii = 0; ii < pv->comb_check_nthreads; ii++)
- {
- if( pv->block_score[ii] >= ( threshold / 2 ) )
- {
- if (pv->block_score[ii] <= threshold)
- {
- /* Blend video content that scores between
- ( threshold / 2 ) and threshold. */
- send_to_blend = 1;
- pv->mask_box_color = 2;
- }
- else if( pv->block_score[ii] > threshold )
- {
- /* Yadif deinterlace video content above the threshold. */
- pv->mask_box_color = 1;
- return 1;
- }
- }
- }
-
- if( send_to_blend )
- {
- return 2;
- }
- else
- {
- /* Consider this frame to be uncombed. */
- return 0;
- }
-}
-
-static void check_filtered_combing_mask( hb_filter_private_t * pv, int segment, int start, int stop )
-{
- /* Go through the mask in X*Y blocks. If any of these windows
- have threshold or more combed pixels, consider the whole
- frame to be combed and send it on to be deinterlaced. */
-
- /* Block mask threshold -- The number of pixels
- in a block_width * block_height window of
- he mask that need to show combing for the
- whole frame to be seen as such. */
- int threshold = pv->block_threshold;
- int block_width = pv->block_width;
- int block_height = pv->block_height;
- int block_x, block_y;
- int block_score = 0;
- uint8_t * mask_p;
- int x, y, pp;
-
- for( pp = 0; pp < 1; pp++ )
- {
- int stride = pv->mask_filtered->plane[pp].stride;
- int width = pv->mask_filtered->plane[pp].width;
-
- pv->mask_box_x = -1;
- pv->mask_box_y = -1;
- pv->mask_box_color = 0;
-
- for( y = start; y < ( stop - block_height + 1 ); y = y + block_height )
- {
- for( x = 0; x < ( width - block_width ); x = x + block_width )
- {
- block_score = 0;
-
- for( block_y = 0; block_y < block_height; block_y++ )
- {
- int my = y + block_y;
- mask_p = &pv->mask_filtered->plane[pp].data[my*stride + x];
-
- for( block_x = 0; block_x < block_width; block_x++ )
- {
- block_score += mask_p[0];
- mask_p++;
- }
- }
-
- if (pv->comb_check_complete)
- {
- // Some other thread found coming before this one
- return;
- }
-
- if( block_score >= ( threshold / 2 ) )
- {
- pv->mask_box_x = x;
- pv->mask_box_y = y;
-
- pv->block_score[segment] = block_score;
- if( block_score > threshold )
- {
- pv->comb_check_complete = 1;
- return;
- }
- }
- }
- }
- }
-}
-
-static void check_combing_mask( hb_filter_private_t * pv, int segment, int start, int stop )
-{
- /* Go through the mask in X*Y blocks. If any of these windows
- have threshold or more combed pixels, consider the whole
- frame to be combed and send it on to be deinterlaced. */
-
- /* Block mask threshold -- The number of pixels
- in a block_width * block_height window of
- he mask that need to show combing for the
- whole frame to be seen as such. */
- int threshold = pv->block_threshold;
- int block_width = pv->block_width;
- int block_height = pv->block_height;
- int block_x, block_y;
- int block_score = 0;
- uint8_t * mask_p;
- int x, y, pp;
-
- for( pp = 0; pp < 1; pp++ )
- {
- int stride = pv->mask->plane[pp].stride;
- int width = pv->mask->plane[pp].width;
-
- for (y = start; y < (stop - block_height + 1); y = y + block_height)
- {
- for (x = 0; x < (width - block_width); x = x + block_width)
- {
- block_score = 0;
-
- for( block_y = 0; block_y < block_height; block_y++ )
- {
- int mask_y = y + block_y;
- mask_p = &pv->mask->plane[pp].data[mask_y * stride + x];
-
- for( block_x = 0; block_x < block_width; block_x++ )
- {
- /* We only want to mark a pixel in a block as combed
- if the adjacent pixels are as well. Got to
- handle the sides separately. */
- if( (x + block_x) == 0 )
- {
- block_score += mask_p[0] & mask_p[1];
- }
- else if( (x + block_x) == (width -1) )
- {
- block_score += mask_p[-1] & mask_p[0];
- }
- else
- {
- block_score += mask_p[-1] & mask_p[0] & mask_p[1];
- }
-
- mask_p++;
- }
- }
-
- if (pv->comb_check_complete)
- {
- // Some other thread found coming before this one
- return;
- }
-
- if( block_score >= ( threshold / 2 ) )
- {
- pv->mask_box_x = x;
- pv->mask_box_y = y;
-
- pv->block_score[segment] = block_score;
- if( block_score > threshold )
- {
- pv->comb_check_complete = 1;
- return;
- }
- }
- }
- }
- }
-}
-
-static void build_gamma_lut( hb_filter_private_t * pv )
-{
- int i;
- for( i = 0; i < 256; i++ )
- {
- pv->gamma_lut[i] = pow( ( (float)i / (float)255 ), 2.2f );
- }
-}
-
-static void detect_gamma_combed_segment( hb_filter_private_t * pv, int segment_start, int segment_stop )
-{
- /* A mish-mash of various comb detection tricks
- picked up from neuron2's Decomb plugin for
- AviSynth and tritical's IsCombedT and
- IsCombedTIVTC plugins. */
-
- /* Comb scoring algorithm */
- /* Motion threshold */
- float mthresh = (float)pv->motion_threshold / (float)255;
- /* Spatial threshold */
- float athresh = (float)pv->spatial_threshold / (float)255;
- float athresh6 = 6 *athresh;
-
- /* One pas for Y, one pass for U, one pass for V */
- int pp;
- for( pp = 0; pp < 1; pp++ )
- {
- int x, y;
- int stride = pv->ref[0]->plane[pp].stride;
- int width = pv->ref[0]->plane[pp].width;
- int height = pv->ref[0]->plane[pp].height;
-
- /* Comb detection has to start at y = 2 and end at
- y = height - 2, because it needs to examine
- 2 pixels above and 2 below the current pixel. */
- if( segment_start < 2 )
- segment_start = 2;
- if( segment_stop > height - 2 )
- segment_stop = height - 2;
-
- for( y = segment_start; y < segment_stop; y++ )
- {
- /* These are just to make the buffer locations easier to read. */
- int up_2 = -2 * stride ;
- int up_1 = -1 * stride;
- int down_1 = stride;
- int down_2 = 2 * stride;
-
- /* We need to examine a column of 5 pixels
- in the prev, cur, and next frames. */
- uint8_t * prev = &pv->ref[0]->plane[pp].data[y * stride];
- uint8_t * cur = &pv->ref[1]->plane[pp].data[y * stride];
- uint8_t * next = &pv->ref[2]->plane[pp].data[y * stride];
- uint8_t * mask = &pv->mask->plane[pp].data[y * stride];
-
- memset(mask, 0, stride);
-
- for( x = 0; x < width; x++ )
- {
- float up_diff, down_diff;
- up_diff = pv->gamma_lut[cur[0]] - pv->gamma_lut[cur[up_1]];
- down_diff = pv->gamma_lut[cur[0]] - pv->gamma_lut[cur[down_1]];
-
- if( ( up_diff > athresh && down_diff > athresh ) ||
- ( up_diff < -athresh && down_diff < -athresh ) )
- {
- /* The pixel above and below are different,
- and they change in the same "direction" too.*/
- int motion = 0;
- if( mthresh > 0 )
- {
- /* Make sure there's sufficient motion between frame t-1 to frame t+1. */
- if( fabs( pv->gamma_lut[prev[0]] - pv->gamma_lut[cur[0]] ) > mthresh &&
- fabs( pv->gamma_lut[cur[up_1]] - pv->gamma_lut[next[up_1]] ) > mthresh &&
- fabs( pv->gamma_lut[cur[down_1]] - pv->gamma_lut[next[down_1]] ) > mthresh )
- motion++;
- if( fabs( pv->gamma_lut[next[0]] - pv->gamma_lut[cur[0]] ) > mthresh &&
- fabs( pv->gamma_lut[prev[up_1]] - pv->gamma_lut[cur[up_1]] ) > mthresh &&
- fabs( pv->gamma_lut[prev[down_1]] - pv->gamma_lut[cur[down_1]] ) > mthresh )
- motion++;
-
- }
- else
- {
- /* User doesn't want to check for motion,
- so move on to the spatial check. */
- motion = 1;
- }
-
- if( motion || ( pv->deinterlaced_frames==0 && pv->blended_frames==0 && pv->unfiltered_frames==0) )
- {
-
- /* Tritical's noise-resistant combing scorer.
- The check is done on a bob+blur convolution. */
- float combing = fabs( pv->gamma_lut[cur[up_2]]
- + ( 4 * pv->gamma_lut[cur[0]] )
- + pv->gamma_lut[cur[down_2]]
- - ( 3 * ( pv->gamma_lut[cur[up_1]]
- + pv->gamma_lut[cur[down_1]] ) ) );
- /* If the frame is sufficiently combed,
- then mark it down on the mask as 1. */
- if( combing > athresh6 )
- {
- mask[0] = 1;
- }
- }
- }
-
- cur++;
- prev++;
- next++;
- mask++;
- }
- }
- }
-}
-
-
-static void detect_combed_segment( hb_filter_private_t * pv, int segment_start, int segment_stop )
-{
- /* A mish-mash of various comb detection tricks
- picked up from neuron2's Decomb plugin for
- AviSynth and tritical's IsCombedT and
- IsCombedTIVTC plugins. */
-
- /* Comb scoring algorithm */
- int spatial_metric = pv->spatial_metric;
- /* Motion threshold */
- int mthresh = pv->motion_threshold;
- /* Spatial threshold */
- int athresh = pv->spatial_threshold;
- int athresh_squared = athresh * athresh;
- int athresh6 = 6 * athresh;
-
- /* One pas for Y, one pass for U, one pass for V */
- int pp;
- for( pp = 0; pp < 1; pp++ )
- {
- int x, y;
- int stride = pv->ref[0]->plane[pp].stride;
- int width = pv->ref[0]->plane[pp].width;
- int height = pv->ref[0]->plane[pp].height;
-
- /* Comb detection has to start at y = 2 and end at
- y = height - 2, because it needs to examine
- 2 pixels above and 2 below the current pixel. */
- if( segment_start < 2 )
- segment_start = 2;
- if( segment_stop > height - 2 )
- segment_stop = height - 2;
-
- for( y = segment_start; y < segment_stop; y++ )
- {
- /* These are just to make the buffer locations easier to read. */
- int up_2 = -2 * stride ;
- int up_1 = -1 * stride;
- int down_1 = stride;
- int down_2 = 2 * stride;
-
- /* We need to examine a column of 5 pixels
- in the prev, cur, and next frames. */
- uint8_t * prev = &pv->ref[0]->plane[pp].data[y * stride];
- uint8_t * cur = &pv->ref[1]->plane[pp].data[y * stride];
- uint8_t * next = &pv->ref[2]->plane[pp].data[y * stride];
- uint8_t * mask = &pv->mask->plane[pp].data[y * stride];
-
- memset(mask, 0, stride);
-
- for( x = 0; x < width; x++ )
- {
- int up_diff = cur[0] - cur[up_1];
- int down_diff = cur[0] - cur[down_1];
-
- if( ( up_diff > athresh && down_diff > athresh ) ||
- ( up_diff < -athresh && down_diff < -athresh ) )
- {
- /* The pixel above and below are different,
- and they change in the same "direction" too.*/
- int motion = 0;
- if( mthresh > 0 )
- {
- /* Make sure there's sufficient motion between frame t-1 to frame t+1. */
- if( abs( prev[0] - cur[0] ) > mthresh &&
- abs( cur[up_1] - next[up_1] ) > mthresh &&
- abs( cur[down_1] - next[down_1] ) > mthresh )
- motion++;
- if( abs( next[0] - cur[0] ) > mthresh &&
- abs( prev[up_1] - cur[up_1] ) > mthresh &&
- abs( prev[down_1] - cur[down_1] ) > mthresh )
- motion++;
- }
- else
- {
- /* User doesn't want to check for motion,
- so move on to the spatial check. */
- motion = 1;
- }
-
- if( motion || ( pv->deinterlaced_frames==0 && pv->blended_frames==0 && pv->unfiltered_frames==0) )
- {
- /* That means it's time for the spatial check.
- We've got several options here. */
- if( spatial_metric == 0 )
- {
- /* Simple 32detect style comb detection */
- if( ( abs( cur[0] - cur[down_2] ) < 10 ) &&
- ( abs( cur[0] - cur[down_1] ) > 15 ) )
- {
- mask[0] = 1;
- }
- }
- else if( spatial_metric == 1 )
- {
- /* This, for comparison, is what IsCombed uses.
- It's better, but still noise senstive. */
- int combing = ( cur[up_1] - cur[0] ) *
- ( cur[down_1] - cur[0] );
-
- if( combing > athresh_squared )
- {
- mask[0] = 1;
- }
- }
- else if( spatial_metric == 2 )
- {
- /* Tritical's noise-resistant combing scorer.
- The check is done on a bob+blur convolution. */
- int combing = abs( cur[up_2]
- + ( 4 * cur[0] )
- + cur[down_2]
- - ( 3 * ( cur[up_1]
- + cur[down_1] ) ) );
-
- /* If the frame is sufficiently combed,
- then mark it down on the mask as 1. */
- if( combing > athresh6 )
- {
- mask[0] = 1;
- }
- }
- }
- }
-
- cur++;
- prev++;
- next++;
- mask++;
- }
- }
- }
-}
-
// This function calls all the eedi2 filters in sequence for a given plane.
// It outputs the final interpolated image to pv->eedi_full[DST2PF].
static void eedi2_interpolate_plane( hb_filter_private_t * pv, int plane )
@@ -1095,446 +513,6 @@ static void eedi2_planer( hb_filter_private_t * pv )
taskset_cycle( &pv->eedi2_taskset );
}
-
-static void mask_dilate_thread( void *thread_args_v )
-{
- hb_filter_private_t * pv;
- int segment, segment_start, segment_stop;
- decomb_thread_arg_t *thread_args = thread_args_v;
-
- pv = thread_args->pv;
- segment = thread_args->segment;
-
- hb_log("mask dilate thread started for segment %d", segment);
-
- while (1)
- {
- /*
- * Wait here until there is work to do.
- */
- taskset_thread_wait4start( &pv->mask_dilate_taskset, segment );
-
- if (taskset_thread_stop(&pv->mask_dilate_taskset, segment))
- {
- /*
- * No more work to do, exit this thread.
- */
- break;
- }
-
- int xx, yy, pp;
-
- int count;
- int dilation_threshold = 4;
-
- for( pp = 0; pp < 1; pp++ )
- {
- int width = pv->mask_filtered->plane[pp].width;
- int height = pv->mask_filtered->plane[pp].height;
- int stride = pv->mask_filtered->plane[pp].stride;
-
- int start, stop, p, c, n;
- segment_start = thread_args->segment_start[pp];
- segment_stop = segment_start + thread_args->segment_height[pp];
-
- if (segment_start == 0)
- {
- start = 1;
- p = 0;
- c = 1;
- n = 2;
- }
- else
- {
- start = segment_start;
- p = segment_start - 1;
- c = segment_start;
- n = segment_start + 1;
- }
-
- if (segment_stop == height)
- {
- stop = height -1;
- }
- else
- {
- stop = segment_stop;
- }
-
- uint8_t *curp = &pv->mask_filtered->plane[pp].data[p * stride + 1];
- uint8_t *cur = &pv->mask_filtered->plane[pp].data[c * stride + 1];
- uint8_t *curn = &pv->mask_filtered->plane[pp].data[n * stride + 1];
- uint8_t *dst = &pv->mask_temp->plane[pp].data[c * stride + 1];
-
- for( yy = start; yy < stop; yy++ )
- {
- for( xx = 1; xx < width - 1; xx++ )
- {
- if (cur[xx])
- {
- dst[xx] = 1;
- continue;
- }
-
- count = curp[xx-1] + curp[xx] + curp[xx+1] +
- cur [xx-1] + cur [xx+1] +
- curn[xx-1] + curn[xx] + curn[xx+1];
-
- dst[xx] = count >= dilation_threshold;
- }
- curp += stride;
- cur += stride;
- curn += stride;
- dst += stride;
- }
- }
-
- taskset_thread_complete( &pv->mask_dilate_taskset, segment );
- }
-
- /*
- * Finished this segment, let everyone know.
- */
- taskset_thread_complete( &pv->mask_dilate_taskset, segment );
-}
-
-static void mask_erode_thread( void *thread_args_v )
-{
- hb_filter_private_t * pv;
- int segment, segment_start, segment_stop;
- decomb_thread_arg_t *thread_args = thread_args_v;
-
- pv = thread_args->pv;
- segment = thread_args->segment;
-
- hb_log("mask erode thread started for segment %d", segment);
-
- while (1)
- {
- /*
- * Wait here until there is work to do.
- */
- taskset_thread_wait4start( &pv->mask_erode_taskset, segment );
-
- if( taskset_thread_stop( &pv->mask_erode_taskset, segment ) )
- {
- /*
- * No more work to do, exit this thread.
- */
- break;
- }
-
- int xx, yy, pp;
-
- int count;
- int erosion_threshold = 2;
-
- for( pp = 0; pp < 1; pp++ )
- {
- int width = pv->mask_filtered->plane[pp].width;
- int height = pv->mask_filtered->plane[pp].height;
- int stride = pv->mask_filtered->plane[pp].stride;
-
- int start, stop, p, c, n;
- segment_start = thread_args->segment_start[pp];
- segment_stop = segment_start + thread_args->segment_height[pp];
-
- if (segment_start == 0)
- {
- start = 1;
- p = 0;
- c = 1;
- n = 2;
- }
- else
- {
- start = segment_start;
- p = segment_start - 1;
- c = segment_start;
- n = segment_start + 1;
- }
-
- if (segment_stop == height)
- {
- stop = height -1;
- }
- else
- {
- stop = segment_stop;
- }
-
- uint8_t *curp = &pv->mask_temp->plane[pp].data[p * stride + 1];
- uint8_t *cur = &pv->mask_temp->plane[pp].data[c * stride + 1];
- uint8_t *curn = &pv->mask_temp->plane[pp].data[n * stride + 1];
- uint8_t *dst = &pv->mask_filtered->plane[pp].data[c * stride + 1];
-
- for( yy = start; yy < stop; yy++ )
- {
- for( xx = 1; xx < width - 1; xx++ )
- {
- if( cur[xx] == 0 )
- {
- dst[xx] = 0;
- continue;
- }
-
- count = curp[xx-1] + curp[xx] + curp[xx+1] +
- cur [xx-1] + cur [xx+1] +
- curn[xx-1] + curn[xx] + curn[xx+1];
-
- dst[xx] = count >= erosion_threshold;
- }
- curp += stride;
- cur += stride;
- curn += stride;
- dst += stride;
- }
- }
-
- taskset_thread_complete( &pv->mask_erode_taskset, segment );
- }
-
- /*
- * Finished this segment, let everyone know.
- */
- taskset_thread_complete( &pv->mask_erode_taskset, segment );
-}
-
-static void mask_filter_thread( void *thread_args_v )
-{
- hb_filter_private_t * pv;
- int segment, segment_start, segment_stop;
- decomb_thread_arg_t *thread_args = thread_args_v;
-
- pv = thread_args->pv;
- segment = thread_args->segment;
-
- hb_log("mask filter thread started for segment %d", segment);
-
- while (1)
- {
- /*
- * Wait here until there is work to do.
- */
- taskset_thread_wait4start( &pv->mask_filter_taskset, segment );
-
- if( taskset_thread_stop( &pv->mask_filter_taskset, segment ) )
- {
- /*
- * No more work to do, exit this thread.
- */
- break;
- }
-
- int xx, yy, pp;
-
- for( pp = 0; pp < 1; pp++ )
- {
- int width = pv->mask->plane[pp].width;
- int height = pv->mask->plane[pp].height;
- int stride = pv->mask->plane[pp].stride;
-
- int start, stop, p, c, n;
- segment_start = thread_args->segment_start[pp];
- segment_stop = segment_start + thread_args->segment_height[pp];
-
- if (segment_start == 0)
- {
- start = 1;
- p = 0;
- c = 1;
- n = 2;
- }
- else
- {
- start = segment_start;
- p = segment_start - 1;
- c = segment_start;
- n = segment_start + 1;
- }
-
- if (segment_stop == height)
- {
- stop = height - 1;
- }
- else
- {
- stop = segment_stop;
- }
-
- uint8_t *curp = &pv->mask->plane[pp].data[p * stride + 1];
- uint8_t *cur = &pv->mask->plane[pp].data[c * stride + 1];
- uint8_t *curn = &pv->mask->plane[pp].data[n * stride + 1];
- uint8_t *dst = (pv->filter_mode == FILTER_CLASSIC ) ?
- &pv->mask_filtered->plane[pp].data[c * stride + 1] :
- &pv->mask_temp->plane[pp].data[c * stride + 1] ;
-
- for( yy = start; yy < stop; yy++ )
- {
- for( xx = 1; xx < width - 1; xx++ )
- {
- int h_count, v_count;
-
- h_count = cur[xx-1] & cur[xx] & cur[xx+1];
- v_count = curp[xx] & cur[xx] & curn[xx];
-
- if (pv->filter_mode == FILTER_CLASSIC)
- {
- dst[xx] = h_count;
- }
- else
- {
- dst[xx] = h_count & v_count;
- }
- }
- curp += stride;
- cur += stride;
- curn += stride;
- dst += stride;
- }
- }
-
- taskset_thread_complete( &pv->mask_filter_taskset, segment );
- }
-
- /*
- * Finished this segment, let everyone know.
- */
- taskset_thread_complete( &pv->mask_filter_taskset, segment );
-}
-
-static void decomb_check_thread( void *thread_args_v )
-{
- hb_filter_private_t * pv;
- int segment, segment_start, segment_stop;
- decomb_thread_arg_t *thread_args = thread_args_v;
-
- pv = thread_args->pv;
- segment = thread_args->segment;
-
- hb_log("decomb check thread started for segment %d", segment);
-
- while (1)
- {
- /*
- * Wait here until there is work to do.
- */
- taskset_thread_wait4start( &pv->decomb_check_taskset, segment );
-
- if( taskset_thread_stop( &pv->decomb_check_taskset, segment ) )
- {
- /*
- * No more work to do, exit this thread.
- */
- break;
- }
-
- segment_start = thread_args->segment_start[0];
- segment_stop = segment_start + thread_args->segment_height[0];
-
- if( pv->mode & MODE_FILTER )
- {
- check_filtered_combing_mask(pv, segment, segment_start, segment_stop);
- }
- else
- {
- check_combing_mask(pv, segment, segment_start, segment_stop);
- }
-
- taskset_thread_complete( &pv->decomb_check_taskset, segment );
- }
-
- /*
- * Finished this segment, let everyone know.
- */
- taskset_thread_complete( &pv->decomb_check_taskset, segment );
-}
-
-/*
- * comb detect this segment of all three planes in a single thread.
- */
-static void decomb_filter_thread( void *thread_args_v )
-{
- hb_filter_private_t * pv;
- int segment, segment_start, segment_stop;
- decomb_thread_arg_t *thread_args = thread_args_v;
-
- pv = thread_args->pv;
- segment = thread_args->segment;
-
- hb_log("decomb filter thread started for segment %d", segment);
-
- while (1)
- {
- /*
- * Wait here until there is work to do.
- */
- taskset_thread_wait4start( &pv->decomb_filter_taskset, segment );
-
- if( taskset_thread_stop( &pv->decomb_filter_taskset, segment ) )
- {
- /*
- * No more work to do, exit this thread.
- */
- break;
- }
-
- /*
- * Process segment (for now just from luma)
- */
- int pp;
- for( pp = 0; pp < 1; pp++)
- {
- segment_start = thread_args->segment_start[pp];
- segment_stop = segment_start + thread_args->segment_height[pp];
-
- if( pv->mode & MODE_GAMMA )
- {
- detect_gamma_combed_segment( pv, segment_start, segment_stop );
- }
- else
- {
- detect_combed_segment( pv, segment_start, segment_stop );
- }
- }
-
- taskset_thread_complete( &pv->decomb_filter_taskset, segment );
- }
-
- /*
- * Finished this segment, let everyone know.
- */
- taskset_thread_complete( &pv->decomb_filter_taskset, segment );
-}
-
-static int comb_segmenter( hb_filter_private_t * pv )
-{
- /*
- * Now that all data for decomb detection is ready for
- * our threads, fire them off and wait for their completion.
- */
- taskset_cycle( &pv->decomb_filter_taskset );
-
- if( pv->mode & MODE_FILTER )
- {
- taskset_cycle( &pv->mask_filter_taskset );
- if( pv->filter_mode == FILTER_ERODE_DILATE )
- {
- taskset_cycle( &pv->mask_erode_taskset );
- taskset_cycle( &pv->mask_dilate_taskset );
- taskset_cycle( &pv->mask_erode_taskset );
- }
- //return check_filtered_combing_mask( pv );
- }
- else
- {
- //return check_combing_mask( pv );
- }
- reset_combing_results(pv);
- taskset_cycle( &pv->decomb_check_taskset );
- return check_combing_results(pv);
-}
-
/* EDDI: Edge Directed Deinterlacing Interpolation
Checks 4 different slopes to see if there is more similarity along a diagonal
than there was vertically. If a diagonal is more similar, then it indicates
@@ -1546,7 +524,7 @@ static int comb_segmenter( hb_filter_private_t * pv )
+ ABS(cur[-stride+1+j] - cur[+stride+1-j]);\
if( score < spatial_score ){\
spatial_score = score;\
- if( ( pv->mode & MODE_CUBIC ) && !vertical_edge )\
+ if( ( pv->mode & MODE_DECOMB_CUBIC ) && !vertical_edge )\
{\
switch(j)\
{\
@@ -1590,7 +568,7 @@ static void yadif_filter_line(
uint8_t *next2 = parity ? cur : next;
int x;
- int eedi2_mode = ( pv->mode & MODE_EEDI2 );
+ int eedi2_mode = ( pv->mode & MODE_DECOMB_EEDI2 );
/* We can replace spatial_pred with this interpolation*/
uint8_t * eedi2_guess = NULL;
@@ -1639,7 +617,7 @@ static void yadif_filter_line(
ABS(cur[-stride+1] - cur[+stride+1]) - 1;
/* Spatial pred is either a bilinear or cubic vertical interpolation. */
- if( ( pv->mode & MODE_CUBIC ) && !vertical_edge)
+ if( ( pv->mode & MODE_DECOMB_CUBIC ) && !vertical_edge)
{
spatial_pred = cubic_interpolate_pixel( cur[-3*stride], cur[-stride], cur[+stride], cur[3*stride] );
}
@@ -1649,10 +627,10 @@ static void yadif_filter_line(
}
// YADIF_CHECK requires a margin to avoid invalid memory access.
- // In MODE_CUBIC, margin needed is 2 + ABS(param).
+ // In MODE_DECOMB_CUBIC, margin needed is 2 + ABS(param).
// Else, the margin needed is 1 + ABS(param).
int margin = 2;
- if (pv->mode & MODE_CUBIC)
+ if (pv->mode & MODE_DECOMB_CUBIC)
margin = 3;
if (x >= margin && x <= width - (margin + 1))
@@ -1743,9 +721,9 @@ static void yadif_decomb_filter_thread( void *thread_args_v )
* Process all three planes, but only this segment of it.
*/
hb_buffer_t *dst;
- int parity, tff, is_combed;
+ int parity, tff, mode;
- is_combed = pv->yadif_arguments[segment].is_combed;
+ mode = pv->yadif_arguments[segment].mode;
dst = yadif_work->dst;
tff = yadif_work->tff;
parity = yadif_work->parity;
@@ -1769,7 +747,7 @@ static void yadif_decomb_filter_thread( void *thread_args_v )
uint8_t *cur = &pv->ref[1]->plane[pp].data[start * stride];
uint8_t *next = &pv->ref[2]->plane[pp].data[start * stride];
- if( is_combed == 2 )
+ if (mode == MODE_DECOMB_BLEND)
{
/* These will be useful if we ever do temporal blending. */
for( yy = start; yy < segment_stop; yy += 2 )
@@ -1780,7 +758,7 @@ static void yadif_decomb_filter_thread( void *thread_args_v )
cur += stride * 2;
}
}
- else if (pv->mode == MODE_CUBIC && is_combed)
+ else if (mode == MODE_DECOMB_CUBIC)
{
for( yy = start; yy < segment_stop; yy += 2 )
{
@@ -1790,7 +768,7 @@ static void yadif_decomb_filter_thread( void *thread_args_v )
cur += stride * 2;
}
}
- else if ((pv->mode & MODE_YADIF) && is_combed == 1)
+ else if (mode & MODE_DECOMB_YADIF)
{
for( yy = start; yy < segment_stop; yy += 2 )
{
@@ -1856,70 +834,47 @@ static void yadif_filter( hb_filter_private_t * pv,
int tff)
{
/* If we're running comb detection, do it now, otherwise default to true. */
- int is_combed;
+ int is_combed = HB_COMB_HEAVY;
+ int mode = 0;
- if (!pv->skip_comb_check)
+ if (pv->mode & MODE_DECOMB_SELECTIVE)
{
- is_combed = pv->spatial_metric >= 0 ? comb_segmenter( pv ) : 1;
- }
- else
- {
- is_combed = pv->is_combed;
+ is_combed = pv->ref[1]->s.combed;
}
- /* The comb detector suggests three different values:
- 0: Don't comb this frame.
- 1: Deinterlace this frame.
- 2: Blend this frame.
- Since that might conflict with the filter's mode,
- it may be necesary to adjust this value. */
- if( is_combed == 1 && (pv->mode == MODE_BLEND) )
- {
- /* All combed frames are getting blended */
- is_combed = 2;
- }
- else if( is_combed == 2 && !( pv->mode & MODE_BLEND ) )
+ // Pick a mode based on the comb detect state and selected decomb modes
+ if ((pv->mode & MODE_DECOMB_BLEND) && is_combed == HB_COMB_LIGHT )
{
- /* Blending is disabled, so force interpolation of these frames. */
- is_combed = 1;
+ mode = MODE_DECOMB_BLEND;
}
- if( is_combed == 1 &&
- ( pv->mode & MODE_BLEND ) &&
- !( pv->mode & ( MODE_YADIF | MODE_EEDI2 | MODE_CUBIC ) ) )
+ else if (is_combed != HB_COMB_NONE)
{
- /* Deinterlacers are disabled, blending isn't, so blend these frames. */
- is_combed = 2;
- }
- else if( is_combed &&
- !( pv->mode & ( MODE_BLEND | MODE_YADIF | MODE_EEDI2 | MODE_CUBIC | MODE_MASK ) ) )
- {
- /* No deinterlacer or mask chosen, pass the frame through. */
- is_combed = 0;
+ mode = pv->mode & ~MODE_DECOMB_SELECTIVE;
}
- if( is_combed == 1 )
+ if (mode == MODE_DECOMB_BLEND)
{
- pv->deinterlaced_frames++;
+ pv->blended++;
}
- else if( is_combed == 2 )
+ else if (mode != 0)
{
- pv->blended_frames++;
+ pv->deinterlaced++;
}
else
{
- pv->unfiltered_frames++;
+ pv->unfiltered++;
}
+ pv->frames++;
- if( is_combed == 1 && ( pv->mode & MODE_EEDI2 ) )
+ if (mode & MODE_DECOMB_EEDI2)
{
/* Generate an EEDI2 interpolation */
eedi2_planer( pv );
}
- pv->is_combed = is_combed;
- if( is_combed )
+ if (mode != 0)
{
- if( ( pv->mode & MODE_EEDI2 ) && !( pv->mode & MODE_YADIF ) && is_combed == 1 )
+ if ((mode & MODE_DECOMB_EEDI2 ) && !(mode & MODE_DECOMB_YADIF))
{
// Just pass through the EEDI2 interpolation
int pp;
@@ -1954,7 +909,7 @@ static void yadif_filter( hb_filter_private_t * pv,
pv->yadif_arguments[segment].parity = parity;
pv->yadif_arguments[segment].tff = tff;
pv->yadif_arguments[segment].dst = dst;
- pv->yadif_arguments[segment].is_combed = is_combed;
+ pv->yadif_arguments[segment].mode = mode;
}
/*
@@ -1970,7 +925,7 @@ static void yadif_filter( hb_filter_private_t * pv,
else
{
/* Just passing through... */
- pv->yadif_arguments[0].is_combed = is_combed; // 0
+ pv->yadif_arguments[0].mode = mode; // 0
hb_buffer_copy(dst, pv->ref[1]);
}
}
@@ -1980,35 +935,25 @@ static int hb_decomb_init( hb_filter_object_t * filter,
{
filter->private_data = calloc( 1, sizeof(struct hb_filter_private_s) );
hb_filter_private_t * pv = filter->private_data;
-
- build_gamma_lut( pv );
-
- pv->deinterlaced_frames = 0;
- pv->blended_frames = 0;
- pv->unfiltered_frames = 0;
-
- pv->yadif_ready = 0;
-
- pv->mode = MODE_YADIF | MODE_BLEND | MODE_CUBIC |
- MODE_GAMMA | MODE_FILTER;
- pv->filter_mode = FILTER_ERODE_DILATE;
- pv->spatial_metric = 2;
- pv->motion_threshold = 3;
- pv->spatial_threshold = 3;
- pv->block_threshold = 40;
- pv->block_width = 16;
- pv->block_height = 16;
-
- pv->magnitude_threshold = 10;
- pv->variance_threshold = 20;
- pv->laplacian_threshold = 20;
- pv->dilation_threshold = 4;
- pv->erosion_threshold = 2;
- pv->noise_threshold = 50;
+ hb_buffer_list_clear(&pv->out_list);
+
+ pv->deinterlaced = 0;
+ pv->blended = 0;
+ pv->unfiltered = 0;
+ pv->frames = 0;
+ pv->yadif_ready = 0;
+
+ pv->mode = MODE_DECOMB_YADIF | MODE_DECOMB_BLEND |
+ MODE_DECOMB_CUBIC;
+ pv->magnitude_threshold = 10;
+ pv->variance_threshold = 20;
+ pv->laplacian_threshold = 20;
+ pv->dilation_threshold = 4;
+ pv->erosion_threshold = 2;
+ pv->noise_threshold = 50;
pv->maximum_search_distance = 24;
- pv->post_processing = 1;
-
- pv->parity = PARITY_DEFAULT;
+ pv->post_processing = 1;
+ pv->parity = PARITY_DEFAULT;
if (filter->settings)
{
@@ -2016,20 +961,10 @@ static int hb_decomb_init( hb_filter_object_t * filter,
// Get comb detection settings
hb_dict_extract_int(&pv->mode, dict, "mode");
- hb_dict_extract_int(&pv->spatial_metric, dict, "spatial-metric");
- hb_dict_extract_int(&pv->motion_threshold, dict,
- "motion-thresh");
- hb_dict_extract_int(&pv->spatial_threshold, dict,
- "spatial-thresh");
- hb_dict_extract_int(&pv->filter_mode, dict, "filter-mode");
- hb_dict_extract_int(&pv->block_threshold, dict,
- "block-thresh");
- hb_dict_extract_int(&pv->block_width, dict, "block-width");
- hb_dict_extract_int(&pv->block_height, dict, "block-height");
// Get deinterlace settings
hb_dict_extract_int(&pv->parity, dict, "parity");
- if (pv->mode & MODE_EEDI2)
+ if (pv->mode & MODE_DECOMB_EEDI2)
{
hb_dict_extract_int(&pv->magnitude_threshold, dict,
"magnitude-thresh");
@@ -2060,19 +995,8 @@ static int hb_decomb_init( hb_filter_object_t * filter,
pv->segment_height[1] = hb_image_height(init->pix_fmt, pv->segment_height[0], 1);
pv->segment_height[2] = hb_image_height(init->pix_fmt, pv->segment_height[0], 2);
- /* Allocate buffers to store comb masks. */
- pv->mask = hb_frame_buffer_init(init->pix_fmt,
- init->geometry.width, init->geometry.height);
- pv->mask_filtered = hb_frame_buffer_init(init->pix_fmt,
- init->geometry.width, init->geometry.height);
- pv->mask_temp = hb_frame_buffer_init(init->pix_fmt,
- init->geometry.width, init->geometry.height);
- memset(pv->mask->data, 0, pv->mask->size);
- memset(pv->mask_filtered->data, 0, pv->mask_filtered->size);
- memset(pv->mask_temp->data, 0, pv->mask_temp->size);
-
int ii;
- if( pv->mode & MODE_EEDI2 )
+ if( pv->mode & MODE_DECOMB_EEDI2 )
{
/* Allocate half-height eedi2 buffers */
for( ii = 0; ii < 4; ii++ )
@@ -2141,276 +1065,7 @@ static int hb_decomb_init( hb_filter_object_t * filter,
yadif_prev_thread_args = thread_args;
}
- /*
- * Create comb detection taskset.
- */
- if( taskset_init( &pv->decomb_filter_taskset, pv->cpu_count,
- sizeof( decomb_thread_arg_t ) ) == 0 )
- {
- hb_error( "decomb could not initialize taskset" );
- }
-
- decomb_thread_arg_t *decomb_prev_thread_args = NULL;
- for( ii = 0; ii < pv->cpu_count; ii++ )
- {
- decomb_thread_arg_t *thread_args;
-
- thread_args = taskset_thread_args( &pv->decomb_filter_taskset, ii );
- thread_args->pv = pv;
- thread_args->segment = ii;
-
- int pp;
- for (pp = 0; pp < 3; pp++)
- {
- if (decomb_prev_thread_args != NULL)
- {
- thread_args->segment_start[pp] =
- decomb_prev_thread_args->segment_start[pp] +
- decomb_prev_thread_args->segment_height[pp];
- }
- if( ii == pv->cpu_count - 1 )
- {
- /*
- * Final segment
- */
- thread_args->segment_height[pp] =
- hb_image_height(init->pix_fmt, init->geometry.height, pp) -
- thread_args->segment_start[pp];
- } else {
- thread_args->segment_height[pp] = pv->segment_height[pp];
- }
- }
-
- if( taskset_thread_spawn( &pv->decomb_filter_taskset, ii,
- "decomb_filter_segment",
- decomb_filter_thread,
- HB_NORMAL_PRIORITY ) == 0 )
- {
- hb_error( "decomb could not spawn thread" );
- }
-
- decomb_prev_thread_args = thread_args;
- }
-
- pv->comb_check_nthreads = init->geometry.height / pv->block_height;
-
- if (pv->comb_check_nthreads > pv->cpu_count)
- pv->comb_check_nthreads = pv->cpu_count;
-
- pv->block_score = calloc(pv->comb_check_nthreads, sizeof(int));
-
- /*
- * Create comb check taskset.
- */
- if( taskset_init( &pv->decomb_check_taskset, pv->comb_check_nthreads,
- sizeof( decomb_thread_arg_t ) ) == 0 )
- {
- hb_error( "decomb check could not initialize taskset" );
- }
-
- decomb_prev_thread_args = NULL;
- for( ii = 0; ii < pv->comb_check_nthreads; ii++ )
- {
- decomb_thread_arg_t *thread_args;
-
- thread_args = taskset_thread_args( &pv->decomb_check_taskset, ii );
- thread_args->pv = pv;
- thread_args->segment = ii;
-
- int pp;
- for (pp = 0; pp < 3; pp++)
- {
- if (decomb_prev_thread_args != NULL)
- {
- thread_args->segment_start[pp] =
- decomb_prev_thread_args->segment_start[pp] +
- decomb_prev_thread_args->segment_height[pp];
- }
-
- // Make segment hight a multiple of block_height
- int h = hb_image_height(init->pix_fmt, init->geometry.height, pp) / pv->comb_check_nthreads;
- h = h / pv->block_height * pv->block_height;
- if (h == 0)
- h = pv->block_height;
-
- if (ii == pv->comb_check_nthreads - 1)
- {
- /*
- * Final segment
- */
- thread_args->segment_height[pp] =
- hb_image_height(init->pix_fmt, init->geometry.height, pp) -
- thread_args->segment_start[pp];
- } else {
- thread_args->segment_height[pp] = h;
- }
- }
-
- if( taskset_thread_spawn( &pv->decomb_check_taskset, ii,
- "decomb_check_segment",
- decomb_check_thread,
- HB_NORMAL_PRIORITY ) == 0 )
- {
- hb_error( "decomb check could not spawn thread" );
- }
-
- decomb_prev_thread_args = thread_args;
- }
-
- if( pv->mode & MODE_FILTER )
- {
- if( taskset_init( &pv->mask_filter_taskset, pv->cpu_count,
- sizeof( decomb_thread_arg_t ) ) == 0 )
- {
- hb_error( "maske filter could not initialize taskset" );
- }
-
- decomb_prev_thread_args = NULL;
- for( ii = 0; ii < pv->cpu_count; ii++ )
- {
- decomb_thread_arg_t *thread_args;
-
- thread_args = taskset_thread_args( &pv->mask_filter_taskset, ii );
- thread_args->pv = pv;
- thread_args->segment = ii;
-
- int pp;
- for (pp = 0; pp < 3; pp++)
- {
- if (decomb_prev_thread_args != NULL)
- {
- thread_args->segment_start[pp] =
- decomb_prev_thread_args->segment_start[pp] +
- decomb_prev_thread_args->segment_height[pp];
- }
-
- if( ii == pv->cpu_count - 1 )
- {
- /*
- * Final segment
- */
- thread_args->segment_height[pp] =
- hb_image_height(init->pix_fmt, init->geometry.height, pp) -
- thread_args->segment_start[pp];
- } else {
- thread_args->segment_height[pp] = pv->segment_height[pp];
- }
- }
-
- if( taskset_thread_spawn( &pv->mask_filter_taskset, ii,
- "mask_filter_segment",
- mask_filter_thread,
- HB_NORMAL_PRIORITY ) == 0 )
- {
- hb_error( "mask filter could not spawn thread" );
- }
-
- decomb_prev_thread_args = thread_args;
- }
-
- if( pv->filter_mode == FILTER_ERODE_DILATE )
- {
- if( taskset_init( &pv->mask_erode_taskset, pv->cpu_count,
- sizeof( decomb_thread_arg_t ) ) == 0 )
- {
- hb_error( "mask erode could not initialize taskset" );
- }
-
- decomb_prev_thread_args = NULL;
- for( ii = 0; ii < pv->cpu_count; ii++ )
- {
- decomb_thread_arg_t *thread_args;
-
- thread_args = taskset_thread_args( &pv->mask_erode_taskset, ii );
- thread_args->pv = pv;
- thread_args->segment = ii;
-
- int pp;
- for (pp = 0; pp < 3; pp++)
- {
- if (decomb_prev_thread_args != NULL)
- {
- thread_args->segment_start[pp] =
- decomb_prev_thread_args->segment_start[pp] +
- decomb_prev_thread_args->segment_height[pp];
- }
-
- if( ii == pv->cpu_count - 1 )
- {
- /*
- * Final segment
- */
- thread_args->segment_height[pp] =
- hb_image_height(init->pix_fmt, init->geometry.height, pp) -
- thread_args->segment_start[pp];
- } else {
- thread_args->segment_height[pp] = pv->segment_height[pp];
- }
- }
-
- if( taskset_thread_spawn( &pv->mask_erode_taskset, ii,
- "mask_erode_segment",
- mask_erode_thread,
- HB_NORMAL_PRIORITY ) == 0 )
- {
- hb_error( "mask erode could not spawn thread" );
- }
-
- decomb_prev_thread_args = thread_args;
- }
-
- if( taskset_init( &pv->mask_dilate_taskset, pv->cpu_count,
- sizeof( decomb_thread_arg_t ) ) == 0 )
- {
- hb_error( "mask dilate could not initialize taskset" );
- }
-
- decomb_prev_thread_args = NULL;
- for( ii = 0; ii < pv->cpu_count; ii++ )
- {
- decomb_thread_arg_t *thread_args;
-
- thread_args = taskset_thread_args( &pv->mask_dilate_taskset, ii );
- thread_args->pv = pv;
- thread_args->segment = ii;
-
- int pp;
- for (pp = 0; pp < 3; pp++)
- {
- if (decomb_prev_thread_args != NULL)
- {
- thread_args->segment_start[pp] =
- decomb_prev_thread_args->segment_start[pp] +
- decomb_prev_thread_args->segment_height[pp];
- }
-
- if( ii == pv->cpu_count - 1 )
- {
- /*
- * Final segment
- */
- thread_args->segment_height[pp] =
- hb_image_height(init->pix_fmt, init->geometry.height, pp) -
- thread_args->segment_start[pp];
- } else {
- thread_args->segment_height[pp] = pv->segment_height[pp];
- }
- }
-
- if( taskset_thread_spawn( &pv->mask_dilate_taskset, ii,
- "mask_dilate_segment",
- mask_dilate_thread,
- HB_NORMAL_PRIORITY ) == 0 )
- {
- hb_error( "mask dilate could not spawn thread" );
- }
-
- decomb_prev_thread_args = thread_args;
- }
- }
- }
-
- if( pv->mode & MODE_EEDI2 )
+ if( pv->mode & MODE_DECOMB_EEDI2 )
{
/*
* Create eedi2 taskset.
@@ -2477,28 +1132,16 @@ static void hb_decomb_close( hb_filter_object_t * filter )
return;
}
- hb_log("decomb: deinterlaced %i | blended %i | unfiltered %i | total %i", pv->deinterlaced_frames, pv->blended_frames, pv->unfiltered_frames, pv->deinterlaced_frames + pv->blended_frames + pv->unfiltered_frames);
+ hb_log("decomb: deinterlaced %i | blended %i | unfiltered %i | total %i",
+ pv->deinterlaced, pv->blended, pv->unfiltered, pv->frames);
taskset_fini( &pv->yadif_taskset );
- taskset_fini( &pv->decomb_filter_taskset );
- taskset_fini( &pv->decomb_check_taskset );
- if( pv->mode & MODE_FILTER )
- {
- taskset_fini( &pv->mask_filter_taskset );
- if( pv->filter_mode == FILTER_ERODE_DILATE )
- {
- taskset_fini( &pv->mask_erode_taskset );
- taskset_fini( &pv->mask_dilate_taskset );
- }
- }
-
- if( pv->mode & MODE_EEDI2 )
+ if( pv->mode & MODE_DECOMB_EEDI2 )
{
taskset_fini( &pv->eedi2_taskset );
}
-
/* Cleanup reference buffers. */
int ii;
for (ii = 0; ii < 3; ii++)
@@ -2506,12 +1149,7 @@ static void hb_decomb_close( hb_filter_object_t * filter )
hb_buffer_close(&pv->ref[ii]);
}
- /* Cleanup combing masks. */
- hb_buffer_close(&pv->mask);
- hb_buffer_close(&pv->mask_filtered);
- hb_buffer_close(&pv->mask_temp);
-
- if( pv->mode & MODE_EEDI2 )
+ if( pv->mode & MODE_DECOMB_EEDI2 )
{
/* Cleanup eedi-half buffers */
int ii;
@@ -2527,7 +1165,7 @@ static void hb_decomb_close( hb_filter_object_t * filter )
}
}
- if( pv->post_processing > 1 && ( pv->mode & MODE_EEDI2 ) )
+ if( pv->post_processing > 1 && ( pv->mode & MODE_DECOMB_EEDI2 ) )
{
if (pv->cx2) eedi2_aligned_free(pv->cx2);
if (pv->cy2) eedi2_aligned_free(pv->cy2);
@@ -2535,8 +1173,6 @@ static void hb_decomb_close( hb_filter_object_t * filter )
if (pv->tmpc) eedi2_aligned_free(pv->tmpc);
}
- free(pv->block_score);
-
/*
* free memory for yadif structs
*/
@@ -2568,26 +1204,92 @@ static void fill_stride(hb_buffer_t * buf)
}
}
+static void process_frame( hb_filter_private_t * pv )
+{
+ if ((pv->mode & MODE_DECOMB_SELECTIVE) &&
+ pv->ref[1]->s.combed == HB_COMB_NONE)
+ {
+ // Input buffer is not combed. Just make a dup of it.
+ hb_buffer_t * buf = hb_buffer_dup(pv->ref[1]);
+ hb_buffer_list_append(&pv->out_list, buf);
+ pv->frames++;
+ pv->unfiltered++;
+ }
+ else
+ {
+ /* Determine if top-field first layout */
+ int tff;
+ if (pv->parity < 0)
+ {
+ tff = !!(pv->ref[1]->s.flags & PIC_FLAG_TOP_FIELD_FIRST);
+ }
+ else
+ {
+ tff = (pv->parity & 1) ^ 1;
+ }
+
+ /* deinterlace both fields if bob */
+ int frame, num_frames = 1;
+ if (pv->mode & MODE_DECOMB_BOB)
+ {
+ num_frames = 2;
+ }
+
+ // Will need up to 2 buffers simultaneously
+
+ /* Perform yadif filtering */
+ for (frame = 0; frame < num_frames; frame++)
+ {
+ hb_buffer_t * buf;
+ int parity = frame ^ tff ^ 1;
+
+ // tff for eedi2
+ pv->tff = !parity;
+
+ buf = hb_frame_buffer_init(pv->ref[1]->f.fmt,
+ pv->ref[1]->f.width,
+ pv->ref[1]->f.height);
+ yadif_filter(pv, buf, parity, tff);
+
+ /* Copy buffered settings to output buffer settings */
+ buf->s = pv->ref[1]->s;
+
+ hb_buffer_list_append(&pv->out_list, buf);
+ }
+
+ /* if this frame was deinterlaced and bob mode is engaged, halve
+ the duration of the saved timestamps. */
+ if (pv->mode & MODE_DECOMB_BOB)
+ {
+ hb_buffer_t *first = hb_buffer_list_head(&pv->out_list);
+ hb_buffer_t *second = hb_buffer_list_tail(&pv->out_list);
+ first->s.stop -= (first->s.stop - first->s.start) / 2LL;
+ second->s.start = first->s.stop;
+ second->s.new_chap = 0;
+ }
+ }
+}
+
static int hb_decomb_work( hb_filter_object_t * filter,
hb_buffer_t ** buf_in,
hb_buffer_t ** buf_out )
{
hb_filter_private_t * pv = filter->private_data;
hb_buffer_t * in = *buf_in;
- hb_buffer_list_t list;
- hb_buffer_list_clear(&list);
+ // Input buffer is always consumed.
+ *buf_in = NULL;
if (in->s.flags & HB_BUF_FLAG_EOF)
{
- *buf_out = in;
- *buf_in = NULL;
+ // Duplicate last frame and process refs
+ store_ref(pv, hb_buffer_dup(pv->ref[2]));
+ process_frame(pv);
+ hb_buffer_list_append(&pv->out_list, in);
+ *buf_out = hb_buffer_list_clear(&pv->out_list);
return HB_FILTER_DONE;
}
- /* Store current frame in yadif cache */
- *buf_in = NULL;
fill_stride(in);
- store_ref(pv, in);
// yadif requires 3 buffers, prev, cur, and next. For the first
// frame, there can be no prev, so we duplicate the first frame.
@@ -2595,106 +1297,16 @@ static int hb_decomb_work( hb_filter_object_t * filter,
{
// If yadif is not ready, store another ref and return HB_FILTER_DELAY
store_ref(pv, hb_buffer_dup(in));
+ store_ref(pv, in);
pv->yadif_ready = 1;
// Wait for next
return HB_FILTER_DELAY;
}
- /* Determine if top-field first layout */
- int tff;
- if( pv->parity < 0 )
- {
- tff = !!(in->s.flags & PIC_FLAG_TOP_FIELD_FIRST);
- }
- else
- {
- tff = (pv->parity & 1) ^ 1;
- }
-
- /* deinterlace both fields if bob */
- int frame, num_frames = 1;
- if (pv->mode & MODE_BOB)
- {
- num_frames = 2;
- }
-
- // Will need up to 2 buffers simultaneously
- int idx = 0;
- hb_buffer_t * o_buf[2] = {NULL,};
-
- /* Perform yadif filtering */
- for( frame = 0; frame < num_frames; frame++ )
- {
- int parity = frame ^ tff ^ 1;
-
- /* Skip the second run if the frame is uncombed */
- if (frame && pv->is_combed == 0)
- {
- break;
- }
-
- // tff for eedi2
- pv->tff = !parity;
-
- if (o_buf[idx] == NULL)
- {
- o_buf[idx] = hb_video_buffer_init(in->f.width, in->f.height);
- }
-
- if (frame)
- pv->skip_comb_check = 1;
- else
- pv->skip_comb_check = 0;
-
- yadif_filter(pv, o_buf[idx], parity, tff);
-
- // If bob, add all frames to output
- // else, if not combed, add frame to output
- // else if final iteration, add frame to output
- if ((pv->mode & MODE_BOB) ||
- pv->is_combed == 0 ||
- frame == num_frames - 1)
- {
- /* Copy buffered settings to output buffer settings */
- o_buf[idx]->s = pv->ref[1]->s;
-
- o_buf[idx]->next = NULL;
- hb_buffer_list_append(&list, o_buf[idx]);
-
- // Indicate that buffer was consumed
- o_buf[idx] = NULL;
-
- idx ^= 1;
-
- if ((pv->mode & MODE_MASK) && pv->spatial_metric >= 0 )
- {
- if (pv->mode == MODE_MASK ||
- ((pv->mode & MODE_MASK) &&
- (pv->mode & MODE_FILTER)) ||
- ((pv->mode & MODE_MASK) &&
- (pv->mode & MODE_GAMMA)) ||
- pv->is_combed)
- {
- apply_mask(pv, hb_buffer_list_tail(&list));
- }
- }
- }
- }
- hb_buffer_close(&o_buf[0]);
- hb_buffer_close(&o_buf[1]);
-
- /* if this frame was deinterlaced and bob mode is engaged, halve
- the duration of the saved timestamps. */
- if ((pv->mode & MODE_BOB) && pv->is_combed)
- {
- hb_buffer_t *first = hb_buffer_list_head(&list);
- hb_buffer_t *second = hb_buffer_list_tail(&list);
- first->s.stop -= (first->s.stop - first->s.start) / 2LL;
- second->s.start = first->s.stop;
- second->s.new_chap = 0;
- }
+ store_ref(pv, in);
+ process_frame(pv);
- *buf_out = hb_buffer_list_clear(&list);
+ *buf_out = hb_buffer_list_clear(&pv->out_list);
return HB_FILTER_OK;
}
diff --git a/libhb/decomb.h b/libhb/decomb.h
new file mode 100644
index 000000000..4aa60a608
--- /dev/null
+++ b/libhb/decomb.h
@@ -0,0 +1,24 @@
+/* decomb.h
+
+ Copyright (c) 2003-2016 HandBrake Team
+ 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 v2.
+ For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
+ */
+
+#ifndef HB_DECOMB_H
+#define HB_DECOMB_H
+
+#define MODE_DECOMB_YADIF 1 // Use yadif
+#define MODE_DECOMB_BLEND 2 // Use blending interpolation
+#define MODE_DECOMB_CUBIC 4 // Use cubic interpolation
+#define MODE_DECOMB_EEDI2 8 // Use EEDI2 interpolation
+#define MODE_DECOMB_BOB 16 // Deinterlace each field to a separate frame
+#define MODE_DECOMB_SELECTIVE 32 // Selectively deinterlace based on comb detection
+
+#define MODE_YADIF_ENABLE 1
+#define MODE_YADIF_SPATIAL 2
+#define MODE_YADIF_BOB 4
+
+#endif // HB_DECOMB_H
diff --git a/libhb/internal.h b/libhb/internal.h
index 9ad22507f..7c70a2bb7 100644
--- a/libhb/internal.h
+++ b/libhb/internal.h
@@ -465,6 +465,7 @@ enum
};
extern hb_filter_object_t hb_filter_detelecine;
+extern hb_filter_object_t hb_filter_comb_detect;
extern hb_filter_object_t hb_filter_decomb;
extern hb_filter_object_t hb_filter_deinterlace;
extern hb_filter_object_t hb_filter_vfr;
diff --git a/libhb/libhb_presets.list b/libhb/libhb_presets.list
index cb46ba084..0009d8674 100644
--- a/libhb/libhb_presets.list
+++ b/libhb/libhb_presets.list
@@ -1,6 +1,6 @@
<resources>
<section name="PresetTemplate">
- <integer name="VersionMajor" value="12" />
+ <integer name="VersionMajor" value="13" />
<integer name="VersionMinor" value="0" />
<integer name="VersionMicro" value="0" />
<json name="Preset" file="preset_template.json" />
diff --git a/libhb/param.c b/libhb/param.c
index 350f81773..46bc80f06 100644
--- a/libhb/param.c
+++ b/libhb/param.c
@@ -75,18 +75,28 @@ static hb_filter_param_t detelecine_presets[] =
{ 0, NULL, NULL, NULL }
};
-static hb_filter_param_t decomb_presets[] =
+static hb_filter_param_t comb_detect_presets[] =
{
+ { 0, "Off", "off", "disable=1" },
{ 1, "Custom", "custom", NULL },
{ 2, "Default", "default",
- "mode=391:spatial-metric=2:motion-thresh=3:spatial-thresh=3:"
- "filter-mode=2:block-thresh=40"
+ "mode=3:spatial-metric=2:motion-thresh=3:spatial-thresh=3:"
+ "filter-mode=2:block-thresh=40:block-width=16:block-height=16"
},
{ 3, "Fast", "fast",
- "mode=7:motion-thresh=6:spatial-thresh=9:"
- "filter-mode=1:block-thresh=80"
+ "mode=0:spatial-metric=2:motion-thresh=6:spatial-thresh=9:"
+ "filter-mode=1:block-thresh=80:block-width=16:block-height=16"
},
- { 4, "Bob", "bob", "mode=455" },
+ { 0, NULL, NULL, NULL }
+};
+
+static hb_filter_param_t decomb_presets[] =
+{
+ { 1, "Custom", "custom", NULL },
+ { 2, "Default", "default", "mode=7" },
+ { 4, "Bob", "bob", "mode=23" },
+ { 3, "EEDI2", "eedi2", "mode=15" },
+ { 4, "EEDI2 Bob", "eedi2bob", "mode=31" },
{ 0, NULL, NULL, NULL }
};
@@ -121,6 +131,9 @@ static filter_param_map_t param_map[] =
{ HB_FILTER_DETELECINE, detelecine_presets, NULL,
sizeof(detelecine_presets) / sizeof(hb_filter_param_t) },
+ { HB_FILTER_COMB_DETECT, comb_detect_presets, NULL,
+ sizeof(decomb_presets) / sizeof(hb_filter_param_t) },
+
{ HB_FILTER_DECOMB, decomb_presets, NULL,
sizeof(decomb_presets) / sizeof(hb_filter_param_t) },
@@ -552,6 +565,7 @@ hb_generate_filter_settings(int filter_id, const char *preset, const char *tune,
case HB_FILTER_NLMEANS:
settings = generate_nlmeans_settings(preset, tune, custom);
break;
+ case HB_FILTER_COMB_DETECT:
case HB_FILTER_DECOMB:
case HB_FILTER_DETELECINE:
case HB_FILTER_HQDN3D:
diff --git a/libhb/preset.c b/libhb/preset.c
index 9b5f0616a..d32e4d1e8 100644
--- a/libhb/preset.c
+++ b/libhb/preset.c
@@ -1114,6 +1114,34 @@ int hb_preset_apply_filters(const hb_dict_t *preset, hb_dict_t *job_dict)
}
}
+ const char *comb_preset;
+ comb_preset = hb_value_get_string(hb_dict_get(preset,
+ "PictureCombDetectPreset"));
+ if (comb_preset != NULL)
+ {
+ const char *comb_custom;
+ comb_custom = hb_value_get_string(hb_dict_get(preset,
+ "PictureCombDetectCustom"));
+ filter_settings = hb_generate_filter_settings(HB_FILTER_COMB_DETECT,
+ comb_preset, NULL, comb_custom);
+ if (filter_settings == NULL)
+ {
+ hb_error("Invalid comb detect filter preset (%s)", comb_preset);
+ return -1;
+ }
+ else if (!hb_dict_get_bool(filter_settings, "disable"))
+ {
+ filter_dict = hb_dict_init();
+ hb_dict_set(filter_dict, "ID", hb_value_int(HB_FILTER_COMB_DETECT));
+ hb_dict_set(filter_dict, "Settings", filter_settings);
+ hb_add_filter2(filter_list, filter_dict);
+ }
+ else
+ {
+ hb_value_free(&filter_settings);
+ }
+ }
+
// Decomb or deinterlace filters
const char *deint_filter, *deint_preset, *deint_custom;
deint_filter = hb_value_get_string(hb_dict_get(preset,
@@ -1146,7 +1174,7 @@ int hb_preset_apply_filters(const hb_dict_t *preset, hb_dict_t *job_dict)
hb_error("Invalid deinterlace filter preset (%s)", deint_preset);
return -1;
}
- else if (!hb_dict_get_bool(filter_settings, "disable"))
+ if (!hb_dict_get_bool(filter_settings, "disable"))
{
filter_dict = hb_dict_init();
hb_dict_set(filter_dict, "ID", hb_value_int(filter_id));
@@ -2088,6 +2116,148 @@ static void import_filters_11_1_0(hb_value_t *preset)
"PictureRotate");
}
+// Split the old decomb filter into separate comb detection
+// and decomb filters.
+static void import_deint_12_0_0(hb_value_t *preset)
+{
+ hb_value_t *val = hb_dict_get(preset, "PictureDeinterlaceFilter");
+ if (val == NULL)
+ {
+ return;
+ }
+ const char * deint = hb_value_get_string(val);
+ if (deint == NULL)
+ {
+ // This really shouldn't happen for a valid preset
+ return;
+ }
+ if (strcasecmp(deint, "decomb"))
+ {
+ return;
+ }
+ val = hb_dict_get(preset, "PictureDeinterlacePreset");
+ if (val == NULL)
+ {
+ hb_dict_set(preset, "PictureDeinterlacePreset",
+ hb_value_string("default"));
+ return;
+ }
+ deint = hb_value_get_string(val);
+ if (deint == NULL)
+ {
+ // This really shouldn't happen for a valid preset
+ return;
+ }
+ if (!strcasecmp(deint, "fast"))
+ {
+ // fast -> PictureCombDetectPreset fast
+ // PictureDeinterlacePreset default
+ hb_dict_set(preset, "PictureCombDetectPreset",
+ hb_value_string("fast"));
+ hb_dict_set(preset, "PictureDeinterlacePreset",
+ hb_value_string("default"));
+ return;
+ }
+ else if (!strcasecmp(deint, "bob") || !strcasecmp(deint, "default"))
+ {
+ hb_dict_set(preset, "PictureCombDetectPreset",
+ hb_value_string("default"));
+ return;
+ }
+ else if (strcasecmp(deint, "custom"))
+ {
+ // not custom -> default
+ hb_dict_set(preset, "PictureCombDetectPreset",
+ hb_value_string("default"));
+ hb_dict_set(preset, "PictureDeinterlacePreset",
+ hb_value_string("default"));
+ return;
+ }
+ val = hb_dict_get(preset, "PictureDeinterlaceCustom");
+ if (val == NULL)
+ {
+ hb_dict_set(preset, "PictureDeinterlacePreset",
+ hb_value_string("default"));
+ return;
+ }
+ // Translate custom values
+ deint = hb_value_get_string(val);
+ if (deint == NULL)
+ {
+ // This really shouldn't happen for a valid preset
+ return;
+ }
+
+ hb_dict_t * dict;
+ dict = hb_parse_filter_settings(deint);
+
+ int yadif, blend, cubic, eedi2, mask, bob, gamma, filter, composite;
+ int detect_mode, decomb_mode;
+
+ int mode = 7, spatial_metric = 2, motion_threshold = 3;
+ int spatial_threshold = 3, filter_mode = 2;
+ int block_threshold = 40, block_width = 16, block_height = 16;
+ int magnitude_threshold = 10, variance_threshold = 20;
+ int laplacian_threshold = 20;
+ int dilation_threshold = 4, erosion_threshold = 2, noise_threshold = 50;
+ int maximum_search_distance = 24, post_processing = 1, parity = -1;
+
+ hb_dict_extract_int(&mode, dict, "mode");
+ hb_dict_extract_int(&spatial_metric, dict, "spatial-metric");
+ hb_dict_extract_int(&motion_threshold, dict, "motion-thresh");
+ hb_dict_extract_int(&spatial_threshold, dict, "spatial-thresh");
+ hb_dict_extract_int(&filter_mode, dict, "filter-mode");
+ hb_dict_extract_int(&block_threshold, dict, "block-thresh");
+ hb_dict_extract_int(&block_width, dict, "block-width");
+ hb_dict_extract_int(&block_height, dict, "block-height");
+ hb_dict_extract_int(&magnitude_threshold, dict, "magnitude-thresh");
+ hb_dict_extract_int(&variance_threshold, dict, "variance-thresh");
+ hb_dict_extract_int(&laplacian_threshold, dict, "laplacian-thresh");
+ hb_dict_extract_int(&dilation_threshold, dict, "dilation-thresh");
+ hb_dict_extract_int(&erosion_threshold, dict, "erosion-thresh");
+ hb_dict_extract_int(&noise_threshold, dict, "noise-thresh");
+ hb_dict_extract_int(&maximum_search_distance, dict, "search-distance");
+ hb_dict_extract_int(&post_processing, dict, "postproc");
+ hb_dict_extract_int(&parity, dict, "parity");
+ hb_value_free(&dict);
+
+ yadif = !!(mode & 1);
+ blend = !!(mode & 2);
+ cubic = !!(mode & 4);
+ eedi2 = !!(mode & 8);
+ mask = !!(mode & 32);
+ bob = !!(mode & 64);
+ gamma = !!(mode & 128);
+ filter = !!(mode & 256);
+ composite = !!(mode & 512);
+
+ detect_mode = gamma + filter * 2 + mask * 4 + composite * 8;
+ decomb_mode = yadif + blend * 2 + cubic * 4 + eedi2 * 8 + bob * 16;
+
+ char * custom = hb_strdup_printf("mode=%d:spatial-metric=%d:"
+ "motion-thresh=%d:spatial-thresh=%d:"
+ "filter-mode=%d:block-thresh=%d:"
+ "block-width=%d:block-height=%d",
+ detect_mode, spatial_metric,
+ motion_threshold, spatial_threshold,
+ filter_mode, block_threshold,
+ block_width, block_height);
+ hb_dict_set(preset, "PictureCombDetectCustom", hb_value_string(custom));
+ free(custom);
+
+ custom = hb_strdup_printf("mode=%d:magnitude-thresh=%d:variance-thresh=%d:"
+ "laplacian-thresh=%d:dilation-thresh=%d:"
+ "erosion-thresh=%d:noise-thresh=%d:"
+ "search-distance=%d:postproc=%d:parity=%d",
+ decomb_mode, magnitude_threshold,
+ variance_threshold, laplacian_threshold,
+ dilation_threshold, erosion_threshold,
+ noise_threshold, maximum_search_distance,
+ post_processing, parity);
+ hb_dict_set(preset, "PictureDeinterlaceCustom", hb_value_string(custom));
+ free(custom);
+}
+
static void import_deint_11_0_0(hb_value_t *preset)
{
hb_value_t *val = hb_dict_get(preset, "PictureDeinterlaceFilter");
@@ -2453,9 +2623,17 @@ static void import_video_0_0_0(hb_value_t *preset)
}
}
+static void import_12_0_0(hb_value_t *preset)
+{
+ import_deint_12_0_0(preset);
+}
+
static void import_11_1_0(hb_value_t *preset)
{
import_filters_11_1_0(preset);
+
+ // Import next...
+ import_12_0_0(preset);
}
static void import_11_0_0(hb_value_t *preset)
@@ -2514,6 +2692,11 @@ static int preset_import(hb_value_t *preset, int major, int minor, int micro)
import_11_1_0(preset);
result = 1;
}
+ else if (major == 12 && minor == 0 && micro == 0)
+ {
+ import_12_0_0(preset);
+ result = 1;
+ }
preset_clean(preset, hb_preset_template);
}
return result;
diff --git a/libhb/preset_builtin.json b/libhb/preset_builtin.json
index 16cc2bdaa..fcd70f638 100644
--- a/libhb/preset_builtin.json
+++ b/libhb/preset_builtin.json
@@ -402,9 +402,10 @@
"PictureAutoCrop": true,
"PictureBottomCrop": 0,
"PictureDeblock": 0,
+ "PictureCombDetectPreset": "fast",
"PictureDeinterlaceCustom": "",
"PictureDeinterlaceFilter": "decomb",
- "PictureDeinterlacePreset": "fast",
+ "PictureDeinterlacePreset": "default",
"PictureDenoiseCustom": "",
"PictureDenoiseFilter": "off",
"PictureDetelecine": "off",
@@ -707,6 +708,7 @@
"PictureAutoCrop": true,
"PictureBottomCrop": 0,
"PictureDeblock": 0,
+ "PictureCombDetectPreset": "default",
"PictureDeinterlaceCustom": "",
"PictureDeinterlaceFilter": "decomb",
"PictureDeinterlacePreset": "default",
diff --git a/libhb/preset_template.json b/libhb/preset_template.json
index da113f88a..a66e1e2e5 100644
--- a/libhb/preset_template.json
+++ b/libhb/preset_template.json
@@ -47,6 +47,8 @@
"PictureDARWidth": 0,
"PictureDeblock": 0,
"PictureDeblockCustom": "qp=0:mode=2",
+ "PictureCombDetectCustom": "",
+ "PictureCombDetectPreset": "off",
"PictureDeinterlaceCustom": "",
"PictureDeinterlaceFilter": "off",
"PictureDeinterlacePreset": "default",
diff --git a/libhb/work.c b/libhb/work.c
index 7a1460eec..eaa7facab 100644
--- a/libhb/work.c
+++ b/libhb/work.c
@@ -11,6 +11,7 @@
#include "libavformat/avformat.h"
#include "openclwrapper.h"
#include "opencl.h"
+#include "decomb.h"
#ifdef USE_QSV
#include "qsv_common.h"
@@ -1309,6 +1310,31 @@ static int sanitize_qsv( hb_job_t * job )
return 0;
}
+static void sanitize_filter_list(hb_list_t *list)
+{
+ // Add selective deinterlacing mode if comb detection is enabled
+ if (hb_filter_find(list, HB_FILTER_COMB_DETECT) != NULL)
+ {
+ int selective[] = {HB_FILTER_DECOMB, HB_FILTER_DEINTERLACE};
+ int ii, count = sizeof(selective) / sizeof(int);
+
+ for (ii = 0; ii < count; ii++)
+ {
+ hb_filter_object_t * filter = hb_filter_find(list, selective[ii]);
+ if (filter != NULL)
+ {
+ int mode = hb_dict_get_int(filter->settings, "mode");
+ mode |= MODE_DECOMB_SELECTIVE;
+ hb_dict_set(filter->settings, "mode", hb_value_int(mode));
+ break;
+ }
+ }
+ }
+
+ // Combine HB_FILTER_AVFILTERs that are sequential
+ hb_avfilter_combine(list);
+}
+
/**
* Job initialization rountine.
*
@@ -1416,8 +1442,7 @@ static void do_job(hb_job_t *job)
{
hb_filter_init_t init;
- // Combine HB_FILTER_AVFILTERs that are sequential
- hb_avfilter_combine(job->list_filter);
+ sanitize_filter_list(job->list_filter);
memset(&init, 0, sizeof(init));
init.job = job;