From: Steinar H. Gunderson Date: Wed, 4 Nov 2015 00:53:01 +0000 (+0100) Subject: Add a static image input (fixed to 1280x720 for now, and uploaded rather inefficiently). X-Git-Tag: 1.0.0~166 X-Git-Url: https://git.sesse.net/?p=nageru;a=commitdiff_plain;h=46a29f577f39bf2000553e53683b34b7f50f8dbf Add a static image input (fixed to 1280x720 for now, and uploaded rather inefficiently). --- diff --git a/Makefile b/Makefile index 657f7e7..0b88d60 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,14 @@ CXX=g++ PKG_MODULES = Qt5Core Qt5Gui Qt5Widgets Qt5OpenGLExtensions Qt5OpenGL libusb-1.0 movit lua5.2 libmicrohttpd CXXFLAGS := -O2 -march=native -g -std=gnu++11 -Wall -Wno-deprecated-declarations -Werror -fPIC $(shell pkg-config --cflags $(PKG_MODULES)) -pthread -DMOVIT_SHADER_DIR=\"$(shell pkg-config --variable=shaderdir movit)\" -LDFLAGS=$(shell pkg-config --libs $(PKG_MODULES)) -lEGL -lGL -pthread -lva -lva-drm -lva-x11 -lX11 -lavformat -lavcodec -lavutil -lzita-resampler +LDFLAGS=$(shell pkg-config --libs $(PKG_MODULES)) -lEGL -lGL -pthread -lva -lva-drm -lva-x11 -lX11 -lavformat -lavcodec -lavutil -lswscale -lzita-resampler # Qt objects OBJS=glwidget.o main.o mainwindow.o vumeter.o lrameter.o vu_common.o OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o lrameter.moc.o # Mixer objects -OBJS += h264encode.o mixer.o bmusb/bmusb.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o resampler.o httpd.o ebu_r128_proc.o flags.o +OBJS += h264encode.o mixer.o bmusb/bmusb.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o resampler.o httpd.o ebu_r128_proc.o flags.o image_input.o %.o: %.cpp $(CXX) -MMD -MP $(CPPFLAGS) $(CXXFLAGS) -o $@ -c $< diff --git a/bg.jpeg b/bg.jpeg new file mode 100644 index 0000000..fdb51d9 Binary files /dev/null and b/bg.jpeg differ diff --git a/image_input.cpp b/image_input.cpp new file mode 100644 index 0000000..b4fc657 --- /dev/null +++ b/image_input.cpp @@ -0,0 +1,92 @@ +#include "image_input.h" + +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +using namespace std; + +ImageInput::ImageInput(const std::string &filename) + : movit::FlatInput({movit::COLORSPACE_sRGB, movit::GAMMA_sRGB}, movit::FORMAT_RGBA_POSTMULTIPLIED_ALPHA, + GL_UNSIGNED_BYTE, 1280, 720) // FIXME +{ + AVFormatContext *format_ctx = nullptr; + if (avformat_open_input(&format_ctx, filename.c_str(), nullptr, nullptr) != 0) { + fprintf(stderr, "%s: Error opening file\n", filename.c_str()); + exit(1); + } + + if (avformat_find_stream_info(format_ctx, nullptr) < 0) { + fprintf(stderr, "%s: Error finding stream info\n", filename.c_str()); + exit(1); + } + + int stream_index = -1; + for (unsigned i = 0; i < format_ctx->nb_streams; ++i) { + if (format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + stream_index = i; + break; + } + } + if (stream_index == -1) { + fprintf(stderr, "%s: No video stream found\n", filename.c_str()); + exit(1); + } + + AVCodecContext *codec_ctx = format_ctx->streams[stream_index]->codec; + AVCodec *codec = avcodec_find_decoder(codec_ctx->codec_id); + if (codec == nullptr) { + fprintf(stderr, "%s: Cannot find decoder\n", filename.c_str()); + exit(1); + } + if (avcodec_open2(codec_ctx, codec, nullptr) < 0) { + fprintf(stderr, "%s: Cannot open decoder\n", filename.c_str()); + exit(1); + } + + // Read packets until we have a frame. + int frame_finished = 0; + AVFrame *frame = av_frame_alloc(); + do { + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = nullptr; + pkt.size = 0; + if (av_read_frame(format_ctx, &pkt) < 0) { + fprintf(stderr, "%s: Cannot read frame\n", filename.c_str()); + exit(1); + } + if (pkt.stream_index != stream_index) { + continue; + } + + if (avcodec_decode_video2(codec_ctx, frame, &frame_finished, &pkt) < 0) { + fprintf(stderr, "%s: Cannot decode frame\n", filename.c_str()); + exit(1); + } + } while (!frame_finished); + + // TODO: Scale down if needed! + AVPicture pic; + avpicture_alloc(&pic, PIX_FMT_RGBA, frame->width, frame->height); + SwsContext *sws_ctx = sws_getContext(frame->width, frame->height, + (PixelFormat)frame->format, frame->width, frame->height, + PIX_FMT_RGBA, SWS_BICUBIC, nullptr, nullptr, nullptr); + if (sws_ctx == nullptr) { + fprintf(stderr, "%s: Could not create scaler context\n", filename.c_str()); + exit(1); + } + sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, pic.data, pic.linesize); + sws_freeContext(sws_ctx); + + size_t len = frame->width * frame->height * 4; + image_data.reset(new uint8_t[len]); + av_image_copy_to_buffer(image_data.get(), len, pic.data, pic.linesize, PIX_FMT_RGBA, frame->width, frame->height, 1); + set_pixel_data(image_data.get()); +} diff --git a/image_input.h b/image_input.h new file mode 100644 index 0000000..edc86c8 --- /dev/null +++ b/image_input.h @@ -0,0 +1,22 @@ +#ifndef _IMAGE_INPUT_H +#define _IMAGE_INPUT_H 1 + +#include +#include + +#include + + +// An output that takes its input from a static image, loaded with ffmpeg. +// comes from a single 2D array with chunky pixels. +class ImageInput : public movit::FlatInput { +public: + ImageInput(const std::string &filename); + + std::string effect_type_id() const override { return "ImageInput"; } + +private: + std::unique_ptr image_data; +}; + +#endif // !defined(_IMAGE_INPUT_H) diff --git a/theme.cpp b/theme.cpp index 4ea8cd7..bd12590 100644 --- a/theme.cpp +++ b/theme.cpp @@ -21,6 +21,8 @@ #include #include +#include "image_input.h" + namespace movit { class ResourcePool; } // namespace movit @@ -63,7 +65,8 @@ Effect *get_effect(lua_State *L, int idx) luaL_testudata(L, idx, "IntegralPaddingEffect") || luaL_testudata(L, idx, "OverlayEffect") || luaL_testudata(L, idx, "ResizeEffect") || - luaL_testudata(L, idx, "MixEffect")) { + luaL_testudata(L, idx, "MixEffect") || + luaL_testudata(L, idx, "ImageInput")) { return (Effect *)lua_touserdata(L, idx); } luaL_error(L, "Error: Index #%d was not an Effect type\n", idx); @@ -76,6 +79,13 @@ bool checkbool(lua_State* L, int idx) return lua_toboolean(L, idx); } +std::string checkstdstring(lua_State *L, int index) +{ + size_t len; + const char* cstr = lua_tolstring(L, index, &len); + return std::string(cstr, len); +} + int EffectChain_new(lua_State* L) { assert(lua_gettop(L) == 2); @@ -102,7 +112,11 @@ int EffectChain_add_effect(lua_State* L) // TODO: Better error reporting. Effect *effect = get_effect(L, 2); if (lua_gettop(L) == 2) { - chain->add_effect(effect); + if (effect->num_inputs() == 0) { + chain->add_input((Input *)effect); + } else { + chain->add_effect(effect); + } } else { vector inputs; for (int idx = 3; idx <= lua_gettop(L); ++idx) { @@ -179,6 +193,13 @@ int LiveInputWrapper_connect_signal(lua_State* L) return 0; } +int ImageInput_new(lua_State* L) +{ + assert(lua_gettop(L) == 1); + std::string filename = checkstdstring(L, 1); + return wrap_lua_object(L, "ImageInput", filename); +} + int WhiteBalanceEffect_new(lua_State* L) { assert(lua_gettop(L) == 0); @@ -225,12 +246,10 @@ int Effect_set_float(lua_State *L) { assert(lua_gettop(L) == 3); Effect *effect = (Effect *)get_effect(L, 1); - size_t len; - const char* cstr = lua_tolstring(L, 2, &len); - std::string key(cstr, len); + std::string key = checkstdstring(L, 2); float value = luaL_checknumber(L, 3); if (!effect->set_float(key, value)) { - luaL_error(L, "Effect refused set_float(\"%s\", %d) (invalid key?)", cstr, int(value)); + luaL_error(L, "Effect refused set_float(\"%s\", %d) (invalid key?)", key.c_str(), int(value)); } return 0; } @@ -239,12 +258,10 @@ int Effect_set_int(lua_State *L) { assert(lua_gettop(L) == 3); Effect *effect = (Effect *)get_effect(L, 1); - size_t len; - const char* cstr = lua_tolstring(L, 2, &len); - std::string key(cstr, len); + std::string key = checkstdstring(L, 2); float value = luaL_checknumber(L, 3); if (!effect->set_int(key, value)) { - luaL_error(L, "Effect refused set_int(\"%s\", %d) (invalid key?)", cstr, int(value)); + luaL_error(L, "Effect refused set_int(\"%s\", %d) (invalid key?)", key.c_str(), int(value)); } return 0; } @@ -253,15 +270,13 @@ int Effect_set_vec3(lua_State *L) { assert(lua_gettop(L) == 5); Effect *effect = (Effect *)get_effect(L, 1); - size_t len; - const char* cstr = lua_tolstring(L, 2, &len); - std::string key(cstr, len); + std::string key = checkstdstring(L, 2); float v[3]; v[0] = luaL_checknumber(L, 3); v[1] = luaL_checknumber(L, 4); v[2] = luaL_checknumber(L, 5); if (!effect->set_vec3(key, v)) { - luaL_error(L, "Effect refused set_vec3(\"%s\", %f, %f, %f) (invalid key?)", cstr, + luaL_error(L, "Effect refused set_vec3(\"%s\", %f, %f, %f) (invalid key?)", key.c_str(), v[0], v[1], v[2]); } return 0; @@ -271,16 +286,14 @@ int Effect_set_vec4(lua_State *L) { assert(lua_gettop(L) == 6); Effect *effect = (Effect *)get_effect(L, 1); - size_t len; - const char* cstr = lua_tolstring(L, 2, &len); - std::string key(cstr, len); + std::string key = checkstdstring(L, 2); float v[4]; v[0] = luaL_checknumber(L, 3); v[1] = luaL_checknumber(L, 4); v[2] = luaL_checknumber(L, 5); v[3] = luaL_checknumber(L, 6); if (!effect->set_vec4(key, v)) { - luaL_error(L, "Effect refused set_vec4(\"%s\", %f, %f, %f, %f) (invalid key?)", cstr, + luaL_error(L, "Effect refused set_vec4(\"%s\", %f, %f, %f, %f) (invalid key?)", key.c_str(), v[0], v[1], v[2], v[3]); } return 0; @@ -299,6 +312,15 @@ const luaL_Reg LiveInputWrapper_funcs[] = { { NULL, NULL } }; +const luaL_Reg ImageInput_funcs[] = { + { "new", ImageInput_new }, + { "set_float", Effect_set_float }, + { "set_int", Effect_set_int }, + { "set_vec3", Effect_set_vec3 }, + { "set_vec4", Effect_set_vec4 }, + { NULL, NULL } +}; + const luaL_Reg WhiteBalanceEffect_funcs[] = { { "new", WhiteBalanceEffect_new }, { "set_float", Effect_set_float }, @@ -414,6 +436,7 @@ Theme::Theme(const char *filename, ResourcePool *resource_pool, unsigned num_car register_class("EffectChain", EffectChain_funcs); register_class("LiveInputWrapper", LiveInputWrapper_funcs); + register_class("ImageInput", ImageInput_funcs); register_class("WhiteBalanceEffect", WhiteBalanceEffect_funcs); register_class("ResampleEffect", ResampleEffect_funcs); register_class("PaddingEffect", PaddingEffect_funcs); diff --git a/theme.lua b/theme.lua index 93f79ff..abba220 100644 --- a/theme.lua +++ b/theme.lua @@ -25,10 +25,12 @@ local preview_signal_num = 1 local INPUT0_SIGNAL_NUM = 0 local INPUT1_SIGNAL_NUM = 1 local SBS_SIGNAL_NUM = 2 +local STATIC_SIGNAL_NUM = 3 --- A “fake” signal number that signifies that we are fading from one input +-- “fake” signal numbers that signifies that we are fading from one input -- to the next. -local FADE_SIGNAL_NUM = 3 +local FADE_VTV_SIGNAL_NUM = 4 -- Video to/from video. +local FADE_VTP_SIGNAL_NUM = 5 -- Video to/from static picture. -- The main live chain. function make_sbs_chain(hq) @@ -86,15 +88,24 @@ local main_chain_hq = make_sbs_chain(true) local main_chain_lq = make_sbs_chain(false) -- A chain to fade between two inputs (live chain only) -local fade_chain_hq = EffectChain.new(16, 9) -local fade_chain_hq_input0 = fade_chain_hq:add_live_input(true) -local fade_chain_hq_wb0_effect = fade_chain_hq:add_effect(WhiteBalanceEffect.new()) -local fade_chain_hq_input1 = fade_chain_hq:add_live_input(true) -local fade_chain_hq_wb1_effect = fade_chain_hq:add_effect(WhiteBalanceEffect.new()) -fade_chain_hq_input0:connect_signal(0) -fade_chain_hq_input1:connect_signal(1) -local fade_chain_mix_effect = fade_chain_hq:add_effect(MixEffect.new(), fade_chain_hq_wb0_effect, fade_chain_hq_wb1_effect) -fade_chain_hq:finalize(true) +local fade_vtv_chain_hq = EffectChain.new(16, 9) +local fade_vtv_chain_hq_input0 = fade_vtv_chain_hq:add_live_input(true) +local fade_vtv_chain_hq_wb0_effect = fade_vtv_chain_hq:add_effect(WhiteBalanceEffect.new()) +local fade_vtv_chain_hq_input1 = fade_vtv_chain_hq:add_live_input(true) +local fade_vtv_chain_hq_wb1_effect = fade_vtv_chain_hq:add_effect(WhiteBalanceEffect.new()) +fade_vtv_chain_hq_input0:connect_signal(0) +fade_vtv_chain_hq_input1:connect_signal(1) +local fade_vtv_chain_mix_effect = fade_vtv_chain_hq:add_effect(MixEffect.new(), fade_vtv_chain_hq_wb0_effect, fade_vtv_chain_hq_wb1_effect) +fade_vtv_chain_hq:finalize(true) + +-- A chain to fade between an inputs and a picture (live chain only) +local fade_vtp_chain_hq = EffectChain.new(16, 9) +local fade_vtp_chain_hq_input0 = fade_vtp_chain_hq:add_live_input(true) +local fade_vtp_chain_hq_wb0_effect = fade_vtp_chain_hq:add_effect(WhiteBalanceEffect.new()) +local fade_vtp_chain_hq_input1 = fade_vtp_chain_hq:add_effect(ImageInput.new("bg.jpeg")) +fade_vtp_chain_hq_input0:connect_signal(0) +local fade_vtp_chain_mix_effect = fade_vtp_chain_hq:add_effect(MixEffect.new(), fade_vtp_chain_hq_wb0_effect, fade_vtp_chain_hq_input1) +fade_vtp_chain_hq:finalize(true) -- A chain to show a single input on screen (HQ version). local simple_chain_hq = EffectChain.new(16, 9) @@ -110,10 +121,20 @@ simple_chain_lq_input:connect_signal(0) -- First input card. Can be changed whe local simple_chain_lq_wb_effect = simple_chain_lq:add_effect(WhiteBalanceEffect.new()) simple_chain_lq:finalize(false) +-- A chain to show a single static picture on screen (HQ version). +local static_chain_hq = EffectChain.new(16, 9) +local static_chain_hq_input = static_chain_hq:add_effect(ImageInput.new("bg.jpeg")) +static_chain_hq:finalize(true) + +-- A chain to show a single static picture on screen (LQ version). +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) + -- 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() - return 3 + return 4 end -- Returns the name for each additional channel (starting from 2). @@ -125,6 +146,8 @@ function channel_name(channel) return "Input 2" elseif channel == 4 then return "Side-by-side" + elseif channel == 5 then + return "Static picture" end end @@ -151,7 +174,7 @@ function finish_transitions(t) end -- If live is fade but de-facto single, make it so. - if live_signal_num == FADE_SIGNAL_NUM and t >= transition_end then + if (live_signal_num == FADE_VTV_SIGNAL_NUM or live_signal_num == FADE_VTP_SIGNAL_NUM) and t >= transition_end then live_signal_num = fade_dst_signal end end @@ -169,8 +192,12 @@ function get_transitions(t) return {"Cut"} end - if (live_signal_num == INPUT0_SIGNAL_NUM and preview_signal_num == INPUT1_SIGNAL_NUM) or - (live_signal_num == INPUT1_SIGNAL_NUM and preview_signal_num == INPUT0_SIGNAL_NUM) then + if (live_signal_num == INPUT0_SIGNAL_NUM or + live_signal_num == INPUT1_SIGNAL_NUM or + live_signal_num == STATIC_SIGNAL_NUM) and + (preview_signal_num == INPUT0_SIGNAL_NUM or + preview_signal_num == INPUT1_SIGNAL_NUM or + preview_signal_num == STATIC_SIGNAL_NUM) then return {"Cut", "", "Fade"} end @@ -189,7 +216,7 @@ end function transition_clicked(num, t) if num == 0 then -- Cut. - if live_signal_num == FADE_SIGNAL_NUM then + if live_signal_num == FADE_VTV_SIGNAL_NUM or live_signal_num == FADE_VTP_SIGNAL_NUM then -- Ongoing fade; finish it immediately. finish_transitions(transition_end) end @@ -256,7 +283,17 @@ function transition_clicked(num, t) fade_src_signal = live_signal_num fade_dst_signal = preview_signal_num preview_signal_num = live_signal_num - live_signal_num = FADE_SIGNAL_NUM + live_signal_num = FADE_VTV_SIGNAL_NUM + elseif ((live_signal_num == INPUT0_SIGNAL_NUM or live_signal_num == INPUT1_SIGNAL_NUM) and + preview_signal_num == STATIC_SIGNAL_NUM) or + ((preview_signal_num == INPUT0_SIGNAL_NUM or preview_signal_num == INPUT1_SIGNAL_NUM) and + live_signal_num == STATIC_SIGNAL_NUM) then + transition_start = t + transition_end = t + 1.0 + fade_src_signal = live_signal_num + fade_dst_signal = preview_signal_num + preview_signal_num = live_signal_num + live_signal_num = FADE_VTP_SIGNAL_NUM else -- Fades involving SBS are ignored (we have no chain for it). end @@ -293,28 +330,38 @@ function get_chain(num, t, width, height) set_neutral_color_from_signal(simple_chain_hq_wb_effect, live_signal_num) end return simple_chain_hq, prepare - elseif live_signal_num == FADE_SIGNAL_NUM then -- Fade. + elseif live_signal_num == STATIC_SIGNAL_NUM then -- Static picture. prepare = function() - fade_chain_hq_input0:connect_signal(fade_src_signal) - set_neutral_color_from_signal(fade_chain_hq_wb0_effect, fade_src_signal) - fade_chain_hq_input1:connect_signal(fade_dst_signal) - set_neutral_color_from_signal(fade_chain_hq_wb1_effect, fade_dst_signal) - local tt = (t - transition_start) / (transition_end - transition_start) - if tt < 0.0 then - tt = 0.0 - elseif tt > 1.0 then - tt = 1.0 + end + return static_chain_hq, prepare + elseif live_signal_num == FADE_VTV_SIGNAL_NUM then -- Fade video-to-video. + prepare = function() + fade_vtv_chain_hq_input0:connect_signal(fade_src_signal) + set_neutral_color_from_signal(fade_vtv_chain_hq_wb0_effect, fade_src_signal) + fade_vtv_chain_hq_input1:connect_signal(fade_dst_signal) + set_neutral_color_from_signal(fade_vtv_chain_hq_wb1_effect, fade_dst_signal) + local tt = calc_fade_progress(t, transition_start, transition_end) + + fade_vtv_chain_mix_effect:set_float("strength_first", 1.0 - tt) + fade_vtv_chain_mix_effect:set_float("strength_second", tt) + end + return fade_vtv_chain_hq, prepare + elseif live_signal_num == FADE_VTP_SIGNAL_NUM then -- Fade video-to-picture (or picture-to-video). + prepare = function() + local tt + if fade_src_signal == STATIC_SIGNAL_NUM then + fade_vtp_chain_hq_input0:connect_signal(fade_dst_signal) + set_neutral_color_from_signal(fade_vtp_chain_hq_wb0_effect, fade_dst_signal) + tt = 1.0 - calc_fade_progress(t, transition_start, transition_end) + else + fade_vtp_chain_hq_input0:connect_signal(fade_src_signal) + set_neutral_color_from_signal(fade_vtp_chain_hq_wb0_effect, fade_src_signal) + tt = calc_fade_progress(t, transition_start, transition_end) end - - -- Make the fade look maybe a tad more natural, by pumping it - -- through a sigmoid function. - tt = 10.0 * tt - 5.0 - tt = 1.0 / (1.0 + math.exp(-tt)) - - fade_chain_mix_effect:set_float("strength_first", 1.0 - tt) - fade_chain_mix_effect:set_float("strength_second", tt) + fade_vtp_chain_mix_effect:set_float("strength_first", 1.0 - tt) + fade_vtp_chain_mix_effect:set_float("strength_second", tt) end - return fade_chain_hq, prepare + return fade_vtp_chain_hq, prepare end -- SBS code (live_signal_num == SBS_SIGNAL_NUM). @@ -363,6 +410,11 @@ function get_chain(num, t, width, height) end return main_chain_lq.chain, prepare end + if num == STATIC_SIGNAL_NUM + 2 then + prepare = function() + end + return static_chain_lq, prepare + end end function place_rectangle(resample_effect, resize_effect, padding_effect, x0, y0, x1, y1, screen_width, screen_height, input_width, input_height) @@ -526,3 +578,19 @@ function set_neutral_color_from_signal(effect, signal) set_neutral_color(effect, input1_neutral_color) end end + +function calc_fade_progress(t, transition_start, transition_end) + local tt = (t - transition_start) / (transition_end - transition_start) + if tt < 0.0 then + tt = 0.0 + elseif tt > 1.0 then + tt = 1.0 + end + + -- Make the fade look maybe a tad more natural, by pumping it + -- through a sigmoid function. + tt = 10.0 * tt - 5.0 + tt = 1.0 / (1.0 + math.exp(-tt)) + + return tt +end