1 /*
2 libndi
3 Copyright (C) 2020 VideoLAN
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #define _POSIX_C_SOURCE 200809L
21 #include <stdio.h>
22 #include <string.h>
23 #include <stdlib.h>
24 #include <inttypes.h>
25 #include <time.h>
26
27 #include <sys/types.h>
28 #ifdef _WIN32
29 #include <ws2tcpip.h>
30 #include <winsock2.h>
31 #else
32 #include <sys/socket.h>
33 #include <netdb.h>
34 #endif
35
36 #ifdef HAVE_POLL
37 #include <poll.h>
38 #else
39 #include "compat/poll.c"
40 #endif
41
42 #include <libavutil/common.h>
43 #include <libavutil/frame.h>
44 #include <libavutil/fifo.h>
45
46 #include "libndi.h"
47
48 uint8_t ndi_xortab[] = {
49 0x4e, 0x44, 0x49, 0xae, 0x2c, 0x20, 0xa9, 0x32, 0x30, 0x31, 0x37, 0x20,
50 0x4e, 0x65, 0x77, 0x54, 0x65, 0x6b, 0x2c, 0x20, 0x50, 0x72, 0x6f, 0x70,
51 0x72, 0x69, 0x65, 0x74, 0x79, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x43, 0x6f,
52 0x6e, 0x66, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2e, 0x20,
53 0x59, 0x6f, 0x75, 0x20, 0x61, 0x72, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x76,
54 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20,
55 0x74, 0x68, 0x65, 0x20, 0x4e, 0x44, 0x49, 0xae, 0x20, 0x53, 0x44, 0x4b,
56 0x20, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x61, 0x74, 0x20,
57 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6e, 0x65, 0x77, 0x2e, 0x74,
58 0x6b, 0x2f, 0x6e, 0x64, 0x69, 0x73, 0x64, 0x6b, 0x5f, 0x6c, 0x69, 0x63,
59 0x65, 0x6e, 0x73, 0x65, 0x2f, 0x00, 0x00, 0x00
60 };
61
62 typedef struct ndi_message
63 {
64 uint8_t *buf;
65 int len;
66 } ndi_message;
67
68 struct ndi_ctx
69 {
70 /* buffers */
71 AVFifoBuffer *fifo;
72
73 int target_size;
74
75 int socket_fd;
76
77 ndi_message ndi_request[4];
78 int pending_requests;
79
80 ndi_data_cb callback;
81 void *user_data;
82
83 /* options */
84 char *ip;
85 char *port;
86 };
87
88 /* Probably could merge scramble and unscramble */
ndi_scramble_type1(uint8_t * buf,int len,uint32_t seed)89 static void ndi_scramble_type1(uint8_t *buf, int len, uint32_t seed)
90 {
91 uint64_t seed64 = ((uint64_t)seed << 32) | seed;
92 uint64_t seed1 = seed64 ^ 0xb711674bd24f4b24ULL;
93 uint64_t seed2 = seed64 ^ 0xb080d84f1fe3bf44ULL;
94
95 if(len > 7) {
96 uint64_t *buf64 = (uint64_t*)buf;
97 int qwords = len / 8;
98 uint64_t tmp1 = seed1;
99 for(int i = 0; i < qwords; i++) {
100 seed1 = seed2;
101 tmp1 ^= (tmp1 << 23);
102 tmp1 = ((seed1 >> 9 ^ tmp1) >> 17) ^ tmp1 ^ seed1;
103 seed2 = tmp1 ^ buf64[i];
104 buf64[i] ^= tmp1 + seed1;
105 tmp1 = seed1;
106 }
107
108 buf = buf + qwords * 8;
109 len -= qwords * 8;
110 }
111
112 if(len) {
113 uint64_t remainder = 0;
114 memcpy(&remainder, buf, len);
115 seed1 ^= seed1 << 23;
116 seed1 = ((seed2 >> 9 ^ seed1) >> 17) ^ seed1 ^ seed2;
117 remainder ^= seed1 + seed2;
118 memcpy(buf, &remainder, len);
119 }
120 }
121
ndi_unscramble_type1(uint8_t * buf,int len,uint32_t seed)122 static void ndi_unscramble_type1(uint8_t *buf, int len, uint32_t seed)
123 {
124 uint64_t seed64 = ((uint64_t)seed << 32) | seed;
125 uint64_t seed1 = seed64 ^ 0xb711674bd24f4b24ULL;
126 uint64_t seed2 = seed64 ^ 0xb080d84f1fe3bf44ULL;
127
128 if(len > 7) {
129 uint64_t *buf64 = (uint64_t*)buf;
130 int qwords = len / 8;
131 uint64_t tmp1 = seed1;
132 for(int i = 0; i < qwords; i++) {
133 seed1 = seed2;
134 tmp1 ^= (tmp1 << 23);
135 tmp1 = ((seed1 >> 9 ^ tmp1) >> 17) ^ tmp1 ^ seed1;
136 buf64[i] ^= tmp1 + seed1;
137 seed2 = tmp1 ^ buf64[i];
138 tmp1 = seed1;
139 }
140
141 buf = buf + qwords * 8;
142 len -= qwords * 8;
143 }
144
145 if(len) {
146 uint64_t remainder = 0;
147 memcpy(&remainder, buf, len);
148 seed1 ^= seed1 << 23;
149 seed1 = ((seed2 >> 9 ^ seed1) >> 17) ^ seed1 ^ seed2;
150 remainder ^= seed1 + seed2;
151 memcpy(buf, &remainder, len);
152 }
153 }
154
ndi_unscramble_type2(uint8_t * buf,int len,uint32_t seed)155 static void ndi_unscramble_type2(uint8_t *buf, int len, uint32_t seed)
156 {
157 int xor_len = 128;
158
159 if(len >= 8) {
160 uint64_t *buf64 = (uint64_t*)buf;
161 int len8 = len >> 3;
162 int64_t tmp;
163 for(int i = 0; i < len8; i++) {
164 tmp = seed;
165 seed = buf64[i] & 0xffffffff;
166 buf64[i] = ((tmp * len * -0x61c8864680b583ebLL + 0xc42bd7dee6270f1bLL) ^ buf64[i]) * -0xe217c1e66c88cc3LL + 0x2daa8c593b1b4591LL;
167 }
168 }
169
170 if(len < xor_len)
171 xor_len = len;
172
173 for(int i = 0; i < xor_len; i++)
174 buf[i] ^= ndi_xortab[i];
175 }
176
create_text_message(uint8_t * dst,int * dst_len,char * payload,int payload_len)177 static void create_text_message(uint8_t *dst, int *dst_len, char *payload, int payload_len)
178 {
179 /* Version/Scrambling type */
180 dst[0] = 0x01;
181 dst[1] = 0x80;
182
183 /* Message Type */
184 dst[2] = NDI_DATA_TEXT;
185 dst[3] = 0;
186
187 /* Header Length */
188 dst[4] = 8;
189 dst[5] = 0;
190 dst[6] = 0;
191 dst[7] = 0;
192
193 /* Payload Length */
194 dst[8] = payload_len;
195 dst[9] = 0;
196 dst[10] = 0;
197 dst[11] = 0;
198
199 /* 8 bytes of zero */
200 memset(&dst[12], 0, 8);
201
202 /* Copy payload */
203 memcpy(&dst[20], payload, payload_len);
204
205 *dst_len = 20 + payload_len;
206 }
207
process_video_message(ndi_ctx * ndi_ctx,uint8_t * data,int header_len,int payload_len)208 static int process_video_message(ndi_ctx *ndi_ctx, uint8_t *data, int header_len, int payload_len)
209 {
210 ndi_data ndi_data = {0};
211
212 uint32_t fourcc = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
213 uint32_t width = (data[7] << 24) | (data[6] << 16) | (data[5] << 8) | data[4];
214 uint32_t height = (data[11] << 24) | (data[10] << 16) | (data[9] << 8) | data[8];
215 uint32_t fps_num = (data[15] << 24) | (data[14] << 16) | (data[13] << 8) | data[12];
216 uint32_t fps_den = (data[19] << 24) | (data[18] << 20) | (data[17] << 8) | data[16];
217
218 // XXX: some more things in the header
219
220 ndi_data.data_type = NDI_DATA_VIDEO;
221 ndi_data.data = data+header_len;
222 ndi_data.len = payload_len;
223
224 ndi_data.fourcc = fourcc;
225 ndi_data.fps_num = fps_num;
226 ndi_data.fps_den = fps_den;
227 ndi_data.width = width;
228 ndi_data.height = height;
229
230 ndi_ctx->callback(&ndi_data, ndi_ctx->user_data);
231
232 return 0;
233 }
234
process_audio_message(ndi_ctx * ndi_ctx,uint8_t * data,int header_len,int payload_len)235 static int process_audio_message(ndi_ctx *ndi_ctx, uint8_t *data, int header_len, int payload_len)
236 {
237 (void)payload_len; // XXX: Why is this unused?
238
239 int ret = 0;
240 ndi_data ndi_data = {0};
241 uint32_t fourcc = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
242 uint32_t samples = (data[7] << 24) | (data[6] << 16) | (data[5] << 8) | data[4];
243 uint32_t num_channels = (data[11] << 24) | (data[10] << 16) | (data[9] << 8) | data[8];
244 uint32_t sample_rate = (data[15] << 24) | (data[14] << 16) | (data[13] << 8) | data[12];
245 float scale_factors[16];
246 uint32_t num_nonzero_channels = 0;
247 uint16_t bps = sizeof(int16_t);
248
249 // XXX: some more things in the header
250 data += header_len;
251
252 if(fourcc == MKTAG('f','o','w','t')) {
253 bps = sizeof(float);
254 for(uint32_t i = 0; i < num_channels; i++) {
255 uint32_t tmp = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
256 memcpy(&scale_factors[i], &tmp, sizeof(float));
257 if(scale_factors[i] != 0.0f)
258 num_nonzero_channels++;
259
260 data += sizeof(float);
261 }
262 }
263 else if(fourcc == MKTAG('s','o','w','t')) {
264 for(uint32_t i = 0; i < num_channels; i++) {
265 scale_factors[i] = 1.0f;
266 }
267 num_nonzero_channels = num_channels;
268 }
269
270 // XXX: Bounds check audio samples
271
272 ndi_data.data_type = NDI_DATA_AUDIO;
273 ndi_data.fourcc = fourcc;
274 ndi_data.samples = samples;
275 ndi_data.num_channels = num_channels;
276 ndi_data.sample_rate = sample_rate;
277
278 for(uint32_t i = 0; i < num_channels; i++) {
279 ndi_data.buf[i] = av_buffer_alloc(ndi_data.num_channels * ndi_data.samples * bps);
280 if(!ndi_data.buf[i]) {
281 ret = -1;
282 goto end;
283 }
284 }
285
286 for(uint32_t j = 0; j < samples; j++) {
287 for(uint32_t i = 0; i < num_channels; i++) {
288 if(scale_factors[i] == 0.0f)
289 memset(&ndi_data.buf[i]->data[4*j], 0, bps);
290 else {
291 if(bps == 2) {
292 ndi_data.buf[i]->data[2*j+0] = data[1];
293 ndi_data.buf[i]->data[2*j+1] = data[0];
294 } else if(bps == 4) {
295 float sf = scale_factors[i] / 32767.0f;
296 int16_t sample = ((uint16_t)data[1] << 8) | data[0];
297 sf *= sample;
298 memcpy(&ndi_data.buf[i]->data[4*j], &sf, sizeof(sf));
299 }
300 data += sizeof(int16_t);
301 }
302 }
303 }
304
305 ndi_ctx->callback(&ndi_data, ndi_ctx->user_data);
306
307 end:
308 for(uint32_t i = 0; i < num_channels; i++)
309 av_buffer_unref(&ndi_data.buf[i]);
310
311 return ret;
312 }
313
test_scramblev1(void)314 static void test_scramblev1(void)
315 {
316 srand( time(NULL) );
317 uint8_t buf[23], buf2[23];
318 uint32_t seed = rand();
319
320 /* Generate random numbers */
321 for(size_t i = 0; i < sizeof(buf); i++)
322 buf[i] = rand();
323
324 memcpy(buf2, buf, sizeof(buf));
325 ndi_scramble_type1(buf, sizeof(buf), seed);
326 ndi_unscramble_type1(buf, sizeof(buf), seed);
327
328 int ret = memcmp(buf, buf2, sizeof(buf));
329
330 if(ret)
331 fprintf(stderr, "scrambling mismatch \n");
332 }
333
process_ndi_packet(ndi_ctx * ndi_ctx,uint8_t * data,int len)334 static void process_ndi_packet(ndi_ctx *ndi_ctx, uint8_t *data, int len)
335 {
336 uint32_t seed;
337 (void)len; // FIXME: Actually check length properly!
338
339 /* MSB = scrambled bit */
340 uint16_t header_type = data[0] | (data[1] << 8);
341
342 uint16_t message_type = data[2] | (data[3] << 8);
343 uint8_t scrambling_type = 1;
344
345 uint32_t header_size = data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24);
346 uint32_t payload_len = data[8] | (data[9] << 8) | (data[10] << 16) | (data[11] << 24);
347 seed = header_size + payload_len;
348
349 if(message_type == NDI_DATA_VIDEO && header_type > 3)
350 scrambling_type = 2;
351 else if(message_type == NDI_DATA_AUDIO && header_type > 2)
352 scrambling_type = 2;
353 else if(message_type == NDI_DATA_TEXT && header_type > 2)
354 scrambling_type = 2;
355
356 if(scrambling_type == 1){
357 if(message_type == NDI_DATA_TEXT)
358 ndi_unscramble_type1(data+12, seed, seed);
359 else
360 ndi_unscramble_type1(data+12, header_size, seed);
361 } else {
362 if(message_type == NDI_DATA_TEXT)
363 ndi_unscramble_type2(data+12, seed, seed);
364 else
365 ndi_unscramble_type2(data+12, header_size, seed);
366 }
367
368 int data_len = 12 + header_size + payload_len;
369 //printf("process %i %u %u %u %u \n", data_len, header_size, payload_len, message_type, scrambling_type);
370
371 if(message_type == NDI_DATA_VIDEO)
372 process_video_message(ndi_ctx, data+12, header_size, payload_len);
373 else if(message_type == NDI_DATA_AUDIO)
374 process_audio_message(ndi_ctx, data+12, header_size, payload_len);
375 }
376
handle_ndi_packet(ndi_ctx * ndi_ctx)377 static int handle_ndi_packet(ndi_ctx *ndi_ctx)
378 {
379 if(!ndi_ctx->target_size) {
380 uint8_t data[12];
381 av_fifo_generic_peek(ndi_ctx->fifo, data, 12, NULL); // fixme
382
383 /* MSB = scrambled bit */
384 uint16_t header_type = data[0] | (data[1] << 8);
385
386 if(header_type >> 15) {
387 header_type &= 0x7fff;
388 uint16_t message_type = data[2] | (data[3] << 8);
389
390 uint32_t header_size = data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24);
391 uint32_t payload_len = data[8] | (data[9] << 8) | (data[10] << 16) | (data[11] << 24);
392 ndi_ctx->target_size = 12 + header_size + payload_len;
393 //printf("%u %u %u \n", header_size, payload_len, message_type);
394 }
395
396 //printf("target size %i \n", ndi_ctx->target_size);
397 if(av_fifo_space(ndi_ctx->fifo) < ndi_ctx->target_size)
398 av_fifo_grow(ndi_ctx->fifo, ndi_ctx->target_size * 3 / 2);
399 }
400
401 if(av_fifo_size(ndi_ctx->fifo) >= ndi_ctx->target_size) {
402 /* FIXME: make this zero copy */
403 uint8_t *data = malloc(ndi_ctx->target_size);
404 if(!data)
405 return -1;
406
407 av_fifo_generic_read(ndi_ctx->fifo, data, ndi_ctx->target_size, NULL);
408 process_ndi_packet(ndi_ctx, data, ndi_ctx->target_size);
409 //printf("draining %i \n", ndi_ctx->target_size);
410 free(data);
411 ndi_ctx->target_size = 0;
412 }
413
414 return 0;
415 }
416
receive_ndi_packet(ndi_ctx * ndi_ctx)417 static int receive_ndi_packet(ndi_ctx *ndi_ctx)
418 {
419 uint8_t tmp[5000];
420
421 /* TODO: Zero copy */
422 int len = recv(ndi_ctx->socket_fd, (void *)tmp, 5000, 0);
423 if(len < 0)
424 printf("bad \n");
425
426 if(len == 0)
427 printf("end \n");
428
429 if(av_fifo_space(ndi_ctx->fifo) < len)
430 av_fifo_grow(ndi_ctx->fifo, 5000); // fixme
431
432 av_fifo_generic_write(ndi_ctx->fifo, tmp, len, NULL);
433
434 if(handle_ndi_packet(ndi_ctx) < 0) {
435 printf("handle fail \n");
436 return -1;
437 }
438
439 return 0;
440 }
441
request_ndi_data(ndi_ctx * ndi_ctx)442 static int request_ndi_data(ndi_ctx *ndi_ctx)
443 {
444 int pending = ndi_ctx->pending_requests;
445 for(int i = 0; i < pending; i++) {
446 ndi_message *ndi_request = &ndi_ctx->ndi_request[i];
447 // XXX: Check failure
448 send(ndi_ctx->socket_fd, (void *)ndi_request->buf, ndi_request->len, 0);
449 free(ndi_request->buf);
450 ndi_request->buf = NULL;
451 ndi_ctx->pending_requests--;
452 }
453
454 return 0;
455 }
456
libndi_init(void)457 ndi_ctx *libndi_init(void)
458 {
459 ndi_ctx *ndi_ctx = calloc(1, sizeof(*ndi_ctx));
460 if(!ndi_ctx) {
461 fprintf(stderr, "malloc failed \n");
462 return NULL;
463 }
464
465 ndi_ctx->fifo = av_fifo_alloc(10000);
466 if(!ndi_ctx->fifo)
467 goto end;
468
469 return ndi_ctx;
470
471 end:
472 libndi_close(ndi_ctx);
473 return NULL;
474
475 }
476
libndi_setup(ndi_ctx * ndi_ctx,ndi_opts * ndi_opts)477 int libndi_setup(ndi_ctx *ndi_ctx, ndi_opts *ndi_opts)
478 {
479 if(ndi_ctx->ip) {
480 free(ndi_ctx->ip);
481 ndi_ctx->ip = NULL;
482 }
483
484 if(ndi_ctx->port) {
485 free(ndi_ctx->port);
486 ndi_ctx->port = NULL;
487 }
488
489 if(!ndi_opts->ip || !ndi_opts->port) {
490 fprintf(stderr, "IP or port not set \n");
491 return -1;
492 }
493
494 ndi_ctx->ip = strdup(ndi_opts->ip);
495 if(!ndi_ctx->ip) {
496 fprintf(stderr, "Malloc failed \n");
497 return -1;
498 }
499
500 ndi_ctx->port = strdup(ndi_opts->port);
501 if(!ndi_ctx->port) {
502 fprintf(stderr, "Malloc failed \n");
503 return -1;
504 }
505
506 /* Create and scramble request messages */
507 char *tx_msgs[4] = {
508 "<ndi_version text=\"3\" video=\"4\" audio=\"3\" sdk=\"3.5.1\" platform=\"LINUX\"/>",
509 "<ndi_video quality=\"high\"/>",
510 "<ndi_enabled_streams video=\"true\" audio=\"true\" text=\"true\"/>",
511 };
512
513 char tally_msg[64];
514 int ret = snprintf(tally_msg, sizeof(tally_msg),
515 "<ndi_tally on_program=\"%s\" on_preview=\"%s\"/>",
516 (ndi_opts->initial_tally_state == NDI_TALLY_LIVE) ? "true" : "false",
517 (ndi_opts->initial_tally_state == NDI_TALLY_PREVIEW) ? "true" : "false");
518
519 if (ret < 0 || ret >= (int)sizeof(tally_msg))
520 return -1;
521
522 tx_msgs[3] = tally_msg;
523
524 for(size_t i = 0; i < sizeof(tx_msgs) / sizeof(*tx_msgs); i++) {
525 size_t payload_len = strlen(tx_msgs[i]) + 1;
526 ndi_message *ndi_request = &ndi_ctx->ndi_request[i];
527 ndi_request->buf = calloc(1, payload_len + 20);
528 if(!ndi_request->buf) {
529 fprintf(stderr, "Malloc failed \n");
530 return -1;
531 }
532
533 int dst_len = 0;
534 create_text_message(ndi_request->buf, &dst_len, tx_msgs[i], payload_len);
535 ndi_scramble_type1(ndi_request->buf+12, 8+payload_len, 8+payload_len);
536 ndi_request->len = dst_len;
537 }
538
539 ndi_ctx->pending_requests = 4;
540
541 return 0;
542 }
543
544 #ifdef _WIN32
system_InitWSA(int hi,int lo)545 static int system_InitWSA(int hi, int lo) {
546 WSADATA data;
547
548 if (WSAStartup(MAKEWORD(hi, lo), &data) == 0) {
549 if (LOBYTE(data.wVersion) == 2 && HIBYTE(data.wVersion) == 2) return 0;
550 /* Winsock DLL is not usable */
551 WSACleanup();
552 }
553 return -1;
554 }
555
system_Init(void)556 static void system_Init(void) {
557 if (system_InitWSA(2, 2) && system_InitWSA(1, 1))
558 fputs("Error: cannot initialize Winsocks\n", stderr);
559 }
560 #else
system_Init(void)561 static void system_Init(void) { return; }
562 #endif
563
libndi_receive_data(ndi_ctx * ndi_ctx,ndi_data_cb callback,void * user_data)564 void libndi_receive_data(ndi_ctx *ndi_ctx, ndi_data_cb callback, void *user_data)
565 {
566 ndi_ctx->callback = callback;
567 ndi_ctx->user_data = user_data;
568
569 /* connect to socket */
570 int ret;
571 struct addrinfo hints, *res, *p;
572 memset(&hints, 0, sizeof hints);
573 hints.ai_family = AF_UNSPEC;
574 hints.ai_socktype = SOCK_STREAM;
575
576 system_Init();
577
578 if ((ret = getaddrinfo(ndi_ctx->ip, ndi_ctx->port, &hints, &res)) != 0) {
579 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret));
580 goto end;
581 }
582
583 for(p = res; p != NULL; p = p->ai_next) {
584 ndi_ctx->socket_fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
585 if(ndi_ctx->socket_fd < 0)
586 continue;
587
588 ret = connect(ndi_ctx->socket_fd, res->ai_addr, res->ai_addrlen);
589 if(ret < 0) {
590 printf("can't connect \n");
591 freeaddrinfo(res);
592 goto end;
593 }
594 }
595
596 freeaddrinfo(res);
597
598 struct pollfd fds[1];
599 fds[0].fd = ndi_ctx->socket_fd;
600 fds[0].events = POLLIN | POLLOUT;
601 while(poll(fds, 1, 10000)) {
602 if(fds[0].revents & POLLOUT) {
603 if(request_ndi_data(ndi_ctx) < 0)
604 goto end;
605
606 if(!ndi_ctx->pending_requests)
607 fds[0].events = POLLIN;
608 }
609
610 if(fds[0].revents & POLLIN)
611 receive_ndi_packet(ndi_ctx);
612 }
613
614 end:
615 return;
616 }
617
libndi_close(ndi_ctx * ndi_ctx)618 void libndi_close(ndi_ctx *ndi_ctx)
619 {
620 if(!ndi_ctx)
621 return;
622
623 if(ndi_ctx->ip)
624 free(ndi_ctx->ip);
625
626 if(ndi_ctx->port)
627 free(ndi_ctx->port);
628
629 for(int i = 0; i < 4; i++)
630 free(ndi_ctx->ndi_request[i].buf);
631
632 av_fifo_free(ndi_ctx->fifo);
633
634 free(ndi_ctx);
635 }
636