X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=theme.cpp;h=9e04740d60b3636a636bf1460a4bfd4afb9e3a0a;hb=ba2dc655c098912b8860676596fe89c1305affe9;hp=1d0cd2d643db006a4fae075f8aed7c817e5e176f;hpb=c6af897e02de1e5653de71fc4993449e828bb94f;p=nageru diff --git a/theme.cpp b/theme.cpp index 1d0cd2d..9e04740 100644 --- a/theme.cpp +++ b/theme.cpp @@ -1,17 +1,19 @@ #include "theme.h" #include +#include +#include #include #include #include #include #include #include +#include #include #include #include #include -#include #include #include #include @@ -19,13 +21,19 @@ #include #include #include +#include #include #include -#include #include "defs.h" +#include "deinterlace_effect.h" +#include "flags.h" #include "image_input.h" -#include "mixer.h" +#include "input.h" +#include "input_state.h" +#include "pbo_frame_allocator.h" + +class Mixer; namespace movit { class ResourcePool; @@ -44,19 +52,20 @@ namespace { struct InputStateInfo { InputStateInfo(const InputState& input_state); - unsigned last_width[MAX_CARDS], last_height[MAX_CARDS]; - bool last_interlaced[MAX_CARDS], last_has_signal[MAX_CARDS]; - unsigned last_frame_rate_nom[MAX_CARDS], last_frame_rate_den[MAX_CARDS]; + unsigned last_width[MAX_VIDEO_CARDS], last_height[MAX_VIDEO_CARDS]; + bool last_interlaced[MAX_VIDEO_CARDS], last_has_signal[MAX_VIDEO_CARDS], last_is_connected[MAX_VIDEO_CARDS]; + unsigned last_frame_rate_nom[MAX_VIDEO_CARDS], last_frame_rate_den[MAX_VIDEO_CARDS]; }; InputStateInfo::InputStateInfo(const InputState &input_state) { - for (unsigned signal_num = 0; signal_num < MAX_CARDS; ++signal_num) { + for (unsigned signal_num = 0; signal_num < MAX_VIDEO_CARDS; ++signal_num) { 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; last_has_signal[signal_num] = false; + last_is_connected[signal_num] = false; continue; } const PBOFrameAllocator::Userdata *userdata = (const PBOFrameAllocator::Userdata *)frame.frame->userdata; @@ -64,6 +73,7 @@ InputStateInfo::InputStateInfo(const InputState &input_state) last_height[signal_num] = userdata->last_height[frame.field_number]; last_interlaced[signal_num] = userdata->last_interlaced; last_has_signal[signal_num] = userdata->last_has_signal; + last_is_connected[signal_num] = userdata->last_is_connected; last_frame_rate_nom[signal_num] = userdata->last_frame_rate_nom; last_frame_rate_den[signal_num] = userdata->last_frame_rate_den; } @@ -192,7 +202,8 @@ int EffectChain_add_live_input(lua_State* L) EffectChain *chain = (EffectChain *)luaL_checkudata(L, 1, "EffectChain"); bool override_bounce = checkbool(L, 2); bool deinterlace = checkbool(L, 3); - return wrap_lua_object(L, "LiveInputWrapper", theme, chain, override_bounce, deinterlace); + bmusb::PixelFormat pixel_format = global_flags.ten_bit_input ? bmusb::PixelFormat_10BitYCbCr : bmusb::PixelFormat_8BitYCbCr; + return wrap_lua_object(L, "LiveInputWrapper", theme, chain, pixel_format, override_bounce, deinterlace); } int EffectChain_add_effect(lua_State* L) @@ -251,25 +262,36 @@ int EffectChain_finalize(lua_State* L) if (is_main_chain) { YCbCrFormat output_ycbcr_format; - // We actually output 4:2:0 in the end, but chroma subsampling - // happens in a pass not run by Movit (see Mixer::subsample_chroma()). + // We actually output 4:2:0 and/or 4:2:2 in the end, but chroma subsampling + // happens in a pass not run by Movit (see ChromaSubsampler::subsample_chroma()). output_ycbcr_format.chroma_subsampling_x = 1; output_ycbcr_format.chroma_subsampling_y = 1; - // Rec. 709 would be the sane thing to do, but it seems many players - // (e.g. MPlayer and VLC) just default to BT.601 coefficients no matter - // what (see discussions in e.g. https://trac.ffmpeg.org/ticket/4978). - // We _do_ set the right flags, though, so that a player that works - // properly doesn't have to guess. - output_ycbcr_format.luma_coefficients = YCBCR_REC_601; + // This will be overridden if HDMI/SDI output is in force. + if (global_flags.ycbcr_rec709_coefficients) { + output_ycbcr_format.luma_coefficients = YCBCR_REC_709; + } else { + output_ycbcr_format.luma_coefficients = YCBCR_REC_601; + } + output_ycbcr_format.full_range = false; - output_ycbcr_format.num_levels = 256; + output_ycbcr_format.num_levels = 1 << global_flags.x264_bit_depth; + + GLenum type = global_flags.x264_bit_depth > 8 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE; + + chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, output_ycbcr_format, YCBCR_OUTPUT_SPLIT_Y_AND_CBCR, type); - chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, output_ycbcr_format, YCBCR_OUTPUT_SPLIT_Y_AND_CBCR); - chain->set_dither_bits(8); + // If we're using zerocopy video encoding (so the destination + // Y texture is owned by VA-API and will be unavailable for + // display), add a copy, where we'll only be using the Y component. + if (global_flags.use_zerocopy) { + chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, output_ycbcr_format, YCBCR_OUTPUT_INTERLEAVED, type); // Add a copy where we'll only be using the Y component. + } + chain->set_dither_bits(global_flags.x264_bit_depth > 8 ? 16 : 8); chain->set_output_origin(OUTPUT_ORIGIN_TOP_LEFT); + } else { + chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED); } - chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED); chain->finalize(); return 0; @@ -379,6 +401,16 @@ int InputStateInfo_get_has_signal(lua_State* L) return 1; } +int InputStateInfo_get_is_connected(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_is_connected[signal_num]); + return 1; +} + int InputStateInfo_get_frame_rate_nom(lua_State* L) { assert(lua_gettop(L) == 2); @@ -556,6 +588,7 @@ const luaL_Reg InputStateInfo_funcs[] = { { "get_height", InputStateInfo_get_height }, { "get_interlaced", InputStateInfo_get_interlaced }, { "get_has_signal", InputStateInfo_get_has_signal }, + { "get_is_connected", InputStateInfo_get_is_connected }, { "get_frame_rate_nom", InputStateInfo_get_frame_rate_nom }, { "get_frame_rate_den", InputStateInfo_get_frame_rate_den }, { NULL, NULL } @@ -563,8 +596,9 @@ const luaL_Reg InputStateInfo_funcs[] = { } // namespace -LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bool override_bounce, bool deinterlace) +LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bmusb::PixelFormat pixel_format, bool override_bounce, bool deinterlace) : theme(theme), + pixel_format(pixel_format), deinterlace(deinterlace) { ImageFormat inout_format; @@ -584,8 +618,9 @@ LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bool overri // Perhaps 601 was only to indicate the subsampling positions, not the // colorspace itself? Tested with a Lenovo X1 gen 3 as input. YCbCrFormat input_ycbcr_format; - input_ycbcr_format.chroma_subsampling_x = 2; + input_ycbcr_format.chroma_subsampling_x = (pixel_format == bmusb::PixelFormat_10BitYCbCr) ? 1 : 2; input_ycbcr_format.chroma_subsampling_y = 1; + input_ycbcr_format.num_levels = (pixel_format == bmusb::PixelFormat_10BitYCbCr) ? 1024 : 256; input_ycbcr_format.cb_x_position = 0.0; input_ycbcr_format.cr_x_position = 0.0; input_ycbcr_format.cb_y_position = 0.5; @@ -609,10 +644,12 @@ LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bool overri num_inputs = 1; } for (unsigned i = 0; i < num_inputs; ++i) { + // When using 10-bit input, we're converting to interleaved through v210Converter. + YCbCrInputSplitting splitting = (pixel_format == bmusb::PixelFormat_10BitYCbCr) ? YCBCR_INPUT_INTERLEAVED : YCBCR_INPUT_SPLIT_Y_AND_CBCR; if (override_bounce) { - inputs.push_back(new NonBouncingYCbCrInput(inout_format, input_ycbcr_format, WIDTH, HEIGHT, YCBCR_INPUT_SPLIT_Y_AND_CBCR)); + inputs.push_back(new NonBouncingYCbCrInput(inout_format, input_ycbcr_format, global_flags.width, global_flags.height, splitting)); } else { - inputs.push_back(new YCbCrInput(inout_format, input_ycbcr_format, WIDTH, HEIGHT, YCBCR_INPUT_SPLIT_Y_AND_CBCR)); + inputs.push_back(new YCbCrInput(inout_format, input_ycbcr_format, global_flags.width, global_flags.height, splitting)); } chain->add_input(inputs.back()); } @@ -661,8 +698,13 @@ void LiveInputWrapper::connect_signal(int signal_num) 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]); + assert(userdata->pixel_format == pixel_format); + if (pixel_format == bmusb::PixelFormat_10BitYCbCr) { + inputs[i]->set_texture_num(0, userdata->tex_444[frame.field_number]); + } else { + 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]); @@ -694,8 +736,8 @@ int call_num_channels(lua_State *L) } // namespace -Theme::Theme(const char *filename, ResourcePool *resource_pool, unsigned num_cards) - : resource_pool(resource_pool), num_cards(num_cards) +Theme::Theme(const string &filename, const vector &search_dirs, ResourcePool *resource_pool, unsigned num_cards) + : resource_pool(resource_pool), num_cards(num_cards), signal_to_card_mapping(global_flags.default_stream_mapping) { L = luaL_newstate(); luaL_openlibs(L); @@ -713,11 +755,36 @@ Theme::Theme(const char *filename, ResourcePool *resource_pool, unsigned num_car register_class("MixEffect", MixEffect_funcs); register_class("InputStateInfo", InputStateInfo_funcs); - // Run script. + // Run script. Search through all directories until we find a file that will load + // (as in, does not return LUA_ERRFILE); then run it. We store load errors + // from all the attempts, and show them once we know we can't find any of them. lua_settop(L, 0); - if (luaL_dofile(L, filename)) { - fprintf(stderr, "error: %s\n", lua_tostring(L, -1)); + vector errors; + bool success = false; + for (size_t i = 0; i < search_dirs.size(); ++i) { + string path = search_dirs[i] + "/" + filename; + int err = luaL_loadfile(L, path.c_str()); + if (err == 0) { + // Success; actually call the code. + if (lua_pcall(L, 0, LUA_MULTRET, 0)) { + fprintf(stderr, "Error when running %s: %s\n", path.c_str(), lua_tostring(L, -1)); + exit(1); + } + success = true; + break; + } + errors.push_back(lua_tostring(L, -1)); lua_pop(L, 1); + if (err != LUA_ERRFILE) { + // The file actually loaded, but failed to parse somehow. Abort; don't try the next one. + break; + } + } + + if (!success) { + for (const string &error : errors) { + fprintf(stderr, "%s\n", error.c_str()); + } exit(1); } assert(lua_gettop(L) == 0); @@ -810,11 +877,16 @@ string Theme::get_channel_name(unsigned channel) fprintf(stderr, "error running function `channel_name': %s\n", lua_tostring(L, -1)); exit(1); } + const char *ret = lua_tostring(L, -1); + if (ret == nullptr) { + fprintf(stderr, "function `channel_name' returned nil for channel %d\n", channel); + exit(1); + } - string ret = lua_tostring(L, -1); + string retstr = ret; lua_pop(L, 1); assert(lua_gettop(L) == 0); - return ret; + return retstr; } int Theme::get_channel_signal(unsigned channel) @@ -843,10 +915,16 @@ std::string Theme::get_channel_color(unsigned channel) exit(1); } - std::string ret = checkstdstring(L, -1); + const char *ret = lua_tostring(L, -1); + if (ret == nullptr) { + fprintf(stderr, "function `channel_color' returned nil for channel %d\n", channel); + exit(1); + } + + string retstr = ret; lua_pop(L, 1); assert(lua_gettop(L) == 0); - return ret; + return retstr; } bool Theme::get_supports_set_wb(unsigned channel) @@ -908,12 +986,28 @@ int Theme::map_signal(int signal_num) if (signal_to_card_mapping.count(signal_num)) { return signal_to_card_mapping[signal_num]; } - if (signal_num >= int(num_cards)) { - fprintf(stderr, "WARNING: Theme asked for input %d, but we only have %u card(s).\n", signal_num, num_cards); - fprintf(stderr, "Mapping to card %d instead.\n", signal_num % num_cards); + + int card_index; + if (global_flags.output_card != -1 && num_cards > 1) { + // Try to exclude the output card from the default card_index. + card_index = signal_num % (num_cards - 1); + if (card_index >= global_flags.output_card) { + ++card_index; + } + if (signal_num >= int(num_cards - 1)) { + fprintf(stderr, "WARNING: Theme asked for input %d, but we only have %u input card(s) (card %d is busy with output).\n", + signal_num, num_cards - 1, global_flags.output_card); + fprintf(stderr, "Mapping to card %d instead.\n", card_index); + } + } else { + card_index = signal_num % num_cards; + if (signal_num >= int(num_cards)) { + fprintf(stderr, "WARNING: Theme asked for input %d, but we only have %u card(s).\n", signal_num, num_cards); + fprintf(stderr, "Mapping to card %d instead.\n", card_index); + } } - signal_to_card_mapping[signal_num] = signal_num % num_cards; - return signal_num % num_cards; + signal_to_card_mapping[signal_num] = card_index; + return card_index; } void Theme::set_signal_mapping(int signal_num, int card_num)