grandi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +402 -0
- package/binding.gyp +200 -0
- package/lib/.clang-format +297 -0
- package/lib/grandi.cc +96 -0
- package/lib/grandi_find.cc +365 -0
- package/lib/grandi_find.h +41 -0
- package/lib/grandi_receive.cc +1155 -0
- package/lib/grandi_receive.h +63 -0
- package/lib/grandi_routing.cc +460 -0
- package/lib/grandi_routing.h +37 -0
- package/lib/grandi_send.cc +960 -0
- package/lib/grandi_send.h +45 -0
- package/lib/grandi_util.cc +323 -0
- package/lib/grandi_util.h +137 -0
- package/package.json +84 -0
- package/prebuilds/darwin-arm64/grandi.node +0 -0
- package/prebuilds/darwin-arm64/libndi.dylib +0 -0
- package/prebuilds/darwin-x64/grandi.node +0 -0
- package/prebuilds/darwin-x64/libndi.dylib +0 -0
- package/prebuilds/linux-arm/grandi.node +0 -0
- package/prebuilds/linux-arm/libndi.so.6 +0 -0
- package/prebuilds/linux-arm64/grandi.node +0 -0
- package/prebuilds/linux-arm64/libndi.so.6 +0 -0
- package/prebuilds/linux-ia32/grandi.node +0 -0
- package/prebuilds/linux-ia32/libndi.so.6 +0 -0
- package/prebuilds/linux-x64/grandi.node +0 -0
- package/prebuilds/linux-x64/libndi.so.6 +0 -0
- package/prebuilds/win32-ia32/Processing.NDI.Lib.x86.dll +0 -0
- package/prebuilds/win32-ia32/grandi.node +0 -0
- package/prebuilds/win32-x64/Processing.NDI.Lib.x64.dll +0 -0
- package/prebuilds/win32-x64/grandi.node +0 -0
- package/scripts/preinstall.mjs +429 -0
- package/src/index.ts +141 -0
- package/src/node-gyp-build.d.ts +5 -0
- package/src/types.ts +228 -0
|
@@ -0,0 +1,1155 @@
|
|
|
1
|
+
/* Copyright 2018 Streampunk Media Ltd.
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
#include <chrono>
|
|
17
|
+
#include <Processing.NDI.Lib.h>
|
|
18
|
+
#include <inttypes.h>
|
|
19
|
+
|
|
20
|
+
#ifdef _WIN32
|
|
21
|
+
#ifdef _WIN64
|
|
22
|
+
#pragma comment(lib, "Processing.NDI.Lib.x64.lib")
|
|
23
|
+
#else // _WIN64
|
|
24
|
+
#pragma comment(lib, "Processing.NDI.Lib.x86.lib")
|
|
25
|
+
#endif // _WIN64
|
|
26
|
+
#endif // _WIN32
|
|
27
|
+
|
|
28
|
+
#include "grandi_receive.h"
|
|
29
|
+
#include "grandi_util.h"
|
|
30
|
+
#include "grandi_find.h"
|
|
31
|
+
|
|
32
|
+
namespace {
|
|
33
|
+
uint32_t remainingWaitMs(uint32_t initialWait,
|
|
34
|
+
const std::chrono::steady_clock::time_point &start) {
|
|
35
|
+
if (initialWait == 0)
|
|
36
|
+
return 0;
|
|
37
|
+
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
38
|
+
std::chrono::steady_clock::now() - start)
|
|
39
|
+
.count();
|
|
40
|
+
if (elapsed >= initialWait)
|
|
41
|
+
return 0;
|
|
42
|
+
return initialWait - static_cast<uint32_t>(elapsed);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void freeCapturedFrame(dataCarrier *c, NDIlib_frame_type_e frameType) {
|
|
46
|
+
switch (frameType) {
|
|
47
|
+
case NDIlib_frame_type_video:
|
|
48
|
+
NDIlib_recv_free_video_v2(c->recv, &c->videoFrame);
|
|
49
|
+
break;
|
|
50
|
+
case NDIlib_frame_type_audio:
|
|
51
|
+
NDIlib_recv_free_audio_v3(c->recv, &c->audioFrame);
|
|
52
|
+
break;
|
|
53
|
+
case NDIlib_frame_type_metadata:
|
|
54
|
+
NDIlib_recv_free_metadata(c->recv, &c->metadataFrame);
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
bool captureUntilFrame(dataCarrier *c, NDIlib_frame_type_e desired,
|
|
62
|
+
uint32_t initialWait, int32_t timeoutStatus,
|
|
63
|
+
const char *timeoutMsg, const char *connectionMsg) {
|
|
64
|
+
auto start = std::chrono::steady_clock::now();
|
|
65
|
+
uint32_t waitMs = initialWait;
|
|
66
|
+
|
|
67
|
+
while (true) {
|
|
68
|
+
NDIlib_frame_type_e frameType = NDIlib_recv_capture_v3(
|
|
69
|
+
c->recv, &c->videoFrame, &c->audioFrame, &c->metadataFrame, waitMs);
|
|
70
|
+
|
|
71
|
+
if (frameType == desired)
|
|
72
|
+
return true;
|
|
73
|
+
|
|
74
|
+
switch (frameType) {
|
|
75
|
+
case NDIlib_frame_type_none:
|
|
76
|
+
c->status = timeoutStatus;
|
|
77
|
+
c->errorMsg = timeoutMsg;
|
|
78
|
+
return false;
|
|
79
|
+
case NDIlib_frame_type_error:
|
|
80
|
+
c->status = GRANDI_CONNECTION_LOST;
|
|
81
|
+
c->errorMsg = connectionMsg;
|
|
82
|
+
return false;
|
|
83
|
+
case NDIlib_frame_type_video:
|
|
84
|
+
case NDIlib_frame_type_audio:
|
|
85
|
+
case NDIlib_frame_type_metadata:
|
|
86
|
+
freeCapturedFrame(c, frameType);
|
|
87
|
+
break;
|
|
88
|
+
default:
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
waitMs = remainingWaitMs(initialWait, start);
|
|
93
|
+
if (initialWait != 0 && waitMs == 0) {
|
|
94
|
+
c->status = timeoutStatus;
|
|
95
|
+
c->errorMsg = timeoutMsg;
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} // namespace
|
|
101
|
+
|
|
102
|
+
void finalizeReceive(napi_env env, void *data, void *hint) {
|
|
103
|
+
if (hint == nullptr) {
|
|
104
|
+
NDIlib_recv_destroy((NDIlib_recv_instance_t)data);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
napi_value obj = (napi_value)hint;
|
|
109
|
+
napi_value recvValue;
|
|
110
|
+
if (napi_get_named_property(env, obj, "embedded", &recvValue) != napi_ok)
|
|
111
|
+
return;
|
|
112
|
+
|
|
113
|
+
napi_valuetype result;
|
|
114
|
+
if (napi_typeof(env, recvValue, &result) != napi_ok)
|
|
115
|
+
return;
|
|
116
|
+
if (result != napi_external)
|
|
117
|
+
return;
|
|
118
|
+
|
|
119
|
+
void *recvData;
|
|
120
|
+
if (napi_get_value_external(env, recvValue, &recvData) != napi_ok)
|
|
121
|
+
return;
|
|
122
|
+
NDIlib_recv_destroy((NDIlib_recv_instance_t)recvData);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
napi_value destroyReceive(napi_env env, napi_callback_info info) {
|
|
126
|
+
bool success = false;
|
|
127
|
+
napi_value thisValue;
|
|
128
|
+
size_t argc = 0;
|
|
129
|
+
if (napi_get_cb_info(env, info, &argc, nullptr, &thisValue, nullptr) !=
|
|
130
|
+
napi_ok)
|
|
131
|
+
goto done;
|
|
132
|
+
|
|
133
|
+
napi_value recvValue;
|
|
134
|
+
if (napi_get_named_property(env, thisValue, "embedded", &recvValue) !=
|
|
135
|
+
napi_ok)
|
|
136
|
+
goto done;
|
|
137
|
+
|
|
138
|
+
napi_valuetype type;
|
|
139
|
+
if (napi_typeof(env, recvValue, &type) != napi_ok)
|
|
140
|
+
goto done;
|
|
141
|
+
|
|
142
|
+
if (type == napi_external) {
|
|
143
|
+
void *recvData;
|
|
144
|
+
if (napi_get_value_external(env, recvValue, &recvData) != napi_ok)
|
|
145
|
+
goto done;
|
|
146
|
+
NDIlib_recv_destroy((NDIlib_recv_instance_t)recvData);
|
|
147
|
+
napi_value value;
|
|
148
|
+
if (napi_create_int32(env, 0, &value) == napi_ok)
|
|
149
|
+
napi_set_named_property(env, thisValue, "embedded", value);
|
|
150
|
+
success = true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
done:
|
|
154
|
+
napi_value result;
|
|
155
|
+
if (napi_get_boolean(env, success, &result) != napi_ok)
|
|
156
|
+
napi_get_boolean(env, false, &result);
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
void receiveExecute(napi_env env, void *data) {
|
|
161
|
+
receiveCarrier *c = (receiveCarrier *)data;
|
|
162
|
+
|
|
163
|
+
NDIlib_recv_create_v3_t receiveConfig;
|
|
164
|
+
receiveConfig.color_format = c->colorFormat;
|
|
165
|
+
receiveConfig.bandwidth = c->bandwidth;
|
|
166
|
+
receiveConfig.allow_video_fields = c->allowVideoFields;
|
|
167
|
+
receiveConfig.p_ndi_recv_name = c->name;
|
|
168
|
+
|
|
169
|
+
c->recv = NDIlib_recv_create_v3(&receiveConfig);
|
|
170
|
+
if (!c->recv) {
|
|
171
|
+
c->status = GRANDI_RECEIVE_CREATE_FAIL;
|
|
172
|
+
c->errorMsg = "Failed to create NDI receiver.";
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
NDIlib_recv_connect(c->recv, c->source);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
void receiveComplete(napi_env env, napi_status asyncStatus, void *data) {
|
|
180
|
+
receiveCarrier *c = (receiveCarrier *)data;
|
|
181
|
+
|
|
182
|
+
if (asyncStatus != napi_ok) {
|
|
183
|
+
c->status = asyncStatus;
|
|
184
|
+
c->errorMsg = "Async receiver creation failed to complete.";
|
|
185
|
+
}
|
|
186
|
+
REJECT_STATUS;
|
|
187
|
+
|
|
188
|
+
napi_value result;
|
|
189
|
+
c->status = napi_create_object(env, &result);
|
|
190
|
+
REJECT_STATUS;
|
|
191
|
+
|
|
192
|
+
napi_value embedded;
|
|
193
|
+
c->status =
|
|
194
|
+
napi_create_external(env, c->recv, finalizeReceive, result, &embedded);
|
|
195
|
+
REJECT_STATUS;
|
|
196
|
+
c->status = napi_set_named_property(env, result, "embedded", embedded);
|
|
197
|
+
REJECT_STATUS;
|
|
198
|
+
|
|
199
|
+
napi_value destroyFn;
|
|
200
|
+
c->status = napi_create_function(env, "destroy", NAPI_AUTO_LENGTH,
|
|
201
|
+
destroyReceive, nullptr, &destroyFn);
|
|
202
|
+
REJECT_STATUS;
|
|
203
|
+
c->status = napi_set_named_property(env, result, "destroy", destroyFn);
|
|
204
|
+
REJECT_STATUS;
|
|
205
|
+
|
|
206
|
+
napi_value videoFn;
|
|
207
|
+
c->status = napi_create_function(env, "video", NAPI_AUTO_LENGTH, videoReceive,
|
|
208
|
+
nullptr, &videoFn);
|
|
209
|
+
REJECT_STATUS;
|
|
210
|
+
c->status = napi_set_named_property(env, result, "video", videoFn);
|
|
211
|
+
REJECT_STATUS;
|
|
212
|
+
|
|
213
|
+
napi_value audioFn;
|
|
214
|
+
c->status = napi_create_function(env, "audio", NAPI_AUTO_LENGTH, audioReceive,
|
|
215
|
+
nullptr, &audioFn);
|
|
216
|
+
REJECT_STATUS;
|
|
217
|
+
c->status = napi_set_named_property(env, result, "audio", audioFn);
|
|
218
|
+
REJECT_STATUS;
|
|
219
|
+
|
|
220
|
+
napi_value metadataFn;
|
|
221
|
+
c->status = napi_create_function(env, "metadata", NAPI_AUTO_LENGTH,
|
|
222
|
+
metadataReceive, nullptr, &metadataFn);
|
|
223
|
+
REJECT_STATUS;
|
|
224
|
+
c->status = napi_set_named_property(env, result, "metadata", metadataFn);
|
|
225
|
+
REJECT_STATUS;
|
|
226
|
+
|
|
227
|
+
napi_value dataFn;
|
|
228
|
+
c->status = napi_create_function(env, "data", NAPI_AUTO_LENGTH, dataReceive,
|
|
229
|
+
nullptr, &dataFn);
|
|
230
|
+
REJECT_STATUS;
|
|
231
|
+
c->status = napi_set_named_property(env, result, "data", dataFn);
|
|
232
|
+
REJECT_STATUS;
|
|
233
|
+
|
|
234
|
+
napi_value tallyFn;
|
|
235
|
+
c->status = napi_create_function(env, "tally", NAPI_AUTO_LENGTH,
|
|
236
|
+
setReceiveTally, nullptr, &tallyFn);
|
|
237
|
+
REJECT_STATUS;
|
|
238
|
+
c->status = napi_set_named_property(env, result, "tally", tallyFn);
|
|
239
|
+
REJECT_STATUS;
|
|
240
|
+
|
|
241
|
+
napi_value source, name, uri;
|
|
242
|
+
c->status = napi_create_string_utf8(env, c->source->p_ndi_name,
|
|
243
|
+
NAPI_AUTO_LENGTH, &name);
|
|
244
|
+
REJECT_STATUS;
|
|
245
|
+
if (c->source->p_url_address != NULL) {
|
|
246
|
+
c->status = napi_create_string_utf8(env, c->source->p_url_address,
|
|
247
|
+
NAPI_AUTO_LENGTH, &uri);
|
|
248
|
+
REJECT_STATUS;
|
|
249
|
+
}
|
|
250
|
+
c->status = napi_create_object(env, &source);
|
|
251
|
+
REJECT_STATUS;
|
|
252
|
+
c->status = napi_set_named_property(env, source, "name", name);
|
|
253
|
+
REJECT_STATUS;
|
|
254
|
+
c->status = napi_set_named_property(env, source, "urlAddress", uri);
|
|
255
|
+
REJECT_STATUS;
|
|
256
|
+
c->status = napi_set_named_property(env, result, "source", source);
|
|
257
|
+
REJECT_STATUS;
|
|
258
|
+
|
|
259
|
+
napi_value colorFormat;
|
|
260
|
+
c->status = napi_create_int32(env, (int32_t)c->colorFormat, &colorFormat);
|
|
261
|
+
REJECT_STATUS;
|
|
262
|
+
c->status = napi_set_named_property(env, result, "colorFormat", colorFormat);
|
|
263
|
+
REJECT_STATUS;
|
|
264
|
+
|
|
265
|
+
napi_value bandwidth;
|
|
266
|
+
c->status = napi_create_int32(env, (int32_t)c->bandwidth, &bandwidth);
|
|
267
|
+
REJECT_STATUS;
|
|
268
|
+
c->status = napi_set_named_property(env, result, "bandwidth", bandwidth);
|
|
269
|
+
REJECT_STATUS;
|
|
270
|
+
|
|
271
|
+
napi_value allowVideoFields;
|
|
272
|
+
c->status = napi_get_boolean(env, c->allowVideoFields, &allowVideoFields);
|
|
273
|
+
REJECT_STATUS;
|
|
274
|
+
c->status = napi_set_named_property(env, result, "allowVideoFields",
|
|
275
|
+
allowVideoFields);
|
|
276
|
+
REJECT_STATUS;
|
|
277
|
+
|
|
278
|
+
if (c->name != nullptr) {
|
|
279
|
+
c->status = napi_create_string_utf8(env, c->name, NAPI_AUTO_LENGTH, &name);
|
|
280
|
+
REJECT_STATUS;
|
|
281
|
+
c->status = napi_set_named_property(env, result, "name", name);
|
|
282
|
+
REJECT_STATUS;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
napi_status status;
|
|
286
|
+
status = napi_resolve_deferred(env, c->_deferred, result);
|
|
287
|
+
FLOATING_STATUS;
|
|
288
|
+
|
|
289
|
+
tidyCarrier(env, c);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
napi_value receive(napi_env env, napi_callback_info info) {
|
|
293
|
+
napi_valuetype type;
|
|
294
|
+
receiveCarrier *c = new receiveCarrier;
|
|
295
|
+
|
|
296
|
+
napi_value promise;
|
|
297
|
+
c->status = napi_create_promise(env, &c->_deferred, &promise);
|
|
298
|
+
REJECT_RETURN;
|
|
299
|
+
|
|
300
|
+
size_t argc = 1;
|
|
301
|
+
napi_value args[1];
|
|
302
|
+
c->status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
|
|
303
|
+
REJECT_RETURN;
|
|
304
|
+
|
|
305
|
+
if (argc != (size_t)1)
|
|
306
|
+
REJECT_ERROR_RETURN("Receiver must be created with an object containing at "
|
|
307
|
+
"least a 'source' property.",
|
|
308
|
+
GRANDI_INVALID_ARGS);
|
|
309
|
+
|
|
310
|
+
c->status = napi_typeof(env, args[0], &type);
|
|
311
|
+
REJECT_RETURN;
|
|
312
|
+
bool isArray;
|
|
313
|
+
c->status = napi_is_array(env, args[0], &isArray);
|
|
314
|
+
REJECT_RETURN;
|
|
315
|
+
if ((type != napi_object) || isArray)
|
|
316
|
+
REJECT_ERROR_RETURN("Single argument must be an object, not an array, "
|
|
317
|
+
"containing at least a 'source' property.",
|
|
318
|
+
GRANDI_INVALID_ARGS);
|
|
319
|
+
|
|
320
|
+
napi_value config = args[0];
|
|
321
|
+
napi_value source, colorFormat, bandwidth, allowVideoFields, name;
|
|
322
|
+
// source is an object, not an array, with name and urlAddress
|
|
323
|
+
// convert to a native source
|
|
324
|
+
c->status = napi_get_named_property(env, config, "source", &source);
|
|
325
|
+
REJECT_RETURN;
|
|
326
|
+
c->status = napi_typeof(env, source, &type);
|
|
327
|
+
REJECT_RETURN;
|
|
328
|
+
c->status = napi_is_array(env, source, &isArray);
|
|
329
|
+
REJECT_RETURN;
|
|
330
|
+
if ((type != napi_object) || isArray)
|
|
331
|
+
REJECT_ERROR_RETURN("Source property must be an object and not an array.",
|
|
332
|
+
GRANDI_INVALID_ARGS);
|
|
333
|
+
|
|
334
|
+
napi_value checkType;
|
|
335
|
+
c->status = napi_get_named_property(env, source, "name", &checkType);
|
|
336
|
+
REJECT_RETURN;
|
|
337
|
+
c->status = napi_typeof(env, checkType, &type);
|
|
338
|
+
REJECT_RETURN;
|
|
339
|
+
if (type != napi_string)
|
|
340
|
+
REJECT_ERROR_RETURN("Source property must have a 'name' sub-property that "
|
|
341
|
+
"is of type string.",
|
|
342
|
+
GRANDI_INVALID_ARGS);
|
|
343
|
+
|
|
344
|
+
c->status = napi_get_named_property(env, source, "urlAddress", &checkType);
|
|
345
|
+
REJECT_RETURN;
|
|
346
|
+
c->status = napi_typeof(env, checkType, &type);
|
|
347
|
+
REJECT_RETURN;
|
|
348
|
+
if (type != napi_undefined && type != napi_string)
|
|
349
|
+
REJECT_ERROR_RETURN(
|
|
350
|
+
"Source 'urlAddress' sub-property must be of type string.",
|
|
351
|
+
GRANDI_INVALID_ARGS);
|
|
352
|
+
|
|
353
|
+
c->source = new NDIlib_source_t();
|
|
354
|
+
c->status = makeNativeSource(env, source, c->source);
|
|
355
|
+
REJECT_RETURN;
|
|
356
|
+
|
|
357
|
+
c->status = napi_get_named_property(env, config, "colorFormat", &colorFormat);
|
|
358
|
+
REJECT_RETURN;
|
|
359
|
+
c->status = napi_typeof(env, colorFormat, &type);
|
|
360
|
+
REJECT_RETURN;
|
|
361
|
+
if (type != napi_undefined) {
|
|
362
|
+
if (type != napi_number)
|
|
363
|
+
REJECT_ERROR_RETURN("Color format property must be a number.",
|
|
364
|
+
GRANDI_INVALID_ARGS);
|
|
365
|
+
int32_t enumValue;
|
|
366
|
+
c->status = napi_get_value_int32(env, colorFormat, &enumValue);
|
|
367
|
+
REJECT_RETURN;
|
|
368
|
+
|
|
369
|
+
c->colorFormat = (NDIlib_recv_color_format_e)enumValue;
|
|
370
|
+
if (!validColorFormat(c->colorFormat))
|
|
371
|
+
REJECT_ERROR_RETURN("Invalid colour format value.", GRANDI_INVALID_ARGS);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
c->status = napi_get_named_property(env, config, "bandwidth", &bandwidth);
|
|
375
|
+
REJECT_RETURN;
|
|
376
|
+
c->status = napi_typeof(env, bandwidth, &type);
|
|
377
|
+
REJECT_RETURN;
|
|
378
|
+
if (type != napi_undefined) {
|
|
379
|
+
if (type != napi_number)
|
|
380
|
+
REJECT_ERROR_RETURN("Bandwidth property must be a number.",
|
|
381
|
+
GRANDI_INVALID_ARGS);
|
|
382
|
+
int32_t enumValue;
|
|
383
|
+
c->status = napi_get_value_int32(env, bandwidth, &enumValue);
|
|
384
|
+
REJECT_RETURN;
|
|
385
|
+
|
|
386
|
+
c->bandwidth = (NDIlib_recv_bandwidth_e)enumValue;
|
|
387
|
+
if (!validBandwidth(c->bandwidth))
|
|
388
|
+
REJECT_ERROR_RETURN("Invalid bandwidth value.", GRANDI_INVALID_ARGS);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
c->status = napi_get_named_property(env, config, "allowVideoFields",
|
|
392
|
+
&allowVideoFields);
|
|
393
|
+
REJECT_RETURN;
|
|
394
|
+
c->status = napi_typeof(env, allowVideoFields, &type);
|
|
395
|
+
REJECT_RETURN;
|
|
396
|
+
if (type != napi_undefined) {
|
|
397
|
+
if (type != napi_boolean)
|
|
398
|
+
REJECT_ERROR_RETURN("Allow video fields property must be a Boolean.",
|
|
399
|
+
GRANDI_INVALID_ARGS);
|
|
400
|
+
c->status =
|
|
401
|
+
napi_get_value_bool(env, allowVideoFields, &c->allowVideoFields);
|
|
402
|
+
REJECT_RETURN;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
c->status = napi_get_named_property(env, config, "name", &name);
|
|
406
|
+
REJECT_RETURN;
|
|
407
|
+
c->status = napi_typeof(env, name, &type);
|
|
408
|
+
if (type != napi_undefined) {
|
|
409
|
+
if (type != napi_string)
|
|
410
|
+
REJECT_ERROR_RETURN(
|
|
411
|
+
"Optional name property must be a string when present.",
|
|
412
|
+
GRANDI_INVALID_ARGS);
|
|
413
|
+
size_t namel;
|
|
414
|
+
c->status = napi_get_value_string_utf8(env, name, nullptr, 0, &namel);
|
|
415
|
+
REJECT_RETURN;
|
|
416
|
+
c->name = (char *)malloc(namel + 1);
|
|
417
|
+
c->status =
|
|
418
|
+
napi_get_value_string_utf8(env, name, c->name, namel + 1, &namel);
|
|
419
|
+
REJECT_RETURN;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
napi_value resource_name;
|
|
423
|
+
c->status =
|
|
424
|
+
napi_create_string_utf8(env, "Receive", NAPI_AUTO_LENGTH, &resource_name);
|
|
425
|
+
REJECT_RETURN;
|
|
426
|
+
c->status = napi_create_async_work(env, NULL, resource_name, receiveExecute,
|
|
427
|
+
receiveComplete, c, &c->_request);
|
|
428
|
+
REJECT_RETURN;
|
|
429
|
+
c->status = napi_queue_async_work(env, c->_request);
|
|
430
|
+
REJECT_RETURN;
|
|
431
|
+
|
|
432
|
+
return promise;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
void videoReceiveExecute(napi_env env, void *data) {
|
|
436
|
+
dataCarrier *c = (dataCarrier *)data;
|
|
437
|
+
|
|
438
|
+
if (!captureUntilFrame(
|
|
439
|
+
c, NDIlib_frame_type_video, c->wait, GRANDI_NOT_FOUND,
|
|
440
|
+
"No video data received in the requested time interval.",
|
|
441
|
+
"Received error response from NDI video request. Connection lost."))
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
void videoReceiveComplete(napi_env env, napi_status asyncStatus, void *data) {
|
|
446
|
+
dataCarrier *c = (dataCarrier *)data;
|
|
447
|
+
|
|
448
|
+
if (asyncStatus != napi_ok) {
|
|
449
|
+
c->status = asyncStatus;
|
|
450
|
+
c->errorMsg = "Async video frame receive failed to complete.";
|
|
451
|
+
}
|
|
452
|
+
REJECT_STATUS;
|
|
453
|
+
|
|
454
|
+
napi_value result;
|
|
455
|
+
c->status = napi_create_object(env, &result);
|
|
456
|
+
REJECT_STATUS;
|
|
457
|
+
|
|
458
|
+
int32_t ptps, ptpn;
|
|
459
|
+
ptps = (int32_t)(c->videoFrame.timestamp / 10000000);
|
|
460
|
+
ptpn = (c->videoFrame.timestamp % 10000000) * 100;
|
|
461
|
+
|
|
462
|
+
napi_value param;
|
|
463
|
+
c->status = napi_create_string_utf8(env, "video", NAPI_AUTO_LENGTH, ¶m);
|
|
464
|
+
REJECT_STATUS;
|
|
465
|
+
c->status = napi_set_named_property(env, result, "type", param);
|
|
466
|
+
REJECT_STATUS;
|
|
467
|
+
|
|
468
|
+
c->status = napi_create_int32(env, c->videoFrame.xres, ¶m);
|
|
469
|
+
REJECT_STATUS;
|
|
470
|
+
c->status = napi_set_named_property(env, result, "xres", param);
|
|
471
|
+
REJECT_STATUS;
|
|
472
|
+
|
|
473
|
+
c->status = napi_create_int32(env, c->videoFrame.yres, ¶m);
|
|
474
|
+
REJECT_STATUS;
|
|
475
|
+
c->status = napi_set_named_property(env, result, "yres", param);
|
|
476
|
+
REJECT_STATUS;
|
|
477
|
+
|
|
478
|
+
c->status = napi_create_int32(env, c->videoFrame.frame_rate_N, ¶m);
|
|
479
|
+
REJECT_STATUS;
|
|
480
|
+
c->status = napi_set_named_property(env, result, "frameRateN", param);
|
|
481
|
+
REJECT_STATUS;
|
|
482
|
+
|
|
483
|
+
c->status = napi_create_int32(env, c->videoFrame.frame_rate_D, ¶m);
|
|
484
|
+
REJECT_STATUS;
|
|
485
|
+
c->status = napi_set_named_property(env, result, "frameRateD", param);
|
|
486
|
+
REJECT_STATUS;
|
|
487
|
+
|
|
488
|
+
c->status = napi_create_double(
|
|
489
|
+
env, (double)c->videoFrame.picture_aspect_ratio, ¶m);
|
|
490
|
+
REJECT_STATUS;
|
|
491
|
+
c->status = napi_set_named_property(env, result, "pictureAspectRatio", param);
|
|
492
|
+
REJECT_STATUS;
|
|
493
|
+
|
|
494
|
+
napi_value params, paramn;
|
|
495
|
+
c->status = napi_create_int32(env, ptps, ¶ms);
|
|
496
|
+
REJECT_STATUS;
|
|
497
|
+
c->status = napi_create_int32(env, ptpn, ¶mn);
|
|
498
|
+
REJECT_STATUS;
|
|
499
|
+
c->status = napi_create_array(env, ¶m);
|
|
500
|
+
REJECT_STATUS;
|
|
501
|
+
c->status = napi_set_element(env, param, 0, params);
|
|
502
|
+
REJECT_STATUS;
|
|
503
|
+
c->status = napi_set_element(env, param, 1, paramn);
|
|
504
|
+
REJECT_STATUS;
|
|
505
|
+
c->status = napi_set_named_property(env, result, "timestamp", param);
|
|
506
|
+
REJECT_STATUS;
|
|
507
|
+
|
|
508
|
+
c->status = napi_create_int32(env, c->videoFrame.FourCC, ¶m);
|
|
509
|
+
REJECT_STATUS;
|
|
510
|
+
c->status = napi_set_named_property(env, result, "fourCC", param);
|
|
511
|
+
REJECT_STATUS;
|
|
512
|
+
|
|
513
|
+
c->status = napi_create_int32(env, c->videoFrame.frame_format_type, ¶m);
|
|
514
|
+
REJECT_STATUS;
|
|
515
|
+
c->status = napi_set_named_property(env, result, "frameFormatType", param);
|
|
516
|
+
REJECT_STATUS;
|
|
517
|
+
|
|
518
|
+
c->status = napi_create_int32(env, (int32_t)c->videoFrame.timecode / 10000000,
|
|
519
|
+
¶ms);
|
|
520
|
+
REJECT_STATUS;
|
|
521
|
+
c->status = napi_create_int32(env, (c->videoFrame.timecode % 10000000) * 100,
|
|
522
|
+
¶mn);
|
|
523
|
+
REJECT_STATUS;
|
|
524
|
+
c->status = napi_create_array(env, ¶m);
|
|
525
|
+
REJECT_STATUS;
|
|
526
|
+
c->status = napi_set_element(env, param, 0, params);
|
|
527
|
+
REJECT_STATUS;
|
|
528
|
+
c->status = napi_set_element(env, param, 1, paramn);
|
|
529
|
+
REJECT_STATUS;
|
|
530
|
+
c->status = napi_set_named_property(env, result, "timecode", param);
|
|
531
|
+
REJECT_STATUS;
|
|
532
|
+
|
|
533
|
+
c->status =
|
|
534
|
+
napi_create_int32(env, c->videoFrame.line_stride_in_bytes, ¶m);
|
|
535
|
+
REJECT_STATUS;
|
|
536
|
+
c->status = napi_set_named_property(env, result, "lineStrideBytes", param);
|
|
537
|
+
REJECT_STATUS;
|
|
538
|
+
|
|
539
|
+
if (c->videoFrame.p_metadata != nullptr) {
|
|
540
|
+
c->status = napi_create_string_utf8(env, c->videoFrame.p_metadata,
|
|
541
|
+
NAPI_AUTO_LENGTH, ¶m);
|
|
542
|
+
REJECT_STATUS;
|
|
543
|
+
c->status = napi_set_named_property(env, result, "metadata", param);
|
|
544
|
+
REJECT_STATUS;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
c->status = napi_create_buffer_copy(
|
|
548
|
+
env, c->videoFrame.line_stride_in_bytes * c->videoFrame.yres,
|
|
549
|
+
(void *)c->videoFrame.p_data, nullptr, ¶m);
|
|
550
|
+
REJECT_STATUS;
|
|
551
|
+
c->status = napi_set_named_property(env, result, "data", param);
|
|
552
|
+
REJECT_STATUS;
|
|
553
|
+
|
|
554
|
+
NDIlib_recv_free_video_v2(c->recv, &c->videoFrame);
|
|
555
|
+
|
|
556
|
+
napi_status status;
|
|
557
|
+
status = napi_resolve_deferred(env, c->_deferred, result);
|
|
558
|
+
FLOATING_STATUS;
|
|
559
|
+
|
|
560
|
+
tidyCarrier(env, c);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
napi_value setReceiveTally(napi_env env, napi_callback_info info) {
|
|
564
|
+
napi_status status;
|
|
565
|
+
|
|
566
|
+
size_t argc = 1;
|
|
567
|
+
napi_value args[1];
|
|
568
|
+
napi_value thisValue;
|
|
569
|
+
status = napi_get_cb_info(env, info, &argc, args, &thisValue, nullptr);
|
|
570
|
+
CHECK_STATUS;
|
|
571
|
+
|
|
572
|
+
if (argc != 1)
|
|
573
|
+
NAPI_THROW_ERROR(
|
|
574
|
+
"Receiver tally must be called with a single options object.");
|
|
575
|
+
|
|
576
|
+
napi_value embedded;
|
|
577
|
+
status = napi_get_named_property(env, thisValue, "embedded", &embedded);
|
|
578
|
+
CHECK_STATUS;
|
|
579
|
+
void *recvData;
|
|
580
|
+
status = napi_get_value_external(env, embedded, &recvData);
|
|
581
|
+
CHECK_STATUS;
|
|
582
|
+
NDIlib_recv_instance_t recv = (NDIlib_recv_instance_t)recvData;
|
|
583
|
+
|
|
584
|
+
napi_valuetype type;
|
|
585
|
+
status = napi_typeof(env, args[0], &type);
|
|
586
|
+
CHECK_STATUS;
|
|
587
|
+
bool isArray;
|
|
588
|
+
status = napi_is_array(env, args[0], &isArray);
|
|
589
|
+
CHECK_STATUS;
|
|
590
|
+
if ((type != napi_object) || isArray)
|
|
591
|
+
NAPI_THROW_ERROR("Receiver tally argument must be an object.");
|
|
592
|
+
|
|
593
|
+
bool onProgram = false;
|
|
594
|
+
bool onPreview = false;
|
|
595
|
+
// check for onProgram and onPreview properties
|
|
596
|
+
napi_value checkType;
|
|
597
|
+
status = napi_get_named_property(env, args[0], "onProgram", &checkType);
|
|
598
|
+
CHECK_STATUS;
|
|
599
|
+
status = napi_typeof(env, checkType, &type);
|
|
600
|
+
CHECK_STATUS;
|
|
601
|
+
if (type != napi_undefined) {
|
|
602
|
+
if (type != napi_boolean)
|
|
603
|
+
NAPI_THROW_ERROR("onProgram property must be a Boolean.");
|
|
604
|
+
status = napi_get_value_bool(env, checkType, &onProgram);
|
|
605
|
+
CHECK_STATUS;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
status = napi_get_named_property(env, args[0], "onPreview", &checkType);
|
|
609
|
+
CHECK_STATUS;
|
|
610
|
+
status = napi_typeof(env, checkType, &type);
|
|
611
|
+
CHECK_STATUS;
|
|
612
|
+
if (type != napi_undefined) {
|
|
613
|
+
if (type != napi_boolean)
|
|
614
|
+
NAPI_THROW_ERROR("onPreview property must be a Boolean.");
|
|
615
|
+
status = napi_get_value_bool(env, checkType, &onPreview);
|
|
616
|
+
CHECK_STATUS;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
NDIlib_tally_t tally;
|
|
620
|
+
tally.on_program = onProgram;
|
|
621
|
+
tally.on_preview = onPreview;
|
|
622
|
+
NDIlib_recv_set_tally(recv, &tally);
|
|
623
|
+
|
|
624
|
+
napi_value result;
|
|
625
|
+
status = napi_get_boolean(env, true, &result);
|
|
626
|
+
CHECK_STATUS;
|
|
627
|
+
return result;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
napi_value videoReceive(napi_env env, napi_callback_info info) {
|
|
631
|
+
napi_valuetype type;
|
|
632
|
+
dataCarrier *c = new dataCarrier;
|
|
633
|
+
|
|
634
|
+
napi_value promise;
|
|
635
|
+
c->status = napi_create_promise(env, &c->_deferred, &promise);
|
|
636
|
+
REJECT_RETURN;
|
|
637
|
+
|
|
638
|
+
size_t argc = 1;
|
|
639
|
+
napi_value args[1];
|
|
640
|
+
napi_value thisValue;
|
|
641
|
+
c->status = napi_get_cb_info(env, info, &argc, args, &thisValue, nullptr);
|
|
642
|
+
REJECT_RETURN;
|
|
643
|
+
|
|
644
|
+
napi_value recvValue;
|
|
645
|
+
c->status = napi_get_named_property(env, thisValue, "embedded", &recvValue);
|
|
646
|
+
REJECT_RETURN;
|
|
647
|
+
void *recvData;
|
|
648
|
+
c->status = napi_get_value_external(env, recvValue, &recvData);
|
|
649
|
+
c->recv = (NDIlib_recv_instance_t)recvData;
|
|
650
|
+
REJECT_RETURN;
|
|
651
|
+
|
|
652
|
+
if (argc >= 1) {
|
|
653
|
+
c->status = napi_typeof(env, args[0], &type);
|
|
654
|
+
REJECT_RETURN;
|
|
655
|
+
if (type == napi_number) {
|
|
656
|
+
c->status = napi_get_value_uint32(env, args[0], &c->wait);
|
|
657
|
+
REJECT_RETURN;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
napi_value resource_name;
|
|
662
|
+
c->status = napi_create_string_utf8(env, "VideoReceive", NAPI_AUTO_LENGTH,
|
|
663
|
+
&resource_name);
|
|
664
|
+
REJECT_RETURN;
|
|
665
|
+
c->status =
|
|
666
|
+
napi_create_async_work(env, NULL, resource_name, videoReceiveExecute,
|
|
667
|
+
videoReceiveComplete, c, &c->_request);
|
|
668
|
+
REJECT_RETURN;
|
|
669
|
+
c->status = napi_queue_async_work(env, c->_request);
|
|
670
|
+
REJECT_RETURN;
|
|
671
|
+
|
|
672
|
+
return promise;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
void audioReceiveExecute(napi_env env, void *data) {
|
|
676
|
+
dataCarrier *c = (dataCarrier *)data;
|
|
677
|
+
|
|
678
|
+
if (!captureUntilFrame(
|
|
679
|
+
c, NDIlib_frame_type_audio, c->wait, GRANDI_NOT_FOUND,
|
|
680
|
+
"No audio data received in the requested time interval.",
|
|
681
|
+
"Received error response from NDI audio request. Connection lost."))
|
|
682
|
+
return;
|
|
683
|
+
|
|
684
|
+
const NDIlib_audio_frame_v2_t *audioFrameV2 =
|
|
685
|
+
reinterpret_cast<const NDIlib_audio_frame_v2_t *>(&c->audioFrame);
|
|
686
|
+
|
|
687
|
+
switch (c->audioFormat) {
|
|
688
|
+
case Grandi_audio_format_int_16_interleaved:
|
|
689
|
+
c->audioFrame16s.reference_level = c->referenceLevel;
|
|
690
|
+
c->audioFrame16s.p_data =
|
|
691
|
+
new short[c->audioFrame.no_samples * c->audioFrame.no_channels];
|
|
692
|
+
NDIlib_util_audio_to_interleaved_16s_v2(audioFrameV2, &c->audioFrame16s);
|
|
693
|
+
break;
|
|
694
|
+
case Grandi_audio_format_float_32_interleaved:
|
|
695
|
+
c->audioFrame32fIlvd.p_data =
|
|
696
|
+
new float[c->audioFrame.no_samples * c->audioFrame.no_channels];
|
|
697
|
+
NDIlib_util_audio_to_interleaved_32f_v2(audioFrameV2,
|
|
698
|
+
&c->audioFrame32fIlvd);
|
|
699
|
+
break;
|
|
700
|
+
case Grandi_audio_format_float_32_separate:
|
|
701
|
+
default:
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
void audioReceiveComplete(napi_env env, napi_status asyncStatus, void *data) {
|
|
707
|
+
dataCarrier *c = (dataCarrier *)data;
|
|
708
|
+
|
|
709
|
+
if (asyncStatus != napi_ok) {
|
|
710
|
+
c->status = asyncStatus;
|
|
711
|
+
c->errorMsg = "Async audio frame receive failed to complete.";
|
|
712
|
+
}
|
|
713
|
+
REJECT_STATUS;
|
|
714
|
+
|
|
715
|
+
napi_value result;
|
|
716
|
+
c->status = napi_create_object(env, &result);
|
|
717
|
+
REJECT_STATUS;
|
|
718
|
+
|
|
719
|
+
int32_t ptps, ptpn;
|
|
720
|
+
ptps = (int32_t)(c->audioFrame.timestamp / 10000000);
|
|
721
|
+
ptpn = (c->audioFrame.timestamp % 10000000) * 100;
|
|
722
|
+
|
|
723
|
+
napi_value param;
|
|
724
|
+
c->status = napi_create_string_utf8(env, "audio", NAPI_AUTO_LENGTH, ¶m);
|
|
725
|
+
REJECT_STATUS;
|
|
726
|
+
c->status = napi_set_named_property(env, result, "type", param);
|
|
727
|
+
REJECT_STATUS;
|
|
728
|
+
|
|
729
|
+
c->status = napi_create_int32(env, c->audioFormat, ¶m);
|
|
730
|
+
REJECT_STATUS;
|
|
731
|
+
c->status = napi_set_named_property(env, result, "audioFormat", param);
|
|
732
|
+
REJECT_STATUS;
|
|
733
|
+
|
|
734
|
+
if (c->audioFormat == Grandi_audio_format_int_16_interleaved) {
|
|
735
|
+
c->status = napi_create_int32(env, c->referenceLevel, ¶m);
|
|
736
|
+
REJECT_STATUS;
|
|
737
|
+
c->status = napi_set_named_property(env, result, "referenceLevel", param);
|
|
738
|
+
REJECT_STATUS;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
c->status = napi_create_int32(env, c->audioFrame.sample_rate, ¶m);
|
|
742
|
+
REJECT_STATUS;
|
|
743
|
+
c->status = napi_set_named_property(env, result, "sampleRate", param);
|
|
744
|
+
REJECT_STATUS;
|
|
745
|
+
|
|
746
|
+
c->status = napi_create_int32(env, c->audioFrame.no_channels, ¶m);
|
|
747
|
+
REJECT_STATUS;
|
|
748
|
+
c->status = napi_set_named_property(env, result, "channels", param);
|
|
749
|
+
REJECT_STATUS;
|
|
750
|
+
|
|
751
|
+
c->status = napi_create_int32(env, c->audioFrame.no_samples, ¶m);
|
|
752
|
+
REJECT_STATUS;
|
|
753
|
+
c->status = napi_set_named_property(env, result, "samples", param);
|
|
754
|
+
REJECT_STATUS;
|
|
755
|
+
|
|
756
|
+
int32_t factor =
|
|
757
|
+
(c->audioFormat == Grandi_audio_format_int_16_interleaved) ? 2 : 1;
|
|
758
|
+
c->status = napi_create_int32(
|
|
759
|
+
env, c->audioFrame.channel_stride_in_bytes / factor, ¶m);
|
|
760
|
+
REJECT_STATUS;
|
|
761
|
+
c->status =
|
|
762
|
+
napi_set_named_property(env, result, "channelStrideInBytes", param);
|
|
763
|
+
REJECT_STATUS;
|
|
764
|
+
|
|
765
|
+
napi_value params, paramn;
|
|
766
|
+
c->status = napi_create_int32(env, ptps, ¶ms);
|
|
767
|
+
REJECT_STATUS;
|
|
768
|
+
c->status = napi_create_int32(env, ptpn, ¶mn);
|
|
769
|
+
REJECT_STATUS;
|
|
770
|
+
c->status = napi_create_array(env, ¶m);
|
|
771
|
+
REJECT_STATUS;
|
|
772
|
+
c->status = napi_set_element(env, param, 0, params);
|
|
773
|
+
REJECT_STATUS;
|
|
774
|
+
c->status = napi_set_element(env, param, 1, paramn);
|
|
775
|
+
REJECT_STATUS;
|
|
776
|
+
c->status = napi_set_named_property(env, result, "timestamp", param);
|
|
777
|
+
REJECT_STATUS;
|
|
778
|
+
|
|
779
|
+
c->status = napi_create_int32(
|
|
780
|
+
env, (int32_t)(c->audioFrame.timecode / 10000000), ¶ms);
|
|
781
|
+
REJECT_STATUS;
|
|
782
|
+
c->status = napi_create_int32(env, (c->audioFrame.timecode % 10000000) * 100,
|
|
783
|
+
¶mn);
|
|
784
|
+
REJECT_STATUS;
|
|
785
|
+
c->status = napi_create_array(env, ¶m);
|
|
786
|
+
REJECT_STATUS;
|
|
787
|
+
c->status = napi_set_element(env, param, 0, params);
|
|
788
|
+
REJECT_STATUS;
|
|
789
|
+
c->status = napi_set_element(env, param, 1, paramn);
|
|
790
|
+
REJECT_STATUS;
|
|
791
|
+
c->status = napi_set_named_property(env, result, "timecode", param);
|
|
792
|
+
REJECT_STATUS;
|
|
793
|
+
|
|
794
|
+
if (c->audioFrame.p_metadata != nullptr) {
|
|
795
|
+
c->status = napi_create_string_utf8(env, c->audioFrame.p_metadata,
|
|
796
|
+
NAPI_AUTO_LENGTH, ¶m);
|
|
797
|
+
REJECT_STATUS;
|
|
798
|
+
c->status = napi_set_named_property(env, result, "metadata", param);
|
|
799
|
+
REJECT_STATUS;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
char *rawFloats;
|
|
803
|
+
switch (c->audioFormat) {
|
|
804
|
+
case Grandi_audio_format_int_16_interleaved:
|
|
805
|
+
rawFloats = (char *)c->audioFrame16s.p_data;
|
|
806
|
+
break;
|
|
807
|
+
case Grandi_audio_format_float_32_interleaved:
|
|
808
|
+
rawFloats = (char *)c->audioFrame32fIlvd.p_data;
|
|
809
|
+
break;
|
|
810
|
+
default:
|
|
811
|
+
case Grandi_audio_format_float_32_separate:
|
|
812
|
+
rawFloats = (char *)c->audioFrame.p_data;
|
|
813
|
+
break;
|
|
814
|
+
}
|
|
815
|
+
c->status =
|
|
816
|
+
napi_create_buffer_copy(env,
|
|
817
|
+
(c->audioFrame.channel_stride_in_bytes / factor) *
|
|
818
|
+
c->audioFrame.no_channels,
|
|
819
|
+
rawFloats, nullptr, ¶m);
|
|
820
|
+
REJECT_STATUS;
|
|
821
|
+
|
|
822
|
+
c->status = napi_set_named_property(env, result, "data", param);
|
|
823
|
+
REJECT_STATUS;
|
|
824
|
+
|
|
825
|
+
NDIlib_recv_free_audio_v3(c->recv, &c->audioFrame);
|
|
826
|
+
|
|
827
|
+
napi_status status;
|
|
828
|
+
status = napi_resolve_deferred(env, c->_deferred, result);
|
|
829
|
+
FLOATING_STATUS;
|
|
830
|
+
|
|
831
|
+
tidyCarrier(env, c);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
napi_value dataAndAudioReceive(napi_env env, napi_callback_info info,
|
|
835
|
+
const char *resourceName,
|
|
836
|
+
napi_async_execute_callback execute,
|
|
837
|
+
napi_async_complete_callback complete) {
|
|
838
|
+
napi_valuetype type;
|
|
839
|
+
dataCarrier *c = new dataCarrier;
|
|
840
|
+
|
|
841
|
+
napi_value promise;
|
|
842
|
+
c->status = napi_create_promise(env, &c->_deferred, &promise);
|
|
843
|
+
REJECT_RETURN;
|
|
844
|
+
|
|
845
|
+
size_t argc = 2;
|
|
846
|
+
napi_value args[2];
|
|
847
|
+
napi_value thisValue;
|
|
848
|
+
c->status = napi_get_cb_info(env, info, &argc, args, &thisValue, nullptr);
|
|
849
|
+
REJECT_RETURN;
|
|
850
|
+
|
|
851
|
+
napi_value recvValue;
|
|
852
|
+
c->status = napi_get_named_property(env, thisValue, "embedded", &recvValue);
|
|
853
|
+
REJECT_RETURN;
|
|
854
|
+
void *recvData;
|
|
855
|
+
c->status = napi_get_value_external(env, recvValue, &recvData);
|
|
856
|
+
c->recv = (NDIlib_recv_instance_t)recvData;
|
|
857
|
+
REJECT_RETURN;
|
|
858
|
+
|
|
859
|
+
if (argc >= 1) {
|
|
860
|
+
napi_value configValue, waitValue;
|
|
861
|
+
configValue = args[0];
|
|
862
|
+
c->status = napi_typeof(env, configValue, &type);
|
|
863
|
+
REJECT_RETURN;
|
|
864
|
+
waitValue = (type == napi_number) ? args[0] : args[1];
|
|
865
|
+
if (type == napi_object) {
|
|
866
|
+
bool isArray;
|
|
867
|
+
c->status = napi_is_array(env, configValue, &isArray);
|
|
868
|
+
REJECT_RETURN;
|
|
869
|
+
if (isArray)
|
|
870
|
+
REJECT_ERROR_RETURN(
|
|
871
|
+
"First argument to audio receive cannot be an array.",
|
|
872
|
+
GRANDI_INVALID_ARGS);
|
|
873
|
+
|
|
874
|
+
napi_value param;
|
|
875
|
+
c->status =
|
|
876
|
+
napi_get_named_property(env, configValue, "audioFormat", ¶m);
|
|
877
|
+
REJECT_RETURN;
|
|
878
|
+
c->status = napi_typeof(env, param, &type);
|
|
879
|
+
REJECT_RETURN;
|
|
880
|
+
if (type == napi_number) {
|
|
881
|
+
uint32_t audioFormatN;
|
|
882
|
+
c->status = napi_get_value_uint32(env, param, &audioFormatN);
|
|
883
|
+
REJECT_RETURN;
|
|
884
|
+
if (!validAudioFormat((Grandi_audio_format_e)audioFormatN))
|
|
885
|
+
REJECT_ERROR_RETURN("Invalid audio format specified.",
|
|
886
|
+
GRANDI_INVALID_ARGS);
|
|
887
|
+
c->audioFormat = (Grandi_audio_format_e)audioFormatN;
|
|
888
|
+
} else if (type != napi_undefined)
|
|
889
|
+
REJECT_ERROR_RETURN("Audio format value must be a number if present.",
|
|
890
|
+
GRANDI_INVALID_ARGS);
|
|
891
|
+
|
|
892
|
+
c->status =
|
|
893
|
+
napi_get_named_property(env, configValue, "referenceLevel", ¶m);
|
|
894
|
+
REJECT_RETURN;
|
|
895
|
+
c->status = napi_typeof(env, param, &type);
|
|
896
|
+
REJECT_RETURN;
|
|
897
|
+
if (type == napi_number) {
|
|
898
|
+
c->status = napi_get_value_int32(env, param, &c->referenceLevel);
|
|
899
|
+
REJECT_RETURN;
|
|
900
|
+
} else if (type != napi_undefined)
|
|
901
|
+
REJECT_ERROR_RETURN(
|
|
902
|
+
"Audio reference level must be a number if present.",
|
|
903
|
+
GRANDI_INVALID_ARGS);
|
|
904
|
+
}
|
|
905
|
+
c->status = napi_typeof(env, waitValue, &type);
|
|
906
|
+
REJECT_RETURN;
|
|
907
|
+
if (type == napi_number) {
|
|
908
|
+
c->status = napi_get_value_uint32(env, waitValue, &c->wait);
|
|
909
|
+
REJECT_RETURN;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
napi_value resource_name;
|
|
914
|
+
c->status = napi_create_string_utf8(env, resourceName, NAPI_AUTO_LENGTH,
|
|
915
|
+
&resource_name);
|
|
916
|
+
REJECT_RETURN;
|
|
917
|
+
c->status = napi_create_async_work(env, NULL, resource_name, execute,
|
|
918
|
+
complete, c, &c->_request);
|
|
919
|
+
REJECT_RETURN;
|
|
920
|
+
c->status = napi_queue_async_work(env, c->_request);
|
|
921
|
+
REJECT_RETURN;
|
|
922
|
+
|
|
923
|
+
return promise;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
napi_value audioReceive(napi_env env, napi_callback_info info) {
|
|
927
|
+
return dataAndAudioReceive(env, info, "AudioReceive", audioReceiveExecute,
|
|
928
|
+
audioReceiveComplete);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
void metadataReceiveExecute(napi_env env, void *data) {
|
|
932
|
+
dataCarrier *c = (dataCarrier *)data;
|
|
933
|
+
|
|
934
|
+
if (!captureUntilFrame(c, NDIlib_frame_type_metadata, c->wait,
|
|
935
|
+
GRANDI_NOT_FOUND,
|
|
936
|
+
"No metadata received in the requested time interval.",
|
|
937
|
+
"Received error response from NDI metadata request. "
|
|
938
|
+
"Connection lost."))
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
void metadataReceiveComplete(napi_env env, napi_status asyncStatus,
|
|
943
|
+
void *data) {
|
|
944
|
+
dataCarrier *c = (dataCarrier *)data;
|
|
945
|
+
|
|
946
|
+
if (asyncStatus != napi_ok) {
|
|
947
|
+
c->status = asyncStatus;
|
|
948
|
+
c->errorMsg = "Async metadata payload receive failed to complete.";
|
|
949
|
+
}
|
|
950
|
+
REJECT_STATUS;
|
|
951
|
+
|
|
952
|
+
napi_value result;
|
|
953
|
+
c->status = napi_create_object(env, &result);
|
|
954
|
+
REJECT_STATUS;
|
|
955
|
+
|
|
956
|
+
napi_value param;
|
|
957
|
+
c->status =
|
|
958
|
+
napi_create_string_utf8(env, "metadata", NAPI_AUTO_LENGTH, ¶m);
|
|
959
|
+
REJECT_STATUS;
|
|
960
|
+
c->status = napi_set_named_property(env, result, "type", param);
|
|
961
|
+
REJECT_STATUS;
|
|
962
|
+
|
|
963
|
+
c->status = napi_create_int32(env, c->metadataFrame.length, ¶m);
|
|
964
|
+
REJECT_STATUS;
|
|
965
|
+
c->status = napi_set_named_property(env, result, "length", param);
|
|
966
|
+
REJECT_STATUS;
|
|
967
|
+
|
|
968
|
+
napi_value params, paramn;
|
|
969
|
+
c->status = napi_create_int32(
|
|
970
|
+
env, (int32_t)(c->metadataFrame.timecode / 10000000), ¶ms);
|
|
971
|
+
REJECT_STATUS;
|
|
972
|
+
c->status = napi_create_int32(
|
|
973
|
+
env, (c->metadataFrame.timecode % 10000000) * 100, ¶mn);
|
|
974
|
+
REJECT_STATUS;
|
|
975
|
+
|
|
976
|
+
napi_value timestampArray;
|
|
977
|
+
c->status = napi_create_array(env, ×tampArray);
|
|
978
|
+
REJECT_STATUS;
|
|
979
|
+
c->status = napi_set_element(env, timestampArray, 0, params);
|
|
980
|
+
REJECT_STATUS;
|
|
981
|
+
c->status = napi_set_element(env, timestampArray, 1, paramn);
|
|
982
|
+
REJECT_STATUS;
|
|
983
|
+
c->status = napi_set_named_property(env, result, "timestamp", timestampArray);
|
|
984
|
+
REJECT_STATUS;
|
|
985
|
+
|
|
986
|
+
c->status = napi_create_array(env, ¶m);
|
|
987
|
+
REJECT_STATUS;
|
|
988
|
+
c->status = napi_set_element(env, param, 0, params);
|
|
989
|
+
REJECT_STATUS;
|
|
990
|
+
c->status = napi_set_element(env, param, 1, paramn);
|
|
991
|
+
REJECT_STATUS;
|
|
992
|
+
c->status = napi_set_named_property(env, result, "timecode", param);
|
|
993
|
+
REJECT_STATUS;
|
|
994
|
+
|
|
995
|
+
c->status = napi_create_string_utf8(env, c->metadataFrame.p_data,
|
|
996
|
+
NAPI_AUTO_LENGTH, ¶m);
|
|
997
|
+
REJECT_STATUS;
|
|
998
|
+
c->status = napi_set_named_property(env, result, "data", param);
|
|
999
|
+
REJECT_STATUS;
|
|
1000
|
+
|
|
1001
|
+
NDIlib_recv_free_metadata(c->recv, &c->metadataFrame);
|
|
1002
|
+
|
|
1003
|
+
napi_status status;
|
|
1004
|
+
status = napi_resolve_deferred(env, c->_deferred, result);
|
|
1005
|
+
FLOATING_STATUS;
|
|
1006
|
+
|
|
1007
|
+
tidyCarrier(env, c);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
napi_value metadataReceive(napi_env env, napi_callback_info info) {
|
|
1011
|
+
napi_valuetype type;
|
|
1012
|
+
dataCarrier *c = new dataCarrier;
|
|
1013
|
+
|
|
1014
|
+
napi_value promise;
|
|
1015
|
+
c->status = napi_create_promise(env, &c->_deferred, &promise);
|
|
1016
|
+
REJECT_RETURN;
|
|
1017
|
+
|
|
1018
|
+
size_t argc = 1;
|
|
1019
|
+
napi_value args[1];
|
|
1020
|
+
napi_value thisValue;
|
|
1021
|
+
c->status = napi_get_cb_info(env, info, &argc, args, &thisValue, nullptr);
|
|
1022
|
+
REJECT_RETURN;
|
|
1023
|
+
|
|
1024
|
+
napi_value recvValue;
|
|
1025
|
+
c->status = napi_get_named_property(env, thisValue, "embedded", &recvValue);
|
|
1026
|
+
REJECT_RETURN;
|
|
1027
|
+
void *recvData;
|
|
1028
|
+
c->status = napi_get_value_external(env, recvValue, &recvData);
|
|
1029
|
+
c->recv = (NDIlib_recv_instance_t)recvData;
|
|
1030
|
+
REJECT_RETURN;
|
|
1031
|
+
|
|
1032
|
+
if (argc >= 1) {
|
|
1033
|
+
c->status = napi_typeof(env, args[0], &type);
|
|
1034
|
+
REJECT_RETURN;
|
|
1035
|
+
if (type == napi_number) {
|
|
1036
|
+
c->status = napi_get_value_uint32(env, args[0], &c->wait);
|
|
1037
|
+
REJECT_RETURN;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
napi_value resource_name;
|
|
1042
|
+
c->status = napi_create_string_utf8(env, "MetadataReceive", NAPI_AUTO_LENGTH,
|
|
1043
|
+
&resource_name);
|
|
1044
|
+
REJECT_RETURN;
|
|
1045
|
+
c->status =
|
|
1046
|
+
napi_create_async_work(env, NULL, resource_name, metadataReceiveExecute,
|
|
1047
|
+
metadataReceiveComplete, c, &c->_request);
|
|
1048
|
+
REJECT_RETURN;
|
|
1049
|
+
c->status = napi_queue_async_work(env, c->_request);
|
|
1050
|
+
REJECT_RETURN;
|
|
1051
|
+
|
|
1052
|
+
return promise;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
void dataReceiveExecute(napi_env env, void *data) {
|
|
1056
|
+
dataCarrier *c = (dataCarrier *)data;
|
|
1057
|
+
|
|
1058
|
+
c->frameType = NDIlib_recv_capture_v3(c->recv, &c->videoFrame, &c->audioFrame,
|
|
1059
|
+
&c->metadataFrame, c->wait);
|
|
1060
|
+
switch (c->frameType) {
|
|
1061
|
+
|
|
1062
|
+
// Audio data
|
|
1063
|
+
case NDIlib_frame_type_audio: {
|
|
1064
|
+
const NDIlib_audio_frame_v2_t *audioFrameV2 =
|
|
1065
|
+
reinterpret_cast<const NDIlib_audio_frame_v2_t *>(&c->audioFrame);
|
|
1066
|
+
switch (c->audioFormat) {
|
|
1067
|
+
case Grandi_audio_format_int_16_interleaved:
|
|
1068
|
+
c->audioFrame16s.reference_level = c->referenceLevel;
|
|
1069
|
+
c->audioFrame16s.p_data =
|
|
1070
|
+
new short[c->audioFrame.no_samples * c->audioFrame.no_channels];
|
|
1071
|
+
NDIlib_util_audio_to_interleaved_16s_v2(audioFrameV2, &c->audioFrame16s);
|
|
1072
|
+
break;
|
|
1073
|
+
case Grandi_audio_format_float_32_interleaved:
|
|
1074
|
+
c->audioFrame32fIlvd.p_data =
|
|
1075
|
+
new float[c->audioFrame.no_samples * c->audioFrame.no_channels];
|
|
1076
|
+
NDIlib_util_audio_to_interleaved_32f_v2(audioFrameV2,
|
|
1077
|
+
&c->audioFrame32fIlvd);
|
|
1078
|
+
break;
|
|
1079
|
+
case Grandi_audio_format_float_32_separate:
|
|
1080
|
+
default:
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
break;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// Handle all other types on completion
|
|
1087
|
+
default:
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
void dataReceiveComplete(napi_env env, napi_status asyncStatus, void *data) {
|
|
1093
|
+
dataCarrier *c = (dataCarrier *)data;
|
|
1094
|
+
|
|
1095
|
+
if (asyncStatus != napi_ok) {
|
|
1096
|
+
c->status = asyncStatus;
|
|
1097
|
+
c->errorMsg = "Async data payload receive failed to complete.";
|
|
1098
|
+
}
|
|
1099
|
+
REJECT_STATUS;
|
|
1100
|
+
|
|
1101
|
+
napi_status status;
|
|
1102
|
+
switch (c->frameType) {
|
|
1103
|
+
case NDIlib_frame_type_video:
|
|
1104
|
+
videoReceiveComplete(env, asyncStatus, data);
|
|
1105
|
+
break;
|
|
1106
|
+
case NDIlib_frame_type_audio:
|
|
1107
|
+
audioReceiveComplete(env, asyncStatus, data);
|
|
1108
|
+
break;
|
|
1109
|
+
case NDIlib_frame_type_metadata:
|
|
1110
|
+
metadataReceiveComplete(env, asyncStatus, data);
|
|
1111
|
+
break;
|
|
1112
|
+
case NDIlib_frame_type_error:
|
|
1113
|
+
c->errorMsg =
|
|
1114
|
+
"Received error response from NDI data request. Connection lost.";
|
|
1115
|
+
c->status = GRANDI_CONNECTION_LOST;
|
|
1116
|
+
REJECT_STATUS;
|
|
1117
|
+
break;
|
|
1118
|
+
case NDIlib_frame_type_source_change:
|
|
1119
|
+
napi_value result_sc, param_sc;
|
|
1120
|
+
c->status = napi_create_object(env, &result_sc);
|
|
1121
|
+
REJECT_STATUS;
|
|
1122
|
+
c->status = napi_create_string_utf8(env, "sourceChange", NAPI_AUTO_LENGTH,
|
|
1123
|
+
¶m_sc);
|
|
1124
|
+
REJECT_STATUS;
|
|
1125
|
+
c->status = napi_set_named_property(env, result_sc, "type", param_sc);
|
|
1126
|
+
REJECT_STATUS;
|
|
1127
|
+
status = napi_resolve_deferred(env, c->_deferred, result_sc);
|
|
1128
|
+
FLOATING_STATUS;
|
|
1129
|
+
|
|
1130
|
+
tidyCarrier(env, c);
|
|
1131
|
+
break;
|
|
1132
|
+
case NDIlib_frame_type_status_change:
|
|
1133
|
+
napi_value result, param;
|
|
1134
|
+
c->status = napi_create_object(env, &result);
|
|
1135
|
+
REJECT_STATUS;
|
|
1136
|
+
c->status =
|
|
1137
|
+
napi_create_string_utf8(env, "statusChange", NAPI_AUTO_LENGTH, ¶m);
|
|
1138
|
+
REJECT_STATUS;
|
|
1139
|
+
c->status = napi_set_named_property(env, result, "type", param);
|
|
1140
|
+
REJECT_STATUS;
|
|
1141
|
+
status = napi_resolve_deferred(env, c->_deferred, result);
|
|
1142
|
+
FLOATING_STATUS;
|
|
1143
|
+
|
|
1144
|
+
tidyCarrier(env, c);
|
|
1145
|
+
break;
|
|
1146
|
+
case NDIlib_frame_type_none:
|
|
1147
|
+
case NDIlib_frame_type_max:
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
napi_value dataReceive(napi_env env, napi_callback_info info) {
|
|
1153
|
+
return dataAndAudioReceive(env, info, "DataReceive", dataReceiveExecute,
|
|
1154
|
+
dataReceiveComplete);
|
|
1155
|
+
}
|