@camstack/addon-pipeline 1.2.107 → 1.2.118
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/dist/{addon-utils-A2S9D7pu.mjs → addon-utils-CgBCF-mE.mjs} +50 -14
- package/dist/{addon-utils-Vv2dCqA2.js → addon-utils-DJSN5h8c.js} +51 -15
- package/dist/audio-analyzer/index.js +3 -3
- package/dist/audio-analyzer/index.mjs +3 -3
- package/dist/detection-pipeline/index.js +170 -31
- package/dist/detection-pipeline/index.mjs +169 -30
- package/dist/{dist-CkXAzTZ2.mjs → dist-B763E61Y.mjs} +152 -26
- package/dist/{dist-L-1LMGQs.js → dist-BkO76jAp.js} +152 -26
- package/dist/{event-loop-stall-monitor-DwAho7HD.js → event-loop-stall-monitor-28f4R0JN.js} +234 -1
- package/dist/{event-loop-stall-monitor-fHkfgmlw.mjs → event-loop-stall-monitor-CYEvvp2Z.mjs} +223 -2
- package/dist/{lazy-sharp-DfbyQxET.js → lazy-sharp-BaPl5mRa.js} +1 -1
- package/dist/motion-wasm/index.js +2 -2
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +915 -107
- package/dist/pipeline-runner/index.mjs +914 -106
- package/dist/{process-memory-CgrbLo3l.js → process-memory-BCQJFZZg.js} +1 -1
- package/dist/{process-memory-DlSOzuHP.mjs → process-memory-CK3FnKyZ.mjs} +1 -1
- package/dist/recorder/index.js +446 -77
- package/dist/recorder/index.mjs +446 -77
- package/dist/session-decode/decode-worker-child.js +62 -34
- package/dist/session-decode/decode-worker-child.mjs +61 -33
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BeL4_jgH.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CxgvfkxP.mjs} +3 -3
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CL6Lgzdj.mjs +26 -0
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CmHtm5CX.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BAMyzCWw.mjs} +1 -1
- package/dist/stream-broker/{hostInit-BK9nzKAG.mjs → hostInit-CTkpxp3w.mjs} +3 -3
- package/dist/stream-broker/index.js +279 -198
- package/dist/stream-broker/index.mjs +279 -198
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-bEipQT9l.mjs → worker-protocol-D_2QaXYr.mjs} +6 -3
- package/dist/{worker-protocol-GGnzfWu3.js → worker-protocol-owR7WMRP.js} +6 -3
- package/package.json +1 -1
- package/python/inference_pool.py +4 -1
- package/python/test_inference_pool_preprocess.py +87 -16
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DjndFc5V.mjs +0 -26
|
@@ -2,11 +2,11 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
6
|
-
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-
|
|
7
|
-
const require_worker_protocol = require("../worker-protocol-
|
|
5
|
+
const require_dist = require("../dist-BkO76jAp.js");
|
|
6
|
+
const require_event_loop_stall_monitor = require("../event-loop-stall-monitor-28f4R0JN.js");
|
|
7
|
+
const require_worker_protocol = require("../worker-protocol-owR7WMRP.js");
|
|
8
8
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
9
|
-
const require_lazy_sharp = require("../lazy-sharp-
|
|
9
|
+
const require_lazy_sharp = require("../lazy-sharp-BaPl5mRa.js");
|
|
10
10
|
const require_restream_intent = require("../restream-intent-Cv9x3jmu.js");
|
|
11
11
|
const require_remote_restream = require("../remote-restream-CO36Sr30.js");
|
|
12
12
|
let sharp = require("sharp");
|
|
@@ -303,6 +303,48 @@ var NativeCropRegistry = class {
|
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
305
|
};
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region src/session-decode/retained-content-fallback.ts
|
|
308
|
+
/**
|
|
309
|
+
* Honest ≤640 RAM fallback for `getNativeCrop` full-frame misses.
|
|
310
|
+
* Content-sized contain (16:9 → 640×360, 4:3 → 640×480, square → 640×640),
|
|
311
|
+
* never a detection default and never a cross-process ref. Task 3 owns
|
|
312
|
+
* whether this rung stays after telemetry.
|
|
313
|
+
*/
|
|
314
|
+
var RETAINED_FALLBACK_VIEW = {
|
|
315
|
+
content: {
|
|
316
|
+
width: 640,
|
|
317
|
+
height: 640
|
|
318
|
+
},
|
|
319
|
+
fit: "contain",
|
|
320
|
+
format: "rgb"
|
|
321
|
+
};
|
|
322
|
+
/** Resolve a content-sized RGB view from the process-local registry on demand. */
|
|
323
|
+
function createRetainedContentFallback(registry, ref) {
|
|
324
|
+
return { async materialize() {
|
|
325
|
+
const lease = registry.acquire(ref);
|
|
326
|
+
if (!lease) return null;
|
|
327
|
+
let reason = "error";
|
|
328
|
+
try {
|
|
329
|
+
const view = await lease.resolve(RETAINED_FALLBACK_VIEW);
|
|
330
|
+
if (view.kind !== "view" || view.format !== "rgb" || view.data.byteLength === 0) {
|
|
331
|
+
reason = "drop";
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
reason = "success";
|
|
335
|
+
return {
|
|
336
|
+
data: Buffer.from(view.data.buffer, view.data.byteOffset, view.data.byteLength),
|
|
337
|
+
width: view.width,
|
|
338
|
+
height: view.height,
|
|
339
|
+
format: "rgb",
|
|
340
|
+
timestamp: ref.timestamp,
|
|
341
|
+
...ref.capturedAt !== void 0 ? { capturedAt: ref.capturedAt } : {}
|
|
342
|
+
};
|
|
343
|
+
} finally {
|
|
344
|
+
lease.release(reason);
|
|
345
|
+
}
|
|
346
|
+
} };
|
|
347
|
+
}
|
|
306
348
|
/** {@link RETAINED_FRAME_BUDGET_MB} in bytes. */
|
|
307
349
|
var RETAINED_FRAME_BUDGET_BYTES = (() => {
|
|
308
350
|
const raw = Number(process.env["CAMSTACK_SESSION_RETAIN_MB"]);
|
|
@@ -350,12 +392,24 @@ var RETAINED_FRAME_KEY_PREFIX = "ram:";
|
|
|
350
392
|
function keyOf(handle) {
|
|
351
393
|
return `${handle.shmId}#${handle.slot}#${handle.seq}`;
|
|
352
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
* Node-level RAM store of retained rgb detection frames, keyed by
|
|
397
|
+
* `FrameHandle` identity. Latest-wins with a byte budget.
|
|
398
|
+
*/
|
|
399
|
+
/** One retained frame plus the device it belongs to (for the per-device floor). */
|
|
400
|
+
/**
|
|
401
|
+
* Reserved footprint of a lazy contain ≤640 RGB view. Worst-case square
|
|
402
|
+
* 640×640×3 so 4:3 and portrait never exceed the reservation; 16:9 hydrates
|
|
403
|
+
* later settle to 640×360.
|
|
404
|
+
*/
|
|
405
|
+
var CONTENT_SIZED_RESERVE_BYTES = 640 * 640 * 3;
|
|
353
406
|
var RetainedFrameStore = class RetainedFrameStore {
|
|
354
407
|
nodeId;
|
|
355
408
|
budgetBytes;
|
|
356
409
|
perDeviceFloor;
|
|
357
410
|
timeFloorMs;
|
|
358
411
|
now;
|
|
412
|
+
onEvict;
|
|
359
413
|
/** Insertion-ordered (Map) → globally oldest is `keys().next()`. */
|
|
360
414
|
entries = /* @__PURE__ */ new Map();
|
|
361
415
|
/**
|
|
@@ -374,6 +428,7 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
374
428
|
this.perDeviceFloor = options.perDeviceFloor ?? RETAINED_FRAME_PER_DEVICE_FLOOR;
|
|
375
429
|
this.timeFloorMs = options.timeFloorMs ?? RETAINED_FRAME_TIME_FLOOR_MS;
|
|
376
430
|
this.now = options.now ?? Date.now;
|
|
431
|
+
this.onEvict = options.onEvict;
|
|
377
432
|
}
|
|
378
433
|
/** Number of retained frames (tests / metrics). */
|
|
379
434
|
get size() {
|
|
@@ -390,35 +445,76 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
390
445
|
* decode-worker IPC delivers a fresh Buffer per frame, so no defensive copy
|
|
391
446
|
* is needed.
|
|
392
447
|
*/
|
|
393
|
-
write(deviceId, frame) {
|
|
448
|
+
write(deviceId, frame, fallback) {
|
|
394
449
|
if (frame.format !== "rgb") return null;
|
|
395
|
-
const
|
|
396
|
-
const
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
slot: seq % RetainedFrameStore.SLOT_SPREAD,
|
|
400
|
-
seq,
|
|
401
|
-
width: frame.width,
|
|
402
|
-
height: frame.height,
|
|
403
|
-
format: "rgb",
|
|
404
|
-
pts: performance.now(),
|
|
405
|
-
byteLength,
|
|
406
|
-
nodeId: this.nodeId,
|
|
407
|
-
slotCount: RetainedFrameStore.SLOT_SPREAD
|
|
408
|
-
};
|
|
409
|
-
const key = keyOf(handle);
|
|
410
|
-
this.entries.set(key, {
|
|
450
|
+
const pixelFree = frame.data.byteLength === 0;
|
|
451
|
+
const handle = this.mintHandle(deviceId, frame);
|
|
452
|
+
const reservedBytes = pixelFree ? CONTENT_SIZED_RESERVE_BYTES : handle.byteLength;
|
|
453
|
+
this.insert(handle, {
|
|
411
454
|
frame,
|
|
412
455
|
deviceId,
|
|
413
|
-
writtenAt: this.now()
|
|
456
|
+
writtenAt: this.now(),
|
|
457
|
+
reservedBytes,
|
|
458
|
+
...pixelFree && fallback ? { fallback } : {}
|
|
414
459
|
});
|
|
415
|
-
const deviceKeys = this.perDeviceKeys.get(deviceId);
|
|
416
|
-
if (deviceKeys) deviceKeys.push(key);
|
|
417
|
-
else this.perDeviceKeys.set(deviceId, [key]);
|
|
418
|
-
this.bytes += byteLength;
|
|
419
|
-
this.evictToBudget();
|
|
420
460
|
return handle;
|
|
421
461
|
}
|
|
462
|
+
/**
|
|
463
|
+
* Identity-only handle for native hold/tile. Does not retain pixels or
|
|
464
|
+
* reserve the content-sized worst-case slot — Task 3 continuous path.
|
|
465
|
+
*/
|
|
466
|
+
allocateHandle(deviceId, frame) {
|
|
467
|
+
if (frame.format !== "rgb") return null;
|
|
468
|
+
return this.mintHandle(deviceId, frame);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Remember a detection-only on-demand fallback. Reserves the content-sized
|
|
472
|
+
* worst case until hydrate replaces it with the real JPEG/RGB bytes so the
|
|
473
|
+
* budget can evict unrequested detections. A no-detection frame must never
|
|
474
|
+
* call this.
|
|
475
|
+
*/
|
|
476
|
+
rememberOnDemand(deviceId, handle, fallback) {
|
|
477
|
+
const key = keyOf(handle);
|
|
478
|
+
if (this.entries.has(key)) return;
|
|
479
|
+
this.insert(handle, {
|
|
480
|
+
frame: {
|
|
481
|
+
data: Buffer.alloc(0),
|
|
482
|
+
width: handle.width,
|
|
483
|
+
height: handle.height,
|
|
484
|
+
format: "rgb",
|
|
485
|
+
timestamp: 0
|
|
486
|
+
},
|
|
487
|
+
deviceId,
|
|
488
|
+
writtenAt: this.now(),
|
|
489
|
+
reservedBytes: CONTENT_SIZED_RESERVE_BYTES,
|
|
490
|
+
fallback
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
has(handle) {
|
|
494
|
+
return this.entries.has(keyOf(handle));
|
|
495
|
+
}
|
|
496
|
+
getDeviceId(handle) {
|
|
497
|
+
return this.entries.get(keyOf(handle))?.deviceId;
|
|
498
|
+
}
|
|
499
|
+
statsByDevice(deviceId) {
|
|
500
|
+
const keys = this.perDeviceKeys.get(deviceId) ?? [];
|
|
501
|
+
let bytes = 0;
|
|
502
|
+
for (const key of keys) bytes += this.entries.get(key)?.reservedBytes ?? 0;
|
|
503
|
+
return {
|
|
504
|
+
entries: keys.length,
|
|
505
|
+
bytes
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
deviceStats() {
|
|
509
|
+
return [...this.perDeviceKeys.keys()].map((deviceId) => ({
|
|
510
|
+
deviceId,
|
|
511
|
+
...this.statsByDevice(deviceId)
|
|
512
|
+
}));
|
|
513
|
+
}
|
|
514
|
+
/** Drop one handle (success/error/drop/detach). Idempotent. */
|
|
515
|
+
drop(handle) {
|
|
516
|
+
this.dropKey(keyOf(handle));
|
|
517
|
+
}
|
|
422
518
|
/** The retained frame for `handle`, or `null` if never retained / already evicted. */
|
|
423
519
|
getFrame(handle) {
|
|
424
520
|
return this.entries.get(keyOf(handle))?.frame ?? null;
|
|
@@ -433,11 +529,46 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
433
529
|
*/
|
|
434
530
|
crop(handle, bbox, maxWidth) {
|
|
435
531
|
const entry = this.entries.get(keyOf(handle));
|
|
436
|
-
if (!entry || entry.frame.
|
|
532
|
+
if (!entry || entry.frame.data.byteLength === 0) return null;
|
|
533
|
+
if (entry.frame.format === "jpeg") {
|
|
534
|
+
if (!(bbox.x <= .001 && bbox.y <= .001 && bbox.w >= .999 && bbox.h >= .999)) return null;
|
|
535
|
+
return {
|
|
536
|
+
bytes: entry.frame.data,
|
|
537
|
+
width: entry.frame.width,
|
|
538
|
+
height: entry.frame.height
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
if (entry.frame.format !== "rgb") return null;
|
|
437
542
|
return cropRgb(entry.frame, bbox, maxWidth);
|
|
438
543
|
}
|
|
544
|
+
/**
|
|
545
|
+
* Full-frame / ROI crop that hydrates a pixel-free handle on demand.
|
|
546
|
+
* A missing fallback or failed materialize is an explicit miss — never a
|
|
547
|
+
* 0-byte raster.
|
|
548
|
+
*/
|
|
549
|
+
async cropAsync(handle, bbox, maxWidth) {
|
|
550
|
+
await this.ensureContent(handle);
|
|
551
|
+
return this.crop(handle, bbox, maxWidth);
|
|
552
|
+
}
|
|
553
|
+
/** Materialize a content-sized RGB view once, if the handle still has a fallback. */
|
|
554
|
+
async ensureContent(handle) {
|
|
555
|
+
const key = keyOf(handle);
|
|
556
|
+
const entry = this.entries.get(key);
|
|
557
|
+
if (!entry) return null;
|
|
558
|
+
if (entry.frame.data.byteLength > 0) return entry.frame;
|
|
559
|
+
if (entry.hydrating) return entry.hydrating;
|
|
560
|
+
if (!entry.fallback) return null;
|
|
561
|
+
entry.hydrating = this.hydrate(key, entry);
|
|
562
|
+
try {
|
|
563
|
+
return await entry.hydrating;
|
|
564
|
+
} finally {
|
|
565
|
+
const current = this.entries.get(key);
|
|
566
|
+
if (current) current.hydrating = void 0;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
439
569
|
/** Drop every retained frame (node/addon shutdown). */
|
|
440
570
|
clear() {
|
|
571
|
+
for (const entry of this.entries.values()) entry.fallback?.release?.();
|
|
441
572
|
this.entries.clear();
|
|
442
573
|
this.perDeviceKeys.clear();
|
|
443
574
|
this.bytes = 0;
|
|
@@ -462,7 +593,7 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
462
593
|
if (!entry) continue;
|
|
463
594
|
if (this.timeFloorMs > 0 && nowMs - entry.writtenAt < this.timeFloorMs) continue;
|
|
464
595
|
const deviceKeys = this.perDeviceKeys.get(entry.deviceId);
|
|
465
|
-
if (deviceKeys && deviceKeys.length > this.perDeviceFloor) this.dropKey(key);
|
|
596
|
+
if (deviceKeys && deviceKeys.length > this.perDeviceFloor) this.dropKey(key, "evict");
|
|
466
597
|
}
|
|
467
598
|
if (this.bytes <= this.budgetBytes) return;
|
|
468
599
|
for (const key of oldestFirst) {
|
|
@@ -470,27 +601,68 @@ var RetainedFrameStore = class RetainedFrameStore {
|
|
|
470
601
|
const entry = this.entries.get(key);
|
|
471
602
|
if (!entry) continue;
|
|
472
603
|
const deviceKeys = this.perDeviceKeys.get(entry.deviceId);
|
|
473
|
-
if (deviceKeys && deviceKeys.length > this.perDeviceFloor) this.dropKey(key);
|
|
604
|
+
if (deviceKeys && deviceKeys.length > this.perDeviceFloor) this.dropKey(key, "evict");
|
|
474
605
|
}
|
|
475
606
|
if (this.bytes <= this.budgetBytes) return;
|
|
476
607
|
for (const key of oldestFirst) {
|
|
477
608
|
if (this.bytes <= this.budgetBytes || this.entries.size <= 1) break;
|
|
478
|
-
this.dropKey(key);
|
|
609
|
+
this.dropKey(key, "evict");
|
|
479
610
|
}
|
|
480
611
|
if (this.bytes < 0) this.bytes = 0;
|
|
481
612
|
}
|
|
613
|
+
async hydrate(key, entry) {
|
|
614
|
+
const view = await entry.fallback?.materialize();
|
|
615
|
+
const current = this.entries.get(key);
|
|
616
|
+
if (!current) return null;
|
|
617
|
+
if (!view || view.data.byteLength === 0) return null;
|
|
618
|
+
if (view.format !== "rgb" && view.format !== "jpeg") return null;
|
|
619
|
+
current.frame = view;
|
|
620
|
+
current.fallback = void 0;
|
|
621
|
+
this.bytes -= current.reservedBytes;
|
|
622
|
+
current.reservedBytes = view.data.byteLength;
|
|
623
|
+
this.bytes += current.reservedBytes;
|
|
624
|
+
this.evictToBudget();
|
|
625
|
+
return view;
|
|
626
|
+
}
|
|
627
|
+
mintHandle(deviceId, frame) {
|
|
628
|
+
const seq = ++this.seq;
|
|
629
|
+
const pixelFree = frame.data.byteLength === 0;
|
|
630
|
+
return {
|
|
631
|
+
shmId: `${RETAINED_FRAME_KEY_PREFIX}${this.nodeId}:${deviceId}`,
|
|
632
|
+
slot: seq % RetainedFrameStore.SLOT_SPREAD,
|
|
633
|
+
seq,
|
|
634
|
+
width: frame.width,
|
|
635
|
+
height: frame.height,
|
|
636
|
+
format: "rgb",
|
|
637
|
+
pts: performance.now(),
|
|
638
|
+
byteLength: pixelFree ? 0 : frame.data.byteLength,
|
|
639
|
+
nodeId: this.nodeId,
|
|
640
|
+
slotCount: RetainedFrameStore.SLOT_SPREAD
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
insert(handle, entry) {
|
|
644
|
+
const key = keyOf(handle);
|
|
645
|
+
this.entries.set(key, entry);
|
|
646
|
+
const deviceKeys = this.perDeviceKeys.get(entry.deviceId);
|
|
647
|
+
if (deviceKeys) deviceKeys.push(key);
|
|
648
|
+
else this.perDeviceKeys.set(entry.deviceId, [key]);
|
|
649
|
+
this.bytes += entry.reservedBytes;
|
|
650
|
+
this.evictToBudget();
|
|
651
|
+
}
|
|
482
652
|
/** Remove one frame by key, keeping the byte tally + per-device index exact. */
|
|
483
|
-
dropKey(key) {
|
|
653
|
+
dropKey(key, reason = "drop") {
|
|
484
654
|
const entry = this.entries.get(key);
|
|
485
655
|
if (!entry) return;
|
|
486
656
|
this.entries.delete(key);
|
|
487
|
-
|
|
657
|
+
entry.fallback?.release?.();
|
|
658
|
+
this.bytes -= entry.reservedBytes;
|
|
488
659
|
const deviceKeys = this.perDeviceKeys.get(entry.deviceId);
|
|
489
660
|
if (deviceKeys) {
|
|
490
661
|
const idx = deviceKeys.indexOf(key);
|
|
491
662
|
if (idx >= 0) deviceKeys.splice(idx, 1);
|
|
492
663
|
if (deviceKeys.length === 0) this.perDeviceKeys.delete(entry.deviceId);
|
|
493
664
|
}
|
|
665
|
+
if (reason === "evict") this.onEvict?.(entry.deviceId);
|
|
494
666
|
}
|
|
495
667
|
};
|
|
496
668
|
/**
|
|
@@ -767,7 +939,8 @@ var SessionDecodeSession = class {
|
|
|
767
939
|
this.settleNativeCrop(raw.requestId, {
|
|
768
940
|
bytes: raw.bytes,
|
|
769
941
|
width: raw.width,
|
|
770
|
-
height: raw.height
|
|
942
|
+
height: raw.height,
|
|
943
|
+
...raw.source ? { source: raw.source } : {}
|
|
771
944
|
});
|
|
772
945
|
return;
|
|
773
946
|
case "nativeCropMiss":
|
|
@@ -1027,26 +1200,6 @@ function createSessionDecodeCoordinator(deps) {
|
|
|
1027
1200
|
//#endregion
|
|
1028
1201
|
//#region src/session-decode/session-decode-pump.ts
|
|
1029
1202
|
/**
|
|
1030
|
-
* Detection working width cap — mirrors the shm decoder's
|
|
1031
|
-
* `initScaler` (`nodeav-decoder-session.ts`: `maxW = floor(640 / scale)`,
|
|
1032
|
-
* `scale = 1` default). The worker decodes full source resolution (e.g. a 4K
|
|
1033
|
-
* `native:main` stream); without this downscale `toBuffer` would ship a full
|
|
1034
|
-
* 3840×2160 rgb frame (~24 MB) over IPC per pull, which the detection path
|
|
1035
|
-
* cannot keep up with. Aspect ratio is preserved. A source already ≤ this
|
|
1036
|
-
* width (e.g. the small `native:sub` motion stream) is passed through
|
|
1037
|
-
* unscaled, exactly as the shm scaler's `min(srcW, maxW)` no-ops.
|
|
1038
|
-
*/
|
|
1039
|
-
var WORKING_MAX_WIDTH = 640;
|
|
1040
|
-
/** Aspect-preserving cap to {@link WORKING_MAX_WIDTH}; `undefined` when no downscale is needed. */
|
|
1041
|
-
function resolveWorkingResize(width, height) {
|
|
1042
|
-
if (width <= WORKING_MAX_WIDTH || width <= 0) return void 0;
|
|
1043
|
-
const outWidth = WORKING_MAX_WIDTH;
|
|
1044
|
-
return {
|
|
1045
|
-
width: outWidth,
|
|
1046
|
-
height: Math.round(outWidth * height / width)
|
|
1047
|
-
};
|
|
1048
|
-
}
|
|
1049
|
-
/**
|
|
1050
1203
|
* Build a {@link DecodedFrame} from one pulled {@link VideoFrame}. Downscales to
|
|
1051
1204
|
* the detection working width (see {@link resolveWorkingResize}) so the ROI
|
|
1052
1205
|
* scaler ships a small frame instead of the full source resolution.
|
|
@@ -1055,17 +1208,18 @@ function resolveWorkingResize(width, height) {
|
|
|
1055
1208
|
* time) — this is what lets Step 5 MEASURE session-decode frameAge vs the
|
|
1056
1209
|
* shm-ring cameras.
|
|
1057
1210
|
*/
|
|
1058
|
-
async function toDecodedFrame(frame, format) {
|
|
1059
|
-
const
|
|
1060
|
-
const bytes = await frame.image.toBuffer({
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1211
|
+
async function toDecodedFrame(frame, format, viewSpec) {
|
|
1212
|
+
const geometry = viewSpec ? require_event_loop_stall_monitor.resolveFrameViewGeometry(frame.image.width, frame.image.height, viewSpec) : void 0;
|
|
1213
|
+
const bytes = await frame.image.toBuffer(geometry ? {
|
|
1214
|
+
crop: geometry.sourceCrop,
|
|
1215
|
+
resize: geometry.output,
|
|
1216
|
+
format: viewSpec?.format ?? format
|
|
1217
|
+
} : { format });
|
|
1064
1218
|
return {
|
|
1065
1219
|
data: Buffer.from(bytes),
|
|
1066
|
-
width:
|
|
1067
|
-
height:
|
|
1068
|
-
format,
|
|
1220
|
+
width: geometry?.output.width ?? frame.image.width,
|
|
1221
|
+
height: geometry?.output.height ?? frame.image.height,
|
|
1222
|
+
format: viewSpec?.format ?? format,
|
|
1069
1223
|
timestamp: frame.timestamp,
|
|
1070
1224
|
capturedAt: Date.now()
|
|
1071
1225
|
};
|
|
@@ -1122,9 +1276,9 @@ function startSessionDecodePump(deps) {
|
|
|
1122
1276
|
frame.image.close();
|
|
1123
1277
|
break;
|
|
1124
1278
|
}
|
|
1279
|
+
let imageOwnershipTransferred = false;
|
|
1125
1280
|
try {
|
|
1126
1281
|
if (deps.shouldMaterializeFrame && !deps.shouldMaterializeFrame()) continue;
|
|
1127
|
-
const decoded = await toDecodedFrame(frame, format);
|
|
1128
1282
|
const image = frame.image;
|
|
1129
1283
|
const cropFn = image.nativeCrop;
|
|
1130
1284
|
const nativeCrop = cropFn ? Object.assign((bbox, maxWidth) => cropFn(bbox, maxWidth), {
|
|
@@ -1132,7 +1286,33 @@ function startSessionDecodePump(deps) {
|
|
|
1132
1286
|
release: () => image.releaseNativeLease?.(),
|
|
1133
1287
|
markInferred: () => image.markInferred?.()
|
|
1134
1288
|
}) : void 0;
|
|
1135
|
-
deps.
|
|
1289
|
+
if (deps.frameRegistry && deps.onFrameRef) {
|
|
1290
|
+
const capturedAt = Date.now();
|
|
1291
|
+
const ref = deps.frameRegistry.register(image, {
|
|
1292
|
+
timestamp: frame.timestamp,
|
|
1293
|
+
capturedAt
|
|
1294
|
+
});
|
|
1295
|
+
imageOwnershipTransferred = true;
|
|
1296
|
+
if (!ref) continue;
|
|
1297
|
+
const lazyFrame = {
|
|
1298
|
+
data: Buffer.alloc(0),
|
|
1299
|
+
width: ref.width,
|
|
1300
|
+
height: ref.height,
|
|
1301
|
+
format: ref.format,
|
|
1302
|
+
timestamp: ref.timestamp,
|
|
1303
|
+
capturedAt,
|
|
1304
|
+
frameRef: ref
|
|
1305
|
+
};
|
|
1306
|
+
try {
|
|
1307
|
+
if (!await deps.onFrameRef(lazyFrame, nativeCrop)) deps.frameRegistry.release(ref, "drop");
|
|
1308
|
+
} catch (err) {
|
|
1309
|
+
deps.frameRegistry.release(ref, "error");
|
|
1310
|
+
throw err;
|
|
1311
|
+
}
|
|
1312
|
+
} else {
|
|
1313
|
+
const decoded = await toDecodedFrame(frame, format, deps.inlineViewSpec);
|
|
1314
|
+
deps.onDecodedFrame(decoded, nativeCrop);
|
|
1315
|
+
}
|
|
1136
1316
|
} catch (err) {
|
|
1137
1317
|
deps.logger.debug("session-decode pump: skipped a frame (decode/consume error)", {
|
|
1138
1318
|
tags: { deviceId: deps.source.deviceId },
|
|
@@ -1142,7 +1322,7 @@ function startSessionDecodePump(deps) {
|
|
|
1142
1322
|
}
|
|
1143
1323
|
});
|
|
1144
1324
|
} finally {
|
|
1145
|
-
frame.image.close();
|
|
1325
|
+
if (!imageOwnershipTransferred) frame.image.close();
|
|
1146
1326
|
}
|
|
1147
1327
|
}
|
|
1148
1328
|
} catch (err) {
|
|
@@ -1533,6 +1713,424 @@ function buildBenchRunPipelineInput(req) {
|
|
|
1533
1713
|
};
|
|
1534
1714
|
}
|
|
1535
1715
|
//#endregion
|
|
1716
|
+
//#region src/pipeline-runner/frame-lazy-metrics.ts
|
|
1717
|
+
function emptyFrameLazyCounters() {
|
|
1718
|
+
return {
|
|
1719
|
+
framesDecoded: 0,
|
|
1720
|
+
framesAdmitted: 0,
|
|
1721
|
+
framesDroppedPixelFree: 0,
|
|
1722
|
+
viewsMaterialized: 0,
|
|
1723
|
+
viewsSkipped: 0,
|
|
1724
|
+
workerToRunnerBytes: 0,
|
|
1725
|
+
runnerToPoolRawBytes: 0,
|
|
1726
|
+
runnerToPoolJpegBytes: 0,
|
|
1727
|
+
onDemandFullFrameRequests: 0,
|
|
1728
|
+
onDemandCropRequests: 0,
|
|
1729
|
+
nativeHits: 0,
|
|
1730
|
+
nativeMisses: 0,
|
|
1731
|
+
tileHits: 0,
|
|
1732
|
+
tileMisses: 0,
|
|
1733
|
+
fallbackHits: 0,
|
|
1734
|
+
fallbackMisses: 0,
|
|
1735
|
+
retainedWritesAvoided: 0,
|
|
1736
|
+
residentRefs: 0,
|
|
1737
|
+
residentBytes: 0,
|
|
1738
|
+
releases: 0,
|
|
1739
|
+
evictions: 0,
|
|
1740
|
+
staleMisses: 0
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
var FrameLazyTelemetry = class {
|
|
1744
|
+
node = emptyFrameLazyCounters();
|
|
1745
|
+
cameras = /* @__PURE__ */ new Map();
|
|
1746
|
+
noteDecoded(deviceId) {
|
|
1747
|
+
this.node.framesDecoded += 1;
|
|
1748
|
+
this.camera(deviceId).framesDecoded += 1;
|
|
1749
|
+
}
|
|
1750
|
+
noteAdmitted(deviceId) {
|
|
1751
|
+
this.node.framesAdmitted += 1;
|
|
1752
|
+
this.camera(deviceId).framesAdmitted += 1;
|
|
1753
|
+
}
|
|
1754
|
+
noteDroppedPixelFree(deviceId) {
|
|
1755
|
+
this.node.framesDroppedPixelFree += 1;
|
|
1756
|
+
this.camera(deviceId).framesDroppedPixelFree += 1;
|
|
1757
|
+
}
|
|
1758
|
+
noteView(input) {
|
|
1759
|
+
if (input.materialized) {
|
|
1760
|
+
this.node.viewsMaterialized += 1;
|
|
1761
|
+
this.camera(input.deviceId).viewsMaterialized += 1;
|
|
1762
|
+
} else {
|
|
1763
|
+
this.node.viewsSkipped += 1;
|
|
1764
|
+
this.camera(input.deviceId).viewsSkipped += 1;
|
|
1765
|
+
}
|
|
1766
|
+
this.node.workerToRunnerBytes += input.workerToRunnerBytes;
|
|
1767
|
+
this.camera(input.deviceId).workerToRunnerBytes += input.workerToRunnerBytes;
|
|
1768
|
+
}
|
|
1769
|
+
notePoolBytes(input) {
|
|
1770
|
+
this.node.runnerToPoolRawBytes += input.raw;
|
|
1771
|
+
this.node.runnerToPoolJpegBytes += input.jpeg;
|
|
1772
|
+
this.camera(input.deviceId).runnerToPoolRawBytes += input.raw;
|
|
1773
|
+
this.camera(input.deviceId).runnerToPoolJpegBytes += input.jpeg;
|
|
1774
|
+
}
|
|
1775
|
+
noteOnDemandRequest(input) {
|
|
1776
|
+
if (input.kind === "full-frame") {
|
|
1777
|
+
this.node.onDemandFullFrameRequests += 1;
|
|
1778
|
+
this.camera(input.deviceId).onDemandFullFrameRequests += 1;
|
|
1779
|
+
} else {
|
|
1780
|
+
this.node.onDemandCropRequests += 1;
|
|
1781
|
+
this.camera(input.deviceId).onDemandCropRequests += 1;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
noteHit(kind, deviceId) {
|
|
1785
|
+
this.addHit(this.node, kind, 1);
|
|
1786
|
+
this.addHit(this.camera(deviceId), kind, 1);
|
|
1787
|
+
}
|
|
1788
|
+
noteMiss(kind, deviceId) {
|
|
1789
|
+
this.addMiss(this.node, kind, 1);
|
|
1790
|
+
this.addMiss(this.camera(deviceId), kind, 1);
|
|
1791
|
+
}
|
|
1792
|
+
noteAvoidedWrite(deviceId) {
|
|
1793
|
+
this.node.retainedWritesAvoided += 1;
|
|
1794
|
+
this.camera(deviceId).retainedWritesAvoided += 1;
|
|
1795
|
+
}
|
|
1796
|
+
noteRelease(deviceId) {
|
|
1797
|
+
this.node.releases += 1;
|
|
1798
|
+
if (deviceId !== void 0) this.camera(deviceId).releases += 1;
|
|
1799
|
+
}
|
|
1800
|
+
noteEviction(deviceId) {
|
|
1801
|
+
this.node.evictions += 1;
|
|
1802
|
+
if (deviceId !== void 0) this.camera(deviceId).evictions += 1;
|
|
1803
|
+
}
|
|
1804
|
+
noteStale(deviceId) {
|
|
1805
|
+
this.node.staleMisses += 1;
|
|
1806
|
+
if (deviceId !== void 0) this.camera(deviceId).staleMisses += 1;
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Node occupancy. `residentRefs` is entry count (pending + store), not
|
|
1810
|
+
* outstanding registry leases.
|
|
1811
|
+
*/
|
|
1812
|
+
setResident(input) {
|
|
1813
|
+
this.node.residentRefs = input.refs;
|
|
1814
|
+
this.node.residentBytes = input.bytes;
|
|
1815
|
+
}
|
|
1816
|
+
setCameraResident(deviceId, input) {
|
|
1817
|
+
const counters = this.camera(deviceId);
|
|
1818
|
+
counters.residentRefs = input.refs;
|
|
1819
|
+
counters.residentBytes = input.bytes;
|
|
1820
|
+
}
|
|
1821
|
+
forgetCamera(deviceId) {
|
|
1822
|
+
this.cameras.delete(deviceId);
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Zero occupancy for already-tracked cameras absent from `present`.
|
|
1826
|
+
* Does not create or delete entries — snapshot still does not prune,
|
|
1827
|
+
* and detach still goes through {@link forgetCamera}.
|
|
1828
|
+
*/
|
|
1829
|
+
zeroAbsentResidents(present) {
|
|
1830
|
+
for (const [deviceId, counters] of this.cameras) {
|
|
1831
|
+
if (present.has(deviceId)) continue;
|
|
1832
|
+
counters.residentRefs = 0;
|
|
1833
|
+
counters.residentBytes = 0;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
snapshot(deviceIds) {
|
|
1837
|
+
return {
|
|
1838
|
+
node: { ...this.node },
|
|
1839
|
+
cameras: deviceIds.map((deviceId) => ({
|
|
1840
|
+
deviceId,
|
|
1841
|
+
...this.camera(deviceId)
|
|
1842
|
+
}))
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
trackedCameraCount() {
|
|
1846
|
+
return this.cameras.size;
|
|
1847
|
+
}
|
|
1848
|
+
camera(deviceId) {
|
|
1849
|
+
const existing = this.cameras.get(deviceId);
|
|
1850
|
+
if (existing) return existing;
|
|
1851
|
+
const created = emptyFrameLazyCounters();
|
|
1852
|
+
this.cameras.set(deviceId, created);
|
|
1853
|
+
return created;
|
|
1854
|
+
}
|
|
1855
|
+
addHit(counters, kind, n) {
|
|
1856
|
+
if (kind === "native") counters.nativeHits += n;
|
|
1857
|
+
else if (kind === "tile") counters.tileHits += n;
|
|
1858
|
+
else counters.fallbackHits += n;
|
|
1859
|
+
}
|
|
1860
|
+
addMiss(counters, kind, n) {
|
|
1861
|
+
if (kind === "native") counters.nativeMisses += n;
|
|
1862
|
+
else if (kind === "tile") counters.tileMisses += n;
|
|
1863
|
+
else counters.fallbackMisses += n;
|
|
1864
|
+
}
|
|
1865
|
+
};
|
|
1866
|
+
//#endregion
|
|
1867
|
+
//#region src/pipeline-runner/frame-ref-transport.ts
|
|
1868
|
+
/** A local ref never leaves the grouped process; inline remains rollback/split compatibility. */
|
|
1869
|
+
function selectExecutorFrameSource(coLocated, frameRef, inlineFrame) {
|
|
1870
|
+
return coLocated && frameRef ? { frameRef } : { frame: inlineFrame };
|
|
1871
|
+
}
|
|
1872
|
+
//#endregion
|
|
1873
|
+
//#region src/session-decode/on-demand-jpeg-fallback.ts
|
|
1874
|
+
/** Single content-sized JPEG requested after a detection, never a continuous raw. */
|
|
1875
|
+
var ON_DEMAND_JPEG_VIEW = {
|
|
1876
|
+
content: {
|
|
1877
|
+
width: 640,
|
|
1878
|
+
height: 640
|
|
1879
|
+
},
|
|
1880
|
+
fit: "contain",
|
|
1881
|
+
format: "jpeg"
|
|
1882
|
+
};
|
|
1883
|
+
function createOnDemandJpegFallback(registry, ref) {
|
|
1884
|
+
const lease = registry.acquire(ref);
|
|
1885
|
+
let cached;
|
|
1886
|
+
let released = false;
|
|
1887
|
+
const releaseLease = (reason) => {
|
|
1888
|
+
if (released) return;
|
|
1889
|
+
released = true;
|
|
1890
|
+
lease?.release(reason);
|
|
1891
|
+
};
|
|
1892
|
+
return {
|
|
1893
|
+
async materialize() {
|
|
1894
|
+
if (cached !== void 0) return cached;
|
|
1895
|
+
if (!lease) {
|
|
1896
|
+
cached = null;
|
|
1897
|
+
return null;
|
|
1898
|
+
}
|
|
1899
|
+
try {
|
|
1900
|
+
const view = await lease.resolve(ON_DEMAND_JPEG_VIEW);
|
|
1901
|
+
if (view.kind !== "view" || view.format !== "jpeg" || view.data.byteLength === 0) {
|
|
1902
|
+
cached = null;
|
|
1903
|
+
releaseLease("drop");
|
|
1904
|
+
return null;
|
|
1905
|
+
}
|
|
1906
|
+
cached = {
|
|
1907
|
+
data: Buffer.from(view.data.buffer, view.data.byteOffset, view.data.byteLength),
|
|
1908
|
+
width: view.width,
|
|
1909
|
+
height: view.height,
|
|
1910
|
+
format: "jpeg",
|
|
1911
|
+
timestamp: ref.timestamp,
|
|
1912
|
+
...ref.capturedAt !== void 0 ? { capturedAt: ref.capturedAt } : {}
|
|
1913
|
+
};
|
|
1914
|
+
releaseLease("success");
|
|
1915
|
+
return cached;
|
|
1916
|
+
} catch {
|
|
1917
|
+
cached = null;
|
|
1918
|
+
releaseLease("error");
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1921
|
+
},
|
|
1922
|
+
release() {
|
|
1923
|
+
releaseLease("eviction");
|
|
1924
|
+
}
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
//#endregion
|
|
1928
|
+
//#region src/pipeline-runner/on-demand-retention.ts
|
|
1929
|
+
var CAMSTACK_CONTINUOUS_RETAIN = "CAMSTACK_CONTINUOUS_RETAIN";
|
|
1930
|
+
function isContinuousRetainEnabled(env = process.env) {
|
|
1931
|
+
return env[CAMSTACK_CONTINUOUS_RETAIN] === "1";
|
|
1932
|
+
}
|
|
1933
|
+
function shouldRetainDetectionFrame(input) {
|
|
1934
|
+
if (!input.admitted) return false;
|
|
1935
|
+
if (input.continuousRetain) return true;
|
|
1936
|
+
return input.hasDetection;
|
|
1937
|
+
}
|
|
1938
|
+
function encodeCrossNodeCrop(input) {
|
|
1939
|
+
if (!input.jpeg) return null;
|
|
1940
|
+
return { jpeg: input.jpeg };
|
|
1941
|
+
}
|
|
1942
|
+
function handleKey(handle) {
|
|
1943
|
+
return `${handle.shmId}#${handle.slot}#${handle.seq}`;
|
|
1944
|
+
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Pending occupancy exists only admission → inference result. A detection
|
|
1947
|
+
* transfers to {@link RetainedFrameStore} (authoritative late-media owner).
|
|
1948
|
+
* No-detection leaves zero state. Store eviction owns fallback cleanup.
|
|
1949
|
+
*/
|
|
1950
|
+
var OnDemandRetentionSession = class {
|
|
1951
|
+
store;
|
|
1952
|
+
registry;
|
|
1953
|
+
telemetry;
|
|
1954
|
+
continuousRetain;
|
|
1955
|
+
pending = /* @__PURE__ */ new Map();
|
|
1956
|
+
constructor(options) {
|
|
1957
|
+
this.store = options.store;
|
|
1958
|
+
this.registry = options.registry;
|
|
1959
|
+
this.telemetry = options.telemetry;
|
|
1960
|
+
this.continuousRetain = options.continuousRetain;
|
|
1961
|
+
}
|
|
1962
|
+
get pendingSize() {
|
|
1963
|
+
return this.pending.size;
|
|
1964
|
+
}
|
|
1965
|
+
hasPending(handle) {
|
|
1966
|
+
return this.pending.has(handleKey(handle));
|
|
1967
|
+
}
|
|
1968
|
+
admitFrame(deviceId, frame) {
|
|
1969
|
+
if (this.continuousRetain) {
|
|
1970
|
+
const fallback = frame.frameRef ? createRetainedContentFallback(this.registry, frame.frameRef) : void 0;
|
|
1971
|
+
const handle = this.store.write(deviceId, frame, fallback) ?? void 0;
|
|
1972
|
+
if (handle) this.pending.set(handleKey(handle), {
|
|
1973
|
+
deviceId,
|
|
1974
|
+
ref: frame.frameRef
|
|
1975
|
+
});
|
|
1976
|
+
this.refreshResident();
|
|
1977
|
+
return handle;
|
|
1978
|
+
}
|
|
1979
|
+
const handle = this.store.allocateHandle(deviceId, frame) ?? void 0;
|
|
1980
|
+
if (!handle) return void 0;
|
|
1981
|
+
this.pending.set(handleKey(handle), {
|
|
1982
|
+
deviceId,
|
|
1983
|
+
ref: frame.frameRef
|
|
1984
|
+
});
|
|
1985
|
+
this.telemetry.noteAvoidedWrite(deviceId);
|
|
1986
|
+
this.refreshResident();
|
|
1987
|
+
return handle;
|
|
1988
|
+
}
|
|
1989
|
+
noteFrame(input) {
|
|
1990
|
+
if (input.decoded) this.telemetry.noteDecoded(input.deviceId);
|
|
1991
|
+
if (input.admitted) this.telemetry.noteAdmitted(input.deviceId);
|
|
1992
|
+
if (input.droppedPixelFree) this.telemetry.noteDroppedPixelFree(input.deviceId);
|
|
1993
|
+
}
|
|
1994
|
+
noteInferenceResult(deviceId, handle, result) {
|
|
1995
|
+
if (!handle) return;
|
|
1996
|
+
const key = handleKey(handle);
|
|
1997
|
+
const pending = this.pending.get(key);
|
|
1998
|
+
this.forgetPending(key);
|
|
1999
|
+
if (!shouldRetainDetectionFrame({
|
|
2000
|
+
admitted: true,
|
|
2001
|
+
hasDetection: (result.detections?.length ?? 0) > 0,
|
|
2002
|
+
continuousRetain: this.continuousRetain
|
|
2003
|
+
})) {
|
|
2004
|
+
this.refreshResident();
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
if (this.continuousRetain) {
|
|
2008
|
+
this.refreshResident();
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
if (!pending?.ref) {
|
|
2012
|
+
this.refreshResident();
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
const fallback = createOnDemandJpegFallback(this.registry, pending.ref);
|
|
2016
|
+
this.store.rememberOnDemand(deviceId, handle, fallback);
|
|
2017
|
+
this.refreshResident();
|
|
2018
|
+
}
|
|
2019
|
+
async requestMedia(handle, request) {
|
|
2020
|
+
const deviceId = this.store.getDeviceId(handle) ?? this.pending.get(handleKey(handle))?.deviceId ?? 0;
|
|
2021
|
+
const kind = request.kind === "full-frame" ? "full-frame" : "crop";
|
|
2022
|
+
this.telemetry.noteOnDemandRequest({
|
|
2023
|
+
deviceId,
|
|
2024
|
+
kind
|
|
2025
|
+
});
|
|
2026
|
+
if (request.nativeHit) {
|
|
2027
|
+
this.telemetry.noteHit("native", deviceId);
|
|
2028
|
+
return {
|
|
2029
|
+
tier: "native",
|
|
2030
|
+
bytes: request.nativeHit.bytes,
|
|
2031
|
+
width: request.nativeHit.width,
|
|
2032
|
+
height: request.nativeHit.height
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
this.telemetry.noteMiss("native", deviceId);
|
|
2036
|
+
if (request.tileHit) {
|
|
2037
|
+
this.telemetry.noteHit("tile", deviceId);
|
|
2038
|
+
return {
|
|
2039
|
+
tier: "tile",
|
|
2040
|
+
bytes: request.tileHit.bytes,
|
|
2041
|
+
jpeg: request.tileHit.jpeg ? request.tileHit.bytes : void 0,
|
|
2042
|
+
width: request.tileHit.width,
|
|
2043
|
+
height: request.tileHit.height
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
this.telemetry.noteMiss("tile", deviceId);
|
|
2047
|
+
if (request.recordedStillHit) return {
|
|
2048
|
+
tier: "recorded-still",
|
|
2049
|
+
jpeg: request.recordedStillHit.jpeg,
|
|
2050
|
+
width: request.recordedStillHit.width,
|
|
2051
|
+
height: request.recordedStillHit.height
|
|
2052
|
+
};
|
|
2053
|
+
if (!this.continuousRetain && !this.store.has(handle)) {
|
|
2054
|
+
this.telemetry.noteMiss("fallback", deviceId);
|
|
2055
|
+
return { tier: "miss" };
|
|
2056
|
+
}
|
|
2057
|
+
const view = await this.store.ensureContent(handle);
|
|
2058
|
+
if (view && view.data.byteLength > 0) {
|
|
2059
|
+
this.telemetry.noteHit("fallback", deviceId);
|
|
2060
|
+
this.refreshResident();
|
|
2061
|
+
if (view.format === "jpeg") return {
|
|
2062
|
+
tier: "fallback-jpeg",
|
|
2063
|
+
jpeg: view.data,
|
|
2064
|
+
width: view.width,
|
|
2065
|
+
height: view.height
|
|
2066
|
+
};
|
|
2067
|
+
return {
|
|
2068
|
+
tier: "fallback-jpeg",
|
|
2069
|
+
bytes: view.data,
|
|
2070
|
+
width: view.width,
|
|
2071
|
+
height: view.height
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
this.telemetry.noteMiss("fallback", deviceId);
|
|
2075
|
+
this.telemetry.noteStale(deviceId);
|
|
2076
|
+
return { tier: "miss" };
|
|
2077
|
+
}
|
|
2078
|
+
/**
|
|
2079
|
+
* Pending cleanup. Success never invalidates a store entry already
|
|
2080
|
+
* transferred for late pull. Drop/error/detach drop the store only when the
|
|
2081
|
+
* handle was still pending (not yet transferred). Eviction owns transferred
|
|
2082
|
+
* fallback cleanup.
|
|
2083
|
+
*/
|
|
2084
|
+
release(handle, reason) {
|
|
2085
|
+
const key = handleKey(handle);
|
|
2086
|
+
const pending = this.pending.get(key);
|
|
2087
|
+
this.forgetPending(key);
|
|
2088
|
+
if (reason !== "success" && pending) this.store.drop(handle);
|
|
2089
|
+
this.telemetry.noteRelease(pending?.deviceId ?? this.store.getDeviceId(handle));
|
|
2090
|
+
this.refreshResident();
|
|
2091
|
+
}
|
|
2092
|
+
forgetDevice(deviceId) {
|
|
2093
|
+
for (const [key, pending] of [...this.pending]) {
|
|
2094
|
+
if (pending.deviceId !== deviceId) continue;
|
|
2095
|
+
this.pending.delete(key);
|
|
2096
|
+
}
|
|
2097
|
+
this.telemetry.forgetCamera(deviceId);
|
|
2098
|
+
this.refreshResident();
|
|
2099
|
+
}
|
|
2100
|
+
syncResident() {
|
|
2101
|
+
this.refreshResident();
|
|
2102
|
+
}
|
|
2103
|
+
forgetPending(key) {
|
|
2104
|
+
this.pending.delete(key);
|
|
2105
|
+
}
|
|
2106
|
+
refreshResident() {
|
|
2107
|
+
const byDevice = /* @__PURE__ */ new Map();
|
|
2108
|
+
for (const pending of this.pending.values()) {
|
|
2109
|
+
const cur = byDevice.get(pending.deviceId) ?? {
|
|
2110
|
+
refs: 0,
|
|
2111
|
+
bytes: 0
|
|
2112
|
+
};
|
|
2113
|
+
cur.refs += 1;
|
|
2114
|
+
byDevice.set(pending.deviceId, cur);
|
|
2115
|
+
}
|
|
2116
|
+
for (const stats of this.store.deviceStats()) {
|
|
2117
|
+
const cur = byDevice.get(stats.deviceId) ?? {
|
|
2118
|
+
refs: 0,
|
|
2119
|
+
bytes: 0
|
|
2120
|
+
};
|
|
2121
|
+
cur.refs += stats.entries;
|
|
2122
|
+
cur.bytes += stats.bytes;
|
|
2123
|
+
byDevice.set(stats.deviceId, cur);
|
|
2124
|
+
}
|
|
2125
|
+
this.telemetry.setResident({
|
|
2126
|
+
refs: this.pending.size + this.store.size,
|
|
2127
|
+
bytes: this.registry.estimatedBytes + this.store.retainedBytes
|
|
2128
|
+
});
|
|
2129
|
+
this.telemetry.zeroAbsentResidents(new Set(byDevice.keys()));
|
|
2130
|
+
for (const [deviceId, stats] of byDevice) this.telemetry.setCameraResident(deviceId, stats);
|
|
2131
|
+
}
|
|
2132
|
+
};
|
|
2133
|
+
//#endregion
|
|
1536
2134
|
//#region src/pipeline-runner/capacity/capacity-runner.ts
|
|
1537
2135
|
/**
|
|
1538
2136
|
* Run ONE ramp step: `cameras` independent open-loop pacers for
|
|
@@ -2442,6 +3040,35 @@ function summarizeRunnerHardware(hardware, cpuCores = node_os.cpus().length) {
|
|
|
2442
3040
|
};
|
|
2443
3041
|
}
|
|
2444
3042
|
//#endregion
|
|
3043
|
+
//#region src/pipeline-runner/motion-frame-view.ts
|
|
3044
|
+
var MOTION_FRAME_VIEW = {
|
|
3045
|
+
content: {
|
|
3046
|
+
width: 320,
|
|
3047
|
+
height: 320
|
|
3048
|
+
},
|
|
3049
|
+
fit: "contain",
|
|
3050
|
+
format: "gray"
|
|
3051
|
+
};
|
|
3052
|
+
/**
|
|
3053
|
+
* Materialize the motion gray view under a short-lived registry lease.
|
|
3054
|
+
* Release is idempotent and always runs, including miss/error.
|
|
3055
|
+
*/
|
|
3056
|
+
async function resolveMotionFrameView(registry, ref) {
|
|
3057
|
+
const lease = registry.acquire(ref);
|
|
3058
|
+
if (!lease) return {
|
|
3059
|
+
kind: "miss",
|
|
3060
|
+
reason: ref.registryId === registry.registryId ? "stale-ref" : "foreign-registry"
|
|
3061
|
+
};
|
|
3062
|
+
let reason = "error";
|
|
3063
|
+
try {
|
|
3064
|
+
const view = await lease.resolve(MOTION_FRAME_VIEW);
|
|
3065
|
+
reason = view.kind === "view" ? "success" : "drop";
|
|
3066
|
+
return view;
|
|
3067
|
+
} finally {
|
|
3068
|
+
lease.release(reason);
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
//#endregion
|
|
2445
3072
|
//#region src/pipeline-runner/motion-settle-governor.ts
|
|
2446
3073
|
/**
|
|
2447
3074
|
* MotionSettleGovernor — decides WHEN the root-plane `package-detection` step
|
|
@@ -2704,6 +3331,13 @@ function resolveNativeCropSource(isFullFrame, nativeHit) {
|
|
|
2704
3331
|
if (nativeHit) return "native";
|
|
2705
3332
|
return isFullFrame ? "ram-fullframe" : "miss";
|
|
2706
3333
|
}
|
|
3334
|
+
/**
|
|
3335
|
+
* Runtime hit kind for telemetry. A worker tile is native-resolution quality
|
|
3336
|
+
* (public wire `tier` stays `native`) but must not increment `nativeHits`.
|
|
3337
|
+
*/
|
|
3338
|
+
function resolveNativeCropHitKind(source) {
|
|
3339
|
+
return source === "tile" ? "tile" : "native";
|
|
3340
|
+
}
|
|
2707
3341
|
//#endregion
|
|
2708
3342
|
//#region src/pipeline-runner/native-lease-settings-source.ts
|
|
2709
3343
|
/**
|
|
@@ -2886,18 +3520,23 @@ var FrameQueue = class {
|
|
|
2886
3520
|
latest = null;
|
|
2887
3521
|
_droppedFrames = 0;
|
|
2888
3522
|
clone;
|
|
3523
|
+
dispose;
|
|
2889
3524
|
/**
|
|
2890
3525
|
* `clone` runs on every {@link enqueue} to detach a queue-owned copy from
|
|
2891
3526
|
* the (possibly borrowed) source — required by the D9 Task 7b ownership
|
|
2892
3527
|
* contract above. Motion call sites pass {@link ownFrame}; the detection
|
|
2893
3528
|
* path passes a clone that copies its inner frame's pixel buffer.
|
|
2894
3529
|
*/
|
|
2895
|
-
constructor(maxSize, clone) {
|
|
3530
|
+
constructor(maxSize, clone, dispose) {
|
|
2896
3531
|
this.maxSize = maxSize;
|
|
2897
3532
|
this.clone = clone;
|
|
3533
|
+
this.dispose = dispose;
|
|
2898
3534
|
}
|
|
2899
3535
|
enqueue(item) {
|
|
2900
|
-
if (this.latest !== null)
|
|
3536
|
+
if (this.latest !== null) {
|
|
3537
|
+
this._droppedFrames++;
|
|
3538
|
+
this.dispose?.(this.latest);
|
|
3539
|
+
}
|
|
2901
3540
|
this.latest = this.clone(item);
|
|
2902
3541
|
}
|
|
2903
3542
|
dequeue() {
|
|
@@ -2912,6 +3551,7 @@ var FrameQueue = class {
|
|
|
2912
3551
|
return this._droppedFrames;
|
|
2913
3552
|
}
|
|
2914
3553
|
clear() {
|
|
3554
|
+
if (this.latest !== null) this.dispose?.(this.latest);
|
|
2915
3555
|
this.latest = null;
|
|
2916
3556
|
}
|
|
2917
3557
|
};
|
|
@@ -3314,7 +3954,8 @@ function toFrameInput$1(frame) {
|
|
|
3314
3954
|
width: frame.width,
|
|
3315
3955
|
height: frame.height,
|
|
3316
3956
|
format: frame.format,
|
|
3317
|
-
timestamp: frame.timestamp
|
|
3957
|
+
timestamp: frame.timestamp,
|
|
3958
|
+
...frame.frameRef ? { frameRef: frame.frameRef } : {}
|
|
3318
3959
|
};
|
|
3319
3960
|
}
|
|
3320
3961
|
/**
|
|
@@ -3413,7 +4054,10 @@ var PipelineRunner = class {
|
|
|
3413
4054
|
}
|
|
3414
4055
|
registerCamera(deviceId, registration) {
|
|
3415
4056
|
const motionQueue = new FrameQueue(this.config.maxQueueDepth, ownMotionEntry);
|
|
3416
|
-
const detectionQueue = new FrameQueue(this.config.maxQueueDepth, ownDetectionEntry)
|
|
4057
|
+
const detectionQueue = new FrameQueue(this.config.maxQueueDepth, ownDetectionEntry, (entry) => {
|
|
4058
|
+
if (entry.handle) this.config.releaseRetention?.(entry.handle, "drop");
|
|
4059
|
+
if (entry.frame.frameRef) this.config.releaseFrameRef?.(entry.frame.frameRef, "drop");
|
|
4060
|
+
});
|
|
3417
4061
|
const initialPhase = registration.detectionMode === "disabled" ? "idle" : registration.detectionMode === "always-on" ? "active" : "watching";
|
|
3418
4062
|
const state = {
|
|
3419
4063
|
registration,
|
|
@@ -3811,9 +4455,13 @@ var PipelineRunner = class {
|
|
|
3811
4455
|
};
|
|
3812
4456
|
const semAcquiredAt = Date.now();
|
|
3813
4457
|
const executorHandle = state.detectionQueue.size <= HANDLE_PASSTHROUGH_MAX_QUEUE_DEPTH && this.semaphore.available > 0 ? handle : void 0;
|
|
4458
|
+
let frameReleaseReason = "error";
|
|
3814
4459
|
try {
|
|
3815
4460
|
const result = await this.awaitInferenceBounded(deviceId, frameInput, executorHandle, release);
|
|
3816
|
-
if (result === TIMED_OUT)
|
|
4461
|
+
if (result === TIMED_OUT) {
|
|
4462
|
+
frameReleaseReason = "drop";
|
|
4463
|
+
return;
|
|
4464
|
+
}
|
|
3817
4465
|
const inferDoneAt = Date.now();
|
|
3818
4466
|
const inferenceMs = inferDoneAt - semAcquiredAt;
|
|
3819
4467
|
state.inferenceTimes.push(inferenceMs);
|
|
@@ -3822,6 +4470,7 @@ var PipelineRunner = class {
|
|
|
3822
4470
|
if (result) {
|
|
3823
4471
|
if (result.shed === true) state.shedFrames++;
|
|
3824
4472
|
await this.notifyCallbacks(deviceId, frame, result, streamType, handle);
|
|
4473
|
+
frameReleaseReason = "success";
|
|
3825
4474
|
const emittedAt = Date.now();
|
|
3826
4475
|
const capturedAt = frame.capturedAt;
|
|
3827
4476
|
this.timingSampler.addSample(deviceId, {
|
|
@@ -3833,9 +4482,16 @@ var PipelineRunner = class {
|
|
|
3833
4482
|
endToEnd: emittedAt - enqueuedAt,
|
|
3834
4483
|
detections: result.detections?.length ?? 0
|
|
3835
4484
|
});
|
|
3836
|
-
}
|
|
4485
|
+
} else frameReleaseReason = "drop";
|
|
4486
|
+
} catch {
|
|
4487
|
+
frameReleaseReason = "error";
|
|
3837
4488
|
} finally {
|
|
3838
4489
|
release();
|
|
4490
|
+
try {
|
|
4491
|
+
if (frameReleaseReason === "success" && handle && isContinuousRetainEnabled()) await this.config.hydrateRetainedFrame?.(handle);
|
|
4492
|
+
} catch {}
|
|
4493
|
+
if (handle) this.config.releaseRetention?.(handle, frameReleaseReason);
|
|
4494
|
+
if (frame.frameRef) this.config.releaseFrameRef?.(frame.frameRef, frameReleaseReason);
|
|
3839
4495
|
}
|
|
3840
4496
|
}
|
|
3841
4497
|
/**
|
|
@@ -4185,6 +4841,20 @@ var DEFAULT_CONFIG = {
|
|
|
4185
4841
|
* than raw RGB — the hop transits the hub main process (UDS child↔child
|
|
4186
4842
|
* routing), which OOM'd under raw frames. */
|
|
4187
4843
|
var DETECTION_HOP_JPEG_QUALITY = 82;
|
|
4844
|
+
var SPLIT_DETECTION_VIEW = {
|
|
4845
|
+
content: {
|
|
4846
|
+
width: 640,
|
|
4847
|
+
height: 640
|
|
4848
|
+
},
|
|
4849
|
+
fit: "contain",
|
|
4850
|
+
format: "rgb"
|
|
4851
|
+
};
|
|
4852
|
+
function writeRetainedDetectionFrame(store, deviceId, frame, retention) {
|
|
4853
|
+
if (retention) return retention.admitFrame(deviceId, frame);
|
|
4854
|
+
if (!store) return void 0;
|
|
4855
|
+
if (!isContinuousRetainEnabled()) return store.allocateHandle(deviceId, frame) ?? void 0;
|
|
4856
|
+
return store.write(deviceId, frame, frame.frameRef ? createRetainedContentFallback(require_event_loop_stall_monitor.localFrameRegistry, frame.frameRef) : void 0) ?? void 0;
|
|
4857
|
+
}
|
|
4188
4858
|
var DETECTION_KEEPWARM_MS = 15e3;
|
|
4189
4859
|
/**
|
|
4190
4860
|
* Occupancy-burst safety timeout (both the dial path and the analyzer-tap
|
|
@@ -4505,6 +5175,8 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4505
5175
|
* Initialised in {@link onInitialize} once `nodeId` is known.
|
|
4506
5176
|
*/
|
|
4507
5177
|
retainedFrames = null;
|
|
5178
|
+
frameLazy = new FrameLazyTelemetry();
|
|
5179
|
+
retention = null;
|
|
4508
5180
|
attached = /* @__PURE__ */ new Map();
|
|
4509
5181
|
/**
|
|
4510
5182
|
* Detection decode workers parked across a detach, keyed by deviceId — see
|
|
@@ -4683,7 +5355,15 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4683
5355
|
this.nodeId = raw.includes("/") ? raw.split("/")[0] : raw;
|
|
4684
5356
|
this.retainedFrames = new RetainedFrameStore({
|
|
4685
5357
|
nodeId: this.nodeId,
|
|
4686
|
-
logger: this.ctx.logger.child("retained-frames")
|
|
5358
|
+
logger: this.ctx.logger.child("retained-frames"),
|
|
5359
|
+
onEvict: (deviceId) => this.frameLazy.noteEviction(deviceId)
|
|
5360
|
+
});
|
|
5361
|
+
this.retention = new OnDemandRetentionSession({
|
|
5362
|
+
store: this.retainedFrames,
|
|
5363
|
+
registry: require_event_loop_stall_monitor.localFrameRegistry,
|
|
5364
|
+
telemetry: this.frameLazy,
|
|
5365
|
+
nodeId: this.nodeId,
|
|
5366
|
+
continuousRetain: isContinuousRetainEnabled()
|
|
4687
5367
|
});
|
|
4688
5368
|
this.runner = new PipelineRunner({
|
|
4689
5369
|
maxQueueDepth: this.config.maxQueueDepth,
|
|
@@ -4692,6 +5372,9 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4692
5372
|
minThrottledFps: this.config.minThrottledFps,
|
|
4693
5373
|
processFrame: (deviceId, frame, handle) => this.runInference(deviceId, frame, handle),
|
|
4694
5374
|
analyzeMotion: (deviceId, frame, handle) => this.runMotionAnalysis(deviceId, frame, handle),
|
|
5375
|
+
releaseFrameRef: (ref, reason) => require_event_loop_stall_monitor.localFrameRegistry.release(ref, reason),
|
|
5376
|
+
releaseRetention: (handle, reason) => this.retention?.release(handle, reason),
|
|
5377
|
+
hydrateRetainedFrame: (handle) => this.retainedFrames?.ensureContent(handle) ?? Promise.resolve(null),
|
|
4695
5378
|
onPhaseChanged: (deviceId, phase, meta) => this.handlePhaseChanged(deviceId, phase, meta),
|
|
4696
5379
|
logger: this.ctx.logger,
|
|
4697
5380
|
onOccupancyRecheck: (deviceId, frames) => {
|
|
@@ -4708,6 +5391,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4708
5391
|
this.handleDetectionStreamChange(deviceId, action);
|
|
4709
5392
|
});
|
|
4710
5393
|
this.runner.onResult(async (deviceId, frame, result, _streamType, handle) => {
|
|
5394
|
+
this.retention?.noteInferenceResult(deviceId, handle, result);
|
|
4711
5395
|
this.cutSubjectTiles(deviceId, result, handle);
|
|
4712
5396
|
this.emitInferenceResult(deviceId, frame, result, handle);
|
|
4713
5397
|
const detectionCount = result.detections?.length ?? 0;
|
|
@@ -4803,6 +5487,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
4803
5487
|
this.nativeCropRegistry.clear();
|
|
4804
5488
|
this.retainedFrames?.clear();
|
|
4805
5489
|
this.retainedFrames = null;
|
|
5490
|
+
this.retention = null;
|
|
4806
5491
|
}
|
|
4807
5492
|
async cacheBenchFrame(input) {
|
|
4808
5493
|
const { data, info } = await (await require_lazy_sharp.getSharp())(Buffer.from(input.imageBase64, "base64")).raw().toBuffer({ resolveWithObject: true });
|
|
@@ -5555,11 +6240,22 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
5555
6240
|
* resolve legitimately want the lower-RES honest frame).
|
|
5556
6241
|
*/
|
|
5557
6242
|
async getNativeCrop(input) {
|
|
6243
|
+
const deviceId = this.nativeCropRegistry.deviceIdFor(input.handle) ?? 0;
|
|
6244
|
+
const isFullFrame = input.bbox.x <= .001 && input.bbox.y <= .001 && input.bbox.w >= .999 && input.bbox.h >= .999;
|
|
6245
|
+
this.frameLazy.noteOnDemandRequest({
|
|
6246
|
+
deviceId,
|
|
6247
|
+
kind: isFullFrame && input.maxWidth === void 0 ? "full-frame" : "crop"
|
|
6248
|
+
});
|
|
5558
6249
|
const resolved = await this.resolveNativeCropPixels(input);
|
|
5559
|
-
if (resolved.tier === "native" && resolved.pixels !== null)
|
|
6250
|
+
if (resolved.tier === "native" && resolved.pixels !== null) {
|
|
6251
|
+
this.frameLazy.noteHit(resolveNativeCropHitKind(resolved.pixels.source), deviceId);
|
|
6252
|
+
return this.encodeNativeCropResult(resolved.pixels, resolved.tier, input.encodeJpeg);
|
|
6253
|
+
}
|
|
6254
|
+
this.frameLazy.noteMiss("native", deviceId);
|
|
5560
6255
|
if (resolved.tier !== "ram-fullframe") return null;
|
|
5561
|
-
const crop = this.retainedFrames?.
|
|
6256
|
+
const crop = await this.retainedFrames?.cropAsync(input.handle, input.bbox, input.maxWidth);
|
|
5562
6257
|
if (!crop) {
|
|
6258
|
+
this.frameLazy.noteMiss("fallback", deviceId);
|
|
5563
6259
|
this.ctx?.logger.warn("native full-frame miss — the retained store no longer holds this frame", {
|
|
5564
6260
|
tags: { deviceId: this.nativeCropRegistry.deviceIdFor(input.handle) ?? -1 },
|
|
5565
6261
|
meta: {
|
|
@@ -5573,6 +6269,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
5573
6269
|
});
|
|
5574
6270
|
return null;
|
|
5575
6271
|
}
|
|
6272
|
+
this.frameLazy.noteHit("fallback", deviceId);
|
|
5576
6273
|
return this.encodeNativeCropResult({
|
|
5577
6274
|
bytes: crop.bytes,
|
|
5578
6275
|
width: crop.width,
|
|
@@ -5595,6 +6292,8 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
5595
6292
|
*/
|
|
5596
6293
|
cutSubjectTiles(deviceId, result, handle) {
|
|
5597
6294
|
const outcome = this.dispatchSubjectTiles(result, handle);
|
|
6295
|
+
if ((result.detections?.length ?? 0) > 0) if (outcome === "sent") this.frameLazy.noteHit("tile", deviceId);
|
|
6296
|
+
else this.frameLazy.noteMiss("tile", deviceId);
|
|
5598
6297
|
if (outcome === "sent") return;
|
|
5599
6298
|
const tally = this.tileCutDrops.get(deviceId) ?? {
|
|
5600
6299
|
handleless: 0,
|
|
@@ -5696,7 +6395,8 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
5696
6395
|
if (result) nativeResult = {
|
|
5697
6396
|
bytes: result.bytes,
|
|
5698
6397
|
width: result.width,
|
|
5699
|
-
height: result.height
|
|
6398
|
+
height: result.height,
|
|
6399
|
+
...result.source ? { source: result.source } : {}
|
|
5700
6400
|
};
|
|
5701
6401
|
else missReason = "worker-lease-gone";
|
|
5702
6402
|
} else missReason = "no-registry-entry";
|
|
@@ -5735,6 +6435,16 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
5735
6435
|
* (`null`) — the caller retries — never a silent raw-RGB cross-node fallback.
|
|
5736
6436
|
*/
|
|
5737
6437
|
async encodeNativeCropResult(crop, tier, encodeJpeg) {
|
|
6438
|
+
if (crop.bytes.byteLength >= 2 && crop.bytes[0] === 255 && crop.bytes[1] === 216) {
|
|
6439
|
+
const encoded = encodeCrossNodeCrop({ jpeg: Buffer.from(crop.bytes).toString("base64") });
|
|
6440
|
+
if (!encoded) return null;
|
|
6441
|
+
return {
|
|
6442
|
+
jpeg: encoded.jpeg,
|
|
6443
|
+
width: crop.width,
|
|
6444
|
+
height: crop.height,
|
|
6445
|
+
tier
|
|
6446
|
+
};
|
|
6447
|
+
}
|
|
5738
6448
|
if (encodeJpeg !== true) return {
|
|
5739
6449
|
bytes: crop.bytes,
|
|
5740
6450
|
width: crop.width,
|
|
@@ -5742,12 +6452,17 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
5742
6452
|
tier
|
|
5743
6453
|
};
|
|
5744
6454
|
try {
|
|
5745
|
-
|
|
6455
|
+
const encoded = encodeCrossNodeCrop({
|
|
5746
6456
|
jpeg: (await (await require_lazy_sharp.getSharp())(Buffer.from(crop.bytes), { raw: {
|
|
5747
6457
|
width: crop.width,
|
|
5748
6458
|
height: crop.height,
|
|
5749
6459
|
channels: 3
|
|
5750
6460
|
} }).jpeg({ quality: 90 }).toBuffer()).toString("base64"),
|
|
6461
|
+
bytes: crop.bytes
|
|
6462
|
+
});
|
|
6463
|
+
if (!encoded) return null;
|
|
6464
|
+
return {
|
|
6465
|
+
jpeg: encoded.jpeg,
|
|
5751
6466
|
width: crop.width,
|
|
5752
6467
|
height: crop.height,
|
|
5753
6468
|
tier
|
|
@@ -6088,6 +6803,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
6088
6803
|
this.attached.delete(deviceId);
|
|
6089
6804
|
this.lastMotionAt.delete(deviceId);
|
|
6090
6805
|
this.lastEmittedCameraMetrics.delete(deviceId);
|
|
6806
|
+
this.retention?.forgetDevice(deviceId);
|
|
6091
6807
|
this.runner?.unregisterCamera(deviceId);
|
|
6092
6808
|
this.ctx?.logger.info("detachCamera", {
|
|
6093
6809
|
tags: { deviceId },
|
|
@@ -6282,9 +6998,11 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
6282
6998
|
avgInferenceTimeMs: 0,
|
|
6283
6999
|
queueDepth: 0
|
|
6284
7000
|
};
|
|
7001
|
+
this.retention?.syncResident();
|
|
6285
7002
|
return {
|
|
6286
7003
|
nodeId: this.nodeId,
|
|
6287
|
-
...m
|
|
7004
|
+
...m,
|
|
7005
|
+
frameLazy: this.frameLazy.snapshot(this.getLocalCameras())
|
|
6288
7006
|
};
|
|
6289
7007
|
}
|
|
6290
7008
|
async getCameraMetrics(input) {
|
|
@@ -6444,9 +7162,11 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
6444
7162
|
acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.detectionStreamId, `session-decode:detect:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config), true),
|
|
6445
7163
|
startPump: (source) => {
|
|
6446
7164
|
const dialMs = Date.now() - requestedAt;
|
|
7165
|
+
const useLocalFrameRef = this.executorCoLocated() && process.env["CAMSTACK_DISABLE_LOCAL_FRAME_REF"] !== "1";
|
|
6447
7166
|
log.info("session-decode: detection routed through per-session decode worker (rgb)", { meta: {
|
|
6448
7167
|
streamId: config.detectionStreamId,
|
|
6449
|
-
codec: source.codec
|
|
7168
|
+
codec: source.codec,
|
|
7169
|
+
transport: useLocalFrameRef ? "local-frame-ref" : "inline-compat"
|
|
6450
7170
|
} });
|
|
6451
7171
|
let reservation = null;
|
|
6452
7172
|
return startSessionDecodePump({
|
|
@@ -6459,8 +7179,52 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
6459
7179
|
},
|
|
6460
7180
|
shouldMaterializeFrame: () => {
|
|
6461
7181
|
reservation = this.reserveSharedDetectionFrame(config.deviceId);
|
|
6462
|
-
|
|
7182
|
+
const materialize = reservation.detection || reservation.motion;
|
|
7183
|
+
this.retention?.noteFrame({
|
|
7184
|
+
deviceId: config.deviceId,
|
|
7185
|
+
decoded: true,
|
|
7186
|
+
admitted: reservation.detection,
|
|
7187
|
+
droppedPixelFree: !materialize
|
|
7188
|
+
});
|
|
7189
|
+
if (!materialize) this.frameLazy.noteView({
|
|
7190
|
+
deviceId: config.deviceId,
|
|
7191
|
+
materialized: false,
|
|
7192
|
+
workerToRunnerBytes: 0
|
|
7193
|
+
});
|
|
7194
|
+
return materialize;
|
|
6463
7195
|
},
|
|
7196
|
+
...useLocalFrameRef ? {
|
|
7197
|
+
frameRegistry: require_event_loop_stall_monitor.localFrameRegistry,
|
|
7198
|
+
onFrameRef: async (frame, nativeCrop) => {
|
|
7199
|
+
const current = reservation;
|
|
7200
|
+
reservation = null;
|
|
7201
|
+
if (!current || !frame.frameRef) return false;
|
|
7202
|
+
const handle = current.detection ? writeRetainedDetectionFrame(retainedFrames, config.deviceId, frame, this.retention) : void 0;
|
|
7203
|
+
if (handle && nativeCrop) {
|
|
7204
|
+
this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
|
|
7205
|
+
this.bindFrameCutter(handle, nativeCrop);
|
|
7206
|
+
}
|
|
7207
|
+
this.frameLazy.noteView({
|
|
7208
|
+
deviceId: config.deviceId,
|
|
7209
|
+
materialized: false,
|
|
7210
|
+
workerToRunnerBytes: 0
|
|
7211
|
+
});
|
|
7212
|
+
const admitted = current.detection && runner.enqueueReservedDetectionFrame(config.deviceId, frame, handle);
|
|
7213
|
+
if (current.motion) {
|
|
7214
|
+
const motion = await resolveMotionFrameView(require_event_loop_stall_monitor.localFrameRegistry, frame.frameRef);
|
|
7215
|
+
if (motion.kind === "view") runner.enqueueMotionFrame(config.deviceId, {
|
|
7216
|
+
data: Buffer.from(motion.data),
|
|
7217
|
+
width: motion.width,
|
|
7218
|
+
height: motion.height,
|
|
7219
|
+
format: "gray",
|
|
7220
|
+
timestamp: frame.timestamp,
|
|
7221
|
+
capturedAt: frame.capturedAt
|
|
7222
|
+
}, void 0);
|
|
7223
|
+
}
|
|
7224
|
+
if (admitted) nativeCrop?.markInferred();
|
|
7225
|
+
return admitted;
|
|
7226
|
+
}
|
|
7227
|
+
} : { inlineViewSpec: SPLIT_DETECTION_VIEW },
|
|
6464
7228
|
acquireSpawnSlot: this.buildDecodeSpawnAcquire(log, "detect"),
|
|
6465
7229
|
onFirstFrame: (timing) => {
|
|
6466
7230
|
const phaseActiveAt = this.lastMotionAt.get(config.deviceId);
|
|
@@ -6482,7 +7246,12 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
6482
7246
|
const current = reservation;
|
|
6483
7247
|
reservation = null;
|
|
6484
7248
|
if (!current) return;
|
|
6485
|
-
|
|
7249
|
+
this.frameLazy.noteView({
|
|
7250
|
+
deviceId: config.deviceId,
|
|
7251
|
+
materialized: frame.data.byteLength > 0,
|
|
7252
|
+
workerToRunnerBytes: frame.data.byteLength
|
|
7253
|
+
});
|
|
7254
|
+
const handle = current.detection ? writeRetainedDetectionFrame(retainedFrames, config.deviceId, frame, this.retention) : void 0;
|
|
6486
7255
|
if (handle && nativeCrop) {
|
|
6487
7256
|
this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
|
|
6488
7257
|
this.bindFrameCutter(handle, nativeCrop);
|
|
@@ -6528,6 +7297,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
6528
7297
|
fps: config.motionFps,
|
|
6529
7298
|
format: "gray"
|
|
6530
7299
|
},
|
|
7300
|
+
inlineViewSpec: MOTION_FRAME_VIEW,
|
|
6531
7301
|
acquireSpawnSlot: this.buildDecodeSpawnAcquire(log, "motion"),
|
|
6532
7302
|
onDecodedFrame: (frame) => {
|
|
6533
7303
|
runner.enqueueMotionFrame(config.deviceId, frame, void 0);
|
|
@@ -6701,7 +7471,8 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
6701
7471
|
}
|
|
6702
7472
|
}
|
|
6703
7473
|
let image;
|
|
6704
|
-
|
|
7474
|
+
const executorCoLocated = this.executorCoLocated();
|
|
7475
|
+
if (!executorCoLocated) {
|
|
6705
7476
|
if (frame.format === "rgb") try {
|
|
6706
7477
|
const encoded = await (0, sharp.default)(frame.data, { raw: {
|
|
6707
7478
|
width: frame.width,
|
|
@@ -6713,10 +7484,25 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
6713
7484
|
log.debug("runInference: frame JPEG encode failed — falling back to raw pixels", { meta: { error: require_dist.errMsg(err) } });
|
|
6714
7485
|
}
|
|
6715
7486
|
}
|
|
7487
|
+
if (image !== void 0) this.frameLazy.notePoolBytes({
|
|
7488
|
+
deviceId,
|
|
7489
|
+
raw: 0,
|
|
7490
|
+
jpeg: image.byteLength
|
|
7491
|
+
});
|
|
7492
|
+
else if (executorCoLocated && frame.frameRef) this.frameLazy.notePoolBytes({
|
|
7493
|
+
deviceId,
|
|
7494
|
+
raw: 0,
|
|
7495
|
+
jpeg: 0
|
|
7496
|
+
});
|
|
7497
|
+
else this.frameLazy.notePoolBytes({
|
|
7498
|
+
deviceId,
|
|
7499
|
+
raw: frame.data.byteLength,
|
|
7500
|
+
jpeg: 0
|
|
7501
|
+
});
|
|
6716
7502
|
try {
|
|
6717
7503
|
return await api.pipelineExecutor.runPipeline.mutate({
|
|
6718
7504
|
steps: [...dispatchSteps],
|
|
6719
|
-
...image !== void 0 ? { image } :
|
|
7505
|
+
...image !== void 0 ? { image } : selectExecutorFrameSource(executorCoLocated, frame.frameRef, frame),
|
|
6720
7506
|
deviceId,
|
|
6721
7507
|
...camConfig?.deviceKey ? { deviceKey: camConfig.deviceKey } : {},
|
|
6722
7508
|
plane: "frame"
|
|
@@ -7072,31 +7858,53 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
|
|
|
7072
7858
|
logger: log,
|
|
7073
7859
|
admitWorker: this.buildLiveWorkerAdmit(deviceId, "occupancy"),
|
|
7074
7860
|
acquire: () => this.acquireSessionDecodeRestream(api, config.deviceId, config.detectionStreamId, `session-decode:occupancy:${config.deviceId}`, resolveSessionDecodeHostname(config, process.env["CAMSTACK_HUB_URL"]), resolveSessionDecodeOwnerNodeId(config)),
|
|
7075
|
-
startPump: (source) =>
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7861
|
+
startPump: (source) => {
|
|
7862
|
+
const useLocalFrameRef = this.executorCoLocated() && process.env["CAMSTACK_DISABLE_LOCAL_FRAME_REF"] !== "1";
|
|
7863
|
+
return startSessionDecodePump({
|
|
7864
|
+
fork: this.forkDecodeWorkerWithLease,
|
|
7865
|
+
logger: log,
|
|
7866
|
+
source,
|
|
7867
|
+
opts: {
|
|
7868
|
+
fps: config.detectionFps,
|
|
7869
|
+
format: "rgb"
|
|
7870
|
+
},
|
|
7871
|
+
...useLocalFrameRef ? {
|
|
7872
|
+
frameRegistry: require_event_loop_stall_monitor.localFrameRegistry,
|
|
7873
|
+
onFrameRef: (frame, nativeCrop) => {
|
|
7874
|
+
if (settled) return false;
|
|
7875
|
+
const handle = writeRetainedDetectionFrame(retainedFrames, config.deviceId, frame, this.retention);
|
|
7876
|
+
if (handle && nativeCrop) {
|
|
7877
|
+
this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
|
|
7878
|
+
this.bindFrameCutter(handle, nativeCrop);
|
|
7879
|
+
}
|
|
7880
|
+
const admitted = runner.enqueueOccupancyFrame(deviceId, frame, handle);
|
|
7881
|
+
if (admitted) nativeCrop?.markInferred();
|
|
7882
|
+
count++;
|
|
7883
|
+
if (count >= frames) {
|
|
7884
|
+
clearTimeout(safetyTimer);
|
|
7885
|
+
cleanup();
|
|
7886
|
+
}
|
|
7887
|
+
return admitted;
|
|
7888
|
+
}
|
|
7889
|
+
} : { inlineViewSpec: SPLIT_DETECTION_VIEW },
|
|
7890
|
+
acquireSpawnSlot: this.buildDecodeSpawnAcquire(log, "occupancy"),
|
|
7891
|
+
onEnded: () => cleanup(),
|
|
7892
|
+
onDecodedFrame: (frame, nativeCrop) => {
|
|
7893
|
+
if (settled) return;
|
|
7894
|
+
const handle = writeRetainedDetectionFrame(retainedFrames, config.deviceId, frame, this.retention);
|
|
7895
|
+
if (handle && nativeCrop) {
|
|
7896
|
+
this.nativeCropRegistry.register(handle, nativeCrop, config.deviceId);
|
|
7897
|
+
this.bindFrameCutter(handle, nativeCrop);
|
|
7898
|
+
}
|
|
7899
|
+
if (runner.enqueueOccupancyFrame(deviceId, frame, handle)) nativeCrop?.markInferred();
|
|
7900
|
+
count++;
|
|
7901
|
+
if (count >= frames) {
|
|
7902
|
+
clearTimeout(safetyTimer);
|
|
7903
|
+
cleanup();
|
|
7904
|
+
}
|
|
7097
7905
|
}
|
|
7098
|
-
}
|
|
7099
|
-
}
|
|
7906
|
+
});
|
|
7907
|
+
}
|
|
7100
7908
|
});
|
|
7101
7909
|
if (settled) {
|
|
7102
7910
|
clearTimeout(safetyTimer);
|