@camstack/addon-pipeline 1.2.295 → 1.2.296
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/THIRD_PARTY_MODELS.md +8 -0
- package/dist/audio-analyzer/index.js +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/{default-detection-model-CAUBVbgK.mjs → default-detection-model-Co578D8C.mjs} +75 -46
- package/dist/{default-detection-model-wNEISVA9.js → default-detection-model-D1daTtqT.js} +75 -46
- package/dist/detection-pipeline/index.js +1023 -80
- package/dist/detection-pipeline/index.mjs +1023 -80
- package/dist/{dist-BsVcf5wO.js → dist-8up-f2TX.js} +877 -365
- package/dist/{dist-CuxSNLKW.mjs → dist-CCd0Q3nr.mjs} +872 -366
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/{node-Cqb1QiXk.mjs → node-DgMSXSWP.mjs} +1 -1
- package/dist/{node-UFk6I2f6.js → node-lpQgHes9.js} +1 -1
- package/dist/pipeline-runner/index.js +789 -203
- package/dist/pipeline-runner/index.mjs +789 -203
- package/dist/{process-memory-CJV29sPf.js → process-memory-CX_92V_r.js} +1 -1
- package/dist/{process-memory-D0Nvs9rr.mjs → process-memory-DFC_O5zE.mjs} +1 -1
- package/dist/recorder/index.js +4 -6
- package/dist/recorder/index.mjs +4 -6
- package/dist/{segment-demux-js-DGwmf5hu.js → segment-demux-js-DzBx6NN2.js} +1 -1
- package/dist/{segment-demux-js-DIDWw1iE.mjs → segment-demux-js-FZbBuk3F.mjs} +1 -1
- package/dist/session-decode/{decode-worker-child.js → decode-worker-main.js} +481 -72
- package/dist/session-decode/{decode-worker-child.mjs → decode-worker-main.mjs} +482 -71
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B7nBFqva.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C_i7oFBl.mjs} +2 -2
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DUGQKsKL.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-CkbplMHA.mjs +26 -0
- package/dist/stream-broker/demux-worker-child.js +1 -1
- package/dist/stream-broker/demux-worker-child.mjs +1 -1
- package/dist/stream-broker/{hostInit-BKlb3qac.mjs → hostInit-BPtppL3W.mjs} +2 -2
- package/dist/stream-broker/index.js +4 -4
- package/dist/stream-broker/index.mjs +4 -4
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/dist/{worker-protocol-B2MfQLlu.js → worker-protocol-C-G8qmye.js} +3 -1
- package/dist/{worker-protocol-C_W-P_g-.mjs → worker-protocol-D_NzPcnh.mjs} +3 -1
- package/package.json +1 -1
- package/python/inference_pool.py +422 -64
- package/python/test_inference_pool_compile_off_loop.py +414 -0
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CCIyBvRa.mjs +0 -26
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DeD_UFdb.mjs +0 -26
|
@@ -1,6 +1,73 @@
|
|
|
1
|
-
|
|
2
|
-
const require_worker_protocol = require("../worker-protocol-B2MfQLlu.js");
|
|
1
|
+
const require_worker_protocol = require("../worker-protocol-C-G8qmye.js");
|
|
3
2
|
const require_lazy_sharp = require("../lazy-sharp-BuwKlQLR.js");
|
|
3
|
+
var OutboundSendTracker = class {
|
|
4
|
+
inFlight = 0;
|
|
5
|
+
idleWaiters = [];
|
|
6
|
+
/** A send left for the channel; call the returned function from its callback. */
|
|
7
|
+
begin() {
|
|
8
|
+
this.inFlight += 1;
|
|
9
|
+
let done = false;
|
|
10
|
+
return () => {
|
|
11
|
+
if (done) return;
|
|
12
|
+
done = true;
|
|
13
|
+
this.inFlight = Math.max(0, this.inFlight - 1);
|
|
14
|
+
if (this.inFlight > 0) return;
|
|
15
|
+
const waiters = this.idleWaiters;
|
|
16
|
+
this.idleWaiters = [];
|
|
17
|
+
for (const resolve of waiters) resolve();
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
get pending() {
|
|
21
|
+
return this.inFlight;
|
|
22
|
+
}
|
|
23
|
+
/** Resolves when nothing is in flight, or after `boundMs` — whichever is first. */
|
|
24
|
+
whenIdle(boundMs) {
|
|
25
|
+
if (this.inFlight === 0) return Promise.resolve("idle");
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
const timer = setTimeout(() => resolve("bound"), boundMs);
|
|
28
|
+
this.idleWaiters.push(() => {
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
resolve("idle");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
function createWorkerExit(deps) {
|
|
36
|
+
let exiting = false;
|
|
37
|
+
return (code) => {
|
|
38
|
+
if (exiting) return;
|
|
39
|
+
exiting = true;
|
|
40
|
+
let exitCode = code;
|
|
41
|
+
try {
|
|
42
|
+
deps.steps.quiesce();
|
|
43
|
+
} catch {
|
|
44
|
+
exitCode = 1;
|
|
45
|
+
} finally {
|
|
46
|
+
try {
|
|
47
|
+
deps.steps.flushFinalLines();
|
|
48
|
+
} catch {
|
|
49
|
+
exitCode = 1;
|
|
50
|
+
} finally {
|
|
51
|
+
deps.sends.whenIdle(deps.boundMs ?? 250).then(() => deps.exit(exitCode));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Route every event that ends the worker through the one bounded exit:
|
|
58
|
+
* SIGTERM (the parent's `kill()`) and a lost channel exit 0, an uncaught
|
|
59
|
+
* error is reported and exits 1. Before D652 there was no SIGTERM handler and
|
|
60
|
+
* a `kill()` ended the process with its last lines still queued.
|
|
61
|
+
*/
|
|
62
|
+
function wireWorkerExit(deps) {
|
|
63
|
+
deps.on("SIGTERM", () => deps.exitWorker(0));
|
|
64
|
+
deps.on("disconnect", () => deps.exitWorker(0));
|
|
65
|
+
deps.on("uncaughtException", (err) => {
|
|
66
|
+
deps.reportUncaught(err);
|
|
67
|
+
deps.exitWorker(1);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
4
71
|
//#region src/session-decode/color-conversion.ts
|
|
5
72
|
/**
|
|
6
73
|
* Explicit YUV→RGB colorspace/range resolution for the session-decode worker.
|
|
@@ -302,6 +369,18 @@ var FrameSlot = class {
|
|
|
302
369
|
if (!this.reserved || this.reserved.frameId !== frameId) return null;
|
|
303
370
|
return this.reserved.frame;
|
|
304
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Free only the UNDELIVERED slot frame, keeping the reserved one (D652 drain):
|
|
374
|
+
* after a drain no `pull` will ever take the slot frame, while the reserved
|
|
375
|
+
* frame is one the parent may still read. Returns whether a frame was freed.
|
|
376
|
+
*/
|
|
377
|
+
freeUndelivered() {
|
|
378
|
+
const held = this.slot;
|
|
379
|
+
if (!held) return false;
|
|
380
|
+
held.frame.free();
|
|
381
|
+
this.slot = null;
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
305
384
|
/** Free both the slot and the reserved frame (teardown). */
|
|
306
385
|
free() {
|
|
307
386
|
this.slot?.frame.free();
|
|
@@ -615,6 +694,7 @@ var NativeLeaseStore = class {
|
|
|
615
694
|
budgetBytes;
|
|
616
695
|
maxFrames;
|
|
617
696
|
now;
|
|
697
|
+
onEvict;
|
|
618
698
|
bytes = 0;
|
|
619
699
|
/**
|
|
620
700
|
* Frames dropped because the hold bound or the byte ceiling was reached
|
|
@@ -627,6 +707,7 @@ var NativeLeaseStore = class {
|
|
|
627
707
|
this.budgetBytes = options.budgetBytes;
|
|
628
708
|
this.maxFrames = Math.max(1, options.maxFrames);
|
|
629
709
|
this.now = options.now ?? Date.now;
|
|
710
|
+
this.onEvict = options.onEvict;
|
|
630
711
|
}
|
|
631
712
|
/** Frames dropped by a bound rather than released (tests / metrics). */
|
|
632
713
|
get overflowCount() {
|
|
@@ -668,6 +749,7 @@ var NativeLeaseStore = class {
|
|
|
668
749
|
if (oldest === frameId) break;
|
|
669
750
|
this.overflowed += 1;
|
|
670
751
|
this.drop(oldest);
|
|
752
|
+
this.onEvict?.(oldest);
|
|
671
753
|
}
|
|
672
754
|
return true;
|
|
673
755
|
}
|
|
@@ -1351,6 +1433,40 @@ function planPreRollBurst(input) {
|
|
|
1351
1433
|
};
|
|
1352
1434
|
}
|
|
1353
1435
|
/**
|
|
1436
|
+
* Fit a burst inside the hold its frames live in (D652), without shrinking the
|
|
1437
|
+
* history it covers.
|
|
1438
|
+
*
|
|
1439
|
+
* Every pre-roll frame the runner admits is a lazy ref: its pixels are a frame
|
|
1440
|
+
* the worker HOLDS until inference reads it, and the hold keeps
|
|
1441
|
+
* `holdFrames`. A burst of 12 into a hold of 8, read oldest-first by the lane,
|
|
1442
|
+
* evicts its own oldest frames before inference reaches them — 54 of 55 live
|
|
1443
|
+
* `toBuffer miss … released/hold-full` lines named a frameId ≤ 12. So the burst
|
|
1444
|
+
* sends at most `holdFrames - 1` frames (the ceiling D367 already gives the
|
|
1445
|
+
* runner's in-flight frames, leaving the newest slot to the first live frame).
|
|
1446
|
+
*
|
|
1447
|
+
* The REACH is kept: the window handed to the planner is still
|
|
1448
|
+
* `requestedFrames × cadence`, so the capped frames are spread across the same
|
|
1449
|
+
* span of media the operator asked for (D382 — a consumer is served the history
|
|
1450
|
+
* it asked for), sampled less densely. The alternative, raising the hold for the
|
|
1451
|
+
* burst, would pin up to `requestedFrames - holdFrames + 1` extra native rasters
|
|
1452
|
+
* (~24 MB each at 4K) on every session open.
|
|
1453
|
+
*/
|
|
1454
|
+
function planPreRollWithinHold(input) {
|
|
1455
|
+
const requested = Number.isFinite(input.requestedFrames) && input.requestedFrames > 0 ? Math.floor(input.requestedFrames) : 0;
|
|
1456
|
+
const holdCeiling = Math.max(1, Math.floor(input.holdFrames) - 1);
|
|
1457
|
+
const budgetFrames = input.holdEnabled ? Math.min(requested, holdCeiling) : requested;
|
|
1458
|
+
const cadenceMs = Number.isFinite(input.cadenceMs) && input.cadenceMs > 0 ? input.cadenceMs : 1e3;
|
|
1459
|
+
return {
|
|
1460
|
+
budgetFrames,
|
|
1461
|
+
holdCapped: budgetFrames < requested,
|
|
1462
|
+
plan: planPreRollBurst({
|
|
1463
|
+
spanMs: requested * cadenceMs,
|
|
1464
|
+
detectionFps: 1e3 / cadenceMs,
|
|
1465
|
+
maxFrames: budgetFrames
|
|
1466
|
+
})
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1354
1470
|
* The live, per-session budget. Stateful by necessity — a stride is a decision
|
|
1355
1471
|
* about the PREVIOUS admission — and deliberately free of any clock of its own:
|
|
1356
1472
|
* both times are passed in, so a test can drive a catch-up burst without
|
|
@@ -1444,6 +1560,121 @@ var PreRollBurst = class {
|
|
|
1444
1560
|
}
|
|
1445
1561
|
};
|
|
1446
1562
|
//#endregion
|
|
1563
|
+
//#region src/session-decode/pre-roll-hold-ledger.ts
|
|
1564
|
+
var PreRollSettleTracker = class {
|
|
1565
|
+
pending = /* @__PURE__ */ new Set();
|
|
1566
|
+
read = 0;
|
|
1567
|
+
released = 0;
|
|
1568
|
+
lost = {
|
|
1569
|
+
evicted: 0,
|
|
1570
|
+
cleared: 0,
|
|
1571
|
+
"download-failed": 0,
|
|
1572
|
+
miss: 0
|
|
1573
|
+
};
|
|
1574
|
+
burstEnded = false;
|
|
1575
|
+
emitted = false;
|
|
1576
|
+
/** A pre-roll frame was delivered to the runner; inference has not read it yet. */
|
|
1577
|
+
admit(frameId) {
|
|
1578
|
+
if (this.emitted) return;
|
|
1579
|
+
this.pending.add(frameId);
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* One frame's fate. Only the FIRST outcome of a delivered pre-roll frame
|
|
1583
|
+
* counts: a miss that follows an eviction is the same loss, not two. Returns
|
|
1584
|
+
* the summary to send when this settle was the last one owed after the burst
|
|
1585
|
+
* ended, else `null`.
|
|
1586
|
+
*/
|
|
1587
|
+
settle(frameId, outcome) {
|
|
1588
|
+
if (!this.pending.delete(frameId)) return null;
|
|
1589
|
+
if (outcome === "read") this.read += 1;
|
|
1590
|
+
else if (outcome === "released") this.released += 1;
|
|
1591
|
+
else this.lost[outcome] += 1;
|
|
1592
|
+
return this.summaryIfSettled();
|
|
1593
|
+
}
|
|
1594
|
+
/** Every frame still pending is lost the same way at once (a re-dial clearing the hold). */
|
|
1595
|
+
settleAll(outcome) {
|
|
1596
|
+
let summary = null;
|
|
1597
|
+
for (const frameId of [...this.pending]) summary = this.settle(frameId, outcome) ?? summary;
|
|
1598
|
+
return summary;
|
|
1599
|
+
}
|
|
1600
|
+
/** The burst is over: from now on the summary goes out when nothing is owed. */
|
|
1601
|
+
endBurst() {
|
|
1602
|
+
this.burstEnded = true;
|
|
1603
|
+
return this.summaryIfSettled();
|
|
1604
|
+
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Teardown: send what is known now, `unsettled` included. `null` when there
|
|
1607
|
+
* was no burst or the summary already went out.
|
|
1608
|
+
*/
|
|
1609
|
+
flush() {
|
|
1610
|
+
if (this.emitted || !this.burstEnded) return null;
|
|
1611
|
+
return this.emit();
|
|
1612
|
+
}
|
|
1613
|
+
summaryIfSettled() {
|
|
1614
|
+
if (this.emitted || !this.burstEnded || this.pending.size > 0) return null;
|
|
1615
|
+
return this.emit();
|
|
1616
|
+
}
|
|
1617
|
+
emit() {
|
|
1618
|
+
this.emitted = true;
|
|
1619
|
+
return {
|
|
1620
|
+
read: this.read,
|
|
1621
|
+
released: this.released,
|
|
1622
|
+
lostByReason: { ...this.lost },
|
|
1623
|
+
unsettled: this.pending.size
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
//#endregion
|
|
1628
|
+
//#region src/session-decode/native-disposer.ts
|
|
1629
|
+
var NativeDisposer = class {
|
|
1630
|
+
onRefused;
|
|
1631
|
+
refusing = false;
|
|
1632
|
+
refused = 0;
|
|
1633
|
+
constructor(onRefused) {
|
|
1634
|
+
this.onRefused = onRefused;
|
|
1635
|
+
}
|
|
1636
|
+
/** From now on nothing native is disposed (the worker is exiting). Idempotent. */
|
|
1637
|
+
refuseFromNowOn() {
|
|
1638
|
+
this.refusing = true;
|
|
1639
|
+
}
|
|
1640
|
+
get isRefusing() {
|
|
1641
|
+
return this.refusing;
|
|
1642
|
+
}
|
|
1643
|
+
/** Disposes refused since {@link refuseFromNowOn} — diagnostics and tests. */
|
|
1644
|
+
get refusedCount() {
|
|
1645
|
+
return this.refused;
|
|
1646
|
+
}
|
|
1647
|
+
/**
|
|
1648
|
+
* Run `dispose` unless the worker is exiting. `what` names the object for the
|
|
1649
|
+
* refusal line. Returns whether it ran — a caller that dropped its reference
|
|
1650
|
+
* either way loses nothing: an unrun dispose is reclaimed by process exit.
|
|
1651
|
+
*/
|
|
1652
|
+
run(what, dispose) {
|
|
1653
|
+
if (this.refusing) {
|
|
1654
|
+
this.refused += 1;
|
|
1655
|
+
this.onRefused?.(what);
|
|
1656
|
+
return false;
|
|
1657
|
+
}
|
|
1658
|
+
dispose();
|
|
1659
|
+
return true;
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
//#endregion
|
|
1663
|
+
//#region src/session-decode/decode-loop-policy.ts
|
|
1664
|
+
function inputCloseAfterLoop(state) {
|
|
1665
|
+
if (state.exiting) return "none";
|
|
1666
|
+
if (state.draining) return "demuxer-only";
|
|
1667
|
+
return "all";
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* The frame decoded after a drain/stop is nobody's: free it — unless the
|
|
1671
|
+
* worker is exiting, where exit reclaims it and no native call is made.
|
|
1672
|
+
*/
|
|
1673
|
+
function releaseFrameAfterStop(frame, state) {
|
|
1674
|
+
if (state.exiting || frame === null) return;
|
|
1675
|
+
frame.free();
|
|
1676
|
+
}
|
|
1677
|
+
//#endregion
|
|
1447
1678
|
//#region src/session-decode/pull-demuxer-open.ts
|
|
1448
1679
|
/**
|
|
1449
1680
|
* Open the pull input, skipping `avformat_find_stream_info` when the opened
|
|
@@ -2014,10 +2245,11 @@ async function getConstants() {
|
|
|
2014
2245
|
}
|
|
2015
2246
|
}
|
|
2016
2247
|
/** The HOLD store, sized from one session's knobs. */
|
|
2017
|
-
function buildLeaseStore(lease) {
|
|
2248
|
+
function buildLeaseStore(lease, onEvict) {
|
|
2018
2249
|
return new NativeLeaseStore({
|
|
2019
2250
|
budgetBytes: lease.values.budgetMb * MB,
|
|
2020
|
-
maxFrames: lease.values.holdFrames
|
|
2251
|
+
maxFrames: lease.values.holdFrames,
|
|
2252
|
+
...onEvict ? { onEvict } : {}
|
|
2021
2253
|
});
|
|
2022
2254
|
}
|
|
2023
2255
|
/** A TILE store (subject or scene) under `budgetMb`, on the shared tile TTL. */
|
|
@@ -2137,11 +2369,17 @@ function packSinglePlane(plane, linesize, width, height, channels) {
|
|
|
2137
2369
|
else for (let row = 0; row < height; row++) plane.copy(out, row * rowBytes, row * stride, row * stride + rowBytes);
|
|
2138
2370
|
return out;
|
|
2139
2371
|
}
|
|
2140
|
-
/**
|
|
2141
|
-
* The forked child's whole runtime: decode-loop lifecycle, the latest-wins
|
|
2142
|
-
* frame slot, and the ROI scaler for `toBuffer`. One instance per process.
|
|
2143
|
-
*/
|
|
2144
2372
|
var DecodeWorkerChild = class {
|
|
2373
|
+
/**
|
|
2374
|
+
* Every native dispose / close goes through here, and it refuses once the
|
|
2375
|
+
* exit has begun (D652) — see `native-disposer.ts`.
|
|
2376
|
+
*/
|
|
2377
|
+
native;
|
|
2378
|
+
requestExit;
|
|
2379
|
+
constructor(hooks = {}) {
|
|
2380
|
+
this.requestExit = hooks.requestExit ?? (() => void 0);
|
|
2381
|
+
this.native = hooks.disposer ?? new NativeDisposer((what) => this.emitStderr(`decode-worker-child: native dispose left to process exit — ${what}\n`, "debug"));
|
|
2382
|
+
}
|
|
2145
2383
|
nav = null;
|
|
2146
2384
|
consts = null;
|
|
2147
2385
|
hwContext = null;
|
|
@@ -2152,7 +2390,7 @@ var DecodeWorkerChild = class {
|
|
|
2152
2390
|
* ~10GB/h). Detection uses a stable ROI + resize target, so the scaler is
|
|
2153
2391
|
* built once and reused, rebuilt only when the key changes — mirroring the
|
|
2154
2392
|
* shared decoder session (`nodeav-decoder-session.ts`, which reuses one
|
|
2155
|
-
* `this.scaler` for the same reason).
|
|
2393
|
+
* `this.scaler` for the same reason). Reclaimed by process exit (D652).
|
|
2156
2394
|
*/
|
|
2157
2395
|
scaler = null;
|
|
2158
2396
|
scalerKey = "";
|
|
@@ -2177,7 +2415,7 @@ var DecodeWorkerChild = class {
|
|
|
2177
2415
|
* for — the HW analogue of {@link scaler}/{@link scalerKey}. Rebuilt (old
|
|
2178
2416
|
* CLOSED) only when the key changes. Its internal `scale_<be>` OUTPUT hw
|
|
2179
2417
|
* frames pool is the leak-prone VAAPI surface pool, so it is closed on EVERY
|
|
2180
|
-
* re-dial ({@link closeInput}) and
|
|
2418
|
+
* re-dial ({@link closeInput}) and by process exit — this discipline is what stops
|
|
2181
2419
|
* the prior "keeping decoded frames on the GPU leaked the VAAPI surface pool
|
|
2182
2420
|
* on every re-dial" incident from recurring.
|
|
2183
2421
|
*/
|
|
@@ -2189,7 +2427,7 @@ var DecodeWorkerChild = class {
|
|
|
2189
2427
|
* evicts (rebuilds) the hot-path detection filter cache — the crop geometry
|
|
2190
2428
|
* differs from the stable detection geometry, so sharing one cache would
|
|
2191
2429
|
* thrash it. Same dispose discipline: both are closed on every re-dial
|
|
2192
|
-
* ({@link closeInput}) and
|
|
2430
|
+
* ({@link closeInput}) and by process exit. Rebuilt lazily per crop geometry.
|
|
2193
2431
|
*/
|
|
2194
2432
|
nativeCropHwFilter = null;
|
|
2195
2433
|
nativeCropHwFilterKey = "";
|
|
@@ -2201,7 +2439,7 @@ var DecodeWorkerChild = class {
|
|
|
2201
2439
|
* request that follows. Hard-capped at {@link NATIVE_RING_CAP}; single
|
|
2202
2440
|
* ownership via the {@link FrameSlot} `retainSuperseded` hook (a frame enters
|
|
2203
2441
|
* the ring exactly when the slot would otherwise free it). Cleared on every
|
|
2204
|
-
* re-dial
|
|
2442
|
+
* re-dial, reclaimed by process exit (see {@link closeInput}).
|
|
2205
2443
|
*/
|
|
2206
2444
|
nativeRing = new NativeFrameRing(NATIVE_RING_CAP);
|
|
2207
2445
|
/**
|
|
@@ -2210,7 +2448,7 @@ var DecodeWorkerChild = class {
|
|
|
2210
2448
|
* eviction. Replaces the tiny GPU {@link nativeRing} as the default (the ring
|
|
2211
2449
|
* is used only when the lease is disabled via a 0 budget). Because it holds
|
|
2212
2450
|
* DOWNLOADED (system-memory) frames, retaining a large window has no VAAPI
|
|
2213
|
-
* surface-pool starvation risk. Cleared on every re-dial
|
|
2451
|
+
* surface-pool starvation risk. Cleared on every re-dial; reclaimed by process exit.
|
|
2214
2452
|
*/
|
|
2215
2453
|
leaseStore = buildLeaseStore(UNSTARTED_LEASE_KNOBS);
|
|
2216
2454
|
/**
|
|
@@ -2296,7 +2534,7 @@ var DecodeWorkerChild = class {
|
|
|
2296
2534
|
* materialize a HW surface into a leasable software frame at NATIVE
|
|
2297
2535
|
* resolution (no scale). Kept separate from the detection/native-crop filters
|
|
2298
2536
|
* (different geometry). Same dispose discipline: closed on every re-dial
|
|
2299
|
-
* ({@link closeInput})
|
|
2537
|
+
* ({@link closeInput}), since it references the decoder's per-dial
|
|
2300
2538
|
* `hw_frames_ctx`.
|
|
2301
2539
|
*/
|
|
2302
2540
|
nativeLeaseDownloadFilter = null;
|
|
@@ -2390,6 +2628,19 @@ var DecodeWorkerChild = class {
|
|
|
2390
2628
|
started = false;
|
|
2391
2629
|
stopped = false;
|
|
2392
2630
|
/**
|
|
2631
|
+
* Set by `drain` (D652): decoding has stopped but every frame already handed
|
|
2632
|
+
* out is kept, so the parent can still read what it admitted. The decode
|
|
2633
|
+
* loop's per-dial `closeInput` is skipped while this is set — that is what
|
|
2634
|
+
* would free the hold — and `stop` ends the process, whose exit reclaims everything.
|
|
2635
|
+
*/
|
|
2636
|
+
draining = false;
|
|
2637
|
+
/**
|
|
2638
|
+
* Set by the EXIT path (SIGTERM / `stop` / a lost channel / an uncaught
|
|
2639
|
+
* error): nothing native is disposed from here on — the decode loop may
|
|
2640
|
+
* still be inside libav, and process exit reclaims it all (D652).
|
|
2641
|
+
*/
|
|
2642
|
+
exiting = false;
|
|
2643
|
+
/**
|
|
2393
2644
|
* The numeric device id this worker decodes for — learned from the `start`
|
|
2394
2645
|
* message's {@link VideoFrameSource}. The child's stderr is INHERITED by the
|
|
2395
2646
|
* pipeline-runner (silent:false fork), so these lines land in the runner's
|
|
@@ -2423,6 +2674,16 @@ var DecodeWorkerChild = class {
|
|
|
2423
2674
|
preRollBurst = null;
|
|
2424
2675
|
/** Whether the burst's report has already been sent — it is sent exactly once. */
|
|
2425
2676
|
preRollReported = false;
|
|
2677
|
+
/**
|
|
2678
|
+
* What became of the pre-roll frames the burst delivered — read, let go, or
|
|
2679
|
+
* lost after the burst's report went out (D652). Its summary is a second,
|
|
2680
|
+
* later line (`preRollSettled`), never a delay on the report itself.
|
|
2681
|
+
*/
|
|
2682
|
+
preRollSettle = new PreRollSettleTracker();
|
|
2683
|
+
/** Every IPC send until its callback fires, so an exit can wait for them (D652). */
|
|
2684
|
+
outbound = new OutboundSendTracker();
|
|
2685
|
+
/** Frames this burst may send to inference, after the hold cap (D652). */
|
|
2686
|
+
preRollBudgetFrames = 0;
|
|
2426
2687
|
/** Media wall clock of the OLDEST and NEWEST frames the burst admitted. */
|
|
2427
2688
|
preRollFirstMediaWall = null;
|
|
2428
2689
|
preRollLastMediaWall = null;
|
|
@@ -2435,7 +2696,7 @@ var DecodeWorkerChild = class {
|
|
|
2435
2696
|
frames = new FrameSlot({ retainSuperseded: (frameId, frame) => this.captureNativeFrame(frameId, frame) });
|
|
2436
2697
|
/**
|
|
2437
2698
|
* Mirrors whether `this.frames` currently has a pull waiting, so
|
|
2438
|
-
* `
|
|
2699
|
+
* `quiesceForExit` knows to resolve it with `{kind:'ended'}` — `FrameSlot`
|
|
2439
2700
|
* intentionally exposes no pending-pull query, only `markPendingPull()`,
|
|
2440
2701
|
* so the child tracks this in lockstep (set in `handlePull`, cleared
|
|
2441
2702
|
* whenever `consumeDecodedFrame`'s `publish` resolves it).
|
|
@@ -2448,6 +2709,10 @@ var DecodeWorkerChild = class {
|
|
|
2448
2709
|
this.sendError("decode-worker-child: malformed request");
|
|
2449
2710
|
return;
|
|
2450
2711
|
}
|
|
2712
|
+
if (this.exiting) {
|
|
2713
|
+
this.refuseWhileExiting(message);
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2451
2716
|
switch (message.kind) {
|
|
2452
2717
|
case "start":
|
|
2453
2718
|
this.handleStart(message.source, message.opts, message.lease);
|
|
@@ -2470,13 +2735,73 @@ var DecodeWorkerChild = class {
|
|
|
2470
2735
|
case "markInferred":
|
|
2471
2736
|
this.admission.mark(message.frameId);
|
|
2472
2737
|
return;
|
|
2738
|
+
case "drain":
|
|
2739
|
+
this.handleDrain();
|
|
2740
|
+
return;
|
|
2473
2741
|
case "stop":
|
|
2474
2742
|
this.handleStop();
|
|
2475
2743
|
return;
|
|
2476
2744
|
}
|
|
2477
2745
|
}
|
|
2478
|
-
/**
|
|
2479
|
-
|
|
2746
|
+
/** The answer to a request that arrives after the exit began (D652). */
|
|
2747
|
+
refuseWhileExiting(message) {
|
|
2748
|
+
switch (message.kind) {
|
|
2749
|
+
case "toBuffer":
|
|
2750
|
+
this.send({
|
|
2751
|
+
kind: "error",
|
|
2752
|
+
message: "decode-worker-child: exiting — toBuffer refused",
|
|
2753
|
+
frameId: message.frameId
|
|
2754
|
+
});
|
|
2755
|
+
return;
|
|
2756
|
+
case "nativeCrop":
|
|
2757
|
+
this.send({
|
|
2758
|
+
kind: "nativeCropMiss",
|
|
2759
|
+
requestId: message.requestId,
|
|
2760
|
+
reason: "worker exiting"
|
|
2761
|
+
});
|
|
2762
|
+
return;
|
|
2763
|
+
case "pull":
|
|
2764
|
+
this.send({ kind: "ended" });
|
|
2765
|
+
return;
|
|
2766
|
+
case "stop":
|
|
2767
|
+
this.requestExit(0);
|
|
2768
|
+
return;
|
|
2769
|
+
default: return;
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
/**
|
|
2773
|
+
* Stop decoding without giving up a single frame the parent holds (D652).
|
|
2774
|
+
*
|
|
2775
|
+
* The loop ends at its next frame and never re-dials, a dial still opening is
|
|
2776
|
+
* aborted, and a later `pull` answers `ended`. The reserved slot, the hold and
|
|
2777
|
+
* the tile/scene stores are left exactly as they are: those are the frames the
|
|
2778
|
+
* runner already admitted, and the whole point is that they stay readable
|
|
2779
|
+
* until the parent has read them and sends `stop`.
|
|
2780
|
+
*/
|
|
2781
|
+
handleDrain() {
|
|
2782
|
+
if (this.stopped) return;
|
|
2783
|
+
this.draining = true;
|
|
2784
|
+
this.stopped = true;
|
|
2785
|
+
this.ended = true;
|
|
2786
|
+
if (this.redialTimer) {
|
|
2787
|
+
clearTimeout(this.redialTimer);
|
|
2788
|
+
this.redialTimer = null;
|
|
2789
|
+
}
|
|
2790
|
+
this.abortController?.abort();
|
|
2791
|
+
this.native.run("undeliveredSlotFrame", () => this.frames.freeUndelivered());
|
|
2792
|
+
this.endPreRollBurst();
|
|
2793
|
+
}
|
|
2794
|
+
/**
|
|
2795
|
+
* Exit step 1 (`createWorkerExit`): stop everything that runs, WITHOUT a
|
|
2796
|
+
* native call. Before D652 this also freed the frames and disposed the
|
|
2797
|
+
* demuxer, decoder, scalers and GPU context — while the decode loop could
|
|
2798
|
+
* still sit in native I/O, and the process now lives on up to
|
|
2799
|
+
* `WORKER_EXIT_FLUSH_BOUND_MS` after it. Process exit reclaims every native
|
|
2800
|
+
* resource, the GPU pool included, so the exit path disposes nothing.
|
|
2801
|
+
*/
|
|
2802
|
+
quiesceForExit() {
|
|
2803
|
+
this.native.refuseFromNowOn();
|
|
2804
|
+
this.exiting = true;
|
|
2480
2805
|
this.stopped = true;
|
|
2481
2806
|
this.ended = true;
|
|
2482
2807
|
if (this.redialTimer) {
|
|
@@ -2490,21 +2815,19 @@ var DecodeWorkerChild = class {
|
|
|
2490
2815
|
if (this.started) this.logMetrics(true);
|
|
2491
2816
|
this.endPreRollBurst();
|
|
2492
2817
|
this.abortController?.abort();
|
|
2493
|
-
this.frames.free();
|
|
2494
|
-
this.closeInput();
|
|
2495
|
-
this.scaler?.[Symbol.dispose]?.();
|
|
2496
|
-
this.scaler = null;
|
|
2497
|
-
this.scalerKey = "";
|
|
2498
|
-
this.nativeCropScaler?.[Symbol.dispose]?.();
|
|
2499
|
-
this.nativeCropScaler = null;
|
|
2500
|
-
this.nativeCropScalerKey = "";
|
|
2501
|
-
this.hwContext?.[Symbol.dispose]?.();
|
|
2502
|
-
this.hwContext = null;
|
|
2503
2818
|
if (this.pendingPull) {
|
|
2504
2819
|
this.pendingPull = false;
|
|
2505
2820
|
this.send({ kind: "ended" });
|
|
2506
2821
|
}
|
|
2507
2822
|
}
|
|
2823
|
+
/** Exit step 2: the last report lines — `unsettled` included. */
|
|
2824
|
+
flushFinalLines() {
|
|
2825
|
+
const settled = this.preRollSettle.flush();
|
|
2826
|
+
if (settled) this.send({
|
|
2827
|
+
kind: "preRollSettled",
|
|
2828
|
+
...settled
|
|
2829
|
+
});
|
|
2830
|
+
}
|
|
2508
2831
|
/**
|
|
2509
2832
|
* Emit the compact throughput line (window `deliveredFps`); cheap,
|
|
2510
2833
|
* integer-only.
|
|
@@ -2546,7 +2869,7 @@ var DecodeWorkerChild = class {
|
|
|
2546
2869
|
this.started = true;
|
|
2547
2870
|
this.deviceId = source.deviceId;
|
|
2548
2871
|
this.lease = lease;
|
|
2549
|
-
this.leaseStore = buildLeaseStore(lease);
|
|
2872
|
+
this.leaseStore = buildLeaseStore(lease, (frameId) => this.settlePreRoll(frameId, "evicted"));
|
|
2550
2873
|
this.tileStore = buildTileStore(lease.values.tileBudgetMb);
|
|
2551
2874
|
this.sceneStore = buildTileStore(lease.values.sceneBudgetMb);
|
|
2552
2875
|
this.leaseGate = new LeaseActivityGate(lease.values.activityMs);
|
|
@@ -2582,6 +2905,7 @@ var DecodeWorkerChild = class {
|
|
|
2582
2905
|
* supersede declines it (`LeaseAdmissionLedger.unmark`), and count it.
|
|
2583
2906
|
*/
|
|
2584
2907
|
noteLeaseRelease(frameId, held, by) {
|
|
2908
|
+
this.settlePreRoll(frameId, "released");
|
|
2585
2909
|
if (held) {
|
|
2586
2910
|
this.leaseReleased += 1;
|
|
2587
2911
|
return;
|
|
@@ -2599,6 +2923,7 @@ var DecodeWorkerChild = class {
|
|
|
2599
2923
|
}
|
|
2600
2924
|
async handleToBuffer(frameId, opts) {
|
|
2601
2925
|
const frame = this.leaseStore.get(frameId)?.frame ?? this.frames.toBuffer(frameId);
|
|
2926
|
+
this.settlePreRoll(frameId, frame ? "read" : "miss");
|
|
2602
2927
|
if (!frame) {
|
|
2603
2928
|
this.sendError(describeToBufferMiss({
|
|
2604
2929
|
frameId,
|
|
@@ -3038,13 +3363,25 @@ var DecodeWorkerChild = class {
|
|
|
3038
3363
|
* `docs/design/decode-path.md` → "Lease admission".
|
|
3039
3364
|
*/
|
|
3040
3365
|
captureNativeFrame(frameId, frame) {
|
|
3041
|
-
if (!this.leaseStore.enabled)
|
|
3042
|
-
|
|
3043
|
-
|
|
3366
|
+
if (!this.leaseStore.enabled) {
|
|
3367
|
+
this.settlePreRoll(frameId, "released");
|
|
3368
|
+
return this.nativeRing.retain(frameId, frame);
|
|
3369
|
+
}
|
|
3370
|
+
if (!this.leaseGate.active) {
|
|
3371
|
+
this.settlePreRoll(frameId, "released");
|
|
3372
|
+
return false;
|
|
3373
|
+
}
|
|
3374
|
+
if (!this.admission.admit(frameId)) {
|
|
3375
|
+
this.settlePreRoll(frameId, "released");
|
|
3376
|
+
return false;
|
|
3377
|
+
}
|
|
3044
3378
|
if (frame.isHwFrame()) {
|
|
3045
3379
|
const software = this.downloadToSoftware(frame);
|
|
3046
3380
|
frame.free();
|
|
3047
|
-
if (!software)
|
|
3381
|
+
if (!software) {
|
|
3382
|
+
this.settlePreRoll(frameId, "download-failed");
|
|
3383
|
+
return true;
|
|
3384
|
+
}
|
|
3048
3385
|
this.leaseStore.put(frameId, PINNED_RGB_CROP ? toLeasedRgbNavFrame(software) : toLeasedNavFrame(software));
|
|
3049
3386
|
return true;
|
|
3050
3387
|
}
|
|
@@ -3070,7 +3407,7 @@ var DecodeWorkerChild = class {
|
|
|
3070
3407
|
return first;
|
|
3071
3408
|
} catch (err) {
|
|
3072
3409
|
this.emitStderr(`decode-worker-child: native lease download failed — ${errMessage(err)} [forensics: frame=${frame.width}x${frame.height} fmt=${frame.format} hw=${frame.isHwFrame()} filterUses=${this.leaseDownloadFilterUses} filterAgeMs=${Date.now() - this.leaseDownloadFilterBuiltAt} dialAgeMs=${Date.now() - this.dialStartedAt} decoded=${this.framesDecoded}]\n`);
|
|
3073
|
-
this.nativeLeaseDownloadFilter?.close();
|
|
3410
|
+
this.native.run("nativeLeaseDownloadFilter", () => this.nativeLeaseDownloadFilter?.close());
|
|
3074
3411
|
this.nativeLeaseDownloadFilter = null;
|
|
3075
3412
|
this.nativeLeaseDownloadFilterKey = "";
|
|
3076
3413
|
this.leaseDownloadFailuresThisDial++;
|
|
@@ -3098,7 +3435,7 @@ var DecodeWorkerChild = class {
|
|
|
3098
3435
|
this.leaseDownloadFilterUses++;
|
|
3099
3436
|
return cached;
|
|
3100
3437
|
}
|
|
3101
|
-
this.nativeLeaseDownloadFilter?.close();
|
|
3438
|
+
this.native.run("nativeLeaseDownloadFilter", () => this.nativeLeaseDownloadFilter?.close());
|
|
3102
3439
|
this.nativeLeaseDownloadFilter = null;
|
|
3103
3440
|
this.nativeLeaseDownloadFilterKey = "";
|
|
3104
3441
|
const description = buildLeaseDownloadDescription(scaleFilter, color);
|
|
@@ -3207,14 +3544,14 @@ var DecodeWorkerChild = class {
|
|
|
3207
3544
|
const key = nativeCropScalerKey(srcW, srcH, dstW, dstH, srcFormat);
|
|
3208
3545
|
const cached = this.nativeCropScaler;
|
|
3209
3546
|
if (cached && key === this.nativeCropScalerKey) return cached;
|
|
3210
|
-
this.nativeCropScaler?.[Symbol.dispose]?.();
|
|
3547
|
+
this.native.run("nativeCropScaler", () => this.nativeCropScaler?.[Symbol.dispose]?.());
|
|
3211
3548
|
this.nativeCropScaler = null;
|
|
3212
3549
|
this.nativeCropScalerKey = "";
|
|
3213
3550
|
const scaler = new nav.SoftwareScaleContext();
|
|
3214
3551
|
scaler.getContext(srcW, srcH, srcFmt, dstW, dstH, C.AV_PIX_FMT_RGB24, C.SWS_FAST_BILINEAR);
|
|
3215
3552
|
const initRet = scaler.initContext();
|
|
3216
3553
|
if (initRet < 0) {
|
|
3217
|
-
scaler[Symbol.dispose]?.();
|
|
3554
|
+
this.native.run("nativeCropScaler", () => scaler[Symbol.dispose]?.());
|
|
3218
3555
|
throw new Error(`native crop sws_init_context failed: ${initRet}`);
|
|
3219
3556
|
}
|
|
3220
3557
|
this.nativeCropScaler = scaler;
|
|
@@ -3229,7 +3566,7 @@ var DecodeWorkerChild = class {
|
|
|
3229
3566
|
const key = `${region.left},${region.top},${region.width},${region.height}->${target.width}x${target.height}:${sourceColorKey(color)}`;
|
|
3230
3567
|
const cached = this.nativeCropHwFilter;
|
|
3231
3568
|
if (cached && key === this.nativeCropHwFilterKey) return cached;
|
|
3232
|
-
this.nativeCropHwFilter?.close();
|
|
3569
|
+
this.native.run("nativeCropHwFilter", () => this.nativeCropHwFilter?.close());
|
|
3233
3570
|
this.nativeCropHwFilter = null;
|
|
3234
3571
|
this.nativeCropHwFilterKey = "";
|
|
3235
3572
|
const description = buildGpuFilterDescription(scaleFilter, region, target, false, "rgb24", color);
|
|
@@ -3242,8 +3579,7 @@ var DecodeWorkerChild = class {
|
|
|
3242
3579
|
return filter;
|
|
3243
3580
|
}
|
|
3244
3581
|
handleStop() {
|
|
3245
|
-
this.
|
|
3246
|
-
process.exit(0);
|
|
3582
|
+
this.requestExit(0);
|
|
3247
3583
|
}
|
|
3248
3584
|
async runDecodeLoop(url) {
|
|
3249
3585
|
const nav = await getNodeAv();
|
|
@@ -3262,7 +3598,15 @@ var DecodeWorkerChild = class {
|
|
|
3262
3598
|
if (this.stopped) break;
|
|
3263
3599
|
this.emitStderr(`decode-worker-child: dial ended — re-dial scheduled: ${errMessage(err)}\n`);
|
|
3264
3600
|
} finally {
|
|
3265
|
-
|
|
3601
|
+
const close = inputCloseAfterLoop({
|
|
3602
|
+
draining: this.draining,
|
|
3603
|
+
exiting: this.exiting
|
|
3604
|
+
});
|
|
3605
|
+
if (close === "all") this.closeInput();
|
|
3606
|
+
else if (close === "demuxer-only") {
|
|
3607
|
+
this.native.run("demuxer", () => this.demuxer?.[Symbol.dispose]?.());
|
|
3608
|
+
this.demuxer = null;
|
|
3609
|
+
}
|
|
3266
3610
|
}
|
|
3267
3611
|
if (this.stopped) break;
|
|
3268
3612
|
const skipBackoff = this.redialAsSoftware;
|
|
@@ -3306,7 +3650,7 @@ var DecodeWorkerChild = class {
|
|
|
3306
3650
|
});
|
|
3307
3651
|
const demuxer = opened.demuxer;
|
|
3308
3652
|
if (this.stopped) {
|
|
3309
|
-
demuxer[Symbol.dispose]?.();
|
|
3653
|
+
this.native.run("demuxer", () => demuxer[Symbol.dispose]?.());
|
|
3310
3654
|
return;
|
|
3311
3655
|
}
|
|
3312
3656
|
this.demuxer = demuxer;
|
|
@@ -3322,13 +3666,19 @@ var DecodeWorkerChild = class {
|
|
|
3322
3666
|
});
|
|
3323
3667
|
const decoderCreateMs = Date.now() - decoderStartedAt;
|
|
3324
3668
|
if (this.stopped) {
|
|
3325
|
-
decoder[Symbol.dispose]?.();
|
|
3669
|
+
this.native.run("decoder", () => decoder[Symbol.dispose]?.());
|
|
3326
3670
|
return;
|
|
3327
3671
|
}
|
|
3328
3672
|
this.decoder = decoder;
|
|
3329
3673
|
const decodeStartedAt = Date.now();
|
|
3330
3674
|
for await (const frame of decoder.frames(demuxer.packets(videoStream.index))) {
|
|
3331
|
-
if (this.stopped)
|
|
3675
|
+
if (this.stopped) {
|
|
3676
|
+
releaseFrameAfterStop(frame, {
|
|
3677
|
+
draining: this.draining,
|
|
3678
|
+
exiting: this.exiting
|
|
3679
|
+
});
|
|
3680
|
+
break;
|
|
3681
|
+
}
|
|
3332
3682
|
if (!frame) continue;
|
|
3333
3683
|
this.reportDialTiming({
|
|
3334
3684
|
hwContextMs: this.hwContextMs,
|
|
@@ -3458,16 +3808,19 @@ var DecodeWorkerChild = class {
|
|
|
3458
3808
|
});
|
|
3459
3809
|
if (this.preRollMs > 0 && this.preRollMaxFrames > 0) {
|
|
3460
3810
|
const preRollCadenceMs = this.minIntervalMs > 0 ? this.minIntervalMs : 1e3;
|
|
3811
|
+
const withinHold = planPreRollWithinHold({
|
|
3812
|
+
requestedFrames: this.preRollMaxFrames,
|
|
3813
|
+
cadenceMs: preRollCadenceMs,
|
|
3814
|
+
holdFrames: this.lease?.values.holdFrames ?? UNSTARTED_LEASE_KNOBS.values.holdFrames,
|
|
3815
|
+
holdEnabled: this.leaseStore.enabled
|
|
3816
|
+
});
|
|
3817
|
+
this.preRollBudgetFrames = withinHold.budgetFrames;
|
|
3461
3818
|
this.preRollBurst = new PreRollBurst({
|
|
3462
3819
|
budget: {
|
|
3463
|
-
maxFrames:
|
|
3820
|
+
maxFrames: withinHold.budgetFrames,
|
|
3464
3821
|
maxWallMs: PRE_ROLL_BURST_MAX_WALL_MS
|
|
3465
3822
|
},
|
|
3466
|
-
plan:
|
|
3467
|
-
spanMs: this.preRollMaxFrames * preRollCadenceMs,
|
|
3468
|
-
detectionFps: 1e3 / preRollCadenceMs,
|
|
3469
|
-
maxFrames: this.preRollMaxFrames
|
|
3470
|
-
}),
|
|
3823
|
+
plan: withinHold.plan,
|
|
3471
3824
|
startedAtWall: now
|
|
3472
3825
|
});
|
|
3473
3826
|
}
|
|
@@ -3528,12 +3881,27 @@ var DecodeWorkerChild = class {
|
|
|
3528
3881
|
droppedByReason: report.droppedByReason,
|
|
3529
3882
|
strideMs: report.strideMs,
|
|
3530
3883
|
subsampled: report.subsampled,
|
|
3531
|
-
spanMs: first !== null && last !== null ? Math.round(last - first) : 0
|
|
3884
|
+
spanMs: first !== null && last !== null ? Math.round(last - first) : 0,
|
|
3885
|
+
budgetFrames: this.preRollBudgetFrames
|
|
3886
|
+
});
|
|
3887
|
+
const settled = this.preRollSettle.endBurst();
|
|
3888
|
+
if (settled) this.send({
|
|
3889
|
+
kind: "preRollSettled",
|
|
3890
|
+
...settled
|
|
3891
|
+
});
|
|
3892
|
+
}
|
|
3893
|
+
/** One pre-roll frame's fate; sends the settle summary when it was the last owed. */
|
|
3894
|
+
settlePreRoll(frameId, outcome) {
|
|
3895
|
+
const settled = this.preRollSettle.settle(frameId, outcome);
|
|
3896
|
+
if (settled) this.send({
|
|
3897
|
+
kind: "preRollSettled",
|
|
3898
|
+
...settled
|
|
3532
3899
|
});
|
|
3533
3900
|
}
|
|
3534
3901
|
/** Send a `frame` reply to the parent and count it toward `deliveredFps`. */
|
|
3535
3902
|
deliverFrame(reply) {
|
|
3536
3903
|
this.framesDelivered++;
|
|
3904
|
+
if (reply.preRoll) this.preRollSettle.admit(reply.frameId);
|
|
3537
3905
|
this.send(this.toFrameReply(reply));
|
|
3538
3906
|
}
|
|
3539
3907
|
/**
|
|
@@ -3563,7 +3931,7 @@ var DecodeWorkerChild = class {
|
|
|
3563
3931
|
this.emitStderr(`decode-worker-child: GPU filter probe failed — using software decode: ${errMessage(err)}\n`);
|
|
3564
3932
|
return false;
|
|
3565
3933
|
} finally {
|
|
3566
|
-
filter?.close();
|
|
3934
|
+
this.native.run("hwFilterProbe", () => filter?.close());
|
|
3567
3935
|
}
|
|
3568
3936
|
}
|
|
3569
3937
|
/**
|
|
@@ -3578,7 +3946,7 @@ var DecodeWorkerChild = class {
|
|
|
3578
3946
|
if (this.hwFilterDecision === "software") return;
|
|
3579
3947
|
this.hwFilterDecision = "software";
|
|
3580
3948
|
this.redialAsSoftware = true;
|
|
3581
|
-
this.hwFilter?.close();
|
|
3949
|
+
this.native.run("hwFilter", () => this.hwFilter?.close());
|
|
3582
3950
|
this.hwFilter = null;
|
|
3583
3951
|
this.hwFilterKey = "";
|
|
3584
3952
|
this.abortController?.abort();
|
|
@@ -3652,14 +4020,14 @@ var DecodeWorkerChild = class {
|
|
|
3652
4020
|
const key = `${srcKind}:${srcW}x${srcH}->${dstW}x${dstH}:${dstFmt}`;
|
|
3653
4021
|
const cached = this.scaler;
|
|
3654
4022
|
if (cached && key === this.scalerKey) return cached;
|
|
3655
|
-
this.scaler?.[Symbol.dispose]?.();
|
|
4023
|
+
this.native.run("scaler", () => this.scaler?.[Symbol.dispose]?.());
|
|
3656
4024
|
this.scaler = null;
|
|
3657
4025
|
this.scalerKey = "";
|
|
3658
4026
|
const scaler = new nav.SoftwareScaleContext();
|
|
3659
4027
|
scaler.getContext(srcW, srcH, navPixelFormatFor(srcKind, C), dstW, dstH, dstFmt, C.SWS_FAST_BILINEAR);
|
|
3660
4028
|
const initRet = scaler.initContext();
|
|
3661
4029
|
if (initRet < 0) {
|
|
3662
|
-
scaler[Symbol.dispose]?.();
|
|
4030
|
+
this.native.run("scaler", () => scaler[Symbol.dispose]?.());
|
|
3663
4031
|
throw new Error(`sws_init_context failed: ${initRet}`);
|
|
3664
4032
|
}
|
|
3665
4033
|
this.scaler = scaler;
|
|
@@ -3717,7 +4085,7 @@ var DecodeWorkerChild = class {
|
|
|
3717
4085
|
const key = `${region.left},${region.top},${region.width},${region.height}->${target.width}x${target.height}:${pixel}:${sourceColorKey(color)}`;
|
|
3718
4086
|
const cached = this.hwFilter;
|
|
3719
4087
|
if (cached && key === this.hwFilterKey) return cached;
|
|
3720
|
-
this.hwFilter?.close();
|
|
4088
|
+
this.native.run("hwFilter", () => this.hwFilter?.close());
|
|
3721
4089
|
this.hwFilter = null;
|
|
3722
4090
|
this.hwFilterKey = "";
|
|
3723
4091
|
const description = buildGpuFilterDescription(scaleFilter, region, target, fullFrame, pixel, color);
|
|
@@ -3772,25 +4140,30 @@ var DecodeWorkerChild = class {
|
|
|
3772
4140
|
* the next dial's first `toBuffer`.
|
|
3773
4141
|
*/
|
|
3774
4142
|
closeInput() {
|
|
3775
|
-
this.nativeRing.clear();
|
|
3776
|
-
this.leaseStore.clear();
|
|
4143
|
+
this.native.run("nativeRing", () => this.nativeRing.clear());
|
|
4144
|
+
this.native.run("leaseStore", () => this.leaseStore.clear());
|
|
4145
|
+
const cleared = this.preRollSettle.settleAll("cleared");
|
|
4146
|
+
if (cleared) this.send({
|
|
4147
|
+
kind: "preRollSettled",
|
|
4148
|
+
...cleared
|
|
4149
|
+
});
|
|
3777
4150
|
this.tileStore.clear();
|
|
3778
4151
|
this.sceneStore.clear();
|
|
3779
4152
|
this.pendingCuts.clear();
|
|
3780
4153
|
this.tileFrameWidth = 0;
|
|
3781
4154
|
this.tileFrameHeight = 0;
|
|
3782
|
-
this.nativeLeaseDownloadFilter?.close();
|
|
4155
|
+
this.native.run("nativeLeaseDownloadFilter", () => this.nativeLeaseDownloadFilter?.close());
|
|
3783
4156
|
this.nativeLeaseDownloadFilter = null;
|
|
3784
4157
|
this.nativeLeaseDownloadFilterKey = "";
|
|
3785
|
-
this.nativeCropHwFilter?.close();
|
|
4158
|
+
this.native.run("nativeCropHwFilter", () => this.nativeCropHwFilter?.close());
|
|
3786
4159
|
this.nativeCropHwFilter = null;
|
|
3787
4160
|
this.nativeCropHwFilterKey = "";
|
|
3788
|
-
this.hwFilter?.close();
|
|
4161
|
+
this.native.run("hwFilter", () => this.hwFilter?.close());
|
|
3789
4162
|
this.hwFilter = null;
|
|
3790
4163
|
this.hwFilterKey = "";
|
|
3791
|
-
this.decoder?.[Symbol.dispose]?.();
|
|
4164
|
+
this.native.run("decoder", () => this.decoder?.[Symbol.dispose]?.());
|
|
3792
4165
|
this.decoder = null;
|
|
3793
|
-
this.demuxer?.[Symbol.dispose]?.();
|
|
4166
|
+
this.native.run("demuxer", () => this.demuxer?.[Symbol.dispose]?.());
|
|
3794
4167
|
this.demuxer = null;
|
|
3795
4168
|
}
|
|
3796
4169
|
/**
|
|
@@ -3823,7 +4196,13 @@ var DecodeWorkerChild = class {
|
|
|
3823
4196
|
}
|
|
3824
4197
|
}
|
|
3825
4198
|
send(reply) {
|
|
3826
|
-
process.send
|
|
4199
|
+
if (!process.send) return;
|
|
4200
|
+
const done = this.outbound.begin();
|
|
4201
|
+
try {
|
|
4202
|
+
process.send(reply, void 0, void 0, () => done());
|
|
4203
|
+
} catch {
|
|
4204
|
+
done();
|
|
4205
|
+
}
|
|
3827
4206
|
}
|
|
3828
4207
|
/**
|
|
3829
4208
|
* The `User-Agent` this worker's RTSP dial carries — its identity inside the
|
|
@@ -3872,7 +4251,20 @@ var DecodeWorkerChild = class {
|
|
|
3872
4251
|
});
|
|
3873
4252
|
}
|
|
3874
4253
|
};
|
|
3875
|
-
|
|
4254
|
+
//#endregion
|
|
4255
|
+
//#region src/session-decode/decode-worker-main.ts
|
|
4256
|
+
/**
|
|
4257
|
+
* Decode-worker — the process ENTRY (its own vite build entry, the path
|
|
4258
|
+
* `fork-decode-worker.ts` forks). The worker itself is `DecodeWorkerChild` in
|
|
4259
|
+
* `decode-worker-child.ts`; this file only instantiates it and wires the process:
|
|
4260
|
+
* IPC messages, the native preload, the sharp warn sink, and the ONE bounded
|
|
4261
|
+
* exit every ending goes through (D652).
|
|
4262
|
+
*
|
|
4263
|
+
* ‼ SANCTIONED RAW IPC ‼ — `process.on('message')` / `process.send` are the
|
|
4264
|
+
* correct mechanism for this private, directly-forked worker only; see the
|
|
4265
|
+
* note at the top of `decode-worker-child.ts`.
|
|
4266
|
+
*/
|
|
4267
|
+
var worker = new DecodeWorkerChild({ requestExit: (code) => exitWorker(code) });
|
|
3876
4268
|
require_lazy_sharp.setSharpWarnSink((message) => {
|
|
3877
4269
|
worker.emitStderr(`decode-worker-child: ${message}\n`, "warn");
|
|
3878
4270
|
});
|
|
@@ -3880,17 +4272,34 @@ process.on("message", (message) => {
|
|
|
3880
4272
|
worker.handleMessage(message);
|
|
3881
4273
|
});
|
|
3882
4274
|
worker.warm();
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
4275
|
+
/**
|
|
4276
|
+
* The ONE way this worker exits: quiesce (the disposer starts refusing, so
|
|
4277
|
+
* nothing native is freed under a live read), queue the last lines, wait —
|
|
4278
|
+
* bounded — for them to leave, exit. `stop`, SIGTERM (the parent's `kill()`),
|
|
4279
|
+
* a lost channel and an uncaught error all land here, and a step that throws
|
|
4280
|
+
* still exits (with code 1). Before D652 there was no SIGTERM handler and a
|
|
4281
|
+
* `kill()` ended the process with those lines still queued.
|
|
4282
|
+
*/
|
|
4283
|
+
var exitWorker = createWorkerExit({
|
|
4284
|
+
sends: worker.outbound,
|
|
4285
|
+
steps: {
|
|
4286
|
+
quiesce: () => worker.quiesceForExit(),
|
|
4287
|
+
flushFinalLines: () => worker.flushFinalLines()
|
|
4288
|
+
},
|
|
4289
|
+
exit: (code) => process.exit(code)
|
|
3886
4290
|
});
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
4291
|
+
wireWorkerExit({
|
|
4292
|
+
on: (event, listener) => {
|
|
4293
|
+
if (event === "SIGTERM") process.on("SIGTERM", () => listener(void 0));
|
|
4294
|
+
else if (event === "disconnect") process.on("disconnect", () => listener(void 0));
|
|
4295
|
+
else process.on("uncaughtException", (err) => listener(err));
|
|
4296
|
+
},
|
|
4297
|
+
exitWorker,
|
|
4298
|
+
reportUncaught: (err) => {
|
|
4299
|
+
worker.emitStderr(`decode-worker-child: uncaught exception — ${errMessage(err)}\n`);
|
|
4300
|
+
}
|
|
3891
4301
|
});
|
|
3892
4302
|
process.on("unhandledRejection", (reason) => {
|
|
3893
4303
|
worker.emitStderr(`decode-worker-child: unhandled rejection — ${errMessage(reason)}\n`);
|
|
3894
4304
|
});
|
|
3895
4305
|
//#endregion
|
|
3896
|
-
exports.DecodeWorkerChild = DecodeWorkerChild;
|