69static constexpr const char *
MOD_NAME =
"[NDI cap.] ";
76using std::chrono::duration_cast;
77using std::chrono::steady_clock;
87 array<struct audio_frame, 2>
audio;
97 NDIlib_recv_create_v3_t
create_settings{NDIlib_source_t(), NDIlib_recv_color_format_best, NDIlib_recv_bandwidth_highest,
true, NULL};
99 std::chrono::steady_clock::time_point
t0 = std::chrono::steady_clock::now();
106 auto now = steady_clock::now();
107 double seconds = duration_cast<std::chrono::microseconds>(now -
t0).count() / 1000000.0;
110 << seconds <<
" seconds = " <<
frames / seconds <<
" FPS\n";
124 <<
"[:name=<n>][:url=<u>][:audio_level=<l>][:"
125 "bandwidth=<b>][:color=<c>][:extra_ips=<ip>][:"
127 <<
"\n\t" <<
SBOLD(
"-t ndi:help[:extra_ips=<ip>]")
"\n"
131 "\t\tname of the NDI source in form "
132 "\"MACHINE_NAME (NDI_SOURCE_NAME)\"\n";
134 "\t\tURL, typically <ip> or <ip>:<port>\n";
135 col() <<
TBOLD(
"\taudio_level\n") <<
136 "\t\taudio headroom above reference level (in dB, or mic/line, default " << 20 * log(
DEFAULT_AUDIO_DIVISOR) / log(10) <<
")\n";
138 "\t\trequired bandwidth, " <<
TBOLD(<< NDIlib_recv_bandwidth_audio_only << ) <<
" - audio only, " TBOLD(<< NDIlib_recv_bandwidth_lowest <<)
" - lowest, " TBOLD(<< NDIlib_recv_bandwidth_highest <<)
" - highest (default)\n";
140 "\t\tcolor format, " <<
TBOLD(<< NDIlib_recv_color_format_BGRX_BGRA <<) <<
" - BGRX/BGRA, " <<
TBOLD(<< NDIlib_recv_color_format_UYVY_BGRA <<) <<
" - UYVY/BGRA, " <<
141 TBOLD(<< NDIlib_recv_color_format_RGBX_RGBA <<) <<
" - RGBX/RGBA, " <<
TBOLD(<< NDIlib_recv_color_format_UYVY_RGBA <<) <<
" - UYVY/RGBA, " <<
TBOLD(<< NDIlib_recv_color_format_fastest <<) <<
142 " - fastest (UYVY), " <<
TBOLD(<< NDIlib_recv_color_format_best <<) <<
" - best (default, P216/UYVY)\n"
143 "\t\tSelection is on NDI runtime and usually depends on presence of alpha channel. UG ignores alpha channel for YCbCr codecs.\n";
145 "\t\tadditional IP addresses for query in format \"12.0.0.8,13.0.12.8\". Can be used if DNS-SD is not usable.\n";
146 col() <<
TBOLD(
"\tprogressive\n") <<
147 "\t\tprefer progressive capture for interlaced input\n"
150 cout <<
"available sources (tentative, format: name - url):\n";
152 if (pNDI_find ==
nullptr) {
157 uint32_t nr_sources = 0;
158 const NDIlib_source_t* p_sources =
nullptr;
164 p_sources = s->
NDIlib->find_get_current_sources(pNDI_find, &nr_sources);
165 for (
int i = 0; i < static_cast<int>(nr_sources); ++i) {
166 cout <<
"\t" << p_sources[i].p_ndi_name <<
" - " << p_sources[i].p_url_address <<
"\n";
168 if (nr_sources == 0) {
173 s->
NDIlib->find_destroy(pNDI_find);
179 using namespace std::string_literals;
183 if (s->NDIlib == NULL) {
188 printf(
"NDI version %s\n", s->NDIlib->version());
191 if (!s->NDIlib->initialize()) {
197 s->capture_audio =
true;
201 auto tmp =
static_cast<char *
>(alloca(strlen(
fmt) + 1));
203 char *
item, *save_ptr;
204 bool req_show_help =
false;
205 while ((
item = strtok_r(tmp,
":", &save_ptr)) !=
nullptr) {
208 if (strcmp(
item,
"help") == 0) {
209 req_show_help =
true;
212 if (strncmp(
item,
"name=", strlen(
"name=")) == 0) {
213 s->requested_name =
item + strlen(
"name=");
214 }
else if (strncmp(
item,
"url=", strlen(
"url=")) == 0) {
215 s->requested_url =
item + strlen(
"url=");
216 if (isdigit(*save_ptr)) {
217 s->requested_url += string(
":") + strtok_r(
nullptr,
":", &save_ptr);
218 cout << s->requested_url;
220 }
else if (strstr(
item,
"audio_level=") ==
item) {
221 char *
val =
item + strlen(
"audio_level=");
223 if (strcasecmp(
val,
"mic") == 0) {
225 }
else if (strcasecmp(
val,
"line") == 0) {
228 char *endptr =
nullptr;
229 ref_level = strtol(
val, &endptr, 0);
230 if (ref_level < 0 || ref_level >= INT_MAX || *
val ==
'\0' || *endptr !=
'\0') {
236 s->audio_divisor = pow(10.0, ref_level / 20.0);
237 }
else if (strstr(
item,
"extra_ips=") ==
item) {
238 s->find_create_settings.p_extra_ips = strdup(
item +
"extra_ips="s.length());
239 }
else if (strstr(
item,
"bandwidth=") ==
item) {
240 s->create_settings.bandwidth =
static_cast<NDIlib_recv_bandwidth_e
>(stoi(strchr(
item,
'=') + 1));
241 }
else if (strstr(
item,
"color=") ==
item) {
242 s->create_settings.color_format =
static_cast<NDIlib_recv_color_format_e
>(stoi(
item +
"color="s.length()));
243 }
else if (strstr(
item,
"progressive") ==
item) {
244 s->create_settings.allow_video_fields =
false;
266 for (
auto & i : s->audio) {
270 if (s->field_0.p_data !=
nullptr) {
271 s->NDIlib->recv_free_video_v2(s->pNDI_recv, &s->field_0);
275 s->
NDIlib->find_destroy(s->pNDI_find);
277 s->NDIlib->recv_destroy(s->pNDI_recv);
280 s->NDIlib->destroy();
292 <<
" channels, stream has " <<
frame->no_channels <<
"!\n";
306 for (
int i = 0; i <
frame->no_samples; ++i) {
307 float *in = (
float *)(
void *)
frame->p_data + i;
316 in +=
frame->channel_stride_in_bytes /
sizeof(float);
331 for (
int i = 0; i < nr_sources; ++i) {
335 (std::regex_search(s->
requested_name, std::regex(
"(.*)", std::regex::basic)) ||
336 !std::regex_match(sources[i].p_ndi_name, std::regex(
string(
".*(") + s->
requested_name +
")", std::regex::basic)))) {
342 if (strncmp(url_c, sources[i].p_url_address, strlen(url_c)) != 0) {
346 const char *url_suffix = sources[i].p_url_address + strlen(url_c);
347 if (strlen(url_suffix) > 0 && url_suffix[0] !=
':') {
357using convert_t = void (*)(
struct video_frame *,
const uint8_t *,
int in_stride,
int field_idx,
int total_fields);
361 auto *out_p =
reinterpret_cast<uint32_t *
>(out->
tiles[0].
data) + field_idx * out->
tiles[0].
width;
363 for (
unsigned int i = 0; i < out->
tiles[0].
height / total_fields; i += 1) {
364 const auto *in_p =
reinterpret_cast<const uint32_t *
>(data + i * in_stride);
366 uint32_t argb = *in_p++;
367 *out_p++ = (argb & 0xFF000000U) | ((argb & 0xFFU) << 16U) | (argb & 0xFF00U) | ((argb & 0xFF0000U) >> 16U);
369 out_p += (total_fields - 1) * out->
tiles[0].
width;
375 auto *out_p =
reinterpret_cast<uint16_t *
>(out->
tiles[0].
data) + 2 * field_idx * out->
tiles[0].
width;
377 for (
unsigned int i = 0; i < out->
tiles[0].
height / total_fields; i += 1) {
378 const auto *in_y =
reinterpret_cast<const uint16_t *
>(data + i * in_stride);
379 const auto *in_cb_cr =
reinterpret_cast<const uint16_t *
>(data + in_stride * (i + out->
tiles[0].
height / total_fields));
380 OPTIMIZED_FOR (
unsigned int j = 0; j < (width + 1) / 2; j += 1) {
382 *out_p++ = *in_cb_cr++;
384 *out_p++ = *in_cb_cr++;
386 out_p += (total_fields - 1) * out->
tiles[0].
width * 2;
393 auto *out_p = out->
tiles[0].
data + field_idx * linesize;
394 for (
unsigned int i = 0; i < out->
tiles[0].
height / total_fields; i += 1) {
395 memcpy(out_p, data, linesize);
396 out_p += total_fields * linesize;
405 if (s->pNDI_find ==
nullptr) {
407 s->pNDI_find = s->NDIlib->find_create_v2(&s->find_create_settings);
408 if (s->pNDI_find ==
nullptr) {
414 if (s->pNDI_recv ==
nullptr) {
416 uint32_t nr_sources = 0;
417 const NDIlib_source_t* p_sources =
nullptr;
420 s->NDIlib->find_wait_for_sources(s->pNDI_find, 100 );
421 p_sources = s->NDIlib->find_get_current_sources(s->pNDI_find, &nr_sources);
422 if (nr_sources == 0) {
433 s->pNDI_recv = s->NDIlib->recv_create_v3(&s->create_settings);
434 if (s->pNDI_recv ==
nullptr) {
439 s->NDIlib->recv_connect(s->pNDI_recv,
source);
452 case NDIlib_frame_type_none:
457 case NDIlib_frame_type_video:
461 out_desc = {
static_cast<unsigned int>(
video_frame.xres),
463 (
video_frame.frame_format_type == NDIlib_frame_format_type_field_0 ||
464 video_frame.frame_format_type == NDIlib_frame_format_type_field_1 ? 2 : 1),
470 case NDIlib_FourCC_type_UYVA:
473 case NDIlib_FourCC_type_UYVY:
476 case NDIlib_FourCC_type_PA16:
477 case NDIlib_FourCC_type_P216:
481 case NDIlib_FourCC_type_BGRA:
482 case NDIlib_FourCC_type_BGRX:
485 case NDIlib_FourCC_type_RGBA:
486 case NDIlib_FourCC_type_RGBX:
495 array<char,
sizeof(uint32_t) + 1> fcc_s{};
496 memcpy(fcc_s.data(), &
video_frame.FourCC,
sizeof(uint32_t));
501 if (s->last_desc != out_desc) {
503 s->last_desc = out_desc;
504 if (s->field_0.p_data !=
nullptr) {
505 s->NDIlib->recv_free_video_v2(s->pNDI_recv, &s->field_0);
506 s->field_0 = NDIlib_video_frame_v2_t{};
508 if (out_desc.color_spec ==
Y216) {
510 "option to reduce required processing power.\n";
514 if (
video_frame.frame_format_type == NDIlib_frame_format_type_field_0) {
518 if (
video_frame.frame_format_type == NDIlib_frame_format_type_field_1) {
527 int field_count =
video_frame.frame_format_type == NDIlib_frame_format_type_field_1 ? 2 : 1;
528 if (field_count > 1) {
529 if (s->field_0.p_data ==
nullptr) {
532 convert(out, s->field_0.p_data, stride, 0, field_count);
533 s->NDIlib->recv_free_video_v2(s->pNDI_recv, &s->field_0);
534 s->field_0 = NDIlib_video_frame_v2_t{};
540 s->NDIlib->recv_free_video_v2(s->pNDI_recv, &
video_frame);
548 struct dispose_udata_t {
550 NDIlib_recv_instance_t pNDI_recv;
551 void(*recv_free_video_v2)(NDIlib_recv_instance_t p_instance,
const NDIlib_video_frame_v2_t* p_video_data);
554 static auto dispose = [](
struct video_frame *f) {
auto du =
static_cast<dispose_udata_t *
>(f->callbacks.dispose_udata);
555 du->recv_free_video_v2(du->pNDI_recv, &du->video_frame);
567 case NDIlib_frame_type_audio:
569 if (s->capture_audio) {
570 if (
audio_frame.FourCC == NDIlib_FourCC_audio_type_FLTP) {
572 *
audio = &s->audio[s->audio_buf_idx];
573 s->audio_buf_idx = (s->audio_buf_idx + 1) % 2;
574 s->audio[s->audio_buf_idx].
data_len = 0;
579 s->NDIlib->recv_free_audio_v3(s->pNDI_recv, &
audio_frame);
582 case NDIlib_frame_type_metadata:
585 case NDIlib_frame_type_error:
589 case NDIlib_frame_type_status_change:
592 case NDIlib_frame_type_max:
593 assert(0 &&
"NDIlib_frame_type_max is invalid!");
602 *available_cards =
nullptr;
607 if (NDIlib ==
nullptr) {
613 auto pNDI_find = NDIlib->find_create_v2(
nullptr);
614 if (pNDI_find ==
nullptr) {
619 uint32_t nr_sources = 0;
620 const NDIlib_source_t* p_sources =
nullptr;
626 p_sources = NDIlib->find_get_current_sources(pNDI_find, &nr_sources);
629 if (*available_cards ==
nullptr) {
630 NDIlib->find_destroy(pNDI_find);
634 for (
int i = 0; i < static_cast<int>(nr_sources); ++i) {
635 auto& card = (*available_cards)[i];
636 snprintf(card.dev,
sizeof card.dev,
":url=%s", p_sources[i].p_url_address);
637 snprintf(card.name,
sizeof card.name,
"%s", p_sources[i].p_ndi_name);
638 snprintf(card.extra,
sizeof card.extra,
"\"embeddedAudioAvailable\":\"t\"");
639 card.repeatable =
true;
642 NDIlib->find_destroy(pNDI_find);
struct video_frame * frame
struct audio_frame ** audio
int void(** deleter)(void *))
void vf_free(struct video_frame *buf)
Frees video_frame structure.
int vc_get_linesize(unsigned int width, codec_t codec)
struct video_frame * vf_alloc_desc(struct video_desc desc)
Allocates video frame accordig given desc.
struct video_frame * vf_alloc_desc_data(struct video_desc desc)
This file contains definition of some common audio types.
struct audio_frame audio_frame
struct used to comunicate with audio capture/playback device
#define LOG_LEVEL_NOTICE
information that may be interesting
#define LOG_LEVEL_INFO
normal reporting
#define LOG_LEVEL_ERROR
general errors
#define LOG_LEVEL_WARNING
less severe errors
#define VIDCAP_INIT_NOERR
state not initialized, other action performed (typically help)
#define VIDCAP_INIT_OK
initialization successful
#define VIDCAP_NO_GENERIC_FPS_INDICATOR
#define VIDEO_CAPTURE_ABI_VERSION
#define VIDCAP_INIT_FAIL
error ocured
unsigned int audio_capture_channels
@ LIBRARY_CLASS_VIDEO_CAPTURE
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
#define to_fourcc(a, b, c, d)
Creates FourCC word.
static int vidcap_ndi_init(struct vidcap_params *params, void **state)
static const NDIlib_source_t * get_matching_source(struct vidcap_state_ndi *s, const NDIlib_source_t *sources, int nr_sources)
static struct video_frame * vidcap_ndi_grab(void *state, struct audio_frame **audio)
static void convert_BGRA_RGBA(struct video_frame *out, const uint8_t *data, int in_stride, int field_idx, int total_fields)
static void audio_append_pcm(struct vidcap_state_ndi *s, NDIlib_audio_frame_v3_t *frame)
static constexpr double DEFAULT_AUDIO_DIVISOR
static void vidcap_ndi_probe(device_info **available_cards, int *count, void(**deleter)(void *))
static void convert_memcpy(struct video_frame *out, const uint8_t *data, int in_stride, int field_idx, int total_fields)
static const struct video_capture_info vidcap_ndi_info
void(*)(struct video_frame *, const uint8_t *, int in_stride, int field_idx, int total_fields) convert_t
static constexpr const char * MOD_NAME
static void vidcap_ndi_done(void *state)
static void convert_P216_Y216(struct video_frame *out, const uint8_t *data, int in_stride, int field_idx, int total_fields)
#define NDI_PRINT_COPYRIGHT
static void close_ndi_library(LIB_HANDLE hNDILib)
static const NDIlib_t * NDIlib_load(LIB_HANDLE *lib)
#define NDILIB_CPP_DEFAULT_CONSTRUCTORS
struct used to comunicate with audio capture/playback device
char * data
Raw tile data. Pointer must be at least 4B aligned.
unsigned int data_len
length of the data
std::chrono::steady_clock::time_point t0
double audio_divisor
sample divisor derived from audio reference level - 1 for 0 dB, 10 for 20 dB
NDIlib_find_create_t find_create_settings
NDIlib_recv_instance_t pNDI_recv
NDIlib_video_frame_v2_t field_0
stored to asssemble interleaved interlaced video together with field 1
array< struct audio_frame, 2 > audio
NDIlib_recv_create_v3_t create_settings
NDIlib_find_instance_t pNDI_find
void(* dispose)(struct video_frame *)
Struct video_frame represents a video frame and contains video description.
struct tile tiles[FLEXIBLE_ARRAY_MEMBER]
struct video_frame_callbacks callbacks
@ PROGRESSIVE
progressive frame
@ INTERLACED_MERGED
Columngs of both fields are interlaced together.
@ UYVY
YCbCr 422 8-bit - Cb Y0 Cr Y1.
@ VIDEO_CODEC_NONE
dummy color spec
@ Y216
YCbCr 422 16-bit little-endian - Y0 Cb Y1 Cr.
@ RGBA
RGBA 8-bit, big-endian.
int usleep(unsigned int usec)
struct audio_desc audio_desc_from_frame(const struct audio_frame *frame)
bool audio_desc_eq(struct audio_desc a1, struct audio_desc a2)
This is an umbrella header for video functions.
const char * vidcap_params_get_fmt(const struct vidcap_params *params)
unsigned int vidcap_params_get_flags(const struct vidcap_params *params)
#define VIDCAP_FLAG_AUDIO_ANY
bool is_codec_opaque(codec_t codec)