Skip to content

Commit 4a23c68

Browse files
committed
out_ndi: Basic NDI Output.
I have a feeling that it's 4:2:2 or 4:2:0, but it works good enough for the Night of Science, I think.
1 parent 5e25a9a commit 4a23c68

File tree

4 files changed

+155
-1
lines changed

4 files changed

+155
-1
lines changed

GNUmakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ FLTMODS_AVAILABLE += flt_debug flt_gamma_correct flt_flip_x flt_flip_y flt_scale
2424
FLTMODS_AVAILABLE += flt_rot_90 flt_smapper flt_channel_reorder
2525

2626
OUTMODS_AVAILABLE := out_dummy out_sdl2 out_rpi_ws2812b out_udp out_fb out_rpi_hub75
27-
OUTMODS_AVAILABLE += out_sf75_bi_spidev out_ansi out_pixelflut
27+
OUTMODS_AVAILABLE += out_sf75_bi_spidev out_ansi out_pixelflut out_ndi
2828

2929
# List of modules to compile.
3030
GFXMODS_DEFAULT := gfx_twinkle gfx_gol gfx_rainbow gfx_math_sinpi gfx_plasma

src/modules/out_ndi.c

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#include <types.h>
2+
#include <oscore.h>
3+
#include <timers.h>
4+
#include <assert.h>
5+
#include <pthread.h>
6+
#include <string.h>
7+
#include <stdlib.h>
8+
#include <unistd.h>
9+
#include <stdatomic.h>
10+
#include <Processing.NDI.Lib.h> // NDI SDK headers
11+
12+
// Matrix size
13+
#ifndef MATRIX_X
14+
#error Define MATRIX_X as the matrix's X size.
15+
#endif
16+
17+
#ifndef MATRIX_Y
18+
#error Define MATRIX_Y as the matrix's Y size.
19+
#endif
20+
21+
#define FPS 60
22+
23+
static RGB *primary_buffer;
24+
static RGB *front_buffer;
25+
static RGB *back_buffer;
26+
static atomic_bool keep_running;
27+
static atomic_int front_or_back;
28+
29+
static NDIlib_send_instance_t ndi_send = NULL;
30+
static oscore_task ndi_task;
31+
32+
static void* send_task(void *arg);
33+
34+
int init(void) {
35+
assert(sizeof(RGB) == 4);
36+
37+
// Allocate memory for the buffers
38+
primary_buffer = (RGB*)malloc(MATRIX_X * MATRIX_Y * sizeof(RGB));
39+
front_buffer = (RGB*)malloc(MATRIX_X * MATRIX_Y * sizeof(RGB));
40+
back_buffer = (RGB*)malloc(MATRIX_X * MATRIX_Y * sizeof(RGB));
41+
42+
atomic_init(&keep_running, true);
43+
atomic_init(&front_or_back, true);
44+
45+
// Start the NDI sending task
46+
ndi_task = oscore_task_create("NDI Sender", send_task, NULL);
47+
return 0;
48+
}
49+
50+
int getx(int _modno) {
51+
return MATRIX_X;
52+
}
53+
54+
int gety(int _modno) {
55+
return MATRIX_Y;
56+
}
57+
58+
int set(int _modno, int x, int y, RGB color) {
59+
primary_buffer[(y * MATRIX_X + x)] = color;
60+
return 0;
61+
}
62+
63+
RGB get(int _modno, int x, int y) {
64+
return primary_buffer[(y * MATRIX_X + x)];
65+
}
66+
67+
int clear(int _modno) {
68+
// Clear the primary buffer
69+
memset(primary_buffer, 0, MATRIX_X * MATRIX_Y * sizeof(RGB));
70+
return 0;
71+
}
72+
73+
int render(void) {
74+
// Get current buffer
75+
int current_fob = atomic_load(&front_or_back);
76+
RGB *current_buffer = current_fob ? front_buffer : back_buffer;
77+
78+
// Copy primary buffer to current buffer
79+
memcpy(current_buffer, primary_buffer, MATRIX_X * MATRIX_Y * sizeof(RGB));
80+
81+
// Flip buffers
82+
atomic_store(&front_or_back, !current_fob);
83+
84+
return 0;
85+
}
86+
87+
oscore_time wait_until(int _modno, oscore_time desired_usec) {
88+
#ifdef CIMODE
89+
return desired_usec;
90+
#else
91+
return timers_wait_until_core(desired_usec);
92+
#endif
93+
}
94+
95+
void wait_until_break(int _modno) {
96+
#ifndef CIMODE
97+
timers_wait_until_break_core();
98+
#endif
99+
}
100+
101+
void deinit(int _modno) {
102+
// Stop the NDI Sender task
103+
atomic_store(&keep_running, false);
104+
oscore_task_join(&ndi_task);
105+
106+
// Clean up resources
107+
NDIlib_send_destroy(ndi_send);
108+
NDIlib_destroy();
109+
110+
free(primary_buffer);
111+
free(front_buffer);
112+
free(back_buffer);
113+
}
114+
115+
// NDI sending function (runs in a separate task)
116+
static void* send_task(void *arg) {
117+
// Create the NDI sender
118+
NDIlib_initialize();
119+
NDIlib_send_create_t send_create_desc = {0};
120+
send_create_desc.p_ndi_name = "SLED";
121+
send_create_desc.clock_video = false;
122+
send_create_desc.clock_audio = false;
123+
ndi_send = NDIlib_send_create(&send_create_desc);
124+
125+
// Prepopulate frame with static info.
126+
NDIlib_video_frame_v2_t video_frame;
127+
video_frame.xres = MATRIX_X;
128+
video_frame.yres = MATRIX_Y;
129+
video_frame.line_stride_in_bytes = MATRIX_X * sizeof(RGB);
130+
video_frame.picture_aspect_ratio = (float)MATRIX_X / (float)MATRIX_Y;
131+
video_frame.FourCC = NDIlib_FourCC_type_RGBX;
132+
video_frame.frame_format_type = NDIlib_frame_format_type_progressive;
133+
video_frame.frame_rate_N = FPS * 1000;
134+
video_frame.frame_rate_D = 1000;
135+
136+
while (atomic_load(&keep_running)) {
137+
// Get current buffer
138+
int current_fob = atomic_load(&front_or_back);
139+
140+
// Send a video frame with the actual data.
141+
video_frame.p_data = (uint8_t*) (current_fob ? front_buffer : back_buffer);
142+
NDIlib_send_send_video_async_v2(ndi_send, &video_frame);
143+
144+
// Wait for the next frame
145+
usleep(1000000 / FPS);
146+
}
147+
148+
// Force sync for last frame.
149+
NDIlib_send_send_video_async_v2(ndi_send, NULL);
150+
return NULL;
151+
}
152+

src/modules/out_ndi.incs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-I"$NDI_SDK_DIR/include"

src/modules/out_ndi.libs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-L"$NDI_SDK_DIR/lib" -L/usr/local/lib -lndi

0 commit comments

Comments
 (0)