From c81f9cdba4d0ffaa4f3628292f15f59debddef05 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Wed, 11 Aug 2021 01:44:40 +0200 Subject: [PATCH] fxlink: add video capture with frames displayed in an SDL2 window --- CMakeLists.txt | 9 ++++- fxlink/config.h.in | 3 ++ fxlink/interactive.c | 19 +++++++++- fxlink/sdl2.c | 82 ++++++++++++++++++++++++++++++++++++++++++++ fxlink/sdl2.h | 23 +++++++++++++ 5 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 fxlink/sdl2.c create mode 100644 fxlink/sdl2.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f802f8d..1cc155c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ cmake_minimum_required(VERSION 3.15) project(fxSDK VERSION 2.5.2 LANGUAGES C) option(FXLINK_DISABLE_UDISKS2 "Do not build the UDisks2-based features of fxlink") +option(FXLINK_DISABLE_SDL2 "Do not build the SDL2-based features of fxlink") find_package(PkgConfig REQUIRED) pkg_check_modules(libpng REQUIRED libpng16 IMPORTED_TARGET) @@ -12,6 +13,9 @@ pkg_check_modules(libusb REQUIRED libusb-1.0 IMPORTED_TARGET) if(NOT FXLINK_DISABLE_UDISKS2) pkg_check_modules(udisks2 REQUIRED udisks2 IMPORTED_TARGET) endif() +if(NOT FXLINK_DISABLE_SDL2) + pkg_check_modules(sdl2 REQUIRED sdl2 IMPORTED_TARGET) +endif() set(CMAKE_INSTALL_MESSAGE LAZY) set(SRC "${CMAKE_CURRENT_SOURCE_DIR}") @@ -36,12 +40,15 @@ add_custom_target(fxsdk ALL DEPENDS "${BIN}/fxsdk.sh") configure_file(fxlink/config.h.in "${BIN}/include/fxlink/config.h") add_executable(fxlink fxlink/usb.c fxlink/filter.c fxlink/interactive.c fxlink/main.c fxlink/png.c fxlink/properties.c fxlink/ud2.c fxlink/util.c - fxlink/protocol.c) + fxlink/protocol.c fxlink/sdl2.c) target_link_libraries(fxlink PkgConfig::libpng PkgConfig::libusb) # PkgConfig::libudev target_include_directories(fxlink PRIVATE "${BIN}/include/fxlink") if(NOT FXLINK_DISABLE_UDISKS2) target_link_libraries(fxlink PkgConfig::udisks2) endif() +if(NOT FXLINK_DISABLE_SDL2) + target_link_libraries(fxlink PkgConfig::sdl2) +endif() # Install rules diff --git a/fxlink/config.h.in b/fxlink/config.h.in index 377a65e..70a58ef 100644 --- a/fxlink/config.h.in +++ b/fxlink/config.h.in @@ -8,4 +8,7 @@ /* Disables UDisks2 interfaces for systems that don't use it. */ #cmakedefine FXLINK_DISABLE_UDISKS2 +/* Disable SDL2 interfaces. */ +#cmakedefine FXLINK_DISABLE_SDL2 + #endif /* FXLINK_CONFIG_H */ diff --git a/fxlink/interactive.c b/fxlink/interactive.c index 6e7aa73..781fae9 100644 --- a/fxlink/interactive.c +++ b/fxlink/interactive.c @@ -6,6 +6,7 @@ #include "protocol.h" #include "usb.h" #include "png.h" +#include "sdl2.h" #include #include @@ -88,6 +89,18 @@ static void message_finish(message_t *msg) printf("------------------\n"); return; } + + if(!strncmp(msg->header.type, "video", 16)) { + usb_fxlink_image_t *img = (void *)msg->output; + uint8_t **row_pointers = fxlink_protocol_decode_image(msg); + +#ifndef FXLINK_DISABLE_SDL2 + sdl2_stream(row_pointers, img->width, img->height); +#else + warn("SDL2 support disabled, skipping video frame!"); +#endif + return; + } } /* Default to saving to a blob */ @@ -170,9 +183,13 @@ int main_interactive(filter_t *filter, delay_t *delay, libusb_context *context) while(1) { +#ifndef FXLINK_DISABLE_SDL2 + sdl2_tick(); +#endif + int transferred = -1; rc = libusb_bulk_transfer(dh, 0x81, buffer + buffer_size, 2048, - &transferred, 2000); + &transferred, 500); if(rc == LIBUSB_ERROR_NO_DEVICE) { printf("Disconnected, leaving.\n"); diff --git a/fxlink/sdl2.c b/fxlink/sdl2.c new file mode 100644 index 0000000..7b3590f --- /dev/null +++ b/fxlink/sdl2.c @@ -0,0 +1,82 @@ +#include "config.h" +#ifndef FXLINK_DISABLE_SDL2 + +#include "sdl2.h" +#include "util.h" + +static SDL_Window *window = NULL; + +static int init(size_t width, size_t height) +{ + if(!SDL_WasInit(SDL_INIT_VIDEO)) { + int rc = SDL_Init(SDL_INIT_VIDEO); + if(rc < 0) + return err("Cannot initialize SDL: %s\n", SDL_GetError()); + } + + window = SDL_CreateWindow("fxlink", SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, width, height, 0); + return 0; +} + +__attribute__((destructor)) +static void quit(void) +{ + if(!window) + return; + SDL_DestroyWindow(window); + window = NULL; +} + +/* Generate an RGB888 surface from image data. */ +static SDL_Surface *surface_for_image(uint8_t **RGB888_rows, int width, + int height) +{ + /* Little endian setup for RGB */ + SDL_Surface *s = SDL_CreateRGBSurface(0, width, height, 24, + 0x000000ff, 0x0000ff00, 0x0000ff00, 0); + if(!s) { + err("Cannot create surface for image"); + return NULL; + } + + for(int i = 0; i < height; i++) + memcpy(s->pixels + i * s->pitch, RGB888_rows[i], width * 3); + + return s; +} + +void sdl2_stream(uint8_t **RGB888_rows, int width, int height) +{ + if(!window && init(width, height)) + return; + + int current_w, current_h; + SDL_GetWindowSize(window, ¤t_w, ¤t_h); + if(current_w != width || current_h != height) + SDL_SetWindowSize(window, width, height); + + SDL_Surface *src = surface_for_image(RGB888_rows, width, height); + if(!src) + return; + + SDL_Surface *dst = SDL_GetWindowSurface(window); + SDL_BlitSurface(src, NULL, dst, NULL); + SDL_FreeSurface(src); + + SDL_UpdateWindowSurface(window); +} + +void sdl2_tick(void) +{ + if(!window) + return; + + SDL_Event e; + while(SDL_PollEvent(&e)) { + if(e.type == SDL_QUIT) + quit(); + } +} + +#endif /* FXLINK_DISABLE_SDL2 */ diff --git a/fxlink/sdl2.h b/fxlink/sdl2.h new file mode 100644 index 0000000..5133870 --- /dev/null +++ b/fxlink/sdl2.h @@ -0,0 +1,23 @@ +//--- +// fxlink:sdl2 - SDL2 functions +//--- + +#ifndef FXLINK_SDL2_H +#define FXLINK_SDL2_H + +#ifndef FXLINK_DISABLE_SDL2 + +#include + +/* sdl2_stream(): Display a streaming image on the window + This function opens or reuses an SDL2 window to display an image. */ +void sdl2_stream(uint8_t **RGB888_rows, int width, int height); + +/* sdl2_tick(): Handle SDL events + This just needs to be called regularly from the main thread, to respond to + events on the window. */ +void sdl2_tick(void); + +#endif /* FXLINK_DISABLE_SDL2 */ + +#endif /* FXLINK_SDL2_H */