From: Steinar H. Gunderson Date: Wed, 23 Dec 2015 13:35:58 +0000 (+0100) Subject: Transparently send signals through a deinterlacer as needed. X-Git-Tag: 1.0.0~68 X-Git-Url: https://git.sesse.net/?p=nageru;a=commitdiff_plain;h=3cafda5de945dd02d321634abd61aa1e261f2384 Transparently send signals through a deinterlacer as needed. --- diff --git a/bmusb b/bmusb index 9ada4d5..2a06d27 160000 --- a/bmusb +++ b/bmusb @@ -1 +1 @@ -Subproject commit 9ada4d5213c6935c3dc2fdeb34938a375f817428 +Subproject commit 2a06d276da04a363f0a7d4043b319229b0a5fd7f diff --git a/mixer.cpp b/mixer.cpp index 02f4dd2..74d2bf6 100644 --- a/mixer.cpp +++ b/mixer.cpp @@ -342,9 +342,8 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, unsigned num_fields = interlaced ? 2 : 1; timespec frame_upload_start; if (interlaced) { - // NOTE: This isn't deinterlacing. This is just sending the two fields along - // as separate frames without considering anything like the half-field offset. - // We'll need to add a proper deinterlacer on the receiving side to get this right. + // Send the two fields along as separate frames; the other side will need to add + // a deinterlacer to actually get this right. assert(height % 2 == 0); height /= 2; assert(frame_length % 2 == 0); @@ -352,6 +351,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, num_fields = 2; clock_gettime(CLOCK_MONOTONIC, &frame_upload_start); } + userdata->last_interlaced = interlaced; RefCountedFrame new_frame(video_frame); // Upload the textures. diff --git a/pbo_frame_allocator.cpp b/pbo_frame_allocator.cpp index e110633..f64ea5f 100644 --- a/pbo_frame_allocator.cpp +++ b/pbo_frame_allocator.cpp @@ -43,6 +43,7 @@ PBOFrameAllocator::PBOFrameAllocator(size_t frame_size, GLuint width, GLuint hei userdata[i].last_height[0] = height; userdata[i].last_width[1] = 0; userdata[i].last_height[1] = 0; + userdata[i].last_interlaced = false; for (unsigned field = 0; field < 2; ++field) { glBindTexture(GL_TEXTURE_2D, userdata[i].tex_y[field]); check_error(); diff --git a/pbo_frame_allocator.h b/pbo_frame_allocator.h index 321842b..fa75f8a 100644 --- a/pbo_frame_allocator.h +++ b/pbo_frame_allocator.h @@ -32,6 +32,7 @@ public: // The second set is only used for the second field of interlaced inputs. GLuint tex_y[2], tex_cbcr[2]; GLuint last_width[2], last_height[2]; + bool last_interlaced; }; private: diff --git a/theme.cpp b/theme.cpp index defdca1..166dcb5 100644 --- a/theme.cpp +++ b/theme.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,7 @@ struct InputStateInfo { InputStateInfo(const InputState& input_state); unsigned last_width[MAX_CARDS], last_height[MAX_CARDS]; + bool last_interlaced[MAX_CARDS]; }; InputStateInfo::InputStateInfo(const InputState &input_state) @@ -52,11 +54,13 @@ InputStateInfo::InputStateInfo(const InputState &input_state) BufferedFrame frame = input_state.buffered_frames[signal_num][0]; if (frame.frame == nullptr) { last_width[signal_num] = last_height[signal_num] = 0; + last_interlaced[signal_num] = false; continue; } const PBOFrameAllocator::Userdata *userdata = (const PBOFrameAllocator::Userdata *)frame.frame->userdata; last_width[signal_num] = userdata->last_width[frame.field_number]; last_height[signal_num] = userdata->last_height[frame.field_number]; + last_interlaced[signal_num] = userdata->last_interlaced; } } @@ -146,11 +150,12 @@ int EffectChain_new(lua_State* L) int EffectChain_add_live_input(lua_State* L) { - assert(lua_gettop(L) == 2); + assert(lua_gettop(L) == 3); Theme *theme = get_theme_updata(L); EffectChain *chain = (EffectChain *)luaL_checkudata(L, 1, "EffectChain"); bool override_bounce = checkbool(L, 2); - return wrap_lua_object(L, "LiveInputWrapper", theme, chain, override_bounce); + bool deinterlace = checkbool(L, 3); + return wrap_lua_object(L, "LiveInputWrapper", theme, chain, override_bounce, deinterlace); } int EffectChain_add_effect(lua_State* L) @@ -171,7 +176,7 @@ int EffectChain_add_effect(lua_State* L) for (int idx = 3; idx <= lua_gettop(L); ++idx) { if (luaL_testudata(L, idx, "LiveInputWrapper")) { LiveInputWrapper *input = (LiveInputWrapper *)lua_touserdata(L, idx); - inputs.push_back(input->get_input()); + inputs.push_back(input->get_effect()); } else { inputs.push_back(get_effect(L, idx)); } @@ -311,6 +316,16 @@ int InputStateInfo_get_height(lua_State* L) return 1; } +int InputStateInfo_get_interlaced(lua_State* L) +{ + assert(lua_gettop(L) == 2); + InputStateInfo *input_state_info = get_input_state_info(L, 1); + Theme *theme = get_theme_updata(L); + int signal_num = theme->map_signal(luaL_checknumber(L, 2)); + lua_pushboolean(L, input_state_info->last_interlaced[signal_num]); + return 1; +} + int Effect_set_float(lua_State *L) { assert(lua_gettop(L) == 3); @@ -456,13 +471,15 @@ const luaL_Reg MixEffect_funcs[] = { const luaL_Reg InputStateInfo_funcs[] = { { "get_width", InputStateInfo_get_width }, { "get_height", InputStateInfo_get_height }, + { "get_interlaced", InputStateInfo_get_interlaced }, { NULL, NULL } }; } // namespace -LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bool override_bounce) - : theme(theme) +LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bool override_bounce, bool deinterlace) + : theme(theme), + deinterlace(deinterlace) { ImageFormat inout_format; inout_format.color_space = COLORSPACE_sRGB; @@ -490,12 +507,34 @@ LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bool overri input_ycbcr_format.luma_coefficients = YCBCR_REC_709; input_ycbcr_format.full_range = false; - if (override_bounce) { - input = new NonBouncingYCbCrInput(inout_format, input_ycbcr_format, WIDTH, HEIGHT, YCBCR_INPUT_SPLIT_Y_AND_CBCR); + unsigned num_inputs; + if (deinterlace) { + deinterlace_effect = new movit::DeinterlaceEffect(); + + // As per the comments in deinterlace_effect.h, we turn this off. + // The most likely interlaced input for us is either a camera + // (where it's fine to turn it off) or a laptop (where it _should_ + // be turned off). + CHECK(deinterlace_effect->set_int("enable_spatial_interlacing_check", 0)); + + num_inputs = deinterlace_effect->num_inputs(); + assert(num_inputs == FRAME_HISTORY_LENGTH); } else { - input = new YCbCrInput(inout_format, input_ycbcr_format, WIDTH, HEIGHT, YCBCR_INPUT_SPLIT_Y_AND_CBCR); + num_inputs = 1; + } + for (unsigned i = 0; i < num_inputs; ++i) { + if (override_bounce) { + inputs.push_back(new NonBouncingYCbCrInput(inout_format, input_ycbcr_format, WIDTH, HEIGHT, YCBCR_INPUT_SPLIT_Y_AND_CBCR)); + } else { + inputs.push_back(new YCbCrInput(inout_format, input_ycbcr_format, WIDTH, HEIGHT, YCBCR_INPUT_SPLIT_Y_AND_CBCR)); + } + chain->add_input(inputs.back()); + } + + if (deinterlace) { + vector reverse_inputs(inputs.rbegin(), inputs.rend()); + chain->add_effect(deinterlace_effect, reverse_inputs); } - chain->add_input(input); } void LiveInputWrapper::connect_signal(int signal_num) @@ -507,13 +546,47 @@ void LiveInputWrapper::connect_signal(int signal_num) signal_num = theme->map_signal(signal_num); - BufferedFrame frame = theme->input_state->buffered_frames[signal_num][0]; - const PBOFrameAllocator::Userdata *userdata = (const PBOFrameAllocator::Userdata *)frame.frame->userdata; + BufferedFrame first_frame = theme->input_state->buffered_frames[signal_num][0]; + if (first_frame.frame == nullptr) { + // No data yet. + return; + } + unsigned width, height; + { + const PBOFrameAllocator::Userdata *userdata = (const PBOFrameAllocator::Userdata *)first_frame.frame->userdata; + width = userdata->last_width[first_frame.field_number]; + height = userdata->last_height[first_frame.field_number]; + } - input->set_texture_num(0, userdata->tex_y[frame.field_number]); - input->set_texture_num(1, userdata->tex_cbcr[frame.field_number]); - input->set_width(userdata->last_width[frame.field_number]); - input->set_height(userdata->last_height[frame.field_number]); + BufferedFrame last_good_frame = first_frame; + for (unsigned i = 0; i < inputs.size(); ++i) { + BufferedFrame frame = theme->input_state->buffered_frames[signal_num][i]; + if (frame.frame == nullptr) { + // Not enough data; reuse last frame (well, field). + // This is suboptimal, but we have nothing better. + frame = last_good_frame; + } + const PBOFrameAllocator::Userdata *userdata = (const PBOFrameAllocator::Userdata *)frame.frame->userdata; + + if (userdata->last_width[frame.field_number] != width || + userdata->last_height[frame.field_number] != height) { + // Resolution changed; reuse last frame/field. + frame = last_good_frame; + userdata = (const PBOFrameAllocator::Userdata *)frame.frame->userdata; + } + + inputs[i]->set_texture_num(0, userdata->tex_y[frame.field_number]); + inputs[i]->set_texture_num(1, userdata->tex_cbcr[frame.field_number]); + inputs[i]->set_width(userdata->last_width[frame.field_number]); + inputs[i]->set_height(userdata->last_height[frame.field_number]); + + last_good_frame = frame; + } + + if (deinterlace) { + BufferedFrame frame = theme->input_state->buffered_frames[signal_num][0]; + CHECK(deinterlace_effect->set_int("current_field_position", frame.field_number)); + } } Theme::Theme(const char *filename, ResourcePool *resource_pool, unsigned num_cards) diff --git a/theme.h b/theme.h index b60df14..a3bc8ea 100644 --- a/theme.h +++ b/theme.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -78,17 +79,23 @@ private: class LiveInputWrapper { public: - LiveInputWrapper(Theme *theme, movit::EffectChain *chain, bool override_bounce); + LiveInputWrapper(Theme *theme, movit::EffectChain *chain, bool override_bounce, bool deinterlace); void connect_signal(int signal_num); - movit::YCbCrInput *get_input() const + movit::Effect *get_effect() const { - return input; + if (deinterlace) { + return deinterlace_effect; + } else { + return inputs[0]; + } } private: Theme *theme; // Not owned by us. - movit::YCbCrInput *input; // Owned by the chain. + std::vector inputs; // Multiple ones if deinterlacing. Owned by the chain. + movit::Effect *deinterlace_effect = nullptr; // Owned by the chain. + bool deinterlace; }; #endif // !defined(_THEME_H) diff --git a/theme.lua b/theme.lua index eb55733..e633808 100644 --- a/theme.lua +++ b/theme.lua @@ -34,12 +34,12 @@ local STATIC_SIGNAL_NUM = 3 local FADE_SIGNAL_NUM = 4 -- The main live chain. -function make_sbs_chain(hq) +function make_sbs_chain(input0_deint, input1_deint, hq) local chain = EffectChain.new(16, 9) - local input0 = chain:add_live_input(true) + local input0 = chain:add_live_input(not input0_deint, input0_deint) -- Override bounce only if not deinterlacing. input0:connect_signal(0) local input0_wb_effect = chain:add_effect(WhiteBalanceEffect.new()) - local input1 = chain:add_live_input(true) + local input1 = chain:add_live_input(not input1_deint, input1_deint) input1:connect_signal(1) local input1_wb_effect = chain:add_effect(WhiteBalanceEffect.new()) @@ -85,19 +85,29 @@ function make_sbs_chain(hq) } end -local main_chain_hq = make_sbs_chain(true) -local main_chain_lq = make_sbs_chain(false) +-- Make all possible combinations of side-by-side chains. +local sbs_chains = {} +for input0_type, input0_deint in pairs({live = false, livedeint = true}) do + sbs_chains[input0_type] = {} + for input1_type, input1_deint in pairs({live = false, livedeint = true}) do + sbs_chains[input0_type][input1_type] = {} + for _, hq in pairs({true, false}) do + sbs_chains[input0_type][input1_type][hq] = + make_sbs_chain(input0_deint, input1_deint, hq) + end + end +end -- A chain to fade between two inputs, of which either can be a picture -- or a live input. In practice only used live, but we still support the -- hq parameter. -function make_fade_chain(input0_live, input1_live, hq) +function make_fade_chain(input0_live, input0_deint, input1_live, input1_deint, hq) local chain = EffectChain.new(16, 9) local input0, wb0_effect, input0_last, input1, wb1_effect, input1_last if input0_live then - input0 = chain:add_live_input(true) + input0 = chain:add_live_input(false, input0_deint) wb0_effect = chain:add_effect(WhiteBalanceEffect.new()) input0:connect_signal(0) input0_last = wb0_effect @@ -107,7 +117,7 @@ function make_fade_chain(input0_live, input1_live, hq) end if input1_live then - input1 = chain:add_live_input(true) + input1 = chain:add_live_input(false, input1_deint) wb1_effect = chain:add_effect(WhiteBalanceEffect.new()) input1:connect_signal(1) input1_last = wb1_effect @@ -135,29 +145,43 @@ end -- Chains to fade between two inputs, in various configurations. local fade_chains = {} -for input0_type, input0_live in pairs({static = false, live = true}) do +for input0_type, input0_live in pairs({static = false, live = true, livedeint = true}) do + local input0_deint = (input0_live == "livedeint") fade_chains[input0_type] = {} - for input1_type, input1_live in pairs({static = false, live = true}) do + for input1_type, input1_live in pairs({static = false, live = true, livedeint = true}) do + local input1_deint = (input1_live == "livedeint") fade_chains[input0_type][input1_type] = {} for _, hq in pairs({true, false}) do - fade_chains[input0_type][input1_type][hq] = make_fade_chain(input0_live, input1_live, hq) + fade_chains[input0_type][input1_type][hq] = + make_fade_chain(input0_live, input0_deint, input1_live, input1_deint, hq) end end end --- A chain to show a single input on screen (HQ version). -local simple_chain_hq = EffectChain.new(16, 9) -local simple_chain_hq_input = simple_chain_hq:add_live_input(true) -simple_chain_hq_input:connect_signal(0) -- First input card. Can be changed whenever you want. -local simple_chain_hq_wb_effect = simple_chain_hq:add_effect(WhiteBalanceEffect.new()) -simple_chain_hq:finalize(true) +-- A chain to show a single input on screen. +function make_simple_chain(input_deint, hq) + local chain = EffectChain.new(16, 9) --- A chain to show a single input on screen (LQ version). -local simple_chain_lq = EffectChain.new(16, 9) -local simple_chain_lq_input = simple_chain_lq:add_live_input(true) -simple_chain_lq_input:connect_signal(0) -- First input card. Can be changed whenever you want. -local simple_chain_lq_wb_effect = simple_chain_lq:add_effect(WhiteBalanceEffect.new()) -simple_chain_lq:finalize(false) + local input = chain:add_live_input(false, input_deint) + input:connect_signal(0) -- First input card. Can be changed whenever you want. + local wb_effect = chain:add_effect(WhiteBalanceEffect.new()) + chain:finalize(hq) + + return { + chain = chain, + input = input, + wb_effect = wb_effect + } +end + +-- Make all possible combinations of single-input chains. +local simple_chains = {} +for input_type, input_deint in pairs({live = false, livedeint = true}) do + simple_chains[input_type] = {} + for _, hq in pairs({true, false}) do + simple_chains[input_type][hq] = make_simple_chain(input_deint, hq) + end +end -- A chain to show a single static picture on screen (HQ version). local static_chain_hq = EffectChain.new(16, 9) @@ -169,6 +193,17 @@ local static_chain_lq = EffectChain.new(16, 9) local static_chain_lq_input = static_chain_lq:add_effect(ImageInput.new("bg.jpeg")) static_chain_lq:finalize(false) +-- Used for indexing into the tables of chains. +function get_input_type(signals, signal_num) + if signal_num == STATIC_SIGNAL_NUM then + return "static" + elseif signals:get_interlaced(signal_num) then + return "livedeint" + else + return "live" + end +end + -- Returns the number of outputs in addition to the live (0) and preview (1). -- Called only once, at the start of the program. function num_channels() @@ -363,18 +398,20 @@ end function get_chain(num, t, width, height, signals) if num == 0 then -- Live. if live_signal_num == INPUT0_SIGNAL_NUM or live_signal_num == INPUT1_SIGNAL_NUM then -- Plain input. + local input_type = get_input_type(signals, live_signal_num) + local chain = simple_chains[input_type][true] prepare = function() - simple_chain_hq_input:connect_signal(live_signal_num) - set_neutral_color_from_signal(simple_chain_hq_wb_effect, live_signal_num) + chain.input:connect_signal(live_signal_num) + set_neutral_color_from_signal(chain.wb_effect, live_signal_num) end - return simple_chain_hq, prepare + return chain.chain, prepare elseif live_signal_num == STATIC_SIGNAL_NUM then -- Static picture. prepare = function() end return static_chain_hq, prepare elseif live_signal_num == FADE_SIGNAL_NUM then -- Fade. - local input0_type = (fade_src_signal == STATIC_SIGNAL_NUM) and "static" or "live" - local input1_type = (fade_dst_signal == STATIC_SIGNAL_NUM) and "static" or "live" + local input0_type = get_input_type(signals, fade_src_signal) + local input1_type = get_input_type(signals, fade_dst_signal) local chain = fade_chains[input0_type][input1_type][true] prepare = function() if input0_type == "live" then @@ -394,50 +431,63 @@ function get_chain(num, t, width, height, signals) end -- SBS code (live_signal_num == SBS_SIGNAL_NUM). + local input0_type = get_input_type(signals, INPUT0_SIGNAL_NUM) + local input1_type = get_input_type(signals, INPUT1_SIGNAL_NUM) if t > transition_end and zoom_dst == 1.0 then -- Special case: Show only the single image on screen. + local chain = simple_chains[input0_type][true] prepare = function() - simple_chain_hq_input:connect_signal(INPUT0_SIGNAL_NUM) - set_neutral_color(simple_chain_hq_wb_effect, input0_neutral_color) + chain.input:connect_signal(INPUT0_SIGNAL_NUM) + set_neutral_color(chain.wb_effect, input0_neutral_color) end - return simple_chain_hq, prepare + return chain.chain, prepare end + local chain = sbs_chains[input0_type][input1_type][true] prepare = function() if t < transition_start then - prepare_sbs_chain(main_chain_hq, zoom_src, width, height) + prepare_sbs_chain(chain, zoom_src, width, height) elseif t > transition_end then - prepare_sbs_chain(main_chain_hq, zoom_dst, width, height) + prepare_sbs_chain(chain, zoom_dst, width, height) else local tt = (t - transition_start) / (transition_end - transition_start) -- Smooth it a bit. tt = math.sin(tt * 3.14159265358 * 0.5) - prepare_sbs_chain(main_chain_hq, zoom_src + (zoom_dst - zoom_src) * tt, width, height) + prepare_sbs_chain(chain, zoom_src + (zoom_dst - zoom_src) * tt, width, height) end end - return main_chain_hq.chain, prepare + return chain.chain, prepare end if num == 1 then -- Preview. num = preview_signal_num + 2 end + + -- Individual preview inputs. if num == INPUT0_SIGNAL_NUM + 2 then + local input_type = get_input_type(signals, INPUT0_SIGNAL_NUM) + local chain = simple_chains[input_type][false] prepare = function() - simple_chain_lq_input:connect_signal(0) - set_neutral_color(simple_chain_lq_wb_effect, input0_neutral_color) + chain.input:connect_signal(INPUT0_SIGNAL_NUM) + set_neutral_color(chain.wb_effect, input0_neutral_color) end - return simple_chain_lq, prepare + return chain.chain, prepare end if num == INPUT1_SIGNAL_NUM + 2 then + local input_type = get_input_type(signals, INPUT1_SIGNAL_NUM) + local chain = simple_chains[input_type][false] prepare = function() - simple_chain_lq_input:connect_signal(1) - set_neutral_color(simple_chain_lq_wb_effect, input1_neutral_color) + chain.input:connect_signal(INPUT1_SIGNAL_NUM) + set_neutral_color(chain.wb_effect, input1_neutral_color) end - return simple_chain_lq, prepare + return chain.chain, prepare end if num == SBS_SIGNAL_NUM + 2 then + local input0_type = get_input_type(signals, INPUT0_SIGNAL_NUM) + local input1_type = get_input_type(signals, INPUT1_SIGNAL_NUM) + local chain = sbs_chains[input0_type][input1_type][false] prepare = function() - prepare_sbs_chain(main_chain_lq, 0.0, width, height) + prepare_sbs_chain(chain, 0.0, width, height) end - return main_chain_lq.chain, prepare + return chain.chain, prepare end if num == STATIC_SIGNAL_NUM + 2 then prepare = function()