UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
ndi.cpp
Go to the documentation of this file.
1
5/*
6 * Copyright (c) 2018-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 <algorithm>
46#include <array>
47#include <cassert> // for assert
48#include <chrono>
49#include <climits> // for INT_MAX
50#include <cmath> // for log, pow
51#include <iostream>
52#include <memory>
53#include <regex>
54#include <string>
55#include <type_traits> // static_assert
56
57#include "audio/types.h"
58#include "audio/utils.h"
59#include "compat/usleep.h"
60#include "debug.h"
61#include "lib_common.h"
62#include "ndi_common.h"
63#include "utils/color_out.h"
64#include "utils/macros.h" // OPTIMIZED_FOR
65#include "video.h"
66#include "video_capture.h"
67
68static constexpr double DEFAULT_AUDIO_DIVISOR = 1;
69static constexpr const char *MOD_NAME = "[NDI cap.] ";
70
71using std::array;
72using std::cout;
73using std::max;
74using std::min;
75using std::string;
76using std::chrono::duration_cast;
77using std::chrono::steady_clock;
78
79static void vidcap_ndi_done(void *state);
80
82 static_assert(NDILIB_CPP_DEFAULT_CONSTRUCTORS == 0, "Don't use default C++ NDI constructors - we are using run-time dynamic lib load");
84 const NDIlib_t *NDIlib{};
85 NDIlib_recv_instance_t pNDI_recv = nullptr;
86 NDIlib_find_instance_t pNDI_find = nullptr;
87 array<struct audio_frame, 2> audio;
89 bool capture_audio = false;
91
92 NDIlib_video_frame_v2_t field_0{};
93
94 string requested_name; // if not empty recv from requested NDI name
95 string requested_url; // if not empty recv from requested URL (either addr or addr:port)
96 NDIlib_find_create_t find_create_settings{true, nullptr, nullptr};
97 NDIlib_recv_create_v3_t create_settings{NDIlib_source_t(), NDIlib_recv_color_format_best, NDIlib_recv_bandwidth_highest, true, NULL};
98
99 std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
100 int frames = 0;
101
104
105 void print_stats() {
106 auto now = steady_clock::now();
107 double seconds = duration_cast<std::chrono::microseconds>(now - t0).count() / 1000000.0;
108 if (seconds > 5) {
109 LOG(LOG_LEVEL_INFO) << MOD_NAME << frames << " frames in "
110 << seconds << " seconds = " << frames / seconds << " FPS\n";
111 frames = 0;
112 t0 = now;
113 }
114 }
116 free(const_cast<char *>(find_create_settings.p_extra_ips));
117 }
118};
119
120static void show_help(struct vidcap_state_ndi *s) {
121 col() << "Usage:\n"
122 "\t"
123 << SBOLD(SRED("-t ndi")
124 << "[:name=<n>][:url=<u>][:audio_level=<l>][:"
125 "bandwidth=<b>][:color=<c>][:extra_ips=<ip>][:"
126 "progressive]")
127 << "\n\t" << SBOLD("-t ndi:help[:extra_ips=<ip>]") "\n"
128 << "where\n";
129
130 col() << TBOLD("\tname\n") <<
131 "\t\tname of the NDI source in form "
132 "\"MACHINE_NAME (NDI_SOURCE_NAME)\"\n";
133 col() << TBOLD("\turl\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";
137 col() << TBOLD("\tbandwidth\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";
139 col() << TBOLD("\tcolor\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";
144 col() << TBOLD("\textra_ips\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"
148 "\n";
149
150 cout << "available sources (tentative, format: name - url):\n";
151 auto *pNDI_find = s->NDIlib->find_create_v2(&s->find_create_settings);
152 if (pNDI_find == nullptr) {
153 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Cannot create finder object!\n";
154 return;
155 }
156
157 uint32_t nr_sources = 0;
158 const NDIlib_source_t* p_sources = nullptr;
159 // Give sources some time to occur
160 usleep(500 * 1000);
161 // we do not usea NDIlib_find_wait_for_sources() here because: 1) if there is
162 // no source, it will still wait requested amount of time and 2) if there are
163 // more sources, it will continue after first source found while there can be more
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";
167 }
168 if (nr_sources == 0) {
169 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "No sources found!\n";
170 LOG(LOG_LEVEL_INFO) << MOD_NAME << "You can pass \"extra_ips\" if mDNS is not operable.\n";
171 }
172 cout << "\n";
173 s->NDIlib->find_destroy(pNDI_find);
174}
175
176static int vidcap_ndi_init(struct vidcap_params *params, void **state)
177{
179 using namespace std::string_literals;
180 using std::stoi;
181 auto s = new vidcap_state_ndi();
182 s->NDIlib = NDIlib_load(&s->lib);
183 if (s->NDIlib == NULL) {
184 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Cannot open NDI library!\n";
185 delete s;
186 return VIDCAP_INIT_FAIL;
187 }
188 printf("NDI version %s\n", s->NDIlib->version());
189
190 // Not required, but "correct" (see the SDK documentation)
191 if (!s->NDIlib->initialize()) {
192 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Cannot initialize NDI!\n";
193 delete s;
194 return VIDCAP_INIT_FAIL;
195 }
197 s->capture_audio = true;
198 }
199
200 const char *fmt = vidcap_params_get_fmt(params);
201 auto tmp = static_cast<char *>(alloca(strlen(fmt) + 1));
202 strcpy(tmp, fmt);
203 char *item, *save_ptr;
204 bool req_show_help = false;
205 while ((item = strtok_r(tmp, ":", &save_ptr)) != nullptr) {
206 tmp = nullptr;
207
208 if (strcmp(item, "help") == 0) {
209 req_show_help = true; // defer execution to honor extra_ips option (if present);
210 continue;
211 }
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)) { // ...:url=abc:123:bbb
217 s->requested_url += string(":") + strtok_r(nullptr, ":", &save_ptr);
218 cout << s->requested_url;
219 }
220 } else if (strstr(item, "audio_level=") == item) {
221 char *val = item + strlen("audio_level=");
222 long ref_level = 0;
223 if (strcasecmp(val, "mic") == 0) {
224 ref_level = 0;
225 } else if (strcasecmp(val, "line") == 0) {
226 ref_level = 20; // NOLINT
227 } else {
228 char *endptr = nullptr;
229 ref_level = strtol(val, &endptr, 0);
230 if (ref_level < 0 || ref_level >= INT_MAX || *val == '\0' || *endptr != '\0') {
231 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Wrong value: " << val << "!\n";
232 delete s;
233 return VIDCAP_INIT_NOERR;
234 }
235 }
236 s->audio_divisor = pow(10.0, ref_level / 20.0); // NOLINT
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;
245 } else {
246 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Unknown option: " << item << "\n";
247 delete s;
248 return VIDCAP_INIT_NOERR;
249 }
250 }
251
252 if (req_show_help) {
253 show_help(s);
255 return VIDCAP_INIT_NOERR;
256 }
257
258 *state = s;
259 return VIDCAP_INIT_OK;
260}
261
262static void vidcap_ndi_done(void *state)
263{
264 auto s = static_cast<struct vidcap_state_ndi *>(state);
265
266 for (auto & i : s->audio) {
267 free(i.data);
268 }
269
270 if (s->field_0.p_data != nullptr) {
271 s->NDIlib->recv_free_video_v2(s->pNDI_recv, &s->field_0);
272 }
273
274 // Destroy the NDI finder.
275 s->NDIlib->find_destroy(s->pNDI_find);
276
277 s->NDIlib->recv_destroy(s->pNDI_recv);
278
279 // Not required, but nice
280 s->NDIlib->destroy();
281 close_ndi_library(s->lib);
282
283 delete s;
284}
285
286static void audio_append_pcm(struct vidcap_state_ndi *s, NDIlib_audio_frame_v3_t *frame)
287{
288 struct audio_desc d{4, frame->sample_rate, static_cast<int>(audio_capture_channels > 0 ? audio_capture_channels : frame->no_channels), AC_PCM};
289
290 if (frame->no_channels != d.ch_count && s->audio[s->audio_buf_idx].ch_count == 0 && s->audio_buf_idx == 0) {
291 LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Requested " << s->audio[s->audio_buf_idx].ch_count
292 << " channels, stream has " << frame->no_channels << "!\n";
293 }
294
296 free(s->audio[s->audio_buf_idx].data);
297 s->audio[s->audio_buf_idx].bps = 4;
298 s->audio[s->audio_buf_idx].sample_rate = frame->sample_rate;
299 s->audio[s->audio_buf_idx].ch_count = d.ch_count;
300 s->audio[s->audio_buf_idx].data_len = 0;
301 s->audio[s->audio_buf_idx].max_size =
302 4 * d.ch_count * frame->sample_rate / 5; // 200 ms
303 s->audio[s->audio_buf_idx].data = static_cast<char *>(malloc(s->audio[s->audio_buf_idx].max_size));
304 }
305
306 for (int i = 0; i < frame->no_samples; ++i) {
307 float *in = (float *)(void *) frame->p_data + i;
308 int32_t *out = (int32_t *)(void *) s->audio[s->audio_buf_idx].data + i * d.ch_count;
309 int j = 0;
310 for (; j < min(d.ch_count, frame->no_channels); ++j) {
311 if (s->audio[s->audio_buf_idx].data_len >= s->audio[s->audio_buf_idx].max_size) {
312 LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Audio frame too small!\n";
313 return;
314 }
315 *out++ = max<double>(INT32_MIN, min<double>(INT32_MAX, *in / s->audio_divisor * INT32_MAX));
316 in += frame->channel_stride_in_bytes / sizeof(float);
317 s->audio[s->audio_buf_idx].data_len += sizeof(int32_t);
318 }
319 for (; j < d.ch_count; ++j) { // fill excess channels with zeros
320 *out++ = 0;
321 s->audio[s->audio_buf_idx].data_len += sizeof(int32_t);
322 }
323 }
324}
325
326static const NDIlib_source_t *get_matching_source(struct vidcap_state_ndi *s, const NDIlib_source_t *sources, int nr_sources) {
327 if (s->requested_name.empty() && s->requested_url.empty()) {
328 return sources + 0;
329 }
330
331 for (int i = 0; i < nr_sources; ++i) {
332 if (!s->requested_name.empty()) {
333 if (s->requested_name != sources[i].p_ndi_name &&
334 // match also '.*(name)' if name was given and it doesn't contain whole name (`hostname (name)` -> brace pair is checked)
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)))) {
337 continue;
338 }
339 }
340 if (!s->requested_url.empty()) {
341 const char *url_c = s->requested_url.c_str();
342 if (strncmp(url_c, sources[i].p_url_address, strlen(url_c)) != 0) { // doesn't match at all, requested url is not prefix of actual (so it cannot be <host> and <host>:<port>)
343 continue;
344 }
345
346 const char *url_suffix = sources[i].p_url_address + strlen(url_c);
347 if (strlen(url_suffix) > 0 && url_suffix[0] != ':') { // case "xy:z" (user requested "x") or "a:bc" (user requested "a:b")
348 continue;
349 }
350 }
351 return sources + i;
352 }
353
354 return nullptr;
355}
356
357using convert_t = void (*)(struct video_frame *, const uint8_t *, int in_stride, int field_idx, int total_fields);
358
359static void convert_BGRA_RGBA(struct video_frame *out, const uint8_t *data, int in_stride, int field_idx, int total_fields)
360{
361 auto *out_p = reinterpret_cast<uint32_t *>(out->tiles[0].data) + field_idx * out->tiles[0].width;
362 unsigned int width = 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);
365 OPTIMIZED_FOR (unsigned int j = 0; j < width; j++) {
366 uint32_t argb = *in_p++;
367 *out_p++ = (argb & 0xFF000000U) | ((argb & 0xFFU) << 16U) | (argb & 0xFF00U) | ((argb & 0xFF0000U) >> 16U);
368 }
369 out_p += (total_fields - 1) * out->tiles[0].width;
370 }
371}
372
373static void convert_P216_Y216(struct video_frame *out, const uint8_t *data, int in_stride, int field_idx, int total_fields)
374{
375 auto *out_p = reinterpret_cast<uint16_t *>(out->tiles[0].data) + 2 * field_idx * out->tiles[0].width;
376 unsigned int width = 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) {
381 *out_p++ = *in_y++;
382 *out_p++ = *in_cb_cr++;
383 *out_p++ = *in_y++;
384 *out_p++ = *in_cb_cr++;
385 }
386 out_p += (total_fields - 1) * out->tiles[0].width * 2;
387 }
388}
389
390static void convert_memcpy(struct video_frame *out, const uint8_t *data, int in_stride, int field_idx, int total_fields)
391{
392 size_t linesize = vc_get_linesize(out->tiles[0].width, out->color_spec);
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;
397 data += in_stride;
398 }
399}
400
401static struct video_frame *vidcap_ndi_grab(void *state, struct audio_frame **audio)
402{
403 auto s = static_cast<struct vidcap_state_ndi *>(state);
404
405 if (s->pNDI_find == nullptr) {
406 // Create a finder
407 s->pNDI_find = s->NDIlib->find_create_v2(&s->find_create_settings);
408 if (s->pNDI_find == nullptr) {
409 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Cannot create object!\n";
410 return nullptr;
411 }
412 }
413
414 if (s->pNDI_recv == nullptr) {
415 // Wait until there is one source
416 uint32_t nr_sources = 0;
417 const NDIlib_source_t* p_sources = nullptr;
418 // Wait until the sources on the nwtork have changed
419 LOG(LOG_LEVEL_INFO) << MOD_NAME << "Looking for source(s)...\n";
420 s->NDIlib->find_wait_for_sources(s->pNDI_find, 100 /* 100 ms */);
421 p_sources = s->NDIlib->find_get_current_sources(s->pNDI_find, &nr_sources);
422 if (nr_sources == 0) {
423 LOG(LOG_LEVEL_WARNING) << MOD_NAME << "No sources.\n";
424 return nullptr;
425 }
426
427 const NDIlib_source_t *source = get_matching_source(s, p_sources, nr_sources);
428 if (source == nullptr) {
429 return nullptr;
430 }
431
432 // We now have at least one source, so we create a receiver to look at it.
433 s->pNDI_recv = s->NDIlib->recv_create_v3(&s->create_settings);
434 if (s->pNDI_recv == nullptr) {
435 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Unable to create receiver!\n";
436 return nullptr;
437 }
438 // Connect to our sources
439 s->NDIlib->recv_connect(s->pNDI_recv, source);
440
441 LOG(LOG_LEVEL_NOTICE) << MOD_NAME << "Receiving from source: " << source->p_ndi_name << ", URL: " << source->p_url_address << "\n";
442 }
443
444 NDIlib_video_frame_v2_t video_frame;
445 NDIlib_audio_frame_v3_t audio_frame;
446
447 struct video_frame *out = nullptr;
448 video_desc out_desc;
449
450 switch (s->NDIlib->recv_capture_v3(s->pNDI_recv, &video_frame, &audio_frame, nullptr, 200))
451 { // No data
452 case NDIlib_frame_type_none:
453 LOG(LOG_LEVEL_INFO) << MOD_NAME << "No data received.\n";
454 break;
455
456 // Video data
457 case NDIlib_frame_type_video:
458 {
459 convert_t convert = nullptr;
460
461 out_desc = {static_cast<unsigned int>(video_frame.xres),
462 static_cast<unsigned int>(video_frame.yres) *
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),
466 static_cast<double>(video_frame.frame_rate_N) / video_frame.frame_rate_D,
467 video_frame.frame_format_type == NDIlib_frame_format_type_progressive ? PROGRESSIVE : INTERLACED_MERGED,
468 1};
469 switch (video_frame.FourCC) {
470 case NDIlib_FourCC_type_UYVA:
471 LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Detected input format UYVA, dropping alpha! Please report if not desired.\n";
472 // fall through
473 case NDIlib_FourCC_type_UYVY:
474 out_desc.color_spec = UYVY;
475 break;
476 case NDIlib_FourCC_type_PA16:
477 case NDIlib_FourCC_type_P216:
479 out_desc.color_spec = Y216;
480 break;
481 case NDIlib_FourCC_type_BGRA:
482 case NDIlib_FourCC_type_BGRX:
484 // fall through
485 case NDIlib_FourCC_type_RGBA:
486 case NDIlib_FourCC_type_RGBX:
487 out_desc.color_spec = RGBA;
488 break;
489 default:
490 if (video_frame.FourCC == to_fourcc('H', '2', '6', '4') || video_frame.FourCC == to_fourcc('h', '2', '6', '4')) {
491 out_desc.color_spec = H264;
492 } else if (video_frame.FourCC == to_fourcc('H', 'E', 'V', 'C') || video_frame.FourCC == to_fourcc('h', 'e', 'v', 'c')) {
493 out_desc.color_spec = H265;
494 } else {
495 array<char, sizeof(uint32_t) + 1> fcc_s{};
496 memcpy(fcc_s.data(), &video_frame.FourCC, sizeof(uint32_t));
497 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Unsupported codec '" << fcc_s.data() << "', please report to " PACKAGE_BUGREPORT "!\n";
498 return {};
499 }
500 }
501 if (s->last_desc != out_desc) {
502 LOG(LOG_LEVEL_NOTICE) << MOD_NAME << "Received video changed: " << out_desc << "\n";
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{};
507 }
508 if (out_desc.color_spec == Y216) {
509 LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Receiving 16-bit YCbCr, if not needed, consider using \"color=\" "
510 "option to reduce required processing power.\n";
511 }
512 }
513
514 if (video_frame.frame_format_type == NDIlib_frame_format_type_field_0) {
515 s->field_0 = video_frame;
516 return nullptr;
517 }
518 if (video_frame.frame_format_type == NDIlib_frame_format_type_field_1) {
519 if (convert == nullptr) {
521 }
522 }
523
524 if (convert != nullptr) {
525 out = vf_alloc_desc_data(out_desc);
526 int stride = video_frame.line_stride_in_bytes != 0 ? video_frame.line_stride_in_bytes : vc_get_linesize(video_frame.xres, out_desc.color_spec);
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) {
530 LOG(LOG_LEVEL_WARNING) << MOD_NAME << "Missing corresponding field!\n";
531 } else {
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{};
535 }
536 convert(out, video_frame.p_data, stride, 1, field_count);
537 } else {
538 convert(out, video_frame.p_data, stride, 0, 1);
539 }
540 s->NDIlib->recv_free_video_v2(s->pNDI_recv, &video_frame);
542 } else {
543 out = vf_alloc_desc(out_desc);
544 out->tiles[0].data = reinterpret_cast<char*>(video_frame.p_data);
545 if (is_codec_opaque(out_desc.color_spec)) {
546 out->tiles[0].data_len = video_frame.data_size_in_bytes;
547 }
548 struct dispose_udata_t {
549 NDIlib_video_frame_v2_t video_frame;
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);
552 };
553 out->callbacks.dispose_udata = new dispose_udata_t{video_frame, s->pNDI_recv, s->NDIlib->recv_free_video_v2};
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);
556 delete du;
557 free(f);
558 };
559 out->callbacks.dispose = dispose;
560 }
561 s->frames += 1;
562 s->print_stats();
563 return out;
564 break;
565 }
566 // Audio data
567 case NDIlib_frame_type_audio:
568 *audio = nullptr;
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;
575 } else {
576 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Unsupported audio codec 0x" << std::hex << audio_frame.FourCC << std::dec << ", please report!\n";
577 }
578 }
579 s->NDIlib->recv_free_audio_v3(s->pNDI_recv, &audio_frame);
580 break;
581
582 case NDIlib_frame_type_metadata:
583 break;
584
585 case NDIlib_frame_type_error:
586 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Error occured!\n";
587 break;
588
589 case NDIlib_frame_type_status_change:
590 LOG(LOG_LEVEL_NOTICE) << MOD_NAME << "Status changed!\n";
591 break;
592 case NDIlib_frame_type_max:
593 assert(0 && "NDIlib_frame_type_max is invalid!");
594 }
595
596 return nullptr;
597}
598
599static void vidcap_ndi_probe(device_info **available_cards, int *count, void (**deleter)(void *))
600{
601 *deleter = free;
602 *available_cards = nullptr;
603 *count = 0;
604
605 LIB_HANDLE tmp = nullptr;
606 const NDIlib_t *NDIlib = NDIlib_load(&tmp);
607 if (NDIlib == nullptr) {
608 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Cannot open NDI library!\n";
609 return;
610 }
611 std::unique_ptr<std::remove_pointer<LIB_HANDLE>::type, void (*)(LIB_HANDLE)> lib(tmp, close_ndi_library);
612
613 auto pNDI_find = NDIlib->find_create_v2(nullptr);
614 if (pNDI_find == nullptr) {
615 LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Cannot create finder object!\n";
616 return;
617 }
618
619 uint32_t nr_sources = 0;
620 const NDIlib_source_t* p_sources = nullptr;
621 // Give sources some time to occur
622 usleep(100 * 1000);
623 // we do not usea NDIlib_find_wait_for_sources() here because: 1) if there is
624 // no source, it will still wait requested amount of time and 2) if there are
625 // more sources, it will continue after first source found while there can be more
626 p_sources = NDIlib->find_get_current_sources(pNDI_find, &nr_sources);
627
628 *available_cards = (struct device_info *) calloc(nr_sources, sizeof(struct device_info));
629 if (*available_cards == nullptr) {
630 NDIlib->find_destroy(pNDI_find);
631 return;
632 }
633 *count = nr_sources;
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;
640 }
641
642 NDIlib->find_destroy(pNDI_find);
643}
644
652
654
655/* vim: set expandtab sw=8: */
656
convert_t convert
void ** state
int void * val
struct video_frame * frame
const char * fmt
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.
@ AC_PCM
Definition types.h:53
struct audio_frame audio_frame
struct used to comunicate with audio capture/playback device
static void show_help()
Definition wasapi.cpp:193
#define SRED(x)
Definition color_out.h:85
#define SBOLD(x)
Definition color_out.h:80
#define TBOLD(x)
Definition color_out.h:70
#define LOG_LEVEL_NOTICE
information that may be interesting
Definition debug.h:61
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG(level)
Definition debug.h:313
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
#define LIB_HANDLE
Definition dlfunc.h:51
#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
int count
Definition hd-rum.c:44
unsigned int audio_capture_channels
Definition host.cpp:163
@ LIBRARY_CLASS_VIDEO_CAPTURE
Definition lib_common.h:78
#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
#define to_fourcc(a, b, c, d)
Creates FourCC word.
Definition macros.h:114
static int vidcap_ndi_init(struct vidcap_params *params, void **state)
Definition ndi.cpp:176
static const NDIlib_source_t * get_matching_source(struct vidcap_state_ndi *s, const NDIlib_source_t *sources, int nr_sources)
Definition ndi.cpp:326
static struct video_frame * vidcap_ndi_grab(void *state, struct audio_frame **audio)
Definition ndi.cpp:401
static void convert_BGRA_RGBA(struct video_frame *out, const uint8_t *data, int in_stride, int field_idx, int total_fields)
Definition ndi.cpp:359
static void audio_append_pcm(struct vidcap_state_ndi *s, NDIlib_audio_frame_v3_t *frame)
Definition ndi.cpp:286
static constexpr double DEFAULT_AUDIO_DIVISOR
Definition ndi.cpp:68
static void vidcap_ndi_probe(device_info **available_cards, int *count, void(**deleter)(void *))
Definition ndi.cpp:599
static void convert_memcpy(struct video_frame *out, const uint8_t *data, int in_stride, int field_idx, int total_fields)
Definition ndi.cpp:390
static const struct video_capture_info vidcap_ndi_info
Definition ndi.cpp:645
void(*)(struct video_frame *, const uint8_t *, int in_stride, int field_idx, int total_fields) convert_t
Definition ndi.cpp:357
static constexpr const char * MOD_NAME
Definition ndi.cpp:69
static void vidcap_ndi_done(void *state)
Definition ndi.cpp:262
static void convert_P216_Y216(struct video_frame *out, const uint8_t *data, int in_stride, int field_idx, int total_fields)
Definition ndi.cpp:373
#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
#define NDILIB_CPP_DEFAULT_CONSTRUCTORS
Definition ndi_common.h:46
#define min(a, b)
Definition rtp.c:99
#define max(a, b)
Definition rtp.c:98
Definition rtp.c:206
int ch_count
Definition types.h:79
struct used to comunicate with audio capture/playback device
Definition types.h:99
int data_len
Definition types.h:103
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 data_len
length of the data
Definition types.h:288
unsigned int height
Definition types.h:281
std::chrono::steady_clock::time_point t0
Definition ndi.cpp:99
string requested_name
Definition ndi.cpp:94
string requested_url
Definition ndi.cpp:95
int audio_buf_idx
Definition ndi.cpp:88
double audio_divisor
sample divisor derived from audio reference level - 1 for 0 dB, 10 for 20 dB
Definition ndi.cpp:103
NDIlib_find_create_t find_create_settings
Definition ndi.cpp:96
NDIlib_recv_instance_t pNDI_recv
Definition ndi.cpp:85
NDIlib_video_frame_v2_t field_0
stored to asssemble interleaved interlaced video together with field 1
Definition ndi.cpp:92
bool capture_audio
Definition ndi.cpp:89
void print_stats()
Definition ndi.cpp:105
array< struct audio_frame, 2 > audio
Definition ndi.cpp:87
NDIlib_recv_create_v3_t create_settings
Definition ndi.cpp:97
NDIlib_find_instance_t pNDI_find
Definition ndi.cpp:86
const NDIlib_t * NDIlib
Definition ndi.cpp:84
LIB_HANDLE lib
Definition ndi.cpp:83
codec_t color_spec
Definition types.h:182
void(* dispose)(struct video_frame *)
Definition types.h:245
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
struct video_frame_callbacks callbacks
Definition types.h:324
codec_t color_spec
Definition types.h:306
@ PROGRESSIVE
progressive frame
Definition types.h:164
@ INTERLACED_MERGED
Columngs of both fields are interlaced together.
Definition types.h:167
@ UYVY
YCbCr 422 8-bit - Cb Y0 Cr Y1.
Definition types.h:71
@ VIDEO_CODEC_NONE
dummy color spec
Definition types.h:66
@ Y216
YCbCr 422 16-bit little-endian - Y0 Cb Y1 Cr.
Definition types.h:98
@ H264
H.264 frame.
Definition types.h:83
@ H265
H.264 frame.
Definition types.h:84
@ RGBA
RGBA 8-bit, big-endian.
Definition types.h:70
int usleep(unsigned int usec)
Definition usleep.c:47
struct audio_desc audio_desc_from_frame(const struct audio_frame *frame)
Definition utils.cpp:196
bool audio_desc_eq(struct audio_desc a1, struct audio_desc a2)
Definition utils.cpp:189
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)