diff options
-rw-r--r-- | gtk/src/ghb-3.12.ui | 62 | ||||
-rw-r--r-- | gtk/src/ghb-3.14.ui | 62 | ||||
-rw-r--r-- | gtk/src/ghb.ui | 74 | ||||
-rw-r--r-- | gtk/src/hb-backend.c | 12 | ||||
-rw-r--r-- | gtk/src/makedeps.py | 1 | ||||
-rw-r--r-- | libhb/avfilter.c | 11 | ||||
-rw-r--r-- | libhb/builtin_presets.h | 8 | ||||
-rw-r--r-- | libhb/comb_detect.c | 1560 | ||||
-rw-r--r-- | libhb/common.c | 25 | ||||
-rw-r--r-- | libhb/common.h | 2 | ||||
-rw-r--r-- | libhb/decomb.c | 1736 | ||||
-rw-r--r-- | libhb/decomb.h | 24 | ||||
-rw-r--r-- | libhb/internal.h | 1 | ||||
-rw-r--r-- | libhb/libhb_presets.list | 2 | ||||
-rw-r--r-- | libhb/param.c | 26 | ||||
-rw-r--r-- | libhb/preset.c | 185 | ||||
-rw-r--r-- | libhb/preset_builtin.json | 4 | ||||
-rw-r--r-- | libhb/preset_template.json | 2 | ||||
-rw-r--r-- | libhb/work.c | 29 |
19 files changed, 2241 insertions, 1585 deletions
diff --git a/gtk/src/ghb-3.12.ui b/gtk/src/ghb-3.12.ui index ce4d34aae..3e9f9d174 100644 --- a/gtk/src/ghb-3.12.ui +++ b/gtk/src/ghb-3.12.ui @@ -2934,6 +2934,68 @@ JunkLeft:JunkRight:JunkTop:JunkBottom:StrictBreaks:MetricPlane:Parity</property> </object> </child> <child> + <object class="GtkGrid" id="comb_detect_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column_spacing">5</property> + <property name="halign">start</property> + <property name="valign">start</property> + <child> + <object class="GtkLabel" id="comb_detect_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Interlace Detection:</property> + </object> + <packing> + <property name="top_attach">0</property> + <property name="left_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="PictureCombDetectPreset"> + <property name="valign">GTK_ALIGN_CENTER</property> + <property name="width_request">100</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="tooltip_text" translatable="yes">This filter detects interlaced frames. + +If a deinterlace filter is enabled, only frames that this filter finds +to be interlaced will be deinterlaced.</property> + <signal name="changed" handler="setting_widget_changed_cb" swapped="no"/> + </object> + <packing> + <property name="top_attach">0</property> + <property name="left_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="PictureCombDetectCustom"> + <property name="can_focus">True</property> + <property name="tooltip_text" translatable="yes">Custom interlace detection filter string format + +Mode:Spatial Metric:Motion Thresh:Spatial Thresh:Mask Filter Mode: +Block Thresh: Block Width: Block Height</property> + <property name="width_chars">8</property> + <property name="primary_icon_activatable">False</property> + <property name="secondary_icon_activatable">False</property> + <signal name="changed" handler="setting_widget_changed_cb" swapped="no"/> + </object> + <packing> + <property name="top_attach">1</property> + <property name="left_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + </child> + <child> <object class="GtkGrid" id="table14"> <property name="visible">True</property> <property name="can_focus">False</property> diff --git a/gtk/src/ghb-3.14.ui b/gtk/src/ghb-3.14.ui index b210861e4..35139ed59 100644 --- a/gtk/src/ghb-3.14.ui +++ b/gtk/src/ghb-3.14.ui @@ -2935,6 +2935,68 @@ JunkLeft:JunkRight:JunkTop:JunkBottom:StrictBreaks:MetricPlane:Parity</property> </object> </child> <child> + <object class="GtkGrid" id="comb_detect_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column_spacing">5</property> + <property name="halign">start</property> + <property name="valign">start</property> + <child> + <object class="GtkLabel" id="comb_detect_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Interlace Detection:</property> + </object> + <packing> + <property name="top_attach">0</property> + <property name="left_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="PictureCombDetectPreset"> + <property name="valign">GTK_ALIGN_CENTER</property> + <property name="width_request">100</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="tooltip_text" translatable="yes">This filter detects interlaced frames. + +If a deinterlace filter is enabled, only frames that this filter finds +to be interlaced will be deinterlaced.</property> + <signal name="changed" handler="setting_widget_changed_cb" swapped="no"/> + </object> + <packing> + <property name="top_attach">0</property> + <property name="left_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="PictureCombDetectCustom"> + <property name="can_focus">True</property> + <property name="tooltip_text" translatable="yes">Custom interlace detection filter string format + +Mode:Spatial Metric:Motion Thresh:Spatial Thresh:Mask Filter Mode: +Block Thresh: Block Width: Block Height</property> + <property name="width_chars">8</property> + <property name="primary_icon_activatable">False</property> + <property name="secondary_icon_activatable">False</property> + <signal name="changed" handler="setting_widget_changed_cb" swapped="no"/> + </object> + <packing> + <property name="top_attach">1</property> + <property name="left_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + </child> + <child> <object class="GtkGrid" id="table14"> <property name="visible">True</property> <property name="can_focus">False</property> diff --git a/gtk/src/ghb.ui b/gtk/src/ghb.ui index 653cca354..b6edfb6f0 100644 --- a/gtk/src/ghb.ui +++ b/gtk/src/ghb.ui @@ -2939,6 +2939,74 @@ JunkLeft:JunkRight:JunkTop:JunkBottom:StrictBreaks:MetricPlane:Parity</property> </packing> </child> <child> + <object class="GtkGrid" id="comb_detect_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="column_spacing">5</property> + <property name="halign">start</property> + <property name="valign">start</property> + <child> + <object class="GtkLabel" id="comb_detect_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="yes">Interlace Detection:</property> + </object> + <packing> + <property name="top_attach">0</property> + <property name="left_attach">0</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="PictureCombDetectPreset"> + <property name="valign">GTK_ALIGN_CENTER</property> + <property name="width_request">100</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="tooltip_text" translatable="yes">This filter detects interlaced frames. + +If a deinterlace filter is enabled, only frames that this filter finds +to be interlaced will be deinterlaced.</property> + <signal name="changed" handler="setting_widget_changed_cb" swapped="no"/> + </object> + <packing> + <property name="top_attach">0</property> + <property name="left_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="PictureCombDetectCustom"> + <property name="can_focus">True</property> + <property name="tooltip_text" translatable="yes">Custom interlace detection filter string format + +Mode:Spatial Metric:Motion Thresh:Spatial Thresh:Mask Filter Mode: +Block Thresh: Block Width: Block Height</property> + <property name="width_chars">8</property> + <property name="primary_icon_activatable">False</property> + <property name="secondary_icon_activatable">False</property> + <signal name="changed" handler="setting_widget_changed_cb" swapped="no"/> + </object> + <packing> + <property name="top_attach">1</property> + <property name="left_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + </object> + <packing> + <property name="top_attach">0</property> + <property name="left_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> + <child> <object class="GtkGrid" id="table14"> <property name="visible">True</property> <property name="can_focus">False</property> @@ -3038,7 +3106,7 @@ SpatialLuma:SpatialChroma:TemporalLuma:TemporalChroma</property> </object> <packing> <property name="top_attach">0</property> - <property name="left_attach">1</property> + <property name="left_attach">2</property> <property name="width">1</property> <property name="height">1</property> </packing> @@ -3086,8 +3154,8 @@ If your source exhibits 'blockiness', this filter may help clean it up.</propert </child> </object> <packing> - <property name="top_attach">0</property> - <property name="left_attach">2</property> + <property name="top_attach">1</property> + <property name="left_attach">0</property> <property name="width">1</property> <property name="height">1</property> </packing> diff --git a/gtk/src/hb-backend.c b/gtk/src/hb-backend.c index 2a8d79bb9..4ea4d4d4b 100644 --- a/gtk/src/hb-backend.c +++ b/gtk/src/hb-backend.c @@ -362,6 +362,12 @@ static filter_opts_t hqdn3d_preset_opts = }; #endif +static filter_opts_t comb_detect_opts = +{ + .filter_id = HB_FILTER_COMB_DETECT, + .preset = TRUE +}; + static filter_opts_t detel_opts = { .filter_id = HB_FILTER_DETELECINE, @@ -493,6 +499,12 @@ combo_name_map_t combo_name_map[] = generic_opt_get }, { + "PictureCombDetectPreset", + &comb_detect_opts, + filter_opts_set, + filter_opt_get + }, + { "PictureDeinterlaceFilter", &deint_opts, small_opts_set, diff --git a/gtk/src/makedeps.py b/gtk/src/makedeps.py index a0277e840..fa6463e1d 100644 --- a/gtk/src/makedeps.py +++ b/gtk/src/makedeps.py @@ -28,6 +28,7 @@ dep_map = ( DepEntry("VideoFramerate", "VideoFrameratePFR", "auto", True, True), DepEntry("VideoFramerate", "VideoFramerateVFR", "auto", False, True), DepEntry("VideoTwoPass", "VideoTurboTwoPass", "1", False, False), + DepEntry("PictureCombDetectPreset", "PictureCombDetectCustom", "custom", False, True), DepEntry("PictureDeinterlaceFilter", "PictureDeinterlacePreset", "off", True, True), DepEntry("PictureDeinterlaceFilter", "PictureDeinterlacePresetLabel", "off", True, True), DepEntry("PictureDeinterlaceFilter", "PictureDeinterlaceCustom", "off", True, True), 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; |