diff --git a/GNUmakefile b/GNUmakefile index d9e36e6..2c494d0 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -24,7 +24,7 @@ FLTMODS_AVAILABLE += flt_debug flt_gamma_correct flt_flip_x flt_flip_y flt_scale FLTMODS_AVAILABLE += flt_rot_90 flt_smapper flt_channel_reorder OUTMODS_AVAILABLE := out_dummy out_sdl2 out_rpi_ws2812b out_udp out_fb out_rpi_hub75 -OUTMODS_AVAILABLE += out_sf75_bi_spidev out_ansi out_pixelflut +OUTMODS_AVAILABLE += out_sf75_bi_spidev out_ansi out_pixelflut out_ndi # List of modules to compile. GFXMODS_DEFAULT := gfx_twinkle gfx_gol gfx_rainbow gfx_math_sinpi gfx_plasma diff --git a/src/modules/out_ndi.c b/src/modules/out_ndi.c new file mode 100644 index 0000000..ee8b1d4 --- /dev/null +++ b/src/modules/out_ndi.c @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // NDI SDK headers + +// Matrix size +#ifndef MATRIX_X +#error Define MATRIX_X as the matrix's X size. +#endif + +#ifndef MATRIX_Y +#error Define MATRIX_Y as the matrix's Y size. +#endif + +#define NDI_FPS 60 +#define NDI_SCALE_FACTOR 2 // Pixels per Axis + +static RGB *primary_buffer; +static RGB *front_buffer; +static RGB *back_buffer; +static atomic_bool keep_running; +static atomic_int front_or_back; + +static NDIlib_send_instance_t ndi_send = NULL; +static oscore_task ndi_task; + +static void* send_task(void *arg); + +static void upscale_buffer(const RGB *src, int w, int h, int scale, RGB *dst) { + int w2 = w * scale; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + RGB px = src[y*w + x]; + int base = (y*scale)*w2 + x*scale; + for (int dy = 0; dy < scale; dy++) { + for (int dx = 0; dx < scale; dx++) { + dst[base + dy*w2 + dx] = px; + } + } + } + } +} + +int init(void) { + assert(sizeof(RGB) == 4); + + // Allocate memory for the buffers + primary_buffer = (RGB*)malloc(MATRIX_X * MATRIX_Y * sizeof(RGB)); + front_buffer = (RGB*)malloc(MATRIX_X * MATRIX_Y * sizeof(RGB) * (2*NDI_SCALE_FACTOR)); + back_buffer = (RGB*)malloc(MATRIX_X * MATRIX_Y * sizeof(RGB) * (2*NDI_SCALE_FACTOR)); + + atomic_init(&keep_running, true); + atomic_init(&front_or_back, true); + + // Start the NDI sending task + ndi_task = oscore_task_create("NDI Sender", send_task, NULL); + return 0; +} + +int getx(int _modno) { + return MATRIX_X; +} + +int gety(int _modno) { + return MATRIX_Y; +} + +int set(int _modno, int x, int y, RGB color) { + primary_buffer[(y * MATRIX_X + x)] = color; + return 0; +} + +RGB get(int _modno, int x, int y) { + return primary_buffer[(y * MATRIX_X + x)]; +} + +int clear(int _modno) { + // Clear the primary buffer + memset(primary_buffer, 0, MATRIX_X * MATRIX_Y * sizeof(RGB)); + return 0; +} + +int render(void) { + // Get current buffer + int current_fob = atomic_load(&front_or_back); + RGB *alt_buffer = current_fob ? back_buffer : front_buffer; + + // Upscale primary buffer to alternate buffer + upscale_buffer(primary_buffer, MATRIX_X, MATRIX_Y, NDI_SCALE_FACTOR, alt_buffer); + + // Flip buffers + atomic_store(&front_or_back, !current_fob); + + return 0; +} + +oscore_time wait_until(int _modno, oscore_time desired_usec) { +#ifdef CIMODE + return desired_usec; +#else + return timers_wait_until_core(desired_usec); +#endif +} + +void wait_until_break(int _modno) { +#ifndef CIMODE + timers_wait_until_break_core(); +#endif +} + +void deinit(int _modno) { + // Stop the NDI Sender task + atomic_store(&keep_running, false); + oscore_task_join(&ndi_task); + + // Clean up resources + NDIlib_send_destroy(ndi_send); + NDIlib_destroy(); + + free(primary_buffer); + free(front_buffer); + free(back_buffer); +} + +// NDI sending function (runs in a separate task) +static void* send_task(void *arg) { + // Create the NDI sender + NDIlib_initialize(); + NDIlib_send_create_t send_create_desc = {0}; + send_create_desc.p_ndi_name = "SLED"; + send_create_desc.clock_video = false; + send_create_desc.clock_audio = false; + ndi_send = NDIlib_send_create(&send_create_desc); + + // Prepopulate frame with static info. + NDIlib_video_frame_v2_t video_frame; + video_frame.xres = MATRIX_X * NDI_SCALE_FACTOR; + video_frame.yres = MATRIX_Y * NDI_SCALE_FACTOR; + video_frame.line_stride_in_bytes = MATRIX_X * NDI_SCALE_FACTOR * sizeof(RGB); + video_frame.picture_aspect_ratio = (float)MATRIX_X / (float)MATRIX_Y; + video_frame.FourCC = NDIlib_FourCC_type_RGBX; + video_frame.frame_format_type = NDIlib_frame_format_type_progressive; + video_frame.frame_rate_N = NDI_FPS * 1000; + video_frame.frame_rate_D = 1000; + + while (atomic_load(&keep_running)) { + // Get current buffer + int current_fob = atomic_load(&front_or_back); + + // Send a video frame with the current buffer's data. + video_frame.p_data = (uint8_t*) (current_fob ? front_buffer : back_buffer); + NDIlib_send_send_video_async_v2(ndi_send, &video_frame); + + // Wait for the next frame + usleep(1000000 / NDI_FPS); + } + + // Force sync for last frame. + NDIlib_send_send_video_async_v2(ndi_send, NULL); + return NULL; +} + diff --git a/src/modules/out_ndi.incs b/src/modules/out_ndi.incs new file mode 100644 index 0000000..fd5c190 --- /dev/null +++ b/src/modules/out_ndi.incs @@ -0,0 +1 @@ +-I"$NDI_SDK_DIR/include" diff --git a/src/modules/out_ndi.libs b/src/modules/out_ndi.libs new file mode 100644 index 0000000..897f475 --- /dev/null +++ b/src/modules/out_ndi.libs @@ -0,0 +1 @@ +-L"$NDI_SDK_DIR/lib" -L/usr/local/lib -lndi