edge-impulse-linux 1.20.1 → 1.21.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/build/cli/linux/linux.js +1 -0
- package/build/cli/linux/linux.js.map +1 -1
- package/build/cli/linux/runner.js +14 -4
- package/build/cli/linux/runner.js.map +1 -1
- package/build/library/classifier/image-classifier.d.ts +2 -0
- package/build/library/classifier/image-classifier.js +84 -54
- package/build/library/classifier/image-classifier.js.map +1 -1
- package/build/library/data-forwarder.d.ts +1 -1
- package/build/library/data-forwarder.js +17 -33
- package/build/library/data-forwarder.js.map +1 -1
- package/build/library/sensors/gstreamer.d.ts +6 -3
- package/build/library/sensors/gstreamer.js +156 -30
- package/build/library/sensors/gstreamer.js.map +1 -1
- package/build/library/sensors/icamera.d.ts +30 -1
- package/build/library/sensors/imagesnap.d.ts +2 -2
- package/build/library/sensors/imagesnap.js +8 -2
- package/build/library/sensors/imagesnap.js.map +1 -1
- package/build/library/sensors/prophesee.d.ts +2 -2
- package/build/library/sensors/prophesee.js +6 -1
- package/build/library/sensors/prophesee.js.map +1 -1
- package/build/library/sensors/sensors-helper.d.ts +2 -0
- package/build/library/sensors/sensors-helper.js +2 -2
- package/build/library/sensors/sensors-helper.js.map +1 -1
- package/build/sdk/studio/sdk/model/models.js +10 -17
- package/build/sdk/studio/sdk/model/models.js.map +1 -1
- package/package.json +1 -1
- package/test/gstreamer.test.ts +456 -5
- package/test/qualcomm-rb3-gen-2-ubuntu-no-external-camera-inspect-qtiqmmfsrc.txt +438 -0
- package/test/qualcomm-rb3-gen-2-ubuntu-no-external-camera-inspect.txt +958 -0
- package/test/qualcomm-rb3-gen-2-ubuntu-no-external-camera-monitor.txt +159 -0
|
@@ -5,12 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.DataForwarder = void 0;
|
|
7
7
|
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
-
const
|
|
9
|
-
const http_1 = __importDefault(require("http"));
|
|
10
|
-
const https_1 = __importDefault(require("https"));
|
|
8
|
+
const undici_1 = require("undici");
|
|
11
9
|
const encoding_1 = __importDefault(require("../shared/encoding"));
|
|
12
|
-
const keepAliveAgentHttp = new http_1.default.Agent({ keepAlive: true });
|
|
13
|
-
const keepAliveAgentHttps = new https_1.default.Agent({ keepAlive: true });
|
|
14
10
|
class DataForwarder {
|
|
15
11
|
constructor(options) {
|
|
16
12
|
this._samples = [];
|
|
@@ -20,7 +16,7 @@ class DataForwarder {
|
|
|
20
16
|
sensors: options.sensors,
|
|
21
17
|
apiKey: options.apiKey || '',
|
|
22
18
|
hmacKey: options.hmacKey || '0',
|
|
23
|
-
intervalMs: options.intervalMs ||
|
|
19
|
+
intervalMs: options.intervalMs || 1000 / (options.frequency || 1),
|
|
24
20
|
ingestionHost: options.host || 'edgeimpulse.com'
|
|
25
21
|
};
|
|
26
22
|
if (typeof options.frequency === 'undefined' && typeof options.intervalMs === 'undefined') {
|
|
@@ -60,8 +56,7 @@ class DataForwarder {
|
|
|
60
56
|
}
|
|
61
57
|
addData(data) {
|
|
62
58
|
if (data.length !== this._options.sensors.length) {
|
|
63
|
-
throw new Error('Invalid data, expected ' + this._options.sensors.length + ' values, but got: ' +
|
|
64
|
-
data.length);
|
|
59
|
+
throw new Error('Invalid data, expected ' + this._options.sensors.length + ' values, but got: ' + data.length);
|
|
65
60
|
}
|
|
66
61
|
this._samples.push(data);
|
|
67
62
|
}
|
|
@@ -69,8 +64,8 @@ class DataForwarder {
|
|
|
69
64
|
let emptySignature = Array(64).fill('0').join('');
|
|
70
65
|
let data = {
|
|
71
66
|
protected: {
|
|
72
|
-
ver:
|
|
73
|
-
alg:
|
|
67
|
+
ver: 'v1',
|
|
68
|
+
alg: 'HS256',
|
|
74
69
|
iat: Math.floor(Date.now() / 1000) // epoch time, seconds since 1970
|
|
75
70
|
},
|
|
76
71
|
signature: emptySignature,
|
|
@@ -101,7 +96,7 @@ class DataForwarder {
|
|
|
101
96
|
'x-api-key': this._options.apiKey,
|
|
102
97
|
'x-file-name': (0, encoding_1.default)(opts.filename),
|
|
103
98
|
'Content-Type': 'application/json',
|
|
104
|
-
|
|
99
|
+
Connection: 'keep-alive'
|
|
105
100
|
};
|
|
106
101
|
if (opts.label) {
|
|
107
102
|
headers['x-label'] = (0, encoding_1.default)(opts.label);
|
|
@@ -109,29 +104,18 @@ class DataForwarder {
|
|
|
109
104
|
if (opts.allowDuplicates !== true) {
|
|
110
105
|
headers['x-disallow-duplicates'] = '1';
|
|
111
106
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
request_1.default.post(this._options.ingestionHost + '/api/' + category + '/data', {
|
|
119
|
-
headers: headers,
|
|
120
|
-
body: dataBuffer,
|
|
121
|
-
encoding: null,
|
|
122
|
-
agent: agent
|
|
123
|
-
}, (err, response, body) => {
|
|
124
|
-
if (err)
|
|
125
|
-
return rej(err);
|
|
126
|
-
if (response.statusCode !== 200) {
|
|
127
|
-
if (body instanceof Buffer) {
|
|
128
|
-
return rej(body.toString('utf-8'));
|
|
129
|
-
}
|
|
130
|
-
return rej(body || response.statusCode.toString());
|
|
131
|
-
}
|
|
132
|
-
res(body instanceof Buffer ? body.toString('utf-8') : body);
|
|
133
|
-
});
|
|
107
|
+
const category = opts.category;
|
|
108
|
+
const url = this._options.ingestionHost + '/api/' + category + '/data';
|
|
109
|
+
const response = await (0, undici_1.fetch)(url, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: headers,
|
|
112
|
+
body: dataBuffer
|
|
134
113
|
});
|
|
114
|
+
const body = await response.text();
|
|
115
|
+
if (response.status !== 200) {
|
|
116
|
+
throw body || response.status.toString();
|
|
117
|
+
}
|
|
118
|
+
return body;
|
|
135
119
|
}
|
|
136
120
|
}
|
|
137
121
|
exports.DataForwarder = DataForwarder;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-forwarder.js","sourceRoot":"","sources":["../../library/data-forwarder.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA4B;AAC5B,
|
|
1
|
+
{"version":3,"file":"data-forwarder.js","sourceRoot":"","sources":["../../library/data-forwarder.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { EventEmitter } from 'tsee';
|
|
4
4
|
import { SpawnHelperType } from './spawn-helper';
|
|
5
|
-
import { ICamera, ICameraInferenceDimensions, ICameraProfilingInfoEvent, ICameraStartOptions } from './icamera';
|
|
5
|
+
import { ICamera, ICameraInferenceDimensions, ICameraProfilingInfoEvent, ICameraSnapshotForInferenceEvent, ICameraStartOptions } from './icamera';
|
|
6
6
|
type GStreamerCap = {
|
|
7
7
|
type: 'video/x-raw' | 'image/jpeg' | 'nvarguscamerasrc' | 'pylonsrc';
|
|
8
8
|
width: number;
|
|
@@ -13,14 +13,14 @@ type GStreamerCap = {
|
|
|
13
13
|
export type GStreamerMode = 'default' | 'rpi' | 'rpi5' | 'qualcomm-rb3gen2' | 'qualcomm-yupik' | 'unoq';
|
|
14
14
|
export declare class GStreamer extends EventEmitter<{
|
|
15
15
|
snapshot: (buffer: Buffer, filename: string) => void;
|
|
16
|
-
snapshotForInference: (
|
|
16
|
+
snapshotForInference: (ev: ICameraSnapshotForInferenceEvent) => void;
|
|
17
17
|
error: (message: string) => void;
|
|
18
18
|
profilingInfo: (ev: ICameraProfilingInfoEvent) => void;
|
|
19
19
|
}> implements ICamera {
|
|
20
20
|
private _captureProcess?;
|
|
21
21
|
private _tempDir?;
|
|
22
22
|
private _watcher?;
|
|
23
|
-
private
|
|
23
|
+
private _originalAndRgbFilesSeen;
|
|
24
24
|
private _handledFiles;
|
|
25
25
|
private _verbose;
|
|
26
26
|
private _lastFile;
|
|
@@ -35,8 +35,10 @@ export declare class GStreamer extends EventEmitter<{
|
|
|
35
35
|
private _customLaunchCommand;
|
|
36
36
|
private _profiling;
|
|
37
37
|
private _emitsOriginalSizeImages;
|
|
38
|
+
private _emitsRgbBuffers;
|
|
38
39
|
private _overrideColorFormat;
|
|
39
40
|
private _lastGstLaunchCommand;
|
|
41
|
+
private _outputRgbBuffers;
|
|
40
42
|
private _imagesReceived;
|
|
41
43
|
private _offset;
|
|
42
44
|
constructor(verbose: boolean, options?: {
|
|
@@ -46,6 +48,7 @@ export declare class GStreamer extends EventEmitter<{
|
|
|
46
48
|
profiling?: boolean;
|
|
47
49
|
dontRunCleanupLoop?: boolean;
|
|
48
50
|
colorFormat?: string;
|
|
51
|
+
dontOutputRgbBuffers?: boolean;
|
|
49
52
|
});
|
|
50
53
|
init(): Promise<void>;
|
|
51
54
|
listDevices(): Promise<string[]>;
|
|
@@ -20,13 +20,14 @@ const DEFAULT_GST_VIDEO_SOURCE = 'v4l2src';
|
|
|
20
20
|
class GStreamer extends tsee_1.EventEmitter {
|
|
21
21
|
constructor(verbose, options) {
|
|
22
22
|
super();
|
|
23
|
-
this.
|
|
23
|
+
this._originalAndRgbFilesSeen = new Set();
|
|
24
24
|
this._handledFiles = {};
|
|
25
25
|
this._processing = false;
|
|
26
26
|
this._mode = 'default';
|
|
27
27
|
this._isStarted = false;
|
|
28
28
|
this._isRestarting = false;
|
|
29
29
|
this._emitsOriginalSizeImages = false;
|
|
30
|
+
this._emitsRgbBuffers = false;
|
|
30
31
|
this._lastGstLaunchCommand = '';
|
|
31
32
|
this._imagesReceived = 0;
|
|
32
33
|
this._offset = 0;
|
|
@@ -36,6 +37,9 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
36
37
|
this._modeOverride = options?.modeOverride;
|
|
37
38
|
this._profiling = options?.profiling || false;
|
|
38
39
|
this._overrideColorFormat = options?.colorFormat;
|
|
40
|
+
this._outputRgbBuffers = options?.dontOutputRgbBuffers === true ?
|
|
41
|
+
false :
|
|
42
|
+
true;
|
|
39
43
|
if (options?.dontRunCleanupLoop === true) {
|
|
40
44
|
// skip cleanup loop
|
|
41
45
|
}
|
|
@@ -253,16 +257,20 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
253
257
|
}
|
|
254
258
|
let lastPhoto = 0;
|
|
255
259
|
let nextFrame = Date.now();
|
|
256
|
-
this.
|
|
260
|
+
this._originalAndRgbFilesSeen = new Set();
|
|
257
261
|
this._watcher = fs_1.default.watch(this._tempDir, async (eventType, fileName) => {
|
|
258
262
|
if (eventType !== 'rename')
|
|
259
263
|
return;
|
|
260
264
|
if (fileName === null)
|
|
261
265
|
return;
|
|
262
|
-
if (!(fileName.endsWith('.jpeg') || fileName.endsWith('.jpg')))
|
|
266
|
+
if (!(fileName.endsWith('.jpeg') || fileName.endsWith('.jpg') || fileName.endsWith('.rgb')))
|
|
263
267
|
return;
|
|
264
268
|
if (fileName.startsWith('original')) {
|
|
265
|
-
this.
|
|
269
|
+
this._originalAndRgbFilesSeen.add(fileName);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (fileName.endsWith('.rgb')) {
|
|
273
|
+
this._originalAndRgbFilesSeen.add(fileName);
|
|
266
274
|
return;
|
|
267
275
|
}
|
|
268
276
|
if (!this._tempDir)
|
|
@@ -270,10 +278,11 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
270
278
|
if (this._handledFiles[fileName])
|
|
271
279
|
return;
|
|
272
280
|
this._imagesReceived++;
|
|
281
|
+
const tempDir = this._tempDir; // this can get unset later on -> so cache here
|
|
273
282
|
// not next frame yet?
|
|
274
283
|
if (this._processing || Date.now() < nextFrame) {
|
|
275
284
|
this._handledFiles[fileName] = true;
|
|
276
|
-
await this.safeUnlinkFile(path_1.default.join(
|
|
285
|
+
await this.safeUnlinkFile(path_1.default.join(tempDir, fileName));
|
|
277
286
|
return;
|
|
278
287
|
}
|
|
279
288
|
nextFrame = Date.now() + options.intervalMs;
|
|
@@ -282,7 +291,7 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
282
291
|
this._handledFiles[fileName] = true;
|
|
283
292
|
const originalName = fileName.replace('resized', 'original');
|
|
284
293
|
if (this._emitsOriginalSizeImages) {
|
|
285
|
-
if (!this.
|
|
294
|
+
if (!this._originalAndRgbFilesSeen.has(originalName)) {
|
|
286
295
|
let waitForOriginalStart = Date.now();
|
|
287
296
|
if (this._verbose) {
|
|
288
297
|
console.log(PREFIX, `Waiting for original file "${originalName}"...`);
|
|
@@ -290,13 +299,13 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
290
299
|
await new Promise(async (resolve, reject) => {
|
|
291
300
|
let start = Date.now();
|
|
292
301
|
while (1) {
|
|
293
|
-
if (this.
|
|
302
|
+
if (this._originalAndRgbFilesSeen.has(originalName)) {
|
|
294
303
|
return resolve();
|
|
295
304
|
}
|
|
296
305
|
if (Date.now() - start > 1000) {
|
|
297
306
|
return reject(`Did not find original image ("${originalName}") within 1sec`);
|
|
298
307
|
}
|
|
299
|
-
await this.wait(
|
|
308
|
+
await this.wait(1);
|
|
300
309
|
}
|
|
301
310
|
});
|
|
302
311
|
if (this._verbose) {
|
|
@@ -305,19 +314,46 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
305
314
|
}
|
|
306
315
|
}
|
|
307
316
|
}
|
|
317
|
+
const rgbName = fileName.replace('.jpg', '.rgb');
|
|
318
|
+
if (this._emitsRgbBuffers) {
|
|
319
|
+
if (!this._originalAndRgbFilesSeen.has(rgbName)) {
|
|
320
|
+
let waitForRgbStart = Date.now();
|
|
321
|
+
if (this._verbose) {
|
|
322
|
+
console.log(PREFIX, `Waiting for RGB file "${rgbName}"...`);
|
|
323
|
+
}
|
|
324
|
+
await new Promise(async (resolve, reject) => {
|
|
325
|
+
let start = Date.now();
|
|
326
|
+
while (1) {
|
|
327
|
+
if (this._originalAndRgbFilesSeen.has(rgbName)) {
|
|
328
|
+
return resolve();
|
|
329
|
+
}
|
|
330
|
+
if (Date.now() - start > 1000) {
|
|
331
|
+
return reject(`Did not find RGB image ("${rgbName}") within 1sec`);
|
|
332
|
+
}
|
|
333
|
+
await this.wait(1);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
if (this._verbose) {
|
|
337
|
+
console.log(PREFIX, `Waiting for RGB file "${rgbName}" OK ` +
|
|
338
|
+
`(took ${Date.now() - waitForRgbStart}ms.)`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
308
342
|
if (lastPhoto !== 0 && this._verbose) {
|
|
309
343
|
console.log(PREFIX, 'Got snapshot', fileName, 'time since last:', (Date.now() - lastPhoto) + 'ms.', 'size');
|
|
310
344
|
}
|
|
311
345
|
if (this._keepAliveTimeout) {
|
|
312
346
|
clearTimeout(this._keepAliveTimeout);
|
|
313
347
|
}
|
|
314
|
-
const tempDir = this._tempDir;
|
|
315
348
|
try {
|
|
316
|
-
let [imgBuffer, originalImgBuffer] = await Promise.all([
|
|
349
|
+
let [imgBuffer, originalImgBuffer, rgbBuffer,] = await Promise.all([
|
|
317
350
|
fs_1.default.promises.readFile(path_1.default.join(tempDir, fileName)),
|
|
318
351
|
this._emitsOriginalSizeImages ?
|
|
319
352
|
fs_1.default.promises.readFile(path_1.default.join(tempDir, originalName)) :
|
|
320
353
|
null,
|
|
354
|
+
this._emitsRgbBuffers ?
|
|
355
|
+
fs_1.default.promises.readFile(path_1.default.join(tempDir, rgbName)) :
|
|
356
|
+
null,
|
|
321
357
|
]);
|
|
322
358
|
// hash not changed? don't emit another event (streamer does this on Rpi)
|
|
323
359
|
let hash = crypto_1.default.createHash('sha256').update(imgBuffer).digest('hex');
|
|
@@ -325,7 +361,12 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
325
361
|
// snapshot() sends out the original size image
|
|
326
362
|
this.emit('snapshot', originalImgBuffer || imgBuffer, path_1.default.basename(fileName));
|
|
327
363
|
// snapshotForInference() sends out the resized image
|
|
328
|
-
this.emit('snapshotForInference',
|
|
364
|
+
this.emit('snapshotForInference', {
|
|
365
|
+
imageForInferenceJpg: imgBuffer,
|
|
366
|
+
filename: path_1.default.basename(fileName),
|
|
367
|
+
imageFromCameraJpg: originalImgBuffer || imgBuffer,
|
|
368
|
+
imageForInferenceRgb: rgbBuffer || undefined,
|
|
369
|
+
});
|
|
329
370
|
lastPhoto = Date.now();
|
|
330
371
|
// 2 seconds no new data? trigger timeout
|
|
331
372
|
if (this._keepAliveTimeout) {
|
|
@@ -354,7 +395,13 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
354
395
|
(async () => {
|
|
355
396
|
if (originalName) {
|
|
356
397
|
await this.safeUnlinkFile(path_1.default.join(tempDir, originalName));
|
|
357
|
-
this.
|
|
398
|
+
this._originalAndRgbFilesSeen.delete(originalName);
|
|
399
|
+
}
|
|
400
|
+
})(),
|
|
401
|
+
(async () => {
|
|
402
|
+
if (rgbName) {
|
|
403
|
+
await this.safeUnlinkFile(path_1.default.join(tempDir, rgbName));
|
|
404
|
+
this._originalAndRgbFilesSeen.delete(rgbName);
|
|
358
405
|
}
|
|
359
406
|
})(),
|
|
360
407
|
]);
|
|
@@ -389,9 +436,25 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
389
436
|
return;
|
|
390
437
|
}
|
|
391
438
|
else {
|
|
439
|
+
const errMsg = `GStreamer (gst-launch-1.0) stopped before emitting any images. This most likely ` +
|
|
440
|
+
`means that the launch command is incorrect or that your camera is unresponsive. Here is the launch command:\n\n` +
|
|
441
|
+
`${this._lastGstLaunchCommand}\n\n` +
|
|
442
|
+
`You can try one of the following:\n\n` +
|
|
443
|
+
`* If your camera used to work:\n` +
|
|
444
|
+
` * Disconnect and reconnect the camera (if you use an external camera)\n` +
|
|
445
|
+
` * Kill all other GStreamer commands, via: 'sudo killall gst-launch-1.0'\n` +
|
|
446
|
+
`* Run with '--verbose' to see the raw GStreamer output. It might contain a hint why the process fails.\n` +
|
|
447
|
+
`* Run with '--dont-output-rgb-buffers' - this will disable RGB output buffer creation which can help with ` +
|
|
448
|
+
`targets that advertise RGB capabilities on the video source, but don't actually support this.\n\n` +
|
|
449
|
+
`If this does not resolve your issue, then please open a forum post at https://forum.edgeimpulse.com and include:\n\n` +
|
|
450
|
+
`* Your device, operating system, what camera you're using, and how the camera is connected (e.g. USB, CSI)\n` +
|
|
451
|
+
`* The launch command (above)\n` +
|
|
452
|
+
`* The verbose output (run this application with --verbose)\n` +
|
|
453
|
+
`* The output of 'gst-device-monitor-1.0'\n` +
|
|
454
|
+
`* The output of 'gst-inspect-1.0'`;
|
|
392
455
|
if (this._imagesReceived === 0 && this._lastGstLaunchCommand) {
|
|
393
456
|
reject(`Capture process failed with code ${code}\n\n` +
|
|
394
|
-
|
|
457
|
+
`${errMsg}`);
|
|
395
458
|
}
|
|
396
459
|
else {
|
|
397
460
|
reject('Capture process failed with code ' + code);
|
|
@@ -442,6 +505,7 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
442
505
|
}
|
|
443
506
|
async getGstreamerLaunchCommand(device, dimensions, inferenceDims) {
|
|
444
507
|
this._emitsOriginalSizeImages = false;
|
|
508
|
+
this._emitsRgbBuffers = false;
|
|
445
509
|
if (device.id === CUSTOM_GST_LAUNCH_COMMAND) {
|
|
446
510
|
if (!this._customLaunchCommand) {
|
|
447
511
|
throw new Error('_customLaunchCommand is null');
|
|
@@ -524,6 +588,24 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
524
588
|
'pylonsrc',
|
|
525
589
|
];
|
|
526
590
|
}
|
|
591
|
+
const frameReadyArgs = this._profiling ? [`!`, `identity name=frame_ready silent=false`] : [];
|
|
592
|
+
const jpgencDoneArgs = this._profiling ? [`!`, `identity name=jpegenc_done silent=false`] : [];
|
|
593
|
+
const resizeDoneArgs = this._profiling ? [`!`, `identity name=resize_done silent=false`] : [];
|
|
594
|
+
let teeToBothResizedJpgAndRgb;
|
|
595
|
+
let videoFormat = '';
|
|
596
|
+
if (this._outputRgbBuffers) {
|
|
597
|
+
teeToBothResizedJpgAndRgb = [
|
|
598
|
+
`tee name=u`,
|
|
599
|
+
`u. ! queue ! jpegenc ${jpgencDoneArgs.join(' ')} ! multifilesink location=resized%05d.jpg post-messages=true sync=false `,
|
|
600
|
+
`u. ! queue ! multifilesink location=resized%05d.rgb post-messages=true sync=false `,
|
|
601
|
+
];
|
|
602
|
+
videoFormat = ',format=RGB'; // Don't do this if outputRgbBuffers is RGB, because we don't want to force RGB video format
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
teeToBothResizedJpgAndRgb = [
|
|
606
|
+
`jpegenc ${jpgencDoneArgs.join(' ')} ! multifilesink location=resized%05d.jpg post-messages=true sync=false`,
|
|
607
|
+
];
|
|
608
|
+
}
|
|
527
609
|
let cropArgs = [];
|
|
528
610
|
if (inferenceDims) {
|
|
529
611
|
// fast path for fit-shortest and squash
|
|
@@ -535,14 +617,14 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
535
617
|
`!`,
|
|
536
618
|
`videoscale`, `method=lanczos`,
|
|
537
619
|
`!`,
|
|
538
|
-
`video/x-raw,width=${inferenceDims.width},height=${inferenceDims.height}`,
|
|
620
|
+
`video/x-raw${videoFormat},width=${inferenceDims.width},height=${inferenceDims.height}`,
|
|
539
621
|
]);
|
|
540
622
|
}
|
|
541
|
-
else if (inferenceDims.resizeMode === 'squash' || inferenceDims.resizeMode === 'none' /* old model */) {
|
|
623
|
+
else if (inferenceDims.resizeMode === 'squash' || inferenceDims.resizeMode === 'none' /* old model -> squash */) {
|
|
542
624
|
cropArgs.push(`!`);
|
|
543
625
|
cropArgs.push(`videoscale`, `method=lanczos`);
|
|
544
626
|
cropArgs.push(`!`);
|
|
545
|
-
cropArgs.push(`video/x-raw,width=${inferenceDims.width},height=${inferenceDims.height}`);
|
|
627
|
+
cropArgs.push(`video/x-raw${videoFormat},width=${inferenceDims.width},height=${inferenceDims.height}`);
|
|
546
628
|
}
|
|
547
629
|
}
|
|
548
630
|
let args;
|
|
@@ -553,6 +635,12 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
553
635
|
`video/x-raw,width=${cap.width},height=${cap.height},format=YUY2`
|
|
554
636
|
]);
|
|
555
637
|
}
|
|
638
|
+
else if (device.videoSource === 'qtiqmmfsrc') {
|
|
639
|
+
videoSource = videoSource.concat([
|
|
640
|
+
`!`,
|
|
641
|
+
`video/x-raw,width=${cap.width},height=${cap.height},format=NV12`
|
|
642
|
+
]);
|
|
643
|
+
}
|
|
556
644
|
else if (this._mode === 'rpi5') {
|
|
557
645
|
// on RPi 5 we need to set color format
|
|
558
646
|
let colorFormat = '';
|
|
@@ -576,8 +664,6 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
576
664
|
`video/x-raw,width=${cap.width},height=${cap.height}`
|
|
577
665
|
]);
|
|
578
666
|
}
|
|
579
|
-
let frameReadyArgs = this._profiling ? [`!`, `identity name=frame_ready silent=false`] : [];
|
|
580
|
-
let jpgencDoneArgs = this._profiling ? [`!`, `identity name=jpegenc_done silent=false`] : [];
|
|
581
667
|
if (cropArgs.length === 0) {
|
|
582
668
|
args = videoSource.concat([
|
|
583
669
|
...frameReadyArgs,
|
|
@@ -595,11 +681,9 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
595
681
|
let resized = [
|
|
596
682
|
`t. ! queue`,
|
|
597
683
|
...cropArgs,
|
|
684
|
+
...resizeDoneArgs,
|
|
598
685
|
`!`,
|
|
599
|
-
|
|
600
|
-
...jpgencDoneArgs,
|
|
601
|
-
`!`,
|
|
602
|
-
`multifilesink location=resized%05d.jpg post-messages=true sync=false`,
|
|
686
|
+
...teeToBothResizedJpgAndRgb,
|
|
603
687
|
];
|
|
604
688
|
args = videoSource.concat([
|
|
605
689
|
...frameReadyArgs,
|
|
@@ -611,15 +695,51 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
611
695
|
...resized,
|
|
612
696
|
]);
|
|
613
697
|
this._emitsOriginalSizeImages = true;
|
|
698
|
+
if (this._outputRgbBuffers) {
|
|
699
|
+
this._emitsRgbBuffers = true;
|
|
700
|
+
}
|
|
614
701
|
}
|
|
615
702
|
}
|
|
616
703
|
else if (cap.type === 'image/jpeg') {
|
|
617
|
-
|
|
704
|
+
videoSource = videoSource.concat([
|
|
618
705
|
`!`,
|
|
619
706
|
`image/jpeg,width=${cap.width},height=${cap.height}`,
|
|
620
|
-
`!`,
|
|
621
|
-
`multifilesink location=resized%05d.jpg post-messages=true sync=false`
|
|
622
707
|
]);
|
|
708
|
+
if (cropArgs.length === 0) {
|
|
709
|
+
args = videoSource.concat([
|
|
710
|
+
...frameReadyArgs,
|
|
711
|
+
`!`,
|
|
712
|
+
`multifilesink`,
|
|
713
|
+
`location=resized%05d.jpg`,
|
|
714
|
+
`post-messages=true`,
|
|
715
|
+
`sync=false`,
|
|
716
|
+
]);
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
let original = `t. ! queue ! multifilesink location=original%05d.jpg post-messages=true sync=false`;
|
|
720
|
+
let resized = [
|
|
721
|
+
`t. ! queue`,
|
|
722
|
+
`!`,
|
|
723
|
+
`jpegdec`,
|
|
724
|
+
`!`,
|
|
725
|
+
`videoconvert`,
|
|
726
|
+
...cropArgs,
|
|
727
|
+
...resizeDoneArgs,
|
|
728
|
+
`!`,
|
|
729
|
+
...teeToBothResizedJpgAndRgb,
|
|
730
|
+
];
|
|
731
|
+
args = videoSource.concat([
|
|
732
|
+
...frameReadyArgs,
|
|
733
|
+
`!`,
|
|
734
|
+
`tee name=t`,
|
|
735
|
+
original,
|
|
736
|
+
...resized,
|
|
737
|
+
]);
|
|
738
|
+
this._emitsOriginalSizeImages = true;
|
|
739
|
+
if (this._outputRgbBuffers) {
|
|
740
|
+
this._emitsRgbBuffers = true;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
623
743
|
}
|
|
624
744
|
else if (cap.type === 'nvarguscamerasrc') {
|
|
625
745
|
args = [
|
|
@@ -633,7 +753,8 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
633
753
|
}
|
|
634
754
|
return {
|
|
635
755
|
command: 'gst-launch-1.0',
|
|
636
|
-
|
|
756
|
+
// replace multiple spaces with one space
|
|
757
|
+
pipeline: args.join(' ').trim().replace(/(\s+)/g, ' '),
|
|
637
758
|
};
|
|
638
759
|
}
|
|
639
760
|
async stop() {
|
|
@@ -645,7 +766,10 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
645
766
|
this._captureProcess.on('close', code => {
|
|
646
767
|
if (this._watcher) {
|
|
647
768
|
this._watcher.on('close', async () => {
|
|
648
|
-
|
|
769
|
+
try {
|
|
770
|
+
await this.cleanupTempDirAsync();
|
|
771
|
+
}
|
|
772
|
+
catch (ex) { /* noop */ }
|
|
649
773
|
});
|
|
650
774
|
this._watcher.close();
|
|
651
775
|
}
|
|
@@ -1317,6 +1441,8 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
1317
1441
|
const cropTop = Math.max(0, Math.floor((srcH - cropH) / 2));
|
|
1318
1442
|
const cropBottom = Math.max(0, srcH - cropH - cropTop);
|
|
1319
1443
|
return {
|
|
1444
|
+
width: cropW,
|
|
1445
|
+
height: cropH,
|
|
1320
1446
|
left: cropLeft,
|
|
1321
1447
|
right: cropRight,
|
|
1322
1448
|
top: cropTop,
|
|
@@ -1341,7 +1467,7 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
1341
1467
|
}
|
|
1342
1468
|
let filesToUnlink = [];
|
|
1343
1469
|
// copy as we're manipulating this set
|
|
1344
|
-
for (let originalFileName of Array.from(this.
|
|
1470
|
+
for (let originalFileName of Array.from(this._originalAndRgbFilesSeen)) {
|
|
1345
1471
|
let originalNumberMatch = originalFileName.match(/(\d+)/);
|
|
1346
1472
|
let originalNumber = originalNumberMatch ? Number(originalNumberMatch[1]) : NaN;
|
|
1347
1473
|
if (isNaN(originalNumber)) {
|
|
@@ -1356,13 +1482,13 @@ class GStreamer extends tsee_1.EventEmitter {
|
|
|
1356
1482
|
filesToUnlink.push(originalFileName);
|
|
1357
1483
|
}
|
|
1358
1484
|
if (this._verbose) {
|
|
1359
|
-
console.log(PREFIX, `Unlinking ${filesToUnlink.length} original files...`);
|
|
1485
|
+
console.log(PREFIX, `Unlinking ${filesToUnlink.length} original / RGB files...`);
|
|
1360
1486
|
}
|
|
1361
1487
|
if (filesToUnlink.length > 0) {
|
|
1362
1488
|
const tempDir = this._tempDir;
|
|
1363
1489
|
await (0, async_pool_1.asyncPool)(10, filesToUnlink, async (originalFileName) => {
|
|
1364
1490
|
await this.safeUnlinkFile(path_1.default.join(tempDir, originalFileName));
|
|
1365
|
-
this.
|
|
1491
|
+
this._originalAndRgbFilesSeen.delete(originalFileName);
|
|
1366
1492
|
});
|
|
1367
1493
|
}
|
|
1368
1494
|
}
|