diff --git a/src/video_codec.c b/src/video_codec.c index c94f432c8..dd5a52d10 100644 --- a/src/video_codec.c +++ b/src/video_codec.c @@ -692,6 +692,8 @@ static void vc_deinterlace_unaligned(unsigned char *src, long src_linesize, int * * @returns false on unsupported codecs */ +// Sibling of this function is in double-framerate.cpp:avg_lines so consider +// porting changes made here there. bool vc_deinterlace_ex(codec_t codec, unsigned char *src, size_t src_linesize, unsigned char *dst, size_t dst_pitch, size_t lines) { if (is_codec_opaque(codec) && codec_is_planar(codec)) { diff --git a/src/video_display/gl.cpp b/src/video_display/gl.cpp index b741ad822..041daf534 100644 --- a/src/video_display/gl.cpp +++ b/src/video_display/gl.cpp @@ -347,7 +347,7 @@ struct state_gl { GLFWwindow *window = nullptr; bool fs = false; - bool deinterlace = false; + enum class deint { off, on, force } deinterlace = deint::off; struct video_frame *current_frame = nullptr; @@ -385,10 +385,10 @@ struct state_gl { enum modeset_t { MODESET = -2, MODESET_SIZE_ONLY = GLFW_DONT_CARE, NOMODESET = 0 } modeset = NOMODESET; ///< positive vals force framerate bool nodecorate = false; int use_pbo = -1; - #ifdef HWACC_VDPAU struct state_vdpau vdp; #endif + vector scratchpad; ///< scratchpad sized WxHx8 state_gl(struct module *parent) { glfwSetErrorCallback(glfw_print_error); @@ -408,7 +408,14 @@ struct state_gl { module_done(&mod); } - vector scratchpad; ///< scratchpad sized WxHx8 + static const char *deint_to_string(state_gl::deint val) { + switch (val) { + case state_gl::deint::off: return "OFF"; + case state_gl::deint::on: return "ON"; + case state_gl::deint::force: return "FORCE"; + } + return NULL; + } }; static constexpr array gl_supp_codecs = { @@ -477,7 +484,7 @@ static void gl_show_help(bool full) { col() << "options:\n"; col() << TBOLD("\taspect=/") << "\trequested video aspect (eg. 16/9). Leave unset if PAR = 1.\n"; col() << TBOLD("\tcursor") << "\t\tshow visible cursor\n"; - col() << TBOLD("\td") << "\t\tdeinterlace\n"; + col() << TBOLD("\td[force]") << "\tdeinterlace (optionally forcing deinterlace of progressive video)\n"; col() << TBOLD("\tfs[=]") << "\tfullscreen with optional display specification\n"; col() << TBOLD("\tgamma[=]") << "\tgamma value to be added _in addition_ to the hardware gamma correction\n"; col() << TBOLD("\thide-window") << "\tdo not show OpenGL window (useful with Syphon/SPOUT)\n"; @@ -549,8 +556,8 @@ static void *display_gl_parse_fmt(struct state_gl *s, char *ptr) { char *tok, *save_ptr = NULL; while((tok = strtok_r(ptr, ":", &save_ptr)) != NULL) { - if(!strcmp(tok, "d")) { - s->deinterlace = true; + if (!strcmp(tok, "d") || !strcmp(tok, "dforce")) { + s->deinterlace = !strcmp(tok, "d") ? state_gl::deint::on : state_gl::deint::force; } else if(!strncmp(tok, "fs", 2)) { s->fs = true; if (char *val = strchr(tok, '=')) { @@ -663,7 +670,7 @@ static void * display_gl_init(struct module *parent, const char *fmt, unsigned i s->use_pbo = s->use_pbo == -1 ? !check_rpi_pbo_quirks() : s->use_pbo; // don't use PBO for Raspberry Pi (better performance) log_msg(LOG_LEVEL_INFO,"GL setup: fullscreen: %s, deinterlace: %s\n", - s->fs ? "ON" : "OFF", s->deinterlace ? "ON" : "OFF"); + s->fs ? "ON" : "OFF", state_gl::deint_to_string(s->deinterlace)); gl_load_splashscreen(s); for (auto const &i : keybindings) { @@ -694,6 +701,9 @@ static int display_gl_reconfigure(void *state, struct video_desc desc) if (get_bits_per_component(desc.color_spec) > 8) { LOG(LOG_LEVEL_WARNING) << MOD_NAME "Displaying 10+ bits - performance degradation may occur, consider '--param " GL_DISABLE_10B_OPT_PARAM_NAME "'\n"; } + if (desc.interlacing == INTERLACED_MERGED && s->deinterlace == state_gl::deint::off) { + LOG(LOG_LEVEL_WARNING) << MOD_NAME "Receiving interlaced video but deinterlacing is off - suggesting toggling it on (press 'd' or pass cmdline option)\n"; + } s->current_desc = desc; @@ -953,7 +963,7 @@ static void gl_reconfigure_screen(struct state_gl *s, struct video_desc desc) static void gl_render(struct state_gl *s, char *data) { - if (s->deinterlace) { + if (s->deinterlace == state_gl::deint::force || (s->deinterlace == state_gl::deint::on && s->current_display_desc.interlacing == INTERLACED_MERGED)) { if (!vc_deinterlace_ex(s->current_display_desc.color_spec, (unsigned char *) data, vc_get_linesize(s->current_display_desc.width, s->current_display_desc.color_spec), (unsigned char *) data, vc_get_linesize(s->current_display_desc.width, s->current_display_desc.color_spec), @@ -1170,8 +1180,8 @@ static bool display_gl_process_key(struct state_gl *s, long long int key) exit_uv(0); break; case 'd': - s->deinterlace = !s->deinterlace; - log_msg(LOG_LEVEL_NOTICE, "Deinterlacing: %s\n", s->deinterlace ? "ON" : "OFF"); + s->deinterlace = s->deinterlace == state_gl::deint::off ? state_gl::deint::on : state_gl::deint::off; + log_msg(LOG_LEVEL_NOTICE, "Deinterlacing: %s\n", state_gl::deint_to_string(s->deinterlace)); break; case 'p': s->paused = !s->paused; diff --git a/src/video_display/sdl2.cpp b/src/video_display/sdl2.cpp index be69b2524..0dc24e68d 100644 --- a/src/video_display/sdl2.cpp +++ b/src/video_display/sdl2.cpp @@ -119,7 +119,7 @@ struct state_sdl2 { SDL_Texture *texture{nullptr}; bool fs{false}; - bool deinterlace{false}; + enum class deint { off, on, force } deinterlace = deint::off; bool keep_aspect{false}; bool vsync{true}; bool fixed_size{false}; @@ -150,6 +150,14 @@ struct state_sdl2 { ~state_sdl2() { module_done(&mod); } + static const char *deint_to_string(deint val) { + switch (val) { + case deint::off: return "OFF"; + case deint::on: return "ON"; + case deint::force: return "FORCE"; + } + return NULL; + } }; static constexpr array display_sdl2_keybindings{ @@ -169,7 +177,7 @@ static void display_frame(struct state_sdl2 *s, struct video_frame *frame) } } - if (!s->deinterlace) { + if (s->deinterlace == state_sdl2::deint::off || (s->deinterlace == state_sdl2::deint::on && frame->interlacing != INTERLACED_MERGED)) { int pitch; if (codec_is_planar(frame->color_spec)) { pitch = frame->tiles[0].width; @@ -248,9 +256,9 @@ static bool display_sdl2_process_key(struct state_sdl2 *s, int64_t key) { switch (key) { case 'd': - s->deinterlace = !s->deinterlace; + s->deinterlace = s->deinterlace == state_sdl2::deint::off ? state_sdl2::deint::on : state_sdl2::deint::off; log_msg(LOG_LEVEL_INFO, "Deinterlacing: %s\n", - s->deinterlace ? "ON" : "OFF"); + state_sdl2::deint_to_string(s->deinterlace)); return true; case 'f': s->fs = !s->fs; @@ -358,7 +366,7 @@ static void show_help(void) printf("SDL options:\n"); cout << style::bold << fg::red << "\t-d sdl" << fg::reset << "[[:fs|:d|:display=|:driver=|:novsync|:renderer=|:nodecorate|:fixed_size[=WxH]|:window_flags=|:pos=,|:keep-aspect]*|:help]\n" << style::reset; printf("\twhere:\n"); - cout << style::bold <<"\t\t d" << style::reset << " - deinterlace\n"; + cout << style::bold <<"\t\td[force]" << style::reset << " - deinterlace (force even for progresive video)\n"; cout << style::bold <<"\t\t fs" << style::reset << " - fullscreen\n"; cout << style::bold <<"\t\t " << style::reset << " - display index, available indices: "; sdl2_print_displays(); @@ -391,6 +399,10 @@ static int display_sdl2_reconfigure(void *state, struct video_desc desc) { struct state_sdl2 *s = (struct state_sdl2 *) state; + if (desc.interlacing == INTERLACED_MERGED && s->deinterlace == state_sdl2::deint::off) { + LOG(LOG_LEVEL_WARNING) << MOD_NAME "Receiving interlaced video but deinterlacing is off - suggesting toggling it on (press 'd' or pass cmdline option)\n"; + } + s->current_desc = desc; return 1; } @@ -566,8 +578,8 @@ static void *display_sdl2_init(struct module *parent, const char *fmt, unsigned char *tok, *save_ptr; while((tok = strtok_r(tmp, ":", &save_ptr))) { - if (strcmp(tok, "d") == 0) { - s->deinterlace = true; + if (strcmp(tok, "d") == 0 || strcmp(tok, "dforce") == 0) { + s->deinterlace = strcmp(tok, "d") == 0 ? state_sdl2::deint::on : state_sdl2::deint::off; } else if (strncmp(tok, "display=", strlen("display=")) == 0) { s->display_idx = atoi(tok + strlen("display=")); } else if (strncmp(tok, "driver=", strlen("driver=")) == 0) { diff --git a/src/vo_postprocess/deinterlace.c b/src/vo_postprocess/deinterlace.c index 919391b2f..d96a1a69c 100644 --- a/src/vo_postprocess/deinterlace.c +++ b/src/vo_postprocess/deinterlace.c @@ -58,6 +58,7 @@ struct state_deinterlace { struct video_frame *out; ///< for postprocess only + _Bool force; }; static void usage(_Bool for_postprocessor) @@ -66,10 +67,12 @@ static void usage(_Bool for_postprocessor) " by applying linear blend on interleaved odd and even " " fileds.\n\nUsage:\n"); if (for_postprocessor) { - color_printf(TBOLD(TRED("\t-p deinterlace")) " | " TBOLD(TRED("-p deinterlace_blend")) "\n"); + color_printf(TBOLD(TRED("\t-p deinterlace")) "[:options] | " TBOLD(TRED("-p deinterlace_blend")) "[:options]\n"); } else { - color_printf(TBOLD(TRED("\t--capture-filter deinterlace")) " -t \n"); + color_printf(TBOLD(TRED("\t--capture-filter deinterlace")) "[:options] -t \n"); } + color_printf("\noptions:\n" + "\t" TBOLD("force") " - apply deinterlacing even if input is progressive\n"); } static void * deinterlace_blend_init(const char *config) { @@ -81,6 +84,14 @@ static void * deinterlace_blend_init(const char *config) { struct state_deinterlace *s = calloc(1, sizeof(struct state_deinterlace)); assert(s != NULL); + if (strcmp(config, "force") == 0) { + s->force = 1; + } else { + log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unknown option: %s\n", config); + free(s); + return NULL; + } + return s; } @@ -140,16 +151,22 @@ static struct video_frame * deinterlace_getf(void *state) static bool deinterlace_postprocess(void *state, struct video_frame *in, struct video_frame *out, int req_pitch) { - UNUSED(state); assert (req_pitch == vc_get_linesize(in->tiles[0].width, in->color_spec)); assert (video_desc_eq(video_desc_from_frame(out), video_desc_from_frame(in))); assert (in->tiles[0].data_len <= vc_get_linesize(in->tiles[0].width, in->color_spec) * in->tiles[0].height); assert (out->tiles[0].data_len <= vc_get_linesize(in->tiles[0].width, in->color_spec) * in->tiles[0].height); + struct state_deinterlace *s = state; + if (in->interlacing != INTERLACED_MERGED && !s->force) { + memcpy(out->tiles[0].data, in->tiles[0].data, in->tiles[0].data_len); + return true; + } + if (!vc_deinterlace_ex(in->color_spec, (unsigned char *) in->tiles[0].data, vc_get_linesize(in->tiles[0].width, in->color_spec), (unsigned char *) out->tiles[0].data, vc_get_linesize(out->tiles[0].width, in->color_spec), in->tiles[0].height)) { log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot deinterlace, unsupported pixel format '%s'!\n", get_codec_name(in->color_spec)); + memcpy(out->tiles[0].data, in->tiles[0].data, in->tiles[0].data_len); } return true; diff --git a/src/vo_postprocess/double-framerate.cpp b/src/vo_postprocess/double-framerate.cpp index 2508dad65..b81c82ad0 100644 --- a/src/vo_postprocess/double-framerate.cpp +++ b/src/vo_postprocess/double-framerate.cpp @@ -57,7 +57,7 @@ #include "video_display.h" #include "vo_postprocess.h" -#define MOD_NAME "[double_framerate] " +#define MOD_NAME "[temporal deint] " #define TIMEOUT "20ms" #define DFR_DEINTERLACE_IMPOSSIBLE_MSG_ID 0x27ff0a78 @@ -70,10 +70,16 @@ struct state_df { int buffer_current; bool deinterlace; bool nodelay; + bool force; std::chrono::steady_clock::time_point frame_received; }; +static void print_common_opts() { + color_printf("\t" TBOLD("force ") " - apply deinterlacing even if input is not interlaced\n"); + color_printf("\t" TBOLD("nodelay") " - do not delay the other frame to keep timing. Both frames are output in burst. May not work correctly (depends on display).\n"); +} + static void df_usage() { char desc[] = TBOLD("double-framerate") " is an interleaver that " @@ -86,17 +92,20 @@ static void df_usage() color_printf("\t" TBOLD(TRED("-p double_framerate") "[:d][:nodelay]") "\n"); color_printf("\nwhere:\n"); color_printf("\t" TBOLD("d ") " - blend the output\n"); - color_printf("\t" TBOLD("nodelay") " - do not delay the other frame to keep timing. Both frames are output in burst. May not work correctly (depends on display).\n"); + print_common_opts(); } static void * init_common(enum algo algo, const char *config) { bool deinterlace = false; + bool force = false; bool nodelay = false; if (strcmp(config, "d") == 0) { deinterlace = true; } else if (strcmp(config, "nodelay") == 0) { nodelay = true; + } else if (strcmp(config, "force") == 0) { + force = true; } else if (strlen(config) > 0) { log_msg(LOG_LEVEL_ERROR, "Unknown config: %s\n", config); return NULL; @@ -110,6 +119,7 @@ static void * init_common(enum algo algo, const char *config) { s->buffers[0] = s->buffers[1] = NULL; s->buffer_current = 0; s->deinterlace = deinterlace; + s->force = force; s->nodelay = nodelay; if (s->nodelay && commandline_params.find("decoder-drop-policy") == commandline_params.end()) { @@ -171,9 +181,9 @@ static int common_postprocess_reconfigure(void *state, struct video_desc desc) s->in->color_spec = desc.color_spec; s->in->fps = desc.fps; s->in->interlacing = desc.interlacing; - if(desc.interlacing != INTERLACED_MERGED) { - log_msg(LOG_LEVEL_ERROR, "[Double Framerate] Warning: %s video detected. This filter is intended " - "mainly for interlaced merged video. The result might be incorrect.\n", + if (desc.interlacing != INTERLACED_MERGED && !s->force) { + log_msg(LOG_LEVEL_WARNING, MOD_NAME "Warning: %s video detected. This filter is intended " + "mainly for interlaced merged video. Framerate will be needlessly doubled.\n", get_interlacing_description(desc.interlacing)); } @@ -419,16 +429,23 @@ static bool common_postprocess(void *state, struct video_frame *in, struct video { struct state_df *s = (struct state_df *) state; - switch (s->algo) { - case DF: - perform_df(s, in, out, req_pitch); - break; - case BOB: - perform_bob(s, in, out, req_pitch); - break; - case LINEAR: - perform_linear(s, in, out, req_pitch); - break; + if (s->in->interlacing == INTERLACED_MERGED || s->force) { + switch (s->algo) { + case DF: + perform_df(s, in, out, req_pitch); + break; + case BOB: + perform_bob(s, in, out, req_pitch); + break; + case LINEAR: + perform_linear(s, in, out, req_pitch); + break; + } + } else { + s->in->tiles[0].data = s->buffers[0]; // always write to first buffer + if (in) { + memcpy(out->tiles[0].data, in->tiles[0].data, in->tiles[0].data_len); + } } if (!s->nodelay) {