@camstack/addon-pipeline 1.1.54 → 1.1.55
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/audio-analyzer/index.js +1 -1
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/detection-pipeline/index.js +149 -1468
- package/dist/detection-pipeline/index.mjs +129 -1448
- package/dist/{dist-BbaoC680.js → dist-DI57FC8K.js} +59 -3
- package/dist/{dist-CEcTeu1h.mjs → dist-RWGGPwVx.mjs} +59 -3
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +665 -23
- package/dist/pipeline-runner/index.mjs +665 -24
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.mjs +1 -1
- package/dist/{remote-source-plane-vN45KFSm.js → remote-source-plane-CHgvzzA6.js} +1 -1
- package/dist/{remote-source-plane-CNCqC_XG.mjs → remote-source-plane-DU0aRSPv.mjs} +1 -1
- package/dist/session-decode/decode-worker-child.js +222 -3
- package/dist/session-decode/decode-worker-child.mjs +222 -3
- package/dist/step-definitions-CNBFKjZe.js +1514 -0
- package/dist/step-definitions-CP9kVSml.mjs +1479 -0
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-HS0tc6Sa.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-mDCPzmT3.mjs} +3 -3
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B_v5r5ya.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Dn_pxzP-.mjs} +1 -1
- package/dist/stream-broker/{hostInit-DgohfEDA.mjs → hostInit-GM_CI22k.mjs} +3 -3
- package/dist/stream-broker/index.js +2 -2
- package/dist/stream-broker/index.mjs +2 -2
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-DGIt_waM.mjs → worker-protocol-CyVJTZEO.mjs} +1 -0
- package/dist/{worker-protocol-DkL6GDxe.js → worker-protocol-PP4jKHHJ.js} +1 -0
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-CA2Hd6xx.js → MaskShapeCanvas-DI4BY7W2-BChW0ntM.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-DUN1VeC7.js → MotionZonesSettings-NcxxQN8r-DDzqEbSe.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-BqZIAU5s.js → PrivacyMaskSettings-APgPLF7p-B879GXaf.js} +1 -1
- package/embed-dist/assets/{index-CMkYypxB.js → index-CNjQ5rAE.js} +10 -10
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
package/dist/recorder/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("../chunk-D6vf50IK.js");
|
|
2
|
-
const require_dist = require("../dist-
|
|
2
|
+
const require_dist = require("../dist-DI57FC8K.js");
|
|
3
3
|
const require_hub_hostname = require("../hub-hostname-DAJXlOgV.js");
|
|
4
4
|
const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-BLXzxpRB.js");
|
|
5
5
|
let node_child_process = require("node:child_process");
|
package/dist/recorder/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as recordingCapability, B as DeviceType, D as nodePin, I as errMsg, L as BaseAddon, M as storageEvictableCapability, T as migrateConfigToBands, U as hydrateSchema, V as EventCategory, Y as selectAssignedProfileSlots, at as record, ot as string, s as EVENT_PAD_MS, u as RecordingConfigSchema } from "../dist-
|
|
1
|
+
import { A as recordingCapability, B as DeviceType, D as nodePin, I as errMsg, L as BaseAddon, M as storageEvictableCapability, T as migrateConfigToBands, U as hydrateSchema, V as EventCategory, Y as selectAssignedProfileSlots, at as record, ot as string, s as EVENT_PAD_MS, u as RecordingConfigSchema } from "../dist-RWGGPwVx.mjs";
|
|
2
2
|
import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
|
|
3
3
|
import { t as createFileDataPlaneHandler } from "../model-download-service-C-IHWnXx-BPy6aoAx.mjs";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as nodePin, I as errMsg, d as RingBuffer } from "./dist-
|
|
1
|
+
import { D as nodePin, I as errMsg, d as RingBuffer } from "./dist-RWGGPwVx.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
//#region src/stream-broker/stream-broker/decoder-session-proxy.ts
|
|
4
4
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../chunk-D6vf50IK.js");
|
|
3
|
-
const require_worker_protocol = require("../worker-protocol-
|
|
3
|
+
const require_worker_protocol = require("../worker-protocol-PP4jKHHJ.js");
|
|
4
4
|
let node_url = require("node:url");
|
|
5
5
|
let node_fs = require("node:fs");
|
|
6
6
|
node_fs = require_chunk.__toESM(node_fs);
|
|
@@ -237,6 +237,99 @@ var NativeFrameRing = class {
|
|
|
237
237
|
}
|
|
238
238
|
};
|
|
239
239
|
//#endregion
|
|
240
|
+
//#region src/session-decode/native-lease-store.ts
|
|
241
|
+
/**
|
|
242
|
+
* A hard-capped-by-bytes, TTL-and-release map from worker frameId → retained
|
|
243
|
+
* RAM frame. Insertion-ordered (`Map`), so budget eviction is FIFO by age.
|
|
244
|
+
*/
|
|
245
|
+
var NativeLeaseStore = class {
|
|
246
|
+
entries = /* @__PURE__ */ new Map();
|
|
247
|
+
budgetBytes;
|
|
248
|
+
ttlMs;
|
|
249
|
+
now;
|
|
250
|
+
bytes = 0;
|
|
251
|
+
constructor(options) {
|
|
252
|
+
this.budgetBytes = options.budgetBytes;
|
|
253
|
+
this.ttlMs = options.ttlMs;
|
|
254
|
+
this.now = options.now ?? Date.now;
|
|
255
|
+
}
|
|
256
|
+
/** `false` when the store is disabled (`budgetBytes <= 0`). */
|
|
257
|
+
get enabled() {
|
|
258
|
+
return this.budgetBytes > 0;
|
|
259
|
+
}
|
|
260
|
+
/** Number of retained leases (tests / metrics). */
|
|
261
|
+
get size() {
|
|
262
|
+
return this.entries.size;
|
|
263
|
+
}
|
|
264
|
+
/** Total resident bytes across all retained leases (tests / metrics). */
|
|
265
|
+
get totalBytes() {
|
|
266
|
+
return this.bytes;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Take ownership of `frame` under `frameId`. Returns `true` when the store
|
|
270
|
+
* took ownership (caller MUST NOT free), `false` when the store is disabled
|
|
271
|
+
* (`budgetBytes <= 0`) and the frame was freed here — the caller then keeps
|
|
272
|
+
* today's GPU-surface-ring path. Sweeps expired leases first, then evicts the
|
|
273
|
+
* oldest while the total exceeds the budget.
|
|
274
|
+
*/
|
|
275
|
+
put(frameId, frame) {
|
|
276
|
+
if (!this.enabled) {
|
|
277
|
+
frame.free();
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
this.drop(frameId);
|
|
281
|
+
this.sweepExpired();
|
|
282
|
+
this.entries.set(frameId, {
|
|
283
|
+
frame,
|
|
284
|
+
insertedAt: this.now()
|
|
285
|
+
});
|
|
286
|
+
this.bytes += frame.byteLength;
|
|
287
|
+
while (this.bytes > this.budgetBytes && this.entries.size > 0) {
|
|
288
|
+
const oldest = this.entries.keys().next().value;
|
|
289
|
+
if (oldest === void 0) break;
|
|
290
|
+
this.drop(oldest);
|
|
291
|
+
}
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* The retained frame for `frameId`, or `null` if never leased, already
|
|
296
|
+
* released/evicted, OR aged past its TTL (an expired lease is freed + dropped
|
|
297
|
+
* here so a caller that reads it just after expiry does not resurrect it).
|
|
298
|
+
*/
|
|
299
|
+
get(frameId) {
|
|
300
|
+
const entry = this.entries.get(frameId);
|
|
301
|
+
if (!entry) return null;
|
|
302
|
+
if (this.now() - entry.insertedAt > this.ttlMs) {
|
|
303
|
+
this.drop(frameId);
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
return entry.frame;
|
|
307
|
+
}
|
|
308
|
+
/** Free + drop the lease for `frameId` (the prompt release path). Idempotent. */
|
|
309
|
+
release(frameId) {
|
|
310
|
+
this.drop(frameId);
|
|
311
|
+
}
|
|
312
|
+
/** Free + drop every lease older than the TTL. */
|
|
313
|
+
sweepExpired() {
|
|
314
|
+
const cutoff = this.now() - this.ttlMs;
|
|
315
|
+
for (const [frameId, entry] of this.entries) if (entry.insertedAt < cutoff) this.drop(frameId);
|
|
316
|
+
}
|
|
317
|
+
/** Free + drop every retained lease. Idempotent (re-dial + teardown call it). */
|
|
318
|
+
clear() {
|
|
319
|
+
for (const entry of this.entries.values()) entry.frame.free();
|
|
320
|
+
this.entries.clear();
|
|
321
|
+
this.bytes = 0;
|
|
322
|
+
}
|
|
323
|
+
/** Free + remove one entry, keeping the byte tally exact. */
|
|
324
|
+
drop(frameId) {
|
|
325
|
+
const entry = this.entries.get(frameId);
|
|
326
|
+
if (!entry) return;
|
|
327
|
+
this.entries.delete(frameId);
|
|
328
|
+
this.bytes -= entry.frame.byteLength;
|
|
329
|
+
entry.frame.free();
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
//#endregion
|
|
240
333
|
//#region src/session-decode/decode-worker-child.ts
|
|
241
334
|
/**
|
|
242
335
|
* Decode-worker CHILD entry point — Epic C P1, Task 2.
|
|
@@ -262,6 +355,14 @@ var NativeFrameRing = class {
|
|
|
262
355
|
* NOT `$process`)". Raw `process.on('message')` / `process.send` are the
|
|
263
356
|
* correct, sanctioned mechanism for this specific file only.
|
|
264
357
|
*/
|
|
358
|
+
/** Wrap a software YUV420P frame as a lease entry (byte size ≈ w×h×1.5). */
|
|
359
|
+
function toLeasedNavFrame(frame) {
|
|
360
|
+
return {
|
|
361
|
+
frame,
|
|
362
|
+
byteLength: Math.ceil(frame.width * frame.height * 1.5),
|
|
363
|
+
free: () => frame.free()
|
|
364
|
+
};
|
|
365
|
+
}
|
|
265
366
|
/** Re-dial backoff on a transient dial error/EOF (mirrors the decoder's PULL_REDIAL_MS). */
|
|
266
367
|
var REDIAL_MS = 3e3;
|
|
267
368
|
/** How often the worker emits its `framesDecoded/framesSkipped/deliveredFps` line. */
|
|
@@ -281,6 +382,32 @@ var NATIVE_RING_CAP = (() => {
|
|
|
281
382
|
return Math.min(4, Math.max(0, Math.floor(raw)));
|
|
282
383
|
})();
|
|
283
384
|
/**
|
|
385
|
+
* Hard RAM budget (bytes) for the native-frame LEASE store — the primary
|
|
386
|
+
* native-crop survival window. Unlike {@link NATIVE_RING_CAP} (GPU surfaces,
|
|
387
|
+
* leak-prone → tiny), a lease is a downloaded RAM copy, so the window is sized
|
|
388
|
+
* by memory, not a 2-frame count, and the late cross-process crop reliably
|
|
389
|
+
* hits. The primary eviction is the {@link NATIVE_LEASE_TTL_MS} TTL — a 64MB
|
|
390
|
+
* budget held only ~5 native 4K frames (~0.2s), which the late crop still
|
|
391
|
+
* outran; the budget is now a HIGH safety ceiling (default 1024MB) so the TTL
|
|
392
|
+
* is the effective cap and the ~40-200ms crop reliably lands within it.
|
|
393
|
+
* `CAMSTACK_SESSION_NATIVE_LEASE_BUDGET_MB` (default 1024); `0` DISABLES the
|
|
394
|
+
* lease and falls back to the {@link NATIVE_RING_CAP} GPU ring (today's path).
|
|
395
|
+
*/
|
|
396
|
+
var NATIVE_LEASE_BUDGET_BYTES = (() => {
|
|
397
|
+
const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_LEASE_BUDGET_MB"]);
|
|
398
|
+
return (Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 1024) * 1024 * 1024;
|
|
399
|
+
})();
|
|
400
|
+
/**
|
|
401
|
+
* TTL (ms) after which a native-frame lease is treated as a miss and reclaimed.
|
|
402
|
+
* The backstop that bounds in-flight RAM even if the explicit `releaseNativeLease`
|
|
403
|
+
* is dropped — comfortably longer than the detection inference + crop round-trip
|
|
404
|
+
* (~40-200ms). `CAMSTACK_SESSION_NATIVE_LEASE_TTL_MS` (default 500).
|
|
405
|
+
*/
|
|
406
|
+
var NATIVE_LEASE_TTL_MS = (() => {
|
|
407
|
+
const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_LEASE_TTL_MS"]);
|
|
408
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 500;
|
|
409
|
+
})();
|
|
410
|
+
/**
|
|
284
411
|
* The libav GPU scale filter for a hwaccel backend, or `null` when none is
|
|
285
412
|
* known here — those backends fall back to the software crop+scale path.
|
|
286
413
|
* Mirrors `addon-decoder-ffmpeg/src/ffmpeg-args.ts` `gpuScaleFilterForBackend`.
|
|
@@ -520,6 +647,29 @@ var DecodeWorkerChild = class {
|
|
|
520
647
|
* re-dial + teardown (see {@link closeInput}).
|
|
521
648
|
*/
|
|
522
649
|
nativeRing = new NativeFrameRing(NATIVE_RING_CAP);
|
|
650
|
+
/**
|
|
651
|
+
* PRIMARY native-frame survival window — a bounded RAM copy of each shipped
|
|
652
|
+
* detection frame, keyed by frameId, held until release / TTL / budget
|
|
653
|
+
* eviction. Replaces the tiny GPU {@link nativeRing} as the default (the ring
|
|
654
|
+
* is used only when the lease is disabled via a 0 budget). Because it holds
|
|
655
|
+
* DOWNLOADED (system-memory) frames, retaining a large window has no VAAPI
|
|
656
|
+
* surface-pool starvation risk. Cleared on every re-dial + teardown.
|
|
657
|
+
*/
|
|
658
|
+
leaseStore = new NativeLeaseStore({
|
|
659
|
+
budgetBytes: NATIVE_LEASE_BUDGET_BYTES,
|
|
660
|
+
ttlMs: NATIVE_LEASE_TTL_MS
|
|
661
|
+
});
|
|
662
|
+
/**
|
|
663
|
+
* DEDICATED full-frame GPU→system download filtergraph
|
|
664
|
+
* (`scale_<be>=iw:ih,hwdownload,format=nv12,format=yuv420p`) used ONLY to
|
|
665
|
+
* materialize a HW surface into a leasable software frame at NATIVE
|
|
666
|
+
* resolution (no scale). Kept separate from the detection/native-crop filters
|
|
667
|
+
* (different geometry). Same dispose discipline: closed on every re-dial
|
|
668
|
+
* ({@link closeInput}) + teardown, since it references the decoder's per-dial
|
|
669
|
+
* `hw_frames_ctx`.
|
|
670
|
+
*/
|
|
671
|
+
nativeLeaseDownloadFilter = null;
|
|
672
|
+
nativeLeaseDownloadFilterKey = "";
|
|
523
673
|
/** Throttled native-crop hit/miss counters, emitted with the throughput line. */
|
|
524
674
|
nativeCropHits = 0;
|
|
525
675
|
nativeCropMisses = 0;
|
|
@@ -555,7 +705,7 @@ var DecodeWorkerChild = class {
|
|
|
555
705
|
* the reserve-on-pull invariant (`e3c78a9b`) is unit-tested without
|
|
556
706
|
* node-av. See that file's doc comment for the full semantics.
|
|
557
707
|
*/
|
|
558
|
-
frames = new FrameSlot({ retainSuperseded: (frameId, frame) => this.
|
|
708
|
+
frames = new FrameSlot({ retainSuperseded: (frameId, frame) => this.captureNativeFrame(frameId, frame) });
|
|
559
709
|
/**
|
|
560
710
|
* Mirrors whether `this.frames` currently has a pull waiting, so
|
|
561
711
|
* `teardown` knows to resolve it with `{kind:'ended'}` — `FrameSlot`
|
|
@@ -584,6 +734,9 @@ var DecodeWorkerChild = class {
|
|
|
584
734
|
case "nativeCrop":
|
|
585
735
|
this.handleNativeCrop(message.requestId, message.frameId, message.bbox, message.maxWidth);
|
|
586
736
|
return;
|
|
737
|
+
case "releaseNativeLease":
|
|
738
|
+
this.leaseStore.release(message.frameId);
|
|
739
|
+
return;
|
|
587
740
|
case "stop":
|
|
588
741
|
this.handleStop();
|
|
589
742
|
return;
|
|
@@ -685,7 +838,7 @@ var DecodeWorkerChild = class {
|
|
|
685
838
|
* session to software on a GPU-filter error).
|
|
686
839
|
*/
|
|
687
840
|
handleNativeCrop(requestId, frameId, bbox, maxWidth) {
|
|
688
|
-
const frame = this.
|
|
841
|
+
const frame = this.leaseStore.get(frameId)?.frame ?? this.resolveRetainedFrame(frameId);
|
|
689
842
|
if (!frame) {
|
|
690
843
|
this.nativeCropMisses++;
|
|
691
844
|
this.send({
|
|
@@ -714,6 +867,68 @@ var DecodeWorkerChild = class {
|
|
|
714
867
|
});
|
|
715
868
|
}
|
|
716
869
|
}
|
|
870
|
+
/** The GPU-ring or reserved-slot frame for `frameId` (lease-disabled / newest paths). */
|
|
871
|
+
resolveRetainedFrame(frameId) {
|
|
872
|
+
return this.nativeRing.get(frameId) ?? this.frames.toBuffer(frameId);
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Retain a just-superseded DELIVERED frame for later native crops. Transfers
|
|
876
|
+
* ownership from the {@link FrameSlot} (the caller MUST NOT free when this
|
|
877
|
+
* returns `true`).
|
|
878
|
+
*
|
|
879
|
+
* - Lease ENABLED (default): materialize a SOFTWARE RAM copy and put it in the
|
|
880
|
+
* {@link leaseStore}. A HW surface is downloaded to system memory ONCE here
|
|
881
|
+
* (freeing the GPU surface immediately — no VAAPI-pool pin); a software frame
|
|
882
|
+
* is leased directly. A download failure still consumes ownership (freed
|
|
883
|
+
* here) so the slot never double-frees — the crop simply misses → fallback.
|
|
884
|
+
* - Lease DISABLED (`budget 0`): fall back to today's tiny GPU {@link nativeRing}.
|
|
885
|
+
*/
|
|
886
|
+
captureNativeFrame(frameId, frame) {
|
|
887
|
+
if (!this.leaseStore.enabled) return this.nativeRing.retain(frameId, frame);
|
|
888
|
+
if (frame.isHwFrame()) {
|
|
889
|
+
const software = this.downloadToSoftware(frame);
|
|
890
|
+
frame.free();
|
|
891
|
+
if (!software) return true;
|
|
892
|
+
this.leaseStore.put(frameId, toLeasedNavFrame(software));
|
|
893
|
+
return true;
|
|
894
|
+
}
|
|
895
|
+
this.leaseStore.put(frameId, toLeasedNavFrame(frame));
|
|
896
|
+
return true;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Download a HW surface to a NATIVE-resolution software YUV420P frame via the
|
|
900
|
+
* dedicated full-frame download filtergraph. Returns the software frame (owned
|
|
901
|
+
* by the caller) or `null` on any failure (the lease is then simply skipped).
|
|
902
|
+
* `processAllSync` only refs the input surface, so the caller frees it after.
|
|
903
|
+
*/
|
|
904
|
+
downloadToSoftware(frame) {
|
|
905
|
+
const nav = this.nav;
|
|
906
|
+
const scaleFilter = this.gpuScaleFilter;
|
|
907
|
+
if (!nav || !scaleFilter || !this.hwContext) return null;
|
|
908
|
+
try {
|
|
909
|
+
const outputs = this.ensureLeaseDownloadFilter(nav, scaleFilter, frame.width, frame.height).processAllSync(frame);
|
|
910
|
+
const first = outputs[0] ?? null;
|
|
911
|
+
for (let i = 1; i < outputs.length; i++) outputs[i]?.free();
|
|
912
|
+
return first;
|
|
913
|
+
} catch (err) {
|
|
914
|
+
this.emitStderr(`decode-worker-child: native lease download failed — ${errMessage(err)}\n`);
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
/** Build-once / reuse the full-frame GPU→system YUV420P download filtergraph. */
|
|
919
|
+
ensureLeaseDownloadFilter(nav, scaleFilter, srcW, srcH) {
|
|
920
|
+
const key = `${srcW}x${srcH}`;
|
|
921
|
+
const cached = this.nativeLeaseDownloadFilter;
|
|
922
|
+
if (cached && key === this.nativeLeaseDownloadFilterKey) return cached;
|
|
923
|
+
this.nativeLeaseDownloadFilter?.close();
|
|
924
|
+
this.nativeLeaseDownloadFilter = null;
|
|
925
|
+
this.nativeLeaseDownloadFilterKey = "";
|
|
926
|
+
const description = `${scaleFilter}=w=iw:h=ih,hwdownload,format=nv12,format=yuv420p`;
|
|
927
|
+
const filter = nav.FilterAPI.create(description, { hardware: this.hwContext });
|
|
928
|
+
this.nativeLeaseDownloadFilter = filter;
|
|
929
|
+
this.nativeLeaseDownloadFilterKey = key;
|
|
930
|
+
return filter;
|
|
931
|
+
}
|
|
717
932
|
/** Map a normalized bbox to an even-aligned pixel crop + (optionally capped) target. */
|
|
718
933
|
resolveNativeCrop(frame, bbox, maxWidth) {
|
|
719
934
|
return resolveNativeCropGeometry(frame.width, frame.height, bbox, maxWidth);
|
|
@@ -1168,6 +1383,10 @@ var DecodeWorkerChild = class {
|
|
|
1168
1383
|
*/
|
|
1169
1384
|
closeInput() {
|
|
1170
1385
|
this.nativeRing.clear();
|
|
1386
|
+
this.leaseStore.clear();
|
|
1387
|
+
this.nativeLeaseDownloadFilter?.close();
|
|
1388
|
+
this.nativeLeaseDownloadFilter = null;
|
|
1389
|
+
this.nativeLeaseDownloadFilterKey = "";
|
|
1171
1390
|
this.nativeCropHwFilter?.close();
|
|
1172
1391
|
this.nativeCropHwFilter = null;
|
|
1173
1392
|
this.nativeCropHwFilterKey = "";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as isWorkerRequest } from "../worker-protocol-
|
|
1
|
+
import { n as isWorkerRequest } from "../worker-protocol-CyVJTZEO.mjs";
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -233,6 +233,99 @@ var NativeFrameRing = class {
|
|
|
233
233
|
}
|
|
234
234
|
};
|
|
235
235
|
//#endregion
|
|
236
|
+
//#region src/session-decode/native-lease-store.ts
|
|
237
|
+
/**
|
|
238
|
+
* A hard-capped-by-bytes, TTL-and-release map from worker frameId → retained
|
|
239
|
+
* RAM frame. Insertion-ordered (`Map`), so budget eviction is FIFO by age.
|
|
240
|
+
*/
|
|
241
|
+
var NativeLeaseStore = class {
|
|
242
|
+
entries = /* @__PURE__ */ new Map();
|
|
243
|
+
budgetBytes;
|
|
244
|
+
ttlMs;
|
|
245
|
+
now;
|
|
246
|
+
bytes = 0;
|
|
247
|
+
constructor(options) {
|
|
248
|
+
this.budgetBytes = options.budgetBytes;
|
|
249
|
+
this.ttlMs = options.ttlMs;
|
|
250
|
+
this.now = options.now ?? Date.now;
|
|
251
|
+
}
|
|
252
|
+
/** `false` when the store is disabled (`budgetBytes <= 0`). */
|
|
253
|
+
get enabled() {
|
|
254
|
+
return this.budgetBytes > 0;
|
|
255
|
+
}
|
|
256
|
+
/** Number of retained leases (tests / metrics). */
|
|
257
|
+
get size() {
|
|
258
|
+
return this.entries.size;
|
|
259
|
+
}
|
|
260
|
+
/** Total resident bytes across all retained leases (tests / metrics). */
|
|
261
|
+
get totalBytes() {
|
|
262
|
+
return this.bytes;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Take ownership of `frame` under `frameId`. Returns `true` when the store
|
|
266
|
+
* took ownership (caller MUST NOT free), `false` when the store is disabled
|
|
267
|
+
* (`budgetBytes <= 0`) and the frame was freed here — the caller then keeps
|
|
268
|
+
* today's GPU-surface-ring path. Sweeps expired leases first, then evicts the
|
|
269
|
+
* oldest while the total exceeds the budget.
|
|
270
|
+
*/
|
|
271
|
+
put(frameId, frame) {
|
|
272
|
+
if (!this.enabled) {
|
|
273
|
+
frame.free();
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
this.drop(frameId);
|
|
277
|
+
this.sweepExpired();
|
|
278
|
+
this.entries.set(frameId, {
|
|
279
|
+
frame,
|
|
280
|
+
insertedAt: this.now()
|
|
281
|
+
});
|
|
282
|
+
this.bytes += frame.byteLength;
|
|
283
|
+
while (this.bytes > this.budgetBytes && this.entries.size > 0) {
|
|
284
|
+
const oldest = this.entries.keys().next().value;
|
|
285
|
+
if (oldest === void 0) break;
|
|
286
|
+
this.drop(oldest);
|
|
287
|
+
}
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The retained frame for `frameId`, or `null` if never leased, already
|
|
292
|
+
* released/evicted, OR aged past its TTL (an expired lease is freed + dropped
|
|
293
|
+
* here so a caller that reads it just after expiry does not resurrect it).
|
|
294
|
+
*/
|
|
295
|
+
get(frameId) {
|
|
296
|
+
const entry = this.entries.get(frameId);
|
|
297
|
+
if (!entry) return null;
|
|
298
|
+
if (this.now() - entry.insertedAt > this.ttlMs) {
|
|
299
|
+
this.drop(frameId);
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
return entry.frame;
|
|
303
|
+
}
|
|
304
|
+
/** Free + drop the lease for `frameId` (the prompt release path). Idempotent. */
|
|
305
|
+
release(frameId) {
|
|
306
|
+
this.drop(frameId);
|
|
307
|
+
}
|
|
308
|
+
/** Free + drop every lease older than the TTL. */
|
|
309
|
+
sweepExpired() {
|
|
310
|
+
const cutoff = this.now() - this.ttlMs;
|
|
311
|
+
for (const [frameId, entry] of this.entries) if (entry.insertedAt < cutoff) this.drop(frameId);
|
|
312
|
+
}
|
|
313
|
+
/** Free + drop every retained lease. Idempotent (re-dial + teardown call it). */
|
|
314
|
+
clear() {
|
|
315
|
+
for (const entry of this.entries.values()) entry.frame.free();
|
|
316
|
+
this.entries.clear();
|
|
317
|
+
this.bytes = 0;
|
|
318
|
+
}
|
|
319
|
+
/** Free + remove one entry, keeping the byte tally exact. */
|
|
320
|
+
drop(frameId) {
|
|
321
|
+
const entry = this.entries.get(frameId);
|
|
322
|
+
if (!entry) return;
|
|
323
|
+
this.entries.delete(frameId);
|
|
324
|
+
this.bytes -= entry.frame.byteLength;
|
|
325
|
+
entry.frame.free();
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
//#endregion
|
|
236
329
|
//#region src/session-decode/decode-worker-child.ts
|
|
237
330
|
/**
|
|
238
331
|
* Decode-worker CHILD entry point — Epic C P1, Task 2.
|
|
@@ -258,6 +351,14 @@ var NativeFrameRing = class {
|
|
|
258
351
|
* NOT `$process`)". Raw `process.on('message')` / `process.send` are the
|
|
259
352
|
* correct, sanctioned mechanism for this specific file only.
|
|
260
353
|
*/
|
|
354
|
+
/** Wrap a software YUV420P frame as a lease entry (byte size ≈ w×h×1.5). */
|
|
355
|
+
function toLeasedNavFrame(frame) {
|
|
356
|
+
return {
|
|
357
|
+
frame,
|
|
358
|
+
byteLength: Math.ceil(frame.width * frame.height * 1.5),
|
|
359
|
+
free: () => frame.free()
|
|
360
|
+
};
|
|
361
|
+
}
|
|
261
362
|
/** Re-dial backoff on a transient dial error/EOF (mirrors the decoder's PULL_REDIAL_MS). */
|
|
262
363
|
var REDIAL_MS = 3e3;
|
|
263
364
|
/** How often the worker emits its `framesDecoded/framesSkipped/deliveredFps` line. */
|
|
@@ -277,6 +378,32 @@ var NATIVE_RING_CAP = (() => {
|
|
|
277
378
|
return Math.min(4, Math.max(0, Math.floor(raw)));
|
|
278
379
|
})();
|
|
279
380
|
/**
|
|
381
|
+
* Hard RAM budget (bytes) for the native-frame LEASE store — the primary
|
|
382
|
+
* native-crop survival window. Unlike {@link NATIVE_RING_CAP} (GPU surfaces,
|
|
383
|
+
* leak-prone → tiny), a lease is a downloaded RAM copy, so the window is sized
|
|
384
|
+
* by memory, not a 2-frame count, and the late cross-process crop reliably
|
|
385
|
+
* hits. The primary eviction is the {@link NATIVE_LEASE_TTL_MS} TTL — a 64MB
|
|
386
|
+
* budget held only ~5 native 4K frames (~0.2s), which the late crop still
|
|
387
|
+
* outran; the budget is now a HIGH safety ceiling (default 1024MB) so the TTL
|
|
388
|
+
* is the effective cap and the ~40-200ms crop reliably lands within it.
|
|
389
|
+
* `CAMSTACK_SESSION_NATIVE_LEASE_BUDGET_MB` (default 1024); `0` DISABLES the
|
|
390
|
+
* lease and falls back to the {@link NATIVE_RING_CAP} GPU ring (today's path).
|
|
391
|
+
*/
|
|
392
|
+
var NATIVE_LEASE_BUDGET_BYTES = (() => {
|
|
393
|
+
const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_LEASE_BUDGET_MB"]);
|
|
394
|
+
return (Number.isFinite(raw) && raw >= 0 ? Math.floor(raw) : 1024) * 1024 * 1024;
|
|
395
|
+
})();
|
|
396
|
+
/**
|
|
397
|
+
* TTL (ms) after which a native-frame lease is treated as a miss and reclaimed.
|
|
398
|
+
* The backstop that bounds in-flight RAM even if the explicit `releaseNativeLease`
|
|
399
|
+
* is dropped — comfortably longer than the detection inference + crop round-trip
|
|
400
|
+
* (~40-200ms). `CAMSTACK_SESSION_NATIVE_LEASE_TTL_MS` (default 500).
|
|
401
|
+
*/
|
|
402
|
+
var NATIVE_LEASE_TTL_MS = (() => {
|
|
403
|
+
const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_LEASE_TTL_MS"]);
|
|
404
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 500;
|
|
405
|
+
})();
|
|
406
|
+
/**
|
|
280
407
|
* The libav GPU scale filter for a hwaccel backend, or `null` when none is
|
|
281
408
|
* known here — those backends fall back to the software crop+scale path.
|
|
282
409
|
* Mirrors `addon-decoder-ffmpeg/src/ffmpeg-args.ts` `gpuScaleFilterForBackend`.
|
|
@@ -516,6 +643,29 @@ var DecodeWorkerChild = class {
|
|
|
516
643
|
* re-dial + teardown (see {@link closeInput}).
|
|
517
644
|
*/
|
|
518
645
|
nativeRing = new NativeFrameRing(NATIVE_RING_CAP);
|
|
646
|
+
/**
|
|
647
|
+
* PRIMARY native-frame survival window — a bounded RAM copy of each shipped
|
|
648
|
+
* detection frame, keyed by frameId, held until release / TTL / budget
|
|
649
|
+
* eviction. Replaces the tiny GPU {@link nativeRing} as the default (the ring
|
|
650
|
+
* is used only when the lease is disabled via a 0 budget). Because it holds
|
|
651
|
+
* DOWNLOADED (system-memory) frames, retaining a large window has no VAAPI
|
|
652
|
+
* surface-pool starvation risk. Cleared on every re-dial + teardown.
|
|
653
|
+
*/
|
|
654
|
+
leaseStore = new NativeLeaseStore({
|
|
655
|
+
budgetBytes: NATIVE_LEASE_BUDGET_BYTES,
|
|
656
|
+
ttlMs: NATIVE_LEASE_TTL_MS
|
|
657
|
+
});
|
|
658
|
+
/**
|
|
659
|
+
* DEDICATED full-frame GPU→system download filtergraph
|
|
660
|
+
* (`scale_<be>=iw:ih,hwdownload,format=nv12,format=yuv420p`) used ONLY to
|
|
661
|
+
* materialize a HW surface into a leasable software frame at NATIVE
|
|
662
|
+
* resolution (no scale). Kept separate from the detection/native-crop filters
|
|
663
|
+
* (different geometry). Same dispose discipline: closed on every re-dial
|
|
664
|
+
* ({@link closeInput}) + teardown, since it references the decoder's per-dial
|
|
665
|
+
* `hw_frames_ctx`.
|
|
666
|
+
*/
|
|
667
|
+
nativeLeaseDownloadFilter = null;
|
|
668
|
+
nativeLeaseDownloadFilterKey = "";
|
|
519
669
|
/** Throttled native-crop hit/miss counters, emitted with the throughput line. */
|
|
520
670
|
nativeCropHits = 0;
|
|
521
671
|
nativeCropMisses = 0;
|
|
@@ -551,7 +701,7 @@ var DecodeWorkerChild = class {
|
|
|
551
701
|
* the reserve-on-pull invariant (`e3c78a9b`) is unit-tested without
|
|
552
702
|
* node-av. See that file's doc comment for the full semantics.
|
|
553
703
|
*/
|
|
554
|
-
frames = new FrameSlot({ retainSuperseded: (frameId, frame) => this.
|
|
704
|
+
frames = new FrameSlot({ retainSuperseded: (frameId, frame) => this.captureNativeFrame(frameId, frame) });
|
|
555
705
|
/**
|
|
556
706
|
* Mirrors whether `this.frames` currently has a pull waiting, so
|
|
557
707
|
* `teardown` knows to resolve it with `{kind:'ended'}` — `FrameSlot`
|
|
@@ -580,6 +730,9 @@ var DecodeWorkerChild = class {
|
|
|
580
730
|
case "nativeCrop":
|
|
581
731
|
this.handleNativeCrop(message.requestId, message.frameId, message.bbox, message.maxWidth);
|
|
582
732
|
return;
|
|
733
|
+
case "releaseNativeLease":
|
|
734
|
+
this.leaseStore.release(message.frameId);
|
|
735
|
+
return;
|
|
583
736
|
case "stop":
|
|
584
737
|
this.handleStop();
|
|
585
738
|
return;
|
|
@@ -681,7 +834,7 @@ var DecodeWorkerChild = class {
|
|
|
681
834
|
* session to software on a GPU-filter error).
|
|
682
835
|
*/
|
|
683
836
|
handleNativeCrop(requestId, frameId, bbox, maxWidth) {
|
|
684
|
-
const frame = this.
|
|
837
|
+
const frame = this.leaseStore.get(frameId)?.frame ?? this.resolveRetainedFrame(frameId);
|
|
685
838
|
if (!frame) {
|
|
686
839
|
this.nativeCropMisses++;
|
|
687
840
|
this.send({
|
|
@@ -710,6 +863,68 @@ var DecodeWorkerChild = class {
|
|
|
710
863
|
});
|
|
711
864
|
}
|
|
712
865
|
}
|
|
866
|
+
/** The GPU-ring or reserved-slot frame for `frameId` (lease-disabled / newest paths). */
|
|
867
|
+
resolveRetainedFrame(frameId) {
|
|
868
|
+
return this.nativeRing.get(frameId) ?? this.frames.toBuffer(frameId);
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Retain a just-superseded DELIVERED frame for later native crops. Transfers
|
|
872
|
+
* ownership from the {@link FrameSlot} (the caller MUST NOT free when this
|
|
873
|
+
* returns `true`).
|
|
874
|
+
*
|
|
875
|
+
* - Lease ENABLED (default): materialize a SOFTWARE RAM copy and put it in the
|
|
876
|
+
* {@link leaseStore}. A HW surface is downloaded to system memory ONCE here
|
|
877
|
+
* (freeing the GPU surface immediately — no VAAPI-pool pin); a software frame
|
|
878
|
+
* is leased directly. A download failure still consumes ownership (freed
|
|
879
|
+
* here) so the slot never double-frees — the crop simply misses → fallback.
|
|
880
|
+
* - Lease DISABLED (`budget 0`): fall back to today's tiny GPU {@link nativeRing}.
|
|
881
|
+
*/
|
|
882
|
+
captureNativeFrame(frameId, frame) {
|
|
883
|
+
if (!this.leaseStore.enabled) return this.nativeRing.retain(frameId, frame);
|
|
884
|
+
if (frame.isHwFrame()) {
|
|
885
|
+
const software = this.downloadToSoftware(frame);
|
|
886
|
+
frame.free();
|
|
887
|
+
if (!software) return true;
|
|
888
|
+
this.leaseStore.put(frameId, toLeasedNavFrame(software));
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
this.leaseStore.put(frameId, toLeasedNavFrame(frame));
|
|
892
|
+
return true;
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Download a HW surface to a NATIVE-resolution software YUV420P frame via the
|
|
896
|
+
* dedicated full-frame download filtergraph. Returns the software frame (owned
|
|
897
|
+
* by the caller) or `null` on any failure (the lease is then simply skipped).
|
|
898
|
+
* `processAllSync` only refs the input surface, so the caller frees it after.
|
|
899
|
+
*/
|
|
900
|
+
downloadToSoftware(frame) {
|
|
901
|
+
const nav = this.nav;
|
|
902
|
+
const scaleFilter = this.gpuScaleFilter;
|
|
903
|
+
if (!nav || !scaleFilter || !this.hwContext) return null;
|
|
904
|
+
try {
|
|
905
|
+
const outputs = this.ensureLeaseDownloadFilter(nav, scaleFilter, frame.width, frame.height).processAllSync(frame);
|
|
906
|
+
const first = outputs[0] ?? null;
|
|
907
|
+
for (let i = 1; i < outputs.length; i++) outputs[i]?.free();
|
|
908
|
+
return first;
|
|
909
|
+
} catch (err) {
|
|
910
|
+
this.emitStderr(`decode-worker-child: native lease download failed — ${errMessage(err)}\n`);
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
/** Build-once / reuse the full-frame GPU→system YUV420P download filtergraph. */
|
|
915
|
+
ensureLeaseDownloadFilter(nav, scaleFilter, srcW, srcH) {
|
|
916
|
+
const key = `${srcW}x${srcH}`;
|
|
917
|
+
const cached = this.nativeLeaseDownloadFilter;
|
|
918
|
+
if (cached && key === this.nativeLeaseDownloadFilterKey) return cached;
|
|
919
|
+
this.nativeLeaseDownloadFilter?.close();
|
|
920
|
+
this.nativeLeaseDownloadFilter = null;
|
|
921
|
+
this.nativeLeaseDownloadFilterKey = "";
|
|
922
|
+
const description = `${scaleFilter}=w=iw:h=ih,hwdownload,format=nv12,format=yuv420p`;
|
|
923
|
+
const filter = nav.FilterAPI.create(description, { hardware: this.hwContext });
|
|
924
|
+
this.nativeLeaseDownloadFilter = filter;
|
|
925
|
+
this.nativeLeaseDownloadFilterKey = key;
|
|
926
|
+
return filter;
|
|
927
|
+
}
|
|
713
928
|
/** Map a normalized bbox to an even-aligned pixel crop + (optionally capped) target. */
|
|
714
929
|
resolveNativeCrop(frame, bbox, maxWidth) {
|
|
715
930
|
return resolveNativeCropGeometry(frame.width, frame.height, bbox, maxWidth);
|
|
@@ -1164,6 +1379,10 @@ var DecodeWorkerChild = class {
|
|
|
1164
1379
|
*/
|
|
1165
1380
|
closeInput() {
|
|
1166
1381
|
this.nativeRing.clear();
|
|
1382
|
+
this.leaseStore.clear();
|
|
1383
|
+
this.nativeLeaseDownloadFilter?.close();
|
|
1384
|
+
this.nativeLeaseDownloadFilter = null;
|
|
1385
|
+
this.nativeLeaseDownloadFilterKey = "";
|
|
1167
1386
|
this.nativeCropHwFilter?.close();
|
|
1168
1387
|
this.nativeCropHwFilter = null;
|
|
1169
1388
|
this.nativeCropHwFilterKey = "";
|