UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
ndi.c
Go to the documentation of this file.
1
5/*
6 * Copyright (c) 2019-2024 CESNET
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, is permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * 3. Neither the name of CESNET nor the names of its contributors may be
21 * used to endorse or promote products derived from this software without
22 * specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
26 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
27 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
28 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
29 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
34 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
35 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 */
45#include <assert.h> // for assert
46#include <limits.h> // for INT_MAX
47#include <stdbool.h> // for true, false, bool
48#include <stdint.h> // for uint16_t, uintptr_t, uint8_t, int16_t
49#include <stdio.h> // for printf, snprintf
50#include <stdlib.h> // for NULL, free, size_t, calloc, malloc, strtol
51#include <string.h> // for memcpy, strlen, strncpy, strstr, strcmp
52#include <strings.h> // for strcasecmp
53
54#include "audio/types.h"
55#include "audio/utils.h"
56#include "debug.h"
57#include "host.h"
58#include "lib_common.h"
59#include "ndi_common.h"
60#include "types.h"
61#include "utils/color_out.h"
62#include "utils/macros.h" // OPTIMIZED_FOR
63#include "utils/misc.h"
64#include "video.h"
65#include "video_display.h"
66
67#define DEFAULT_AUDIO_LEVEL 0
68#define MOD_NAME "[NDI disp.] "
69
70typedef void ndi_disp_convert_t(const struct video_frame *f, char *out);
71static void ndi_disp_convert_Y216_to_P216(const struct video_frame *f, char *out);
72static void ndi_disp_convert_Y416_to_PA16(const struct video_frame *f, char *out);
73
76 const NDIlib_t *NDIlib;
78 NDIlib_send_instance_t pNDI_send;
79 NDIlib_video_frame_v2_t NDI_video_frame;
84
87};
88
89static void display_ndi_probe(struct device_info **available_cards, int *count, void (**deleter)(void *))
90{
91 *count = 1;
92 *available_cards = calloc(1, sizeof(struct device_info));
93 strncpy((*available_cards)[0].dev, "", sizeof (*available_cards)[0].dev - 1);
94 strncpy((*available_cards)[0].name, "NDI", sizeof (*available_cards)[0].name - 1);
95 strncpy((*available_cards)[0].extra, "\"embeddedAudioAvailable\":\"t\"", sizeof (*available_cards)[0].extra - 1);
96 (*available_cards)[0].repeatable = true;
97 *deleter = free;
98}
99
100static const struct {
102 NDIlib_FourCC_video_type_e ndi_fourcc;
104} codec_mapping[] = {
105 { RGBA, NDIlib_FourCC_type_RGBA, NULL },
106 { UYVY, NDIlib_FourCC_type_UYVY, NULL },
107 { I420, NDIlib_FourCC_video_type_I420, NULL },
108 { Y216, NDIlib_FourCC_type_P216, ndi_disp_convert_Y216_to_P216 },
109 { Y416, NDIlib_FourCC_type_PA16, ndi_disp_convert_Y416_to_PA16 },
111
113{
114 struct display_ndi *s = (struct display_ndi *) state;
115
116 s->desc = desc;
117 free(s->convert_buffer);
119
120 s->NDI_video_frame.xres = s->desc.width;
121 s->NDI_video_frame.yres = s->desc.height;
122 for (size_t i = 0; i < sizeof codec_mapping / sizeof codec_mapping[0]; ++i) {
124 s->NDI_video_frame.FourCC = codec_mapping[i].ndi_fourcc;
125 s->convert = codec_mapping[i].convert;
126 }
127 }
128 assert(s->NDI_video_frame.FourCC != 0);
129 s->NDI_video_frame.frame_rate_N = get_framerate_n(desc.fps);
130 s->NDI_video_frame.frame_rate_D = get_framerate_d(desc.fps);
131 s->NDI_video_frame.frame_format_type = desc.interlacing == PROGRESSIVE ? NDIlib_frame_format_type_progressive : NDIlib_frame_format_type_interleaved;
132 s->NDI_video_frame.timecode = NDIlib_send_timecode_synthesize;
133
134 return true;
135}
136
137static void usage()
138{
139 printf("Usage:\n");
140 color_printf(TERM_BOLD TERM_FG_RED "\t-d ndi" TERM_FG_RESET "[:help][:name=<n>][:audio_level=<x>]\n" TERM_RESET);
141 printf("\twhere\n");
142 color_printf(TERM_BOLD "\t\tname\n" TERM_RESET "\t\t\tthe name of the server\n");
143 color_printf(TERM_BOLD "\t\taudio_level\n" TERM_RESET "\t\t\taudio headroom above reference level (in dB, or mic/line, default %d)\n", DEFAULT_AUDIO_LEVEL);
144 color_printf("\n");
145}
146
152{
153 const char *param = get_commandline_param("color");
154 if (param == NULL || strlen(param) != 2) {
155 return NULL;
156 }
157 int eotf = strtol(param + 1, NULL, 16);
158 const char *space = NULL;
159 switch (eotf) {
160 case 0: return NULL;
161 case 1: space = "Default"; break;
162 case 2: space = "HLG"; break;
163 case 3: space = "PQ"; break;
164 default:
165 log_msg(LOG_LEVEL_ERROR, "Wrong/unsupported HDR EOTF value %d!\n", eotf);
166 return NULL;
167 }
168 size_t metadata_len = 128;
169 char *out = malloc(metadata_len);
170 snprintf(out, metadata_len, "<HDR_PROFILE>%s</HDR_PROFILE>", space);
171 return out;
172}
173
174#define BEGIN_TRY int ret = 0; do
175#define END_TRY while(0);
176#define THROW(x) ret = (x); break;
177#define COMMON
178#define CATCH(x) if (ret == (x))
179#define FAIL (-1)
180#define HELP_SHOWN 1
181static void *display_ndi_init(struct module *parent, const char *fmt, unsigned int flags)
182{
183 UNUSED(flags);
184 UNUSED(parent);
186
187 char *fmt_copy = NULL;
188 struct display_ndi *s = calloc(1, sizeof(struct display_ndi));
190
191 BEGIN_TRY {
192 fmt_copy = strdup(fmt);
193 assert(fmt_copy != NULL);
194
195 const char *ndi_name = NULL;
196 char *tmp = fmt_copy, *item, *save_ptr;
197 while ((item = strtok_r(tmp, ":", &save_ptr)) != NULL) {
198 if (strcmp(item, "help") == 0) {
199 usage();
201 }
202 if (strstr(item, "audio_level=") != NULL) {
203 char *val = item + strlen("audio_level=");
204 if (strcasecmp(val, "mic") == 0) {
205 s->audio_level = 0;
206 } else if (strcasecmp(val, "line") == 0) {
207 s->audio_level = 20; // NOLINT
208 } else {
209 char *endptr = NULL;
210 long val_num = strtol(val, &endptr, 0);
211 if (val_num < 0 || val_num >= INT_MAX || *val == '\0' || *endptr != '\0') {
212 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Wrong value: %s!\n", val);
213 THROW(FAIL);
214 }
215 s->audio_level = val_num; // NOLINT
216 }
217 } else if (strstr(item, "name=") != NULL) {
218 ndi_name = item + strlen("name=");
219 } else {
220 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unknown option: %s!\n", item);
221 THROW(FAIL);
222 }
223 tmp = NULL;
224 }
225
226 s->NDIlib = NDIlib_load(&s->lib);
227 if (s->NDIlib == NULL) {
228 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot open NDI library!\n");
229 THROW(FAIL);
230 }
231
232 printf("%s\n", s->NDIlib->version());
233
234 if (!s->NDIlib->initialize()) {
235 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot initialize NDI library!\n");
236 THROW(FAIL);
237 }
238
239 NDIlib_send_create_t NDI_send_create_desc = { .p_ndi_name = ndi_name, .p_groups = NULL, .clock_video = false, .clock_audio = false };
240 s->pNDI_send = s->NDIlib->send_create(&NDI_send_create_desc);
241 if (s->pNDI_send == NULL) {
242 THROW(FAIL);
243 }
244 } END_TRY
245 COMMON {
246 free(fmt_copy);
247 }
249 free(s);
250 return INIT_NOERR;
251 }
252 CATCH(FAIL) {
253 free(s);
254 return NULL;
255 }
256
258
259 return s;
260}
261
262static void display_ndi_done(void *state)
263{
264 struct display_ndi *s = (struct display_ndi *) state;
265
266 s->NDIlib->send_destroy(s->pNDI_send);
267 free(s->convert_buffer);
268 s->NDIlib->destroy();
271 free(s->video_metadata);
272 free(s);
273}
274
275static struct video_frame *display_ndi_getf(void *state)
276{
277 struct display_ndi *s = (struct display_ndi *) state;
278
279 return vf_alloc_desc_data(s->desc);
280}
281
282static void ndi_disp_convert_Y216_to_P216(const struct video_frame *f, char *out)
283{
284 assert((uintptr_t) out % 2 == 0);
285
286 uint16_t *in = (uint16_t *)(void *) f->tiles[0].data;
287 uint16_t *out_y = (uint16_t *)(void *) out;
288 uint16_t *out_cb_cr = (uint16_t *)(void *) out + f->tiles[0].width * f->tiles[0].height;
289
290 for (unsigned int i = 0; i < f->tiles[0].height; ++i) {
291 unsigned int width = f->tiles[0].width;
292 OPTIMIZED_FOR (unsigned int j = 0; j < (width + 1) / 2; j += 1) {
293 *out_y++ = *in++;
294 *out_cb_cr++ = *in++;
295 *out_y++ = *in++;
296 *out_cb_cr++ = *in++;
297 }
298 }
299}
300
301static void ndi_disp_convert_Y416_to_PA16(const struct video_frame *f, char *out)
302{
303 assert((uintptr_t) out % 2 == 0);
304
305 uint16_t *in = (uint16_t *)(void *) f->tiles[0].data;
306 uint16_t *out_y = (uint16_t *)(void *) out;
307 uint16_t *out_cb_cr = out_y + (size_t) f->tiles[0].width * f->tiles[0].height;
308 uint16_t *out_a = out_cb_cr + (size_t) (f->tiles[0].width + 1) / 2 * f->tiles[0].height;
309
310 for (unsigned int i = 0; i < f->tiles[0].height; ++i) {
311 unsigned int width = f->tiles[0].width;
312 OPTIMIZED_FOR (unsigned int j = 0; j < (width + 1) / 2; j += 1) {
313 *out_a++ = in[0];
314 *out_y++ = in[1];
315 *out_cb_cr++ = (in[2] + in[6]) / 2;
316 *out_a++ = in[4];
317 *out_y++ = in[5];
318 *out_cb_cr++ = (in[3] + in[7]) / 2;
319 in += 8;
320 }
321 }
322}
323
327static bool display_ndi_putf(void *state, struct video_frame *frame, long long flag)
328{
329 struct display_ndi *s = (struct display_ndi *) state;
330
331 if (frame == NULL) {
332 return true;
333 }
334
335 if (flag == PUTF_DISCARD) {
336 vf_free(frame);
337 return true;
338 }
339
340 s->NDIlib->send_send_video_v2(s->pNDI_send, NULL); // wait for the async frame to be processed
342 s->send_frame = NULL;
343
344 if (s->convert != NULL) {
346 s->NDI_video_frame.p_data = (uint8_t *) s->convert_buffer;
347 vf_free(frame);
348 } else {
349 s->NDI_video_frame.p_data = (uint8_t *) frame->tiles[0].data;
350 s->send_frame = frame;
351 }
352
353 s->NDIlib->send_send_video_async_v2(s->pNDI_send, &s->NDI_video_frame);
354
355 return true;
356}
357
358static bool display_ndi_get_property(void *state, int property, void *val, size_t *len)
359{
360 UNUSED(state);
361 codec_t codecs[sizeof codec_mapping / sizeof codec_mapping[0]];
362 int rgb_shift[] = {0, 8, 16};
363 enum interlacing_t supported_il_modes[] = {PROGRESSIVE, INTERLACED_MERGED};
364
365 for (size_t i = 0; i < sizeof codec_mapping / sizeof codec_mapping[0]; ++i) {
366 codecs[i] = codec_mapping[i].ug_codec;
367 }
368
369 switch (property) {
371 if (sizeof codecs <= *len) {
372 memcpy(val, codecs, sizeof codecs);
373 *len = sizeof codecs;
374 } else {
375 return false;
376 }
377 break;
379 if (sizeof rgb_shift > *len) {
380 return false;
381 }
382 memcpy(val, rgb_shift, sizeof rgb_shift);
383 *len = sizeof rgb_shift;
384 break;
386 *(int *) val = PITCH_DEFAULT;
387 *len = sizeof(int);
388 break;
391 *len = sizeof(int);
392 break;
394 if (sizeof(supported_il_modes) <= *len) {
395 memcpy(val, supported_il_modes, sizeof(supported_il_modes));
396 } else {
397 return false;
398 }
399 *len = sizeof(supported_il_modes);
400 break;
402 {
403 assert(*len == sizeof(struct audio_desc));
404 struct audio_desc *desc = (struct audio_desc *) val;
405 desc->bps = desc->bps <= 2 ? 2 : 4;
406 desc->codec = AC_PCM;
407 }
408 break;
409 default:
410 return false;
411 }
412 return true;
413}
414
415#define NDI_SEND_AUDIO(frame, bit_depth) do { \
416 assert((frame)->bps * 8 == (bit_depth)); \
417 NDIlib_audio_frame_interleaved_ ## bit_depth ## s_t NDI_audio_frame = { 0 }; \
418 NDI_audio_frame.sample_rate = (frame)->sample_rate; \
419 NDI_audio_frame.no_channels = (frame)->ch_count; \
420 NDI_audio_frame.reference_level = s->audio_level; \
421 NDI_audio_frame.timecode = NDIlib_send_timecode_synthesize; \
422 NDI_audio_frame.p_data = (int ## bit_depth ## _t *)(void *) (frame)->data; \
423 NDI_audio_frame.no_samples = (frame)->data_len / (frame)->ch_count / ((bit_depth) / 8); \
424 \
425 s->NDIlib->util_send_send_audio_interleaved_ ## bit_depth ## s(s->pNDI_send, &NDI_audio_frame); \
426 } while(0)
427
428static void display_ndi_put_audio_frame(void *state, const struct audio_frame *frame)
429{
430 struct display_ndi *s = (struct display_ndi *) state;
431 switch (frame->bps * 8) {
432#define HANDLE_CASE(b) case b: NDI_SEND_AUDIO(frame, b); break;
433 HANDLE_CASE(16)
434 HANDLE_CASE(32)
435 default:
436 abort();
437 }
438}
439
441 int sample_rate)
442{
443 struct display_ndi *s = (struct display_ndi *) state;
447
448 return true;
449}
450
464
466
467/* vim: set expandtab sw=8: */
convert_t convert
#define UNUSED(x)
void ** state
int void * val
struct video_desc desc
int int int sample_rate
int int channels
struct video_frame * frame
const char * fmt
int void size_t * len
const char unsigned int flags
int void(** deleter)(void *))
int property
int quant_samples
void vf_free(struct video_frame *buf)
Frees video_frame structure.
struct video_frame * vf_alloc_desc_data(struct video_desc desc)
This file contains definition of some common audio types.
@ AC_PCM
Definition types.h:53
int color_printf(const char *format,...)
#define TERM_BOLD
Definition color_out.h:48
#define TERM_FG_RED
Definition color_out.h:50
#define TERM_RESET
Definition color_out.h:47
#define TERM_FG_RESET
Definition color_out.h:59
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LIB_HANDLE
Definition dlfunc.h:51
#define PITCH_DEFAULT
default pitch, i. e. respective linesize
#define PUTF_DISCARD
#define VIDEO_DISPLAY_ABI_VERSION
@ DISPLAY_PROPERTY_BUF_PITCH
requested framebuffer pitch - int (bytes), may be PITCH_DEFAULT
@ DISPLAY_PROPERTY_RGB_SHIFT
red,green,blue shift - int[3] (bits)
@ DISPLAY_PROPERTY_SUPPORTED_IL_MODES
display supported interlacing modes - enum interlacing_t[]
@ DISPLAY_PROPERTY_VIDEO_MODE
requested video mode - int (one of display_prop_vid_mode)
@ DISPLAY_PROPERTY_CODECS
list of natively supported codecs - codec_t[]
@ DISPLAY_PROPERTY_AUDIO_FORMAT
@ DISPLAY_PROPERTY_VIDEO_MERGED
monolithic framebuffer
int count
Definition hd-rum.c:44
const char * get_commandline_param(const char *key)
Definition host.cpp:894
#define INIT_NOERR
Definition host.h:73
@ LIBRARY_CLASS_VIDEO_DISPLAY
Definition lib_common.h:79
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
Definition lib_common.h:144
#define OPTIMIZED_FOR
Definition macros.h:127
int get_framerate_d(double fps)
Definition misc.cpp:190
int get_framerate_n(double fps)
Definition misc.cpp:184
static struct video_frame * display_ndi_getf(void *state)
Definition ndi.c:275
#define DEFAULT_AUDIO_LEVEL
Definition ndi.c:67
codec_t ug_codec
Definition ndi.c:101
static bool display_ndi_reconfigure_audio(void *state, int quant_samples, int channels, int sample_rate)
Definition ndi.c:440
static void display_ndi_put_audio_frame(void *state, const struct audio_frame *frame)
Definition ndi.c:428
#define THROW(x)
Definition ndi.c:176
#define HANDLE_CASE(b)
static char * ndi_disp_format_video_metadata(void)
Definition ndi.c:151
static void * display_ndi_init(struct module *parent, const char *fmt, unsigned int flags)
Definition ndi.c:181
void ndi_disp_convert_t(const struct video_frame *f, char *out)
Definition ndi.c:70
static bool display_ndi_get_property(void *state, int property, void *val, size_t *len)
Definition ndi.c:358
#define COMMON
Definition ndi.c:177
#define BEGIN_TRY
Definition ndi.c:174
static void usage()
Definition ndi.c:137
static void ndi_disp_convert_Y416_to_PA16(const struct video_frame *f, char *out)
Definition ndi.c:301
static void display_ndi_done(void *state)
Definition ndi.c:262
#define CATCH(x)
Definition ndi.c:178
#define HELP_SHOWN
Definition ndi.c:180
static bool display_ndi_reconfigure(void *state, struct video_desc desc)
Definition ndi.c:112
static bool display_ndi_putf(void *state, struct video_frame *frame, long long flag)
Definition ndi.c:327
static void ndi_disp_convert_Y216_to_P216(const struct video_frame *f, char *out)
Definition ndi.c:282
#define MOD_NAME
Definition ndi.c:68
#define FAIL
Definition ndi.c:179
static void display_ndi_probe(struct device_info **available_cards, int *count, void(**deleter)(void *))
Definition ndi.c:89
static const struct @90 codec_mapping[]
#define END_TRY
Definition ndi.c:175
NDIlib_FourCC_video_type_e ndi_fourcc
Definition ndi.c:102
static const struct video_display_info display_ndi_info
Definition ndi.c:451
#define NDI_PRINT_COPYRIGHT
Definition ndi_common.h:184
static void close_ndi_library(LIB_HANDLE hNDILib)
Definition ndi_common.h:177
static const NDIlib_t * NDIlib_load(LIB_HANDLE *lib)
Definition ndi_common.h:77
const char * parent
Definition settings.cpp:198
const char * param
Definition settings.cpp:195
const char * name
Definition settings.cpp:193
int sample_rate
Definition types.h:78
int ch_count
Definition types.h:79
int bps
Definition types.h:77
struct used to comunicate with audio capture/playback device
Definition types.h:99
char * convert_buffer
for codecs that need conversion (eg. Y216->P216)
Definition ndi.c:86
struct video_frame * send_frame
frame that is just being asynchronously sent
Definition ndi.c:83
const NDIlib_t * NDIlib
Definition ndi.c:76
char * video_metadata
Definition ndi.c:80
struct video_desc desc
Definition ndi.c:81
int audio_level
audio reference level - usually 0 or 20
Definition ndi.c:77
NDIlib_video_frame_v2_t NDI_video_frame
Definition ndi.c:79
LIB_HANDLE lib
Definition ndi.c:75
struct audio_desc audio_desc
Definition ndi.c:82
NDIlib_send_instance_t pNDI_send
Definition ndi.c:78
ndi_disp_convert_t * convert
Definition ndi.c:85
Definition hd-rum.c:27
unsigned int width
Definition types.h:280
char * data
Raw tile data. Pointer must be at least 4B aligned.
Definition types.h:287
unsigned int height
Definition types.h:281
codec_t color_spec
Definition types.h:182
enum interlacing_t interlacing
Definition types.h:184
double fps
Definition types.h:183
unsigned int width
width of every tile (!)
Definition types.h:179
unsigned int height
height of every tile (!)
Definition types.h:180
Struct video_frame represents a video frame and contains video description.
Definition types.h:305
struct tile tiles[FLEXIBLE_ARRAY_MEMBER]
Definition types.h:345
This file contains some common data types.
interlacing_t
Definition types.h:163
@ PROGRESSIVE
progressive frame
Definition types.h:164
@ INTERLACED_MERGED
Columngs of both fields are interlaced together.
Definition types.h:167
codec_t
Definition types.h:65
@ UYVY
YCbCr 422 8-bit - Cb Y0 Cr Y1.
Definition types.h:71
@ Y416
interleaved little-endian YCbCr 4444 16-bit - UYVA
Definition types.h:99
@ Y216
YCbCr 422 16-bit little-endian - Y0 Cb Y1 Cr.
Definition types.h:98
@ I420
planar YCbCr 4:2:0
Definition types.h:97
@ RGBA
RGBA 8-bit, big-endian.
Definition types.h:70
codec_t ug_codec
Definition v4l2_common.h:68
This is an umbrella header for video functions.
#define MAX_BPS
maximal (average) number of pixels per know pixel formats (up-round if needed)
Definition video_codec.h:62
#define MAX_PADDING
Definition video_codec.h:63
static struct @76 codecs[]