diff options
41 files changed, 2843 insertions, 1660 deletions
diff --git a/gtk/src/callbacks.c b/gtk/src/callbacks.c index 13d2d8531..b79225df3 100644 --- a/gtk/src/callbacks.c +++ b/gtk/src/callbacks.c @@ -2128,6 +2128,28 @@ setting_widget_changed_cb(GtkWidget *widget, signal_user_data_t *ud) } G_MODULE_EXPORT void +comb_detect_widget_changed_cb(GtkWidget *widget, signal_user_data_t *ud) +{ + ghb_widget_to_setting(ud->settings, widget); + ghb_check_dependency(ud, widget, NULL); + ghb_clear_presets_selection(ud); + ghb_live_reset(ud); + + const char * comb_detect; + comb_detect = ghb_dict_get_string(ud->settings, "PictureCombDetectPreset"); + if (strcasecmp(comb_detect, "off")) + { + const char * deint; + deint = ghb_dict_get_string(ud->settings, "PictureDeinterlaceFilter"); + if (!strcasecmp(deint, "off")) + { + ghb_ui_update(ud, "PictureDeinterlaceFilter", + ghb_string_value("decomb")); + } + } +} + +G_MODULE_EXPORT void deint_filter_changed_cb(GtkWidget *widget, signal_user_data_t *ud) { ghb_widget_to_setting(ud->settings, widget); @@ -2137,6 +2159,14 @@ deint_filter_changed_cb(GtkWidget *widget, signal_user_data_t *ud) ghb_update_ui_combo_box(ud, "PictureDeinterlacePreset", NULL, FALSE); ghb_ui_update(ud, "PictureDeinterlacePreset", ghb_dict_get(ud->settings, "PictureDeinterlacePreset")); + + const char * deint; + deint = ghb_dict_get_string(ud->settings, "PictureDeinterlaceFilter"); + if (!strcasecmp(deint, "off")) + { + ghb_ui_update(ud, "PictureCombDetectPreset", + ghb_string_value("off")); + } } G_MODULE_EXPORT void diff --git a/gtk/src/ghb-3.12.ui b/gtk/src/ghb-3.12.ui index ce4d34aae..023f79c73 100644 --- a/gtk/src/ghb-3.12.ui +++ b/gtk/src/ghb-3.12.ui @@ -2867,6 +2867,7 @@ Players will scale the image in order to achieve the specified aspect.</property <property name="orientation">horizontal</property> <property name="visible">True</property> <property name="can_focus">False</property> + <property name="selection-mode">none</property> <property name="column-spacing">4</property> <property name="row-spacing">32</property> <property name="margin-top">16</property> @@ -2934,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="comb_detect_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..e9c0421a9 100644 --- a/gtk/src/ghb-3.14.ui +++ b/gtk/src/ghb-3.14.ui @@ -2868,6 +2868,7 @@ Players will scale the image in order to achieve the specified aspect.</property <property name="orientation">horizontal</property> <property name="visible">True</property> <property name="can_focus">False</property> + <property name="selection-mode">none</property> <property name="column-spacing">4</property> <property name="row-spacing">32</property> <property name="margin-top">16</property> @@ -2935,6 +2936,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="comb_detect_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..9e327ed53 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="comb_detect_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..473dd3f7f --- /dev/null +++ b/libhb/decomb.h @@ -0,0 +1,25 @@ +/* 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 +#define MODE_DEINTERLACE_QSV 8 + +#endif // HB_DECOMB_H diff --git a/libhb/hb.c b/libhb/hb.c index 5937ae1ba..393ced341 100644 --- a/libhb/hb.c +++ b/libhb/hb.c @@ -1807,6 +1807,7 @@ int hb_global_init() hb_error("hb_qsv_info_init failed!"); return -1; } + hb_param_configure_qsv(); #endif /* libavcodec */ 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..6b3e0b16f 100644 --- a/libhb/param.c +++ b/libhb/param.c @@ -12,6 +12,9 @@ #include "param.h" #include "common.h" #include "colormap.h" +#ifdef USE_QSV +#include "qsv_common.h" +#endif #include <regex.h> static hb_filter_param_t nlmeans_presets[] = @@ -75,18 +78,32 @@ static hb_filter_param_t detelecine_presets[] = { 0, NULL, NULL, NULL } }; +static hb_filter_param_t comb_detect_presets[] = +{ + { 0, "Off", "off", "disable=1" }, + { 1, "Custom", "custom", NULL }, + { 2, "Default", "default", + "mode=3:spatial-metric=2:motion-thresh=1:spatial-thresh=1:" + "filter-mode=2:block-thresh=40:block-width=16:block-height=16" + }, + { 3, "Less Sensitive", "permissive", + "mode=3:spatial-metric=2:motion-thresh=3:spatial-thresh=3:" + "filter-mode=2:block-thresh=40:block-width=16:block-height=16" + }, + { 4, "Fast", "fast", + "mode=0:spatial-metric=2:motion-thresh=2:spatial-thresh=3:" + "filter-mode=1:block-thresh=80:block-width=16:block-height=16" + }, + { 0, NULL, NULL, NULL } +}; + static hb_filter_param_t decomb_presets[] = { { 1, "Custom", "custom", NULL }, - { 2, "Default", "default", - "mode=391:spatial-metric=2:motion-thresh=3:spatial-thresh=3:" - "filter-mode=2:block-thresh=40" - }, - { 3, "Fast", "fast", - "mode=7:motion-thresh=6:spatial-thresh=9:" - "filter-mode=1:block-thresh=80" - }, - { 4, "Bob", "bob", "mode=455" }, + { 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 } }; @@ -96,10 +113,14 @@ static hb_filter_param_t deinterlace_presets[] = { 3, "Default", "default", "mode=3" }, { 2, "Skip Spatial Check", "skip-spatial", "mode=1" }, { 5, "Bob", "bob", "mode=7" }, +#ifdef USE_QSV + { 6, "QSV", "qsv", "mode=11" }, +#endif { 0, NULL, NULL, NULL }, { 2, "Fast", "fast", "mode=1" }, { 3, "Slow", "slow", "mode=1" }, - { 4, "Slower", "slower", "mode=3" } + { 4, "Slower", "slower", "mode=3" }, + { 7, "QSV", "qsv", "mode=3" } }; typedef struct @@ -121,6 +142,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) }, @@ -130,6 +154,16 @@ static filter_param_map_t param_map[] = { HB_FILTER_INVALID, NULL, NULL, 0 } }; +void hb_param_configure_qsv(void) +{ +#ifdef USE_QSV + if (!hb_qsv_available()) + { + memset(&deinterlace_presets[4], 0, sizeof(hb_filter_param_t)); + } +#endif +} + /* NL-means presets and tunes * * Presets adjust strength: @@ -552,6 +586,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/param.h b/libhb/param.h index d61fd389c..0222de39b 100644 --- a/libhb/param.h +++ b/libhb/param.h @@ -19,6 +19,8 @@ struct hb_filter_param_s const char *settings; }; +void hb_param_configure_qsv(void); + hb_dict_t * hb_generate_filter_settings(int filter_id, const char *preset, const char *tune, const char *custom); char * hb_generate_filter_settings_json(int filter_id, const char *preset, 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..4fca293fb 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" @@ -1166,12 +1167,13 @@ static int sanitize_qsv( hb_job_t * job ) // CPU-based deinterlace (validated) case HB_FILTER_DEINTERLACE: - if (filter->settings != NULL && - strcasecmp(filter->settings, "qsv") != 0) + { + int mode = hb_dict_get_int(filter->settings, "mode"); + if (!(mode & MODE_DEINTERLACE_QSV)) { encode_only = 1; } - break; + } break; // other filters will be removed default: @@ -1216,19 +1218,19 @@ static int sanitize_qsv( hb_job_t * job ) { // cropping and scaling always done via VPP filter case HB_FILTER_CROP_SCALE: - if (filter->settings == NULL || *filter->settings == 0) - { - // VPP defaults were set above, so not a problem - // however, this should never happen, print an error - hb_error("do_job: '%s': no settings!", filter->name); - } - else - { - sscanf(filter->settings, "%d:%d:%d:%d:%d:%d", - &vpp_settings[0], &vpp_settings[1], - &vpp_settings[2], &vpp_settings[3], - &vpp_settings[4], &vpp_settings[5]); - } + hb_dict_extract_int(&vpp_settings[0], filter->settings, + "width"); + hb_dict_extract_int(&vpp_settings[1], filter->settings, + "height"); + hb_dict_extract_int(&vpp_settings[2], filter->settings, + "crop-top"); + hb_dict_extract_int(&vpp_settings[3], filter->settings, + "crop-bottom"); + hb_dict_extract_int(&vpp_settings[4], filter->settings, + "crop-left"); + hb_dict_extract_int(&vpp_settings[5], filter->settings, + "crop-right"); + // VPP crop/scale takes precedence over OpenCL scale too if (job->use_opencl) { @@ -1241,8 +1243,9 @@ static int sanitize_qsv( hb_job_t * job ) // pick VPP or CPU deinterlace depending on settings case HB_FILTER_DEINTERLACE: - if (filter->settings == NULL || - strcasecmp(filter->settings, "qsv") == 0) + { + int mode = hb_dict_get_int(filter->settings, "mode"); + if (mode & MODE_DEINTERLACE_QSV) { // deinterlacing via VPP filter vpp_settings[6] = 1; @@ -1254,7 +1257,7 @@ static int sanitize_qsv( hb_job_t * job ) // validated num_cpu_filters++; } - break; + } break; // then, validated filters case HB_FILTER_ROTATE: // TODO: use Media SDK for this @@ -1309,6 +1312,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 +1444,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; diff --git a/macosx/English.lproj/HBPictureViewController.xib b/macosx/English.lproj/HBPictureViewController.xib index 1f6315a0d..9d12a5738 100644 --- a/macosx/English.lproj/HBPictureViewController.xib +++ b/macosx/English.lproj/HBPictureViewController.xib @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9531" systemVersion="15E27e" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10115" systemVersion="15E61b" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none"> <dependencies> <deployment identifier="macosx"/> <development version="6300" identifier="xcode"/> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9531"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10115"/> </dependencies> <objects> <customObject id="-2" userLabel="File's Owner" customClass="HBPictureViewController"> @@ -1151,6 +1151,79 @@ Frames that are not interlaced will suffer some quality degradation.</string> <binding destination="-2" name="value" keyPath="self.filters.flip" id="xdh-96-5s5"/> </connections> </button> + <textField verticalHuggingPriority="750" id="Mg1-Yq-F9S"> + <rect key="frame" x="200" y="52" width="108" height="14"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="right" title="Interlace Detection:" id="xHD-vC-ePQ"> + <font key="font" metaFont="smallSystem"/> + <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + <connections> + <binding destination="-2" name="textColor" keyPath="self.labelColor" id="eKb-Bh-OkE"/> + </connections> + </textField> + <textField verticalHuggingPriority="750" id="RZE-gp-SB7"> + <rect key="frame" x="237" y="26" width="71" height="14"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="right" title="Custom:" id="Da7-pY-5vu"> + <font key="font" metaFont="smallSystem"/> + <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + <connections> + <binding destination="-2" name="hidden" keyPath="self.filters.customCombDetectionSelected" id="x5q-SE-Xds"> + <dictionary key="options"> + <string key="NSValueTransformerName">NSNegateBoolean</string> + </dictionary> + </binding> + <binding destination="-2" name="textColor" keyPath="self.labelColor" id="s78-d3-GZY"/> + </connections> + </textField> + <popUpButton verticalHuggingPriority="750" id="IQG-Nn-HTb"> + <rect key="frame" x="311" y="47" width="114" height="22"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <string key="toolTip">This filter detects interlaced frames. +If a deinterlace filter is enabled, only frames that this filter finds to be interlaced will be deinterlaced.</string> + <popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" inset="2" arrowPosition="arrowAtCenter" preferredEdge="maxY" id="nfb-CJ-1J3"> + <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="smallSystem"/> + <menu key="menu" title="OtherViews" id="Qz4-EY-GFO"/> + </popUpButtonCell> + <connections> + <accessibilityConnection property="title" destination="Mg1-Yq-F9S" id="nhI-oN-u5h"/> + <binding destination="-2" name="contentValues" keyPath="self.filters.combDetectionSettings" id="lKn-uj-nGl"/> + <binding destination="-2" name="selectedValue" keyPath="self.filters.combDetection" previousBinding="lKn-uj-nGl" id="XIZ-dC-cLu"> + <dictionary key="options"> + <string key="NSValueTransformerName">HBCombDetectionTransformer</string> + </dictionary> + </binding> + <binding destination="-2" name="enabled" keyPath="self.filters" id="mJ8-zq-tQ8"> + <dictionary key="options"> + <string key="NSValueTransformerName">NSIsNotNil</string> + </dictionary> + </binding> + <outlet property="nextKeyView" destination="rPg-F2-gtl" id="46r-ZD-dTe"/> + </connections> + </popUpButton> + <textField verticalHuggingPriority="750" id="rPg-F2-gtl"> + <rect key="frame" x="314" y="23" width="108" height="19"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="4YG-Q6-1tM"> + <font key="font" metaFont="smallSystem"/> + <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + <accessibility description="Custom interlace detection settings."/> + <connections> + <binding destination="-2" name="hidden" keyPath="self.filters.customCombDetectionSelected" id="avq-Zl-5gA"> + <dictionary key="options"> + <string key="NSValueTransformerName">NSNegateBoolean</string> + </dictionary> + </binding> + <binding destination="-2" name="value" keyPath="self.filters.combDetectionCustomString" id="pTK-PZ-3ZE"/> + </connections> + </textField> </subviews> <point key="canvasLocation" x="403.5" y="86.5"/> </customView> diff --git a/macosx/HBFilters+UIAdditions.h b/macosx/HBFilters+UIAdditions.h index 71ca839bc..6600e7930 100644 --- a/macosx/HBFilters+UIAdditions.h +++ b/macosx/HBFilters+UIAdditions.h @@ -14,6 +14,8 @@ */ + (NSDictionary *)detelecinePresetsDict; ++ (NSDictionary *)combDetectionPresetsDict; + + (NSDictionary *)deinterlaceTypesDict; + (NSDictionary *)decombPresetsDict; + (NSDictionary *)deinterlacePresetsDict; @@ -24,6 +26,8 @@ - (BOOL)customDetelecineSelected; +@property (nonatomic, readonly) BOOL customCombDetectionSelected; + - (BOOL)deinterlaceEnabled; - (BOOL)customDeinterlaceSelected; @@ -35,6 +39,8 @@ @property (nonatomic, readonly) NSArray *detelecineSettings; +@property (nonatomic, readonly) NSArray *combDetectionSettings; + @property (nonatomic, readonly) NSArray *deinterlaceTypes; @property (nonatomic, readonly) NSArray *deinterlacePresets; @@ -61,6 +67,9 @@ @interface HBDetelecineTransformer : HBGenericDictionaryTransformer @end +@interface HBCombDetectionTransformer : HBGenericDictionaryTransformer +@end + @interface HBDeinterlaceTransformer : HBGenericDictionaryTransformer @end diff --git a/macosx/HBFilters+UIAdditions.m b/macosx/HBFilters+UIAdditions.m index 62fcda372..8141881cc 100644 --- a/macosx/HBFilters+UIAdditions.m +++ b/macosx/HBFilters+UIAdditions.m @@ -79,6 +79,18 @@ static NSDictionary * filterParamsToNamesDict(hb_filter_param_t * (f)(int), int @end +@implementation HBCombDetectionTransformer + +- (instancetype)init +{ + if (self = [super init]) + self.dict = [HBFilters combDetectionPresetsDict]; + + return self; +} + +@end + @implementation HBDeinterlaceTransformer - (instancetype)init @@ -167,6 +179,8 @@ static NSDictionary * filterParamsToNamesDict(hb_filter_param_t * (f)(int), int static NSDictionary *detelecinePresetsDict = nil; +static NSDictionary *combDetectionPresetsDict = nil; + static NSDictionary *deinterlaceTypesDict = nil; static NSDictionary *decombPresetsDict = nil; static NSDictionary *deinterlacePresetsDict = nil; @@ -188,6 +202,16 @@ static NSDictionary *denoiseTypesDict = nil; return detelecinePresetsDict; } ++ (NSDictionary *)combDetectionPresetsDict +{ + if (!combDetectionPresetsDict) + { + combDetectionPresetsDict = filterParamsToNamesDict(hb_filter_param_get_presets, HB_FILTER_COMB_DETECT); + } + return combDetectionPresetsDict; +} + + + (NSDictionary *)deinterlaceTypesDict { if (!deinterlaceTypesDict) @@ -256,6 +280,11 @@ static NSDictionary *denoiseTypesDict = nil; return filterParamsToNamesArray(hb_filter_param_get_presets, HB_FILTER_DETELECINE); } +- (NSArray *)combDetectionSettings +{ + return filterParamsToNamesArray(hb_filter_param_get_presets, HB_FILTER_COMB_DETECT); +} + - (NSArray *)deinterlacePresets { if ([self.deinterlace isEqualToString:@"deinterlace"]) @@ -288,6 +317,11 @@ static NSDictionary *denoiseTypesDict = nil; return [self.detelecine isEqualToString:@"custom"] ? YES : NO; } +- (BOOL)customCombDetectionSelected +{ + return [self.combDetection isEqualToString:@"custom"] ? YES : NO; +} + - (BOOL)customDeinterlaceSelected { return [self.deinterlacePreset isEqualToString:@"custom"] && [self deinterlaceEnabled]; diff --git a/macosx/HBFilters.h b/macosx/HBFilters.h index e173e3630..37416697d 100644 --- a/macosx/HBFilters.h +++ b/macosx/HBFilters.h @@ -19,6 +19,9 @@ extern NSString * const HBFiltersChangedNotification; @property (nonatomic, readwrite, copy) NSString *detelecine; @property (nonatomic, readwrite, copy) NSString *detelecineCustomString; +@property (nonatomic, readwrite, copy) NSString *combDetection; +@property (nonatomic, readwrite, copy) NSString *combDetectionCustomString; + @property (nonatomic, readwrite, copy) NSString *deinterlace; @property (nonatomic, readwrite, copy) NSString *deinterlacePreset; @property (nonatomic, readwrite, copy) NSString *deinterlaceCustomString; diff --git a/macosx/HBFilters.m b/macosx/HBFilters.m index 7df571b8d..910c4186d 100644 --- a/macosx/HBFilters.m +++ b/macosx/HBFilters.m @@ -28,6 +28,8 @@ NSString * const HBFiltersChangedNotification = @"HBFiltersChangedNotification"; { _detelecine = @"off"; _detelecineCustomString = @""; + _combDetection = @"off"; + _combDetectionCustomString = @""; _deinterlace = @"off"; _deinterlaceCustomString = @""; _deinterlacePreset = @"default"; @@ -89,6 +91,41 @@ NSString * const HBFiltersChangedNotification = @"HBFiltersChangedNotification"; [self postChangedNotification]; } +- (void)setCombDetection:(NSString *)combDetection +{ + if (![combDetection isEqualToString:_combDetection]) + { + [[self.undo prepareWithInvocationTarget:self] setCombDetection:_combDetection]; + } + if (combDetection) + { + _combDetection = [combDetection copy]; + } + else + { + _combDetection = @"off"; + } + [self postChangedNotification]; +} + +- (void)setCombDetectionCustomString:(NSString *)combDetectionCustomString +{ + if (![combDetectionCustomString isEqualToString:_combDetectionCustomString]) + { + [[self.undo prepareWithInvocationTarget:self] setCombDetectionCustomString:_combDetectionCustomString]; + } + if (combDetectionCustomString) + { + _combDetectionCustomString = [combDetectionCustomString copy]; + } + else + { + _combDetectionCustomString = @""; + } + + [self postChangedNotification]; +} + - (void)setDeinterlace:(NSString *)deinterlace { if (![deinterlace isEqualToString:_deinterlace]) @@ -286,8 +323,12 @@ NSString * const HBFiltersChangedNotification = @"HBFiltersChangedNotification"; { retval = [NSSet setWithObjects:@"detelecine", nil]; } + else if ([key isEqualToString:@"customCombDetectionSelected"]) + { + retval = [NSSet setWithObjects:@"combDetection", nil]; + } else if ([key isEqualToString:@"denoiseTunesAvailable"] || - [key isEqualToString:@"customDenoiseSelected"]) + [key isEqualToString:@"customDenoiseSelected"]) { retval = [NSSet setWithObjects:@"denoise", @"denoisePreset", nil]; } @@ -300,7 +341,7 @@ NSString * const HBFiltersChangedNotification = @"HBFiltersChangedNotification"; retval = [NSSet setWithObject:@"deinterlace"]; } else if ([key isEqualToString:@"customDeinterlaceSelected"] || - [key isEqualToString:@"deinterlacePresets"]) + [key isEqualToString:@"deinterlacePresets"]) { retval = [NSSet setWithObjects:@"deinterlace", @"deinterlacePreset", nil]; } @@ -328,6 +369,9 @@ NSString * const HBFiltersChangedNotification = @"HBFiltersChangedNotification"; copy->_detelecine = [_detelecine copy]; copy->_detelecineCustomString = [_detelecineCustomString copy]; + copy->_combDetection = [_combDetection copy]; + copy->_combDetectionCustomString = [_combDetectionCustomString copy]; + copy->_deinterlace = [_deinterlace copy]; copy->_deinterlacePreset = [_deinterlacePreset copy]; copy->_deinterlaceCustomString = [_deinterlaceCustomString copy]; @@ -360,6 +404,9 @@ NSString * const HBFiltersChangedNotification = @"HBFiltersChangedNotification"; encodeObject(_detelecine); encodeObject(_detelecineCustomString); + encodeObject(_combDetection); + encodeObject(_combDetectionCustomString); + encodeObject(_deinterlace); encodeObject(_deinterlacePreset); encodeObject(_deinterlaceCustomString); @@ -382,6 +429,9 @@ NSString * const HBFiltersChangedNotification = @"HBFiltersChangedNotification"; decodeObject(_detelecine, NSString); decodeObject(_detelecineCustomString, NSString); + decodeObject(_combDetection, NSString); + decodeObject(_combDetectionCustomString, NSString); + decodeObject(_deinterlace, NSString); decodeObject(_deinterlacePreset, NSString) decodeObject(_deinterlaceCustomString, NSString); @@ -409,6 +459,9 @@ NSString * const HBFiltersChangedNotification = @"HBFiltersChangedNotification"; preset[@"PictureDeinterlacePreset"] = self.deinterlacePreset; preset[@"PictureDeinterlaceCustom"] = self.deinterlaceCustomString; + preset[@"PictureCombDetectPreset"] = self.combDetection; + preset[@"PictureCombDetectCustom"] = self.combDetectionCustomString; + preset[@"PictureDetelecine"] = self.detelecine; preset[@"PictureDetelecineCustom"] = self.detelecineCustomString; @@ -434,6 +487,10 @@ NSString * const HBFiltersChangedNotification = @"HBFiltersChangedNotification"; self.deinterlacePreset = preset[@"PictureDeinterlacePreset"]; self.deinterlaceCustomString = preset[@"PictureDeinterlaceCustom"]; + // Comb detection + self.combDetection = preset[@"PictureCombDetectPreset"]; + self.combDetectionCustomString = preset[@"PictureCombDetectCustom"]; + // Detelecine self.detelecine = preset[@"PictureDetelecine"]; self.detelecineCustomString = preset[@"PictureDetelecineCustom"]; diff --git a/macosx/HBJob+HBJobConversion.m b/macosx/HBJob+HBJobConversion.m index aa109971f..8914b34d4 100644 --- a/macosx/HBJob+HBJobConversion.m +++ b/macosx/HBJob+HBJobConversion.m @@ -411,6 +411,19 @@ hb_value_free(&filter_dict); } + // Comb Detection + if (![self.filters.combDetection isEqualToString:@"off"]) + { + int filter_id = HB_FILTER_COMB_DETECT; + hb_dict_t *filter_dict = hb_generate_filter_settings(filter_id, + self.filters.combDetection.UTF8String, + NULL, + self.filters.combDetectionCustomString.UTF8String); + filter = hb_filter_init(filter_id); + hb_add_filter_dict(job, filter, filter_dict); + hb_value_free(&filter_dict); + } + // Deinterlace if (![self.filters.deinterlace isEqualToString:@"off"]) { diff --git a/test/test.c b/test/test.c index ca4062424..dc53a3e0c 100644 --- a/test/test.c +++ b/test/test.c @@ -46,6 +46,7 @@ #define DEINTERLACE_DEFAULT_PRESET "default" #define DECOMB_DEFAULT_PRESET "default" #define DETELECINE_DEFAULT_PRESET "default" +#define COMB_DETECT_DEFAULT_PRESET "default" #define HQDN3D_DEFAULT_PRESET "medium" #define ROTATE_DEFAULT "angle=180:hflip=0" #define DEBLOCK_DEFAULT "qp=5" @@ -80,6 +81,9 @@ static char * nlmeans_tune = NULL; static int detelecine_disable = 0; static int detelecine_custom = 0; static char * detelecine = NULL; +static int comb_detect_disable = 0; +static int comb_detect_custom = 0; +static char * comb_detect = NULL; static int decomb_disable = 0; static int decomb_custom = 0; static char * decomb = NULL; @@ -912,13 +916,6 @@ static void showFilterPresets(FILE* const out, int filter_id) char * slash = "", * newline; int ii, count = 0, linelen = 0; -#ifdef USE_QSV -if (filter_id == HB_FILTER_DEINTERLACE && hb_qsv_available()) -{ - count = 1; -} -#endif - // Count number of entries we want to display for (ii = 0; names[ii] != NULL; ii++) { @@ -956,12 +953,7 @@ if (filter_id == HB_FILTER_DEINTERLACE && hb_qsv_available()) linelen += len; slash = "/"; } -#ifdef USE_QSV -if (filter_id == HB_FILTER_DEINTERLACE && hb_qsv_available()) -{ - fprintf(out, "/qsv"); -} -#endif + fprintf(out, ">\n"); hb_str_vfree(names); } @@ -1018,6 +1010,9 @@ static void showFilterDefault(FILE* const out, int filter_id) case HB_FILTER_HQDN3D: preset = HQDN3D_DEFAULT_PRESET; break; + case HB_FILTER_COMB_DETECT: + preset = COMB_DETECT_DEFAULT_PRESET; + break; default: break; } @@ -1028,6 +1023,7 @@ static void showFilterDefault(FILE* const out, int filter_id) case HB_FILTER_DECOMB: case HB_FILTER_DETELECINE: case HB_FILTER_HQDN3D: + case HB_FILTER_COMB_DETECT: { hb_dict_t * settings; settings = hb_generate_filter_settings(filter_id, preset, @@ -1434,13 +1430,27 @@ static void ShowHelp() " (default: detected from source)\n" "\n" "### Filters---------------------------------------------------------------\n\n" -" -d, --deinterlace Unconditionally deinterlaces all frames\n"); +" --comb-detect Detect interlace artifacts in frames.\n" +" If not accompanied by the decomb or deinterlace\n" +" filters, this filter only logs the interlaced\n" +" frame count to the activity log.\n" +" If accompanied by the decomb or deinterlace\n" +" filters, it causes these filters to selectively\n" +" deinterlace only those frames where interlacing\n" +" is detected.\n"); + showFilterPresets(out, HB_FILTER_COMB_DETECT); + showFilterKeys(out, HB_FILTER_COMB_DETECT); + showFilterDefault(out, HB_FILTER_COMB_DETECT); + fprintf( out, +" --no-comb-detect Disable preset comb-detect filter\n" +" -d, --deinterlace Deinterlaces using libav yadif.\n"); showFilterPresets(out, HB_FILTER_DEINTERLACE); showFilterKeys(out, HB_FILTER_DEINTERLACE); showFilterDefault(out, HB_FILTER_DEINTERLACE); fprintf( out, " --no-deinterlace Disable preset deinterlace filter\n" -" -5, --decomb Selectively deinterlaces when it detects combing\n"); +" -5, --decomb Deinterlaces using a combination of yadif,\n" +" blend, cubic, or EEDI2 interpolation.\n"); showFilterPresets(out, HB_FILTER_DECOMB); showFilterKeys(out, HB_FILTER_DECOMB); showFilterDefault(out, HB_FILTER_DECOMB); @@ -1806,6 +1816,7 @@ static int ParseOptions( int argc, char ** argv ) #define VERSION 307 #define DESCRIBE 308 #define PAD 309 + #define FILTER_COMB_DETECT 310 for( ;; ) { @@ -1885,6 +1896,8 @@ static int ParseOptions( int argc, char ** argv ) { "nlmeans-tune",required_argument, NULL, FILTER_NLMEANS_TUNE }, { "detelecine", optional_argument, NULL, '9' }, { "no-detelecine", no_argument, &detelecine_disable, 1 }, + { "no-comb-detect", no_argument, &comb_detect_disable, 1 }, + { "comb-detect", optional_argument, NULL, FILTER_COMB_DETECT }, { "decomb", optional_argument, NULL, '5' }, { "no-decomb", no_argument, &decomb_disable, 1 }, { "grayscale", no_argument, NULL, 'g' }, @@ -2313,6 +2326,17 @@ static int ParseOptions( int argc, char ** argv ) detelecine = strdup(DETELECINE_DEFAULT_PRESET); } break; + case FILTER_COMB_DETECT: + free(comb_detect); + if (optarg != NULL) + { + comb_detect = strdup(optarg); + } + else + { + comb_detect = strdup(COMB_DETECT_DEFAULT_PRESET); + } + break; case '5': free(decomb); if (optarg != NULL) @@ -2672,6 +2696,32 @@ static int ParseOptions( int argc, char ** argv ) } } + if (comb_detect != NULL) + { + if (comb_detect_disable) + { + fprintf(stderr, + "Incompatible options --comb-detect and --no-comb-detect\n"); + return -1; + } + if (!hb_validate_filter_preset(HB_FILTER_COMB_DETECT, comb_detect, + NULL, NULL)) + { + // Nothing to do, but must validate preset before + // attempting to validate custom settings to prevent potential + // false positive + } + else if (!hb_validate_filter_string(HB_FILTER_COMB_DETECT, comb_detect)) + { + comb_detect_custom = 1; + } + else + { + fprintf(stderr, "Invalid comb-detect option %s\n", comb_detect); + return -1; + } + } + if (decomb != NULL) { if (decomb_disable) @@ -3523,6 +3573,25 @@ static hb_dict_t * PreparePreset(const char *preset_name) { hb_dict_set(preset, "PictureDeinterlaceFilter", hb_value_string("off")); } + if (comb_detect_disable) + { + hb_dict_set(preset, "PictureCombDetectFilter", hb_value_string("off")); + } + if (comb_detect != NULL) + { + if (!comb_detect_custom) + { + hb_dict_set(preset, "PictureCombDetectPreset", + hb_value_string(comb_detect)); + } + else + { + hb_dict_set(preset, "PictureCombDetectPreset", + hb_value_string("custom")); + hb_dict_set(preset, "PictureCombDetectCustom", + hb_value_string(comb_detect)); + } + } if (deinterlace != NULL) { hb_dict_set(preset, "PictureDeinterlaceFilter", diff --git a/win/CS/HandBrake.ApplicationServices/HandBrake.ApplicationServices.csproj b/win/CS/HandBrake.ApplicationServices/HandBrake.ApplicationServices.csproj index 60f7a7b9d..c6fddcfb7 100644 --- a/win/CS/HandBrake.ApplicationServices/HandBrake.ApplicationServices.csproj +++ b/win/CS/HandBrake.ApplicationServices/HandBrake.ApplicationServices.csproj @@ -137,6 +137,7 @@ <Compile Include="Interop\Json\Encode\SubtitleTrack.cs" />
<Compile Include="Interop\Json\Encode\Video.cs" />
<Compile Include="Interop\Factories\AnamorphicFactory.cs" />
+ <Compile Include="Interop\Model\Encoding\CombDetect.cs" />
<Compile Include="Interop\Model\Encoding\DeinterlaceFilter.cs" />
<Compile Include="Interop\Model\Encoding\HBPresetTune.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
diff --git a/win/CS/HandBrake.ApplicationServices/Interop/HbLib/hb_filter_ids.cs b/win/CS/HandBrake.ApplicationServices/Interop/HbLib/hb_filter_ids.cs index d4164ede6..76059fe8b 100644 --- a/win/CS/HandBrake.ApplicationServices/Interop/HbLib/hb_filter_ids.cs +++ b/win/CS/HandBrake.ApplicationServices/Interop/HbLib/hb_filter_ids.cs @@ -9,23 +9,36 @@ namespace HandBrake.ApplicationServices.Interop.HbLib {
public enum hb_filter_ids
{
- HB_FILTER_QSV_PRE = 1, // for QSV - important to have before other filters
+ HB_FILTER_INVALID = 0,
+ // for QSV - important to have before other filters
+ HB_FILTER_FIRST = 1,
+ HB_FILTER_QSV_PRE = 1,
+
// 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,
// Filters that must operate on the original source image are next
HB_FILTER_DEBLOCK,
- HB_FILTER_HQDN3D,
+ HB_FILTER_DENOISE,
+ HB_FILTER_HQDN3D = HB_FILTER_DENOISE,
HB_FILTER_NLMEANS,
HB_FILTER_RENDER_SUB,
HB_FILTER_CROP_SCALE,
- // Finally filters that don't care what order they are in,
- // except that they must be after the above filters
HB_FILTER_ROTATE,
HB_FILTER_GRAYSCALE,
- HB_FILTER_QSV_POST, // for QSV - important to have as a last one
- HB_FILTER_QSV, // default MSDK VPP filter
+ HB_FILTER_PAD,
+
+ // Finally filters that don't care what order they are in,
+ // except that they must be after the above filters
+ HB_FILTER_AVFILTER,
+
+ // for QSV - important to have as a last one
+ HB_FILTER_QSV_POST,
+ // default MSDK VPP filter
+ HB_FILTER_QSV,
+ HB_FILTER_LAST = HB_FILTER_QSV
}
}
diff --git a/win/CS/HandBrake.ApplicationServices/Interop/Json/Presets/HBPreset.cs b/win/CS/HandBrake.ApplicationServices/Interop/Json/Presets/HBPreset.cs index 260b55960..7e1e94697 100644 --- a/win/CS/HandBrake.ApplicationServices/Interop/Json/Presets/HBPreset.cs +++ b/win/CS/HandBrake.ApplicationServices/Interop/Json/Presets/HBPreset.cs @@ -127,6 +127,16 @@ namespace HandBrake.ApplicationServices.Interop.Json.Presets public string PictureDeinterlaceFilter { get; set; }
/// <summary>
+ /// Gets or sets the picture comb detect preset.
+ /// </summary>
+ public string PictureCombDetectPreset { get; set; }
+
+ /// <summary>
+ /// Gets or sets the picture comb detect custom.
+ /// </summary>
+ public string PictureCombDetectCustom { get; set; }
+
+ /// <summary>
/// Gets or sets the picture deinterlace preset.
/// </summary>
public string PictureDeinterlacePreset { get; set; }
diff --git a/win/CS/HandBrake.ApplicationServices/Interop/Model/Encoding/CombDetect.cs b/win/CS/HandBrake.ApplicationServices/Interop/Model/Encoding/CombDetect.cs new file mode 100644 index 000000000..d98d0d2ec --- /dev/null +++ b/win/CS/HandBrake.ApplicationServices/Interop/Model/Encoding/CombDetect.cs @@ -0,0 +1,34 @@ +// -------------------------------------------------------------------------------------------------------------------- +// <copyright file="CombDetect.cs" company="HandBrake Project (http://handbrake.fr)"> +// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. +// </copyright> +// <summary> +// Defines the CombDetect type. +// </summary> +// -------------------------------------------------------------------------------------------------------------------- + +namespace HandBrake.ApplicationServices.Interop.Model.Encoding +{ + using HandBrake.ApplicationServices.Attributes; + + /// <summary> + /// The CombDetect Type. + /// </summary> + public enum CombDetect + { + [ShortName("off")] + Off, + + [ShortName("custom")] + Custom, + + [ShortName("default")] + Default, + + [ShortName("permissive")] + LessSensitive, + + [ShortName("fast")] + Fast + } +} diff --git a/win/CS/HandBrake.ApplicationServices/Interop/Model/Encoding/Decomb.cs b/win/CS/HandBrake.ApplicationServices/Interop/Model/Encoding/Decomb.cs index eab94ef2a..a2b544029 100644 --- a/win/CS/HandBrake.ApplicationServices/Interop/Model/Encoding/Decomb.cs +++ b/win/CS/HandBrake.ApplicationServices/Interop/Model/Encoding/Decomb.cs @@ -19,13 +19,16 @@ namespace HandBrake.ApplicationServices.Interop.Model.Encoding [ShortName("default")]
Default,
- [ShortName("fast")]
- Fast,
-
[ShortName("bob")]
Bob,
[ShortName("custom")]
- Custom
+ Custom,
+
+ [ShortName("eedi2")]
+ EEDI2,
+
+ [ShortName("eedi2bob")]
+ EEDI2Bob
}
}
diff --git a/win/CS/HandBrakeWPF/Converters/EnumComboConverter.cs b/win/CS/HandBrakeWPF/Converters/EnumComboConverter.cs index b154c4dce..a25cd1100 100644 --- a/win/CS/HandBrakeWPF/Converters/EnumComboConverter.cs +++ b/win/CS/HandBrakeWPF/Converters/EnumComboConverter.cs @@ -15,7 +15,6 @@ namespace HandBrakeWPF.Converters using System;
using HandBrake.ApplicationServices.Model;
- using HandBrake.ApplicationServices.Utilities;
using HandBrake.ApplicationServices.Interop.Model.Encoding;
using HandBrakeWPF.Services.Queue.Model;
@@ -95,6 +94,10 @@ namespace HandBrakeWPF.Converters {
return EnumHelper<DeinterlaceFilter>.GetEnumDisplayValues(typeof(DeinterlaceFilter));
}
+ if (value is IEnumerable<CombDetect>)
+ {
+ return EnumHelper<CombDetect>.GetEnumDisplayValues(typeof(CombDetect));
+ }
// Single Items
if (targetType == typeof(VideoEncoder) || value.GetType() == typeof(VideoEncoder))
@@ -145,6 +148,10 @@ namespace HandBrakeWPF.Converters {
return EnumHelper<DeinterlaceFilter>.GetDisplay((DeinterlaceFilter)value);
}
+ if (targetType == typeof(CombDetect) || value.GetType() == typeof(CombDetect))
+ {
+ return EnumHelper<CombDetect>.GetDisplay((CombDetect)value);
+ }
return null;
}
@@ -215,6 +222,10 @@ namespace HandBrakeWPF.Converters return EnumHelper<DeinterlaceFilter>.GetValue(value.ToString());
}
+ if (targetType == typeof(CombDetect) || value.GetType() == typeof(CombDetect))
+ {
+ return EnumHelper<CombDetect>.GetValue(value.ToString());
+ }
return null;
}
}
diff --git a/win/CS/HandBrakeWPF/Services/Encode/Factories/EncodeFactory.cs b/win/CS/HandBrakeWPF/Services/Encode/Factories/EncodeFactory.cs index 6304c4af3..5167e7218 100644 --- a/win/CS/HandBrakeWPF/Services/Encode/Factories/EncodeFactory.cs +++ b/win/CS/HandBrakeWPF/Services/Encode/Factories/EncodeFactory.cs @@ -435,6 +435,19 @@ namespace HandBrakeWPF.Services.Encode.Factories filter.FilterList.Add(filterItem); } + if (job.DeinterlaceFilter == DeinterlaceFilter.Decomb || job.DeinterlaceFilter == DeinterlaceFilter.Yadif) + { + if (job.CombDetect != CombDetect.Off) + { + IntPtr settingsPtr = HBFunctions.hb_generate_filter_settings_json((int)hb_filter_ids.HB_FILTER_COMB_DETECT, EnumHelper<CombDetect>.GetShortName(job.CombDetect), null, job.CustomCombDetect); + string unparsedJson = Marshal.PtrToStringAnsi(settingsPtr); + JToken settings = JObject.Parse(unparsedJson); + + Filter filterItem = new Filter { ID = (int)hb_filter_ids.HB_FILTER_COMB_DETECT, Settings = settings }; + filter.FilterList.Add(filterItem); + } + } + // Denoise if (job.Denoise != Denoise.Off) { diff --git a/win/CS/HandBrakeWPF/Services/Encode/Model/EncodeTask.cs b/win/CS/HandBrakeWPF/Services/Encode/Model/EncodeTask.cs index 0ae588bc7..6ea377bc6 100644 --- a/win/CS/HandBrakeWPF/Services/Encode/Model/EncodeTask.cs +++ b/win/CS/HandBrakeWPF/Services/Encode/Model/EncodeTask.cs @@ -92,6 +92,8 @@ namespace HandBrakeWPF.Services.Encode.Model this.CustomDeinterlace = task.CustomDeinterlace; this.CustomDenoise = task.CustomDenoise; this.CustomDetelecine = task.CustomDetelecine; + this.CustomCombDetect = task.CustomCombDetect; + this.CombDetect = task.CombDetect; this.Deblock = task.Deblock; this.Decomb = task.Decomb; this.Deinterlace = task.Deinterlace; @@ -298,11 +300,21 @@ namespace HandBrakeWPF.Services.Encode.Model public Decomb Decomb { get; set; } /// <summary> + /// Gets or sets the comb detect. + /// </summary> + public CombDetect CombDetect { get; set; } + + /// <summary> /// Gets or sets CustomDecomb. /// </summary> public string CustomDecomb { get; set; } /// <summary> + /// Gets or sets the custom comb detect. + /// </summary> + public string CustomCombDetect { get; set; } + + /// <summary> /// Gets or sets Detelecine. /// </summary> public Detelecine Detelecine { get; set; } diff --git a/win/CS/HandBrakeWPF/Services/Presets/Factories/JsonPresetFactory.cs b/win/CS/HandBrakeWPF/Services/Presets/Factories/JsonPresetFactory.cs index a92a67adb..8b323d396 100644 --- a/win/CS/HandBrakeWPF/Services/Presets/Factories/JsonPresetFactory.cs +++ b/win/CS/HandBrakeWPF/Services/Presets/Factories/JsonPresetFactory.cs @@ -13,7 +13,6 @@ namespace HandBrakeWPF.Services.Presets.Factories using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
- using System.Windows.Forms.VisualStyles;
using HandBrake.ApplicationServices.Interop.Json.Presets;
using HandBrake.ApplicationServices.Interop.Model;
@@ -132,8 +131,11 @@ namespace HandBrakeWPF.Services.Presets.Factories case "bob":
preset.Task.Decomb = Decomb.Bob;
break;
- case "fast":
- preset.Task.Decomb = Decomb.Fast;
+ case "eedi2":
+ preset.Task.Decomb = Decomb.EEDI2;
+ break;
+ case "eedi2bob":
+ preset.Task.Decomb = Decomb.EEDI2Bob;
break;
default:
preset.Task.Decomb = Decomb.Default;
@@ -173,9 +175,35 @@ namespace HandBrakeWPF.Services.Presets.Factories }
}
+ if (preset.Task.DeinterlaceFilter == DeinterlaceFilter.Yadif || preset.Task.DeinterlaceFilter == DeinterlaceFilter.Decomb)
+ {
+ switch (importedPreset.PictureCombDetectPreset)
+ {
+ case "off":
+ preset.Task.CombDetect = CombDetect.Off;
+ break;
+ case "custom":
+ preset.Task.CombDetect = CombDetect.Custom;
+ break;
+ case "default":
+ preset.Task.CombDetect = CombDetect.Default;
+ break;
+ case "permissive":
+ preset.Task.CombDetect = CombDetect.LessSensitive;
+ break;
+ case "fast":
+ preset.Task.CombDetect = CombDetect.Fast;
+ break;
+ default:
+ preset.Task.CombDetect = CombDetect.Off;
+ break;
+ }
+ }
+
preset.Task.CustomDeinterlace = importedPreset.PictureDetelecineCustom;
preset.Task.CustomDenoise = importedPreset.PictureDenoiseCustom;
preset.Task.CustomDetelecine = importedPreset.PictureDetelecineCustom;
+ preset.Task.CustomCombDetect = importedPreset.PictureCombDetectCustom;
switch (importedPreset.PictureDetelecine)
{
@@ -579,6 +607,8 @@ namespace HandBrakeWPF.Services.Presets.Factories preset.PictureDenoiseTune = EnumHelper<DenoiseTune>.GetShortName(export.Task.DenoiseTune);
preset.PictureDetelecine = EnumHelper<Detelecine>.GetShortName(export.Task.Detelecine);
preset.PictureDetelecineCustom = export.Task.CustomDetelecine;
+ preset.PictureCombDetectPreset = EnumHelper<CombDetect>.GetShortName(export.Task.CombDetect);
+ preset.PictureCombDetectCustom = export.Task.CustomCombDetect;
// Video
preset.VideoEncoder = EnumHelper<VideoEncoder>.GetShortName(export.Task.VideoEncoder);
diff --git a/win/CS/HandBrakeWPF/ViewModels/FiltersViewModel.cs b/win/CS/HandBrakeWPF/ViewModels/FiltersViewModel.cs index 4dd88f6be..7cb50ac37 100644 --- a/win/CS/HandBrakeWPF/ViewModels/FiltersViewModel.cs +++ b/win/CS/HandBrakeWPF/ViewModels/FiltersViewModel.cs @@ -216,6 +216,17 @@ namespace HandBrakeWPF.ViewModels }
/// <summary>
+ /// Comb Detection Presets
+ /// </summary>
+ public IEnumerable<CombDetect> CombDetectPresets
+ {
+ get
+ {
+ return EnumHelper<CombDetect>.GetEnumList();
+ }
+ }
+
+ /// <summary>
/// Gets or sets a value indicating whether Grayscale.
/// </summary>
public bool Grayscale
@@ -256,6 +267,48 @@ namespace HandBrakeWPF.ViewModels }
}
+ public CombDetect SelectedCombDetectPreset
+ {
+ get
+ {
+ return this.CurrentTask.CombDetect;
+ }
+
+ set
+ {
+ this.CurrentTask.CombDetect = value;
+ this.NotifyOfPropertyChange(() => this.SelectedCombDetectPreset);
+
+ // Show / Hide the Custom Control
+ this.NotifyOfPropertyChange(() => this.ShowCombDetectCustom);
+ }
+ }
+
+ /// <summary>
+ /// Show the CombDetect Custom Box.
+ /// </summary>
+ public bool ShowCombDetectCustom
+ {
+ get
+ {
+ return this.SelectedCombDetectPreset == CombDetect.Custom;
+ }
+ }
+
+ public string CustomCombDetect
+ {
+ get
+ {
+ return this.CurrentTask.CustomCombDetect;
+ }
+
+ set
+ {
+ this.CurrentTask.CustomCombDetect = value;
+ this.NotifyOfPropertyChange(() => this.CustomCombDetect);
+ }
+ }
+
/// <summary>
/// Gets or sets SelectedDecomb.
/// </summary>
diff --git a/win/CS/HandBrakeWPF/Views/FiltersView.xaml b/win/CS/HandBrakeWPF/Views/FiltersView.xaml index 494adae51..d40567fcc 100644 --- a/win/CS/HandBrakeWPF/Views/FiltersView.xaml +++ b/win/CS/HandBrakeWPF/Views/FiltersView.xaml @@ -10,7 +10,6 @@ <UserControl.Resources>
<Converters:BooleanToVisibilityConverter x:Key="boolToVisConverter" />
<Converters:EnumComboConverter x:Key="boolComboConverter" />
- <Converters:InverseBooleanConverter x:Key="inverseBooleanConverter" />
<filters:DenoisePresetConverter x:Key="DenoisePresetConverter" />
</UserControl.Resources>
@@ -56,31 +55,58 @@ Visibility="{Binding ShowDetelecineCustom, Converter={StaticResource boolToVisConverter}}"/>
<!-- Deinterlace -->
- <TextBlock Text="{x:Static Properties:ResourcesUI.FiltersView_Deinterlace}" Grid.Row="1" Grid.Column="0" Margin="0,0,0,10" />
- <StackPanel Grid.Row="1" Grid.Column="1" >
+ <TextBlock Text="{x:Static Properties:ResourcesUI.FiltersView_Deinterlace}" VerticalAlignment="Center" Grid.Row="1" Grid.Column="0" Margin="0,0,0,10" />
+ <StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Margin="0,0,0,10">
<ComboBox Width="120" ItemsSource="{Binding DeinterlaceFilterOptions, Converter={StaticResource boolComboConverter}}" HorizontalAlignment="Left"
- SelectedItem="{Binding SelectedDeinterlaceFilter, Converter={StaticResource boolComboConverter}}" Margin="0,0,0,10" />
+ SelectedItem="{Binding SelectedDeinterlaceFilter, Converter={StaticResource boolComboConverter}}" />
</StackPanel>
- <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="2">
- <TextBlock Text="Preset:" VerticalAlignment="Center" Margin="0,0,5,10" Visibility="{Binding IsDeinterlaceDecomb, Converter={StaticResource boolToVisConverter}}" />
- <ComboBox Width="120" ItemsSource="{Binding DecombOptions, Converter={StaticResource boolComboConverter}}" HorizontalAlignment="Left" VerticalAlignment="Center"
- SelectedItem="{Binding SelectedDecomb, Converter={StaticResource boolComboConverter}}"
- Visibility="{Binding IsDecombMode, Converter={StaticResource boolToVisConverter}}" Margin="0,0,0,10" />
+ <Grid Grid.Row="1" Grid.Column="2" Margin="0,0,0,10">
+ <Grid.RowDefinitions>
+ <RowDefinition />
+ <RowDefinition />
+ </Grid.RowDefinitions>
+
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition />
+ <ColumnDefinition Width="15"/>
+ <ColumnDefinition Width="Auto" />
+ <ColumnDefinition />
+ </Grid.ColumnDefinitions>
+
+
+ <TextBlock Text="Preset:" VerticalAlignment="Center" Margin="0,0,5,0" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsDeinterlaceDecomb, Converter={StaticResource boolToVisConverter}}" />
+ <StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal">
+ <ComboBox Width="120" ItemsSource="{Binding DecombOptions, Converter={StaticResource boolComboConverter}}" HorizontalAlignment="Left" VerticalAlignment="Center"
+ SelectedItem="{Binding SelectedDecomb, Converter={StaticResource boolComboConverter}}"
+ Visibility="{Binding IsDecombMode, Converter={StaticResource boolToVisConverter}}" />
+
+ <ComboBox Width="120" ItemsSource="{Binding DeInterlaceOptions, Converter={StaticResource boolComboConverter}}" HorizontalAlignment="Left" VerticalAlignment="Center"
+ SelectedItem="{Binding SelectedDeInterlace, Converter={StaticResource boolComboConverter}}"
+ Visibility="{Binding IsDeinterlaceMode, Converter={StaticResource boolToVisConverter}}" />
+ </StackPanel>
- <ComboBox Width="120" ItemsSource="{Binding DeInterlaceOptions, Converter={StaticResource boolComboConverter}}" HorizontalAlignment="Left" VerticalAlignment="Center"
- SelectedItem="{Binding SelectedDeInterlace, Converter={StaticResource boolComboConverter}}"
- Visibility="{Binding IsDeinterlaceMode, Converter={StaticResource boolToVisConverter}}" Margin="0,0,0,10" />
+ <TextBlock Text="Custom:" VerticalAlignment="Center" Margin="0,0,5,0" Grid.Column="0" Grid.Row="1" Visibility="{Binding ShowDeinterlaceDecombCustom, Converter={StaticResource boolToVisConverter}}" />
+ <StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal">
+ <TextBox Width="120" Text="{Binding CustomDecomb, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Left"
+ Visibility="{Binding ShowDecombCustom, Converter={StaticResource boolToVisConverter}}" Margin="0,5,0,0" MinHeight="22" />
- <TextBlock Text="Custom:" VerticalAlignment="Center" Margin="5,0,5,10" Visibility="{Binding ShowDeinterlaceDecombCustom, Converter={StaticResource boolToVisConverter}}" />
- <TextBox Width="120" Text="{Binding CustomDecomb, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Left"
- Visibility="{Binding ShowDecombCustom, Converter={StaticResource boolToVisConverter}}" Margin="0,0,0,10" />
+ <TextBox Width="120" Text="{Binding CustomDeinterlace, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Left"
+ Visibility="{Binding ShowDeinterlaceCustom, Converter={StaticResource boolToVisConverter}}" Margin="0,5,0,0" MinHeight="22" />
+ </StackPanel>
- <TextBox Width="120" Text="{Binding CustomDeinterlace, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Left"
- Visibility="{Binding ShowDeinterlaceCustom, Converter={StaticResource boolToVisConverter}}" Margin="0,0,0,10" />
- </StackPanel>
+ <TextBlock Text="Interlace Detection:" VerticalAlignment="Center" Margin="0,0,5,0" Grid.Column="3" Grid.Row="0" Visibility="{Binding IsDeinterlaceDecomb, Converter={StaticResource boolToVisConverter}}" />
+ <TextBlock Text="Custom:" VerticalAlignment="Center" Margin="0,0,5,0" Grid.Column="3" Grid.Row="1" Visibility="{Binding ShowCombDetectCustom, Converter={StaticResource boolToVisConverter}}" />
+
+ <ComboBox Width="120" Grid.Row="0" Grid.Column="4" ItemsSource="{Binding CombDetectPresets, Converter={StaticResource boolComboConverter}}" HorizontalAlignment="Left" VerticalAlignment="Center"
+ SelectedItem="{Binding SelectedCombDetectPreset, Converter={StaticResource boolComboConverter}}"
+ Visibility="{Binding IsDeinterlaceDecomb, Converter={StaticResource boolToVisConverter}}"/>
+ <TextBox Width="120" Grid.Row="1" Grid.Column="4" Text="{Binding CustomCombDetect, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" HorizontalAlignment="Left"
+ Visibility="{Binding ShowCombDetectCustom, Converter={StaticResource boolToVisConverter}}" Margin="0,5,0,0" MinHeight="22" />
+ </Grid>
@@ -112,7 +138,7 @@ <StackPanel Orientation="Horizontal" Visibility="{Binding ShowDenoiseCustom, Converter={StaticResource boolToVisConverter}}">
<TextBlock Text="{x:Static Properties:ResourcesUI.FiltersView_Custom}" Margin="5,0,5,0" />
- <TextBox Width="120" Margin="0" Text="{Binding CustomDenoise, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" />
+ <TextBox Width="120" Margin="0" Text="{Binding CustomDenoise, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" MinHeight="22" />
</StackPanel>
</StackPanel>
diff --git a/win/CS/HandBrakeWPF/Views/Styles/Styles.xaml b/win/CS/HandBrakeWPF/Views/Styles/Styles.xaml index 247819000..3b4b0eeb9 100644 --- a/win/CS/HandBrakeWPF/Views/Styles/Styles.xaml +++ b/win/CS/HandBrakeWPF/Views/Styles/Styles.xaml @@ -13,6 +13,10 @@ <Style TargetType="{x:Type TextBlock}">
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
+
+ <Style TargetType="{x:Type TextBox}">
+ <Setter Property="VerticalContentAlignment" Value="Center"/>
+ </Style>
<Style TargetType="{x:Type Button}">
<Setter Property="MinHeight" Value="22"/>
|