@camstack/addon-pipeline 1.1.53 → 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 +289 -1469
- package/dist/detection-pipeline/index.mjs +269 -1449
- package/dist/{dist-DWb62H5U.js → dist-DI57FC8K.js} +167 -8
- package/dist/{dist-BalxNwOt.mjs → dist-RWGGPwVx.mjs} +167 -8
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +772 -21
- package/dist/pipeline-runner/index.mjs +772 -22
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.mjs +1 -1
- package/dist/{remote-source-plane-CZpzIVro.js → remote-source-plane-CHgvzzA6.js} +1 -1
- package/dist/{remote-source-plane-BCJW5CvF.mjs → remote-source-plane-DU0aRSPv.mjs} +1 -1
- package/dist/session-decode/decode-worker-child.js +538 -35
- package/dist/session-decode/decode-worker-child.mjs +538 -35
- package/dist/step-definitions-CNBFKjZe.js +1514 -0
- package/dist/step-definitions-CP9kVSml.mjs +1479 -0
- package/dist/stream-broker/_stub.js +2 -2
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DIN3CcRP.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_types__loadShare__.js-B3gTdHEh.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-C9fwKMfg.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-CGUEq--X.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-pk7qdYXt.mjs → worker-protocol-CyVJTZEO.mjs} +7 -0
- package/dist/{worker-protocol-BCfO8gUF.js → worker-protocol-PP4jKHHJ.js} +7 -0
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-CqOe8eYa.js → MaskShapeCanvas-DI4BY7W2-BChW0ntM.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-CZyLeUnd.js → MotionZonesSettings-NcxxQN8r-DDzqEbSe.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-Cn0hGZnu.js → PrivacyMaskSettings-APgPLF7p-B879GXaf.js} +1 -1
- package/embed-dist/assets/{index-DRl4XYjA.js → index-CNjQ5rAE.js} +10 -10
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
- package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-o4tu_xuc.mjs +0 -26
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,11 +1,85 @@
|
|
|
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);
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
node_path = require_chunk.__toESM(node_path);
|
|
9
|
+
//#region src/session-decode/crop-geometry.ts
|
|
10
|
+
/** Clamp to [min,max] then floor to the nearest even number (YUV420P chroma is 2x2 subsampled). */
|
|
11
|
+
function clampEven(value, min, max) {
|
|
12
|
+
const bounded = Math.min(Math.max(value, min), max);
|
|
13
|
+
return bounded - bounded % 2;
|
|
14
|
+
}
|
|
15
|
+
/** Resolve `FrameImageOptions.crop` against the decoded frame's dimensions, defaulting to the full frame. */
|
|
16
|
+
function resolveCropRegion(frameWidth, frameHeight, crop) {
|
|
17
|
+
const left = clampEven(crop?.left ?? 0, 0, Math.max(0, frameWidth - 2));
|
|
18
|
+
const top = clampEven(crop?.top ?? 0, 0, Math.max(0, frameHeight - 2));
|
|
19
|
+
return {
|
|
20
|
+
left,
|
|
21
|
+
top,
|
|
22
|
+
width: clampEven(crop?.width ?? frameWidth - left, 2, frameWidth - left),
|
|
23
|
+
height: clampEven(crop?.height ?? frameHeight - top, 2, frameHeight - top)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Resolve `FrameImageOptions.resize`, defaulting to the (already cropped) source dimensions. */
|
|
27
|
+
function resolveResizeTarget(cropWidth, cropHeight, resize) {
|
|
28
|
+
if (!resize) return {
|
|
29
|
+
width: cropWidth,
|
|
30
|
+
height: cropHeight
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
width: Math.max(1, Math.round(resize.width)),
|
|
34
|
+
height: Math.max(1, Math.round(resize.height))
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Whether `region` covers the entire `srcW x srcH` frame (i.e. no real crop). */
|
|
38
|
+
function isFullFrameRegion(region, srcW, srcH) {
|
|
39
|
+
return region.left === 0 && region.top === 0 && region.width === srcW && region.height === srcH;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a NORMALIZED [0,1] bbox against a native frame's real pixel dims into
|
|
43
|
+
* an even-aligned pixel crop region. Clamps the box inside the frame so a
|
|
44
|
+
* padded / slightly-out-of-frame bbox never reads past the plane; a degenerate
|
|
45
|
+
* (zero-area) box is widened to the 2px chroma minimum by
|
|
46
|
+
* {@link resolveCropRegion}'s own clamps.
|
|
47
|
+
*/
|
|
48
|
+
function normalizedBboxToCrop(frameWidth, frameHeight, bbox) {
|
|
49
|
+
return {
|
|
50
|
+
left: Math.round(bbox.x * frameWidth),
|
|
51
|
+
top: Math.round(bbox.y * frameHeight),
|
|
52
|
+
width: Math.max(2, Math.round(bbox.w * frameWidth)),
|
|
53
|
+
height: Math.max(2, Math.round(bbox.h * frameHeight))
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Aspect-preserving cap of a native crop region to `maxWidth`. Returns
|
|
58
|
+
* `undefined` (no resize → true native crop) when `maxWidth` is absent or the
|
|
59
|
+
* region is already within it.
|
|
60
|
+
*/
|
|
61
|
+
function resolveNativeCropTarget(region, maxWidth) {
|
|
62
|
+
if (maxWidth === void 0 || maxWidth <= 0 || region.width <= maxWidth) return void 0;
|
|
63
|
+
const width = Math.round(maxWidth);
|
|
64
|
+
return {
|
|
65
|
+
width,
|
|
66
|
+
height: Math.max(1, Math.round(width * region.height / region.width))
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Full native-crop geometry: normalized bbox → even-aligned source region +
|
|
71
|
+
* (optionally width-capped) output target. The single mapping the decode worker
|
|
72
|
+
* uses to turn a detection-res bbox into a native-res crop request.
|
|
73
|
+
*/
|
|
74
|
+
function resolveNativeCropGeometry(frameWidth, frameHeight, bbox, maxWidth) {
|
|
75
|
+
const region = resolveCropRegion(frameWidth, frameHeight, normalizedBboxToCrop(frameWidth, frameHeight, bbox));
|
|
76
|
+
const resize = resolveNativeCropTarget(region, maxWidth);
|
|
77
|
+
return {
|
|
78
|
+
region,
|
|
79
|
+
target: resolveResizeTarget(region.width, region.height, resize)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
9
83
|
//#region src/session-decode/frame-slot.ts
|
|
10
84
|
function toReply(held) {
|
|
11
85
|
return {
|
|
@@ -20,6 +94,10 @@ function toReply(held) {
|
|
|
20
94
|
* pending-pull flag. One instance per decode-worker child session.
|
|
21
95
|
*/
|
|
22
96
|
var FrameSlot = class {
|
|
97
|
+
retainSuperseded;
|
|
98
|
+
constructor(options) {
|
|
99
|
+
this.retainSuperseded = options?.retainSuperseded;
|
|
100
|
+
}
|
|
23
101
|
nextFrameId = 1;
|
|
24
102
|
slot = null;
|
|
25
103
|
/**
|
|
@@ -104,11 +182,154 @@ var FrameSlot = class {
|
|
|
104
182
|
const previousReserved = this.reserved;
|
|
105
183
|
this.reserved = held;
|
|
106
184
|
this.slot = null;
|
|
107
|
-
previousReserved
|
|
185
|
+
if (previousReserved) {
|
|
186
|
+
if (!(this.retainSuperseded?.(previousReserved.frameId, previousReserved.frame) ?? false)) previousReserved.frame.free();
|
|
187
|
+
}
|
|
108
188
|
return toReply(held);
|
|
109
189
|
}
|
|
110
190
|
};
|
|
111
191
|
//#endregion
|
|
192
|
+
//#region src/session-decode/native-frame-ring.ts
|
|
193
|
+
/**
|
|
194
|
+
* A hard-capped, insertion-ordered map from worker frameId → retained frame.
|
|
195
|
+
* `capacity <= 0` disables retention entirely (every `retain` frees immediately
|
|
196
|
+
* and returns false) — the safe "keep the ring off" knob.
|
|
197
|
+
*/
|
|
198
|
+
var NativeFrameRing = class {
|
|
199
|
+
capacity;
|
|
200
|
+
/** Insertion-ordered (Map preserves insertion order) frameId → frame. */
|
|
201
|
+
frames = /* @__PURE__ */ new Map();
|
|
202
|
+
constructor(capacity) {
|
|
203
|
+
this.capacity = capacity;
|
|
204
|
+
}
|
|
205
|
+
/** Number of frames currently retained (for tests / metrics). */
|
|
206
|
+
get size() {
|
|
207
|
+
return this.frames.size;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Take ownership of `frame` under `frameId`. Frees the oldest retained frame
|
|
211
|
+
* when the cap is exceeded (FIFO by frameId insertion). Returns `true` when
|
|
212
|
+
* the ring took ownership (caller MUST NOT free), `false` when retention is
|
|
213
|
+
* disabled (`capacity <= 0`) and the caller still owns/should free the frame.
|
|
214
|
+
*/
|
|
215
|
+
retain(frameId, frame) {
|
|
216
|
+
if (this.capacity <= 0) return false;
|
|
217
|
+
const existing = this.frames.get(frameId);
|
|
218
|
+
if (existing) existing.free();
|
|
219
|
+
this.frames.set(frameId, frame);
|
|
220
|
+
while (this.frames.size > this.capacity) {
|
|
221
|
+
const oldestKey = this.frames.keys().next().value;
|
|
222
|
+
if (oldestKey === void 0) break;
|
|
223
|
+
const oldest = this.frames.get(oldestKey);
|
|
224
|
+
this.frames.delete(oldestKey);
|
|
225
|
+
oldest?.free();
|
|
226
|
+
}
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
/** The retained frame for `frameId`, or `null` if never retained / already evicted. */
|
|
230
|
+
get(frameId) {
|
|
231
|
+
return this.frames.get(frameId) ?? null;
|
|
232
|
+
}
|
|
233
|
+
/** Free and drop every retained frame. Idempotent (re-dial + teardown call it). */
|
|
234
|
+
clear() {
|
|
235
|
+
for (const frame of this.frames.values()) frame.free();
|
|
236
|
+
this.frames.clear();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
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
|
|
112
333
|
//#region src/session-decode/decode-worker-child.ts
|
|
113
334
|
/**
|
|
114
335
|
* Decode-worker CHILD entry point — Epic C P1, Task 2.
|
|
@@ -134,11 +355,59 @@ var FrameSlot = class {
|
|
|
134
355
|
* NOT `$process`)". Raw `process.on('message')` / `process.send` are the
|
|
135
356
|
* correct, sanctioned mechanism for this specific file only.
|
|
136
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
|
+
}
|
|
137
366
|
/** Re-dial backoff on a transient dial error/EOF (mirrors the decoder's PULL_REDIAL_MS). */
|
|
138
367
|
var REDIAL_MS = 3e3;
|
|
139
368
|
/** How often the worker emits its `framesDecoded/framesSkipped/deliveredFps` line. */
|
|
140
369
|
var METRICS_INTERVAL_MS = 1e4;
|
|
141
370
|
/**
|
|
371
|
+
* Hard cap on the native-frame retention ring (native-res crop feature). Kept
|
|
372
|
+
* DELIBERATELY TINY — its only job is to cover the detection→crop latency
|
|
373
|
+
* (typically 1-4 frames), NOT the full shm ring depth. On the HW path every
|
|
374
|
+
* retained slot pins a GPU/VAAPI surface out of the decoder pool (the historic
|
|
375
|
+
* leak site), so the cap MUST stay small; a miss is free (caller falls back to
|
|
376
|
+
* today's detection-frame crop). Override with `CAMSTACK_SESSION_NATIVE_CROP_RING`
|
|
377
|
+
* (0 disables retention entirely); clamped to [0,4], default 2.
|
|
378
|
+
*/
|
|
379
|
+
var NATIVE_RING_CAP = (() => {
|
|
380
|
+
const raw = Number(process.env["CAMSTACK_SESSION_NATIVE_CROP_RING"]);
|
|
381
|
+
if (!Number.isFinite(raw)) return 2;
|
|
382
|
+
return Math.min(4, Math.max(0, Math.floor(raw)));
|
|
383
|
+
})();
|
|
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
|
+
/**
|
|
142
411
|
* The libav GPU scale filter for a hwaccel backend, or `null` when none is
|
|
143
412
|
* known here — those backends fall back to the software crop+scale path.
|
|
144
413
|
* Mirrors `addon-decoder-ffmpeg/src/ffmpeg-args.ts` `gpuScaleFilterForBackend`.
|
|
@@ -276,37 +545,6 @@ function backendToHwDeviceConst(backend, consts) {
|
|
|
276
545
|
default: return null;
|
|
277
546
|
}
|
|
278
547
|
}
|
|
279
|
-
/** Clamp to [min,max] then floor to the nearest even number (YUV420P chroma is 2x2 subsampled). */
|
|
280
|
-
function clampEven(value, min, max) {
|
|
281
|
-
const bounded = Math.min(Math.max(value, min), max);
|
|
282
|
-
return bounded - bounded % 2;
|
|
283
|
-
}
|
|
284
|
-
/** Resolve `FrameImageOptions.crop` against the decoded frame's dimensions, defaulting to the full frame. */
|
|
285
|
-
function resolveCropRegion(frameWidth, frameHeight, crop) {
|
|
286
|
-
const left = clampEven(crop?.left ?? 0, 0, Math.max(0, frameWidth - 2));
|
|
287
|
-
const top = clampEven(crop?.top ?? 0, 0, Math.max(0, frameHeight - 2));
|
|
288
|
-
return {
|
|
289
|
-
left,
|
|
290
|
-
top,
|
|
291
|
-
width: clampEven(crop?.width ?? frameWidth - left, 2, frameWidth - left),
|
|
292
|
-
height: clampEven(crop?.height ?? frameHeight - top, 2, frameHeight - top)
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
/** Resolve `FrameImageOptions.resize`, defaulting to the (already cropped) source dimensions. */
|
|
296
|
-
function resolveResizeTarget(cropWidth, cropHeight, resize) {
|
|
297
|
-
if (!resize) return {
|
|
298
|
-
width: cropWidth,
|
|
299
|
-
height: cropHeight
|
|
300
|
-
};
|
|
301
|
-
return {
|
|
302
|
-
width: Math.max(1, Math.round(resize.width)),
|
|
303
|
-
height: Math.max(1, Math.round(resize.height))
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
/** Whether `region` covers the entire `srcW x srcH` frame (i.e. no real crop). */
|
|
307
|
-
function isFullFrameRegion(region, srcW, srcH) {
|
|
308
|
-
return region.left === 0 && region.top === 0 && region.width === srcW && region.height === srcH;
|
|
309
|
-
}
|
|
310
548
|
/**
|
|
311
549
|
* Build the libav filtergraph description for the GPU crop+scale path.
|
|
312
550
|
*
|
|
@@ -388,6 +626,53 @@ var DecodeWorkerChild = class {
|
|
|
388
626
|
*/
|
|
389
627
|
hwFilter = null;
|
|
390
628
|
hwFilterKey = "";
|
|
629
|
+
/**
|
|
630
|
+
* DEDICATED native-crop GPU filtergraph + software scaler, kept SEPARATE from
|
|
631
|
+
* the detection {@link hwFilter}/{@link scaler} so a native-crop request never
|
|
632
|
+
* evicts (rebuilds) the hot-path detection filter cache — the crop geometry
|
|
633
|
+
* differs from the stable detection geometry, so sharing one cache would
|
|
634
|
+
* thrash it. Same dispose discipline: both are closed on every re-dial
|
|
635
|
+
* ({@link closeInput}) and on teardown. Rebuilt lazily per crop geometry.
|
|
636
|
+
*/
|
|
637
|
+
nativeCropHwFilter = null;
|
|
638
|
+
nativeCropHwFilterKey = "";
|
|
639
|
+
nativeCropScaler = null;
|
|
640
|
+
nativeCropScalerKey = "";
|
|
641
|
+
/**
|
|
642
|
+
* Bounded native-frame retention ring — the survival window for a frame's
|
|
643
|
+
* NATIVE surface between detection (on the shipped small frame) and the crop
|
|
644
|
+
* request that follows. Hard-capped at {@link NATIVE_RING_CAP}; single
|
|
645
|
+
* ownership via the {@link FrameSlot} `retainSuperseded` hook (a frame enters
|
|
646
|
+
* the ring exactly when the slot would otherwise free it). Cleared on every
|
|
647
|
+
* re-dial + teardown (see {@link closeInput}).
|
|
648
|
+
*/
|
|
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 = "";
|
|
673
|
+
/** Throttled native-crop hit/miss counters, emitted with the throughput line. */
|
|
674
|
+
nativeCropHits = 0;
|
|
675
|
+
nativeCropMisses = 0;
|
|
391
676
|
/** Scrypted-style throughput counters (plain integers, no per-frame allocation). */
|
|
392
677
|
framesDecoded = 0;
|
|
393
678
|
/** Child-side skips only (HW-guard drops + probe/degrade frees); slot drops add {@link FrameSlot.droppedCount}. */
|
|
@@ -420,7 +705,7 @@ var DecodeWorkerChild = class {
|
|
|
420
705
|
* the reserve-on-pull invariant (`e3c78a9b`) is unit-tested without
|
|
421
706
|
* node-av. See that file's doc comment for the full semantics.
|
|
422
707
|
*/
|
|
423
|
-
frames = new FrameSlot();
|
|
708
|
+
frames = new FrameSlot({ retainSuperseded: (frameId, frame) => this.captureNativeFrame(frameId, frame) });
|
|
424
709
|
/**
|
|
425
710
|
* Mirrors whether `this.frames` currently has a pull waiting, so
|
|
426
711
|
* `teardown` knows to resolve it with `{kind:'ended'}` — `FrameSlot`
|
|
@@ -446,6 +731,12 @@ var DecodeWorkerChild = class {
|
|
|
446
731
|
case "toBuffer":
|
|
447
732
|
this.handleToBuffer(message.frameId, message.opts);
|
|
448
733
|
return;
|
|
734
|
+
case "nativeCrop":
|
|
735
|
+
this.handleNativeCrop(message.requestId, message.frameId, message.bbox, message.maxWidth);
|
|
736
|
+
return;
|
|
737
|
+
case "releaseNativeLease":
|
|
738
|
+
this.leaseStore.release(message.frameId);
|
|
739
|
+
return;
|
|
449
740
|
case "stop":
|
|
450
741
|
this.handleStop();
|
|
451
742
|
return;
|
|
@@ -470,6 +761,9 @@ var DecodeWorkerChild = class {
|
|
|
470
761
|
this.scaler?.[Symbol.dispose]?.();
|
|
471
762
|
this.scaler = null;
|
|
472
763
|
this.scalerKey = "";
|
|
764
|
+
this.nativeCropScaler?.[Symbol.dispose]?.();
|
|
765
|
+
this.nativeCropScaler = null;
|
|
766
|
+
this.nativeCropScalerKey = "";
|
|
473
767
|
this.hwContext?.[Symbol.dispose]?.();
|
|
474
768
|
this.hwContext = null;
|
|
475
769
|
if (this.pendingPull) {
|
|
@@ -484,7 +778,7 @@ var DecodeWorkerChild = class {
|
|
|
484
778
|
const deliveredDelta = this.framesDelivered - this.lastDeliveredSnapshot;
|
|
485
779
|
const deliveredFps = Math.round(deliveredDelta / windowMs * 1e3 * 10) / 10;
|
|
486
780
|
const skipped = this.framesSkipped + this.frames.droppedCount;
|
|
487
|
-
this.emitStderr(`session-decode metrics {framesDecoded:${this.framesDecoded}, framesSkipped:${skipped}, deliveredFps:${deliveredFps}}${final ? " (final)" : ""}\n`);
|
|
781
|
+
this.emitStderr(`session-decode metrics {framesDecoded:${this.framesDecoded}, framesSkipped:${skipped}, deliveredFps:${deliveredFps}, nativeCropHits:${this.nativeCropHits}, nativeCropMisses:${this.nativeCropMisses}}${final ? " (final)" : ""}\n`);
|
|
488
782
|
this.lastMetricsAt = now;
|
|
489
783
|
this.lastDeliveredSnapshot = this.framesDelivered;
|
|
490
784
|
}
|
|
@@ -534,6 +828,207 @@ var DecodeWorkerChild = class {
|
|
|
534
828
|
this.sendError(`decode-worker-child: toBuffer failed — ${errMessage(err)}`, frameId);
|
|
535
829
|
}
|
|
536
830
|
}
|
|
831
|
+
/**
|
|
832
|
+
* Best-effort NATIVE-resolution crop of a still-retained frame. Resolves the
|
|
833
|
+
* frame from the reserved slot (the latest, most common case) or the
|
|
834
|
+
* retention ring (an older frame detection has since moved past). A miss
|
|
835
|
+
* (frame aged out) or ANY crop error replies `nativeCropMiss` so the caller
|
|
836
|
+
* falls back to today's detection-frame crop — it NEVER degrades the
|
|
837
|
+
* detection session (unlike {@link hwScaleToBuffer}, which flips the whole
|
|
838
|
+
* session to software on a GPU-filter error).
|
|
839
|
+
*/
|
|
840
|
+
handleNativeCrop(requestId, frameId, bbox, maxWidth) {
|
|
841
|
+
const frame = this.leaseStore.get(frameId)?.frame ?? this.resolveRetainedFrame(frameId);
|
|
842
|
+
if (!frame) {
|
|
843
|
+
this.nativeCropMisses++;
|
|
844
|
+
this.send({
|
|
845
|
+
kind: "nativeCropMiss",
|
|
846
|
+
requestId
|
|
847
|
+
});
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
try {
|
|
851
|
+
const resolved = this.resolveNativeCrop(frame, bbox, maxWidth);
|
|
852
|
+
const bytes = frame.isHwFrame() ? this.nativeHwCropToBuffer(frame, resolved) : this.nativeScaleCropToBuffer(frame, resolved);
|
|
853
|
+
this.nativeCropHits++;
|
|
854
|
+
this.send({
|
|
855
|
+
kind: "nativeCropResult",
|
|
856
|
+
requestId,
|
|
857
|
+
bytes,
|
|
858
|
+
width: resolved.target.width,
|
|
859
|
+
height: resolved.target.height
|
|
860
|
+
});
|
|
861
|
+
} catch (err) {
|
|
862
|
+
this.nativeCropMisses++;
|
|
863
|
+
this.emitStderr(`decode-worker-child: native crop failed (requestId ${requestId}) — ${errMessage(err)}\n`);
|
|
864
|
+
this.send({
|
|
865
|
+
kind: "nativeCropMiss",
|
|
866
|
+
requestId
|
|
867
|
+
});
|
|
868
|
+
}
|
|
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
|
+
}
|
|
932
|
+
/** Map a normalized bbox to an even-aligned pixel crop + (optionally capped) target. */
|
|
933
|
+
resolveNativeCrop(frame, bbox, maxWidth) {
|
|
934
|
+
return resolveNativeCropGeometry(frame.width, frame.height, bbox, maxWidth);
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Software native crop — the leak-free path: offset the retained YUV420P
|
|
938
|
+
* frame's plane pointers to the crop origin (zero-copy) and scale ONLY the
|
|
939
|
+
* ROI into a tightly-packed rgb buffer via a DEDICATED scaler (never the
|
|
940
|
+
* detection {@link scaler}). Mirrors {@link scaleRoiToBuffer}, rgb-fixed.
|
|
941
|
+
*/
|
|
942
|
+
nativeScaleCropToBuffer(frame, resolved) {
|
|
943
|
+
const nav = this.nav;
|
|
944
|
+
const C = this.consts;
|
|
945
|
+
if (!nav || !C) throw new Error("node-av not initialized");
|
|
946
|
+
const { region, target } = resolved;
|
|
947
|
+
const planes = frame.data;
|
|
948
|
+
const strides = frame.linesize;
|
|
949
|
+
const yPlane = planes?.[0];
|
|
950
|
+
const uPlane = planes?.[1];
|
|
951
|
+
const vPlane = planes?.[2];
|
|
952
|
+
if (!yPlane || !uPlane || !vPlane) throw new Error("decode-worker-child: retained frame missing planar YUV420P data");
|
|
953
|
+
const yStride = strides[0] ?? 0;
|
|
954
|
+
const cStride = strides[1] ?? 0;
|
|
955
|
+
const ySlice = yPlane.subarray(region.top * yStride + region.left);
|
|
956
|
+
const uSlice = uPlane.subarray(region.top / 2 * cStride + region.left / 2);
|
|
957
|
+
const vSlice = vPlane.subarray(region.top / 2 * cStride + region.left / 2);
|
|
958
|
+
const scaler = this.ensureNativeCropScaler(nav, C, region.width, region.height, target.width, target.height);
|
|
959
|
+
const dstStride = target.width * 3;
|
|
960
|
+
const dstBuffer = Buffer.allocUnsafe(dstStride * target.height);
|
|
961
|
+
const scaledHeight = scaler.scaleSync([
|
|
962
|
+
ySlice,
|
|
963
|
+
uSlice,
|
|
964
|
+
vSlice
|
|
965
|
+
], [
|
|
966
|
+
yStride,
|
|
967
|
+
cStride,
|
|
968
|
+
cStride
|
|
969
|
+
], 0, region.height, [dstBuffer], [dstStride]);
|
|
970
|
+
if (scaledHeight < 0) throw new Error(`native crop sws_scale failed: ${scaledHeight}`);
|
|
971
|
+
if (scaledHeight !== target.height) throw new Error(`native crop scaler produced ${scaledHeight} of ${target.height} rows`);
|
|
972
|
+
return dstBuffer;
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Hardware native crop — run the retained GPU surface through a DEDICATED
|
|
976
|
+
* crop filtergraph (`hwdownload,format=nv12,crop=…,scale=…,format=rgb24`) and
|
|
977
|
+
* pack the small result. Mirrors {@link hwScaleToBuffer} but uses its OWN
|
|
978
|
+
* cached filter and, on error, throws WITHOUT degrading the session.
|
|
979
|
+
*/
|
|
980
|
+
nativeHwCropToBuffer(frame, resolved) {
|
|
981
|
+
const { region, target } = resolved;
|
|
982
|
+
const outputs = this.ensureNativeCropHwFilter(region, target).processAllSync(frame);
|
|
983
|
+
const first = outputs[0];
|
|
984
|
+
if (!first) {
|
|
985
|
+
for (const out of outputs) out.free();
|
|
986
|
+
throw new Error("native crop GPU filtergraph produced no frame");
|
|
987
|
+
}
|
|
988
|
+
try {
|
|
989
|
+
const plane = first.data?.[0];
|
|
990
|
+
if (!plane) throw new Error("native crop GPU frame missing packed plane data");
|
|
991
|
+
return packSinglePlane(plane, first.linesize[0] ?? 0, target.width, target.height, 3);
|
|
992
|
+
} finally {
|
|
993
|
+
for (const out of outputs) out.free();
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
/** Build-once / reuse the dedicated native-crop software scaler (rgb out). */
|
|
997
|
+
ensureNativeCropScaler(nav, C, srcW, srcH, dstW, dstH) {
|
|
998
|
+
const key = `${srcW}x${srcH}->${dstW}x${dstH}`;
|
|
999
|
+
const cached = this.nativeCropScaler;
|
|
1000
|
+
if (cached && key === this.nativeCropScalerKey) return cached;
|
|
1001
|
+
this.nativeCropScaler?.[Symbol.dispose]?.();
|
|
1002
|
+
this.nativeCropScaler = null;
|
|
1003
|
+
this.nativeCropScalerKey = "";
|
|
1004
|
+
const scaler = new nav.SoftwareScaleContext();
|
|
1005
|
+
scaler.getContext(srcW, srcH, C.AV_PIX_FMT_YUV420P, dstW, dstH, C.AV_PIX_FMT_RGB24, C.SWS_FAST_BILINEAR);
|
|
1006
|
+
const initRet = scaler.initContext();
|
|
1007
|
+
if (initRet < 0) {
|
|
1008
|
+
scaler[Symbol.dispose]?.();
|
|
1009
|
+
throw new Error(`native crop sws_init_context failed: ${initRet}`);
|
|
1010
|
+
}
|
|
1011
|
+
this.nativeCropScaler = scaler;
|
|
1012
|
+
this.nativeCropScalerKey = key;
|
|
1013
|
+
return scaler;
|
|
1014
|
+
}
|
|
1015
|
+
/** Build-once / reuse the dedicated native-crop GPU filtergraph (rgb out). */
|
|
1016
|
+
ensureNativeCropHwFilter(region, target) {
|
|
1017
|
+
const nav = this.nav;
|
|
1018
|
+
const scaleFilter = this.gpuScaleFilter;
|
|
1019
|
+
if (!nav || !scaleFilter || !this.hwContext) throw new Error("decode-worker-child: native GPU crop requested without a HW context");
|
|
1020
|
+
const key = `${region.left},${region.top},${region.width},${region.height}->${target.width}x${target.height}`;
|
|
1021
|
+
const cached = this.nativeCropHwFilter;
|
|
1022
|
+
if (cached && key === this.nativeCropHwFilterKey) return cached;
|
|
1023
|
+
this.nativeCropHwFilter?.close();
|
|
1024
|
+
this.nativeCropHwFilter = null;
|
|
1025
|
+
this.nativeCropHwFilterKey = "";
|
|
1026
|
+
const description = buildGpuFilterDescription(scaleFilter, region, target, false, "rgb24");
|
|
1027
|
+
const filter = nav.FilterAPI.create(description, { hardware: this.hwContext });
|
|
1028
|
+
this.nativeCropHwFilter = filter;
|
|
1029
|
+
this.nativeCropHwFilterKey = key;
|
|
1030
|
+
return filter;
|
|
1031
|
+
}
|
|
537
1032
|
handleStop() {
|
|
538
1033
|
this.teardown();
|
|
539
1034
|
process.exit(0);
|
|
@@ -887,6 +1382,14 @@ var DecodeWorkerChild = class {
|
|
|
887
1382
|
* the next dial's first `toBuffer`.
|
|
888
1383
|
*/
|
|
889
1384
|
closeInput() {
|
|
1385
|
+
this.nativeRing.clear();
|
|
1386
|
+
this.leaseStore.clear();
|
|
1387
|
+
this.nativeLeaseDownloadFilter?.close();
|
|
1388
|
+
this.nativeLeaseDownloadFilter = null;
|
|
1389
|
+
this.nativeLeaseDownloadFilterKey = "";
|
|
1390
|
+
this.nativeCropHwFilter?.close();
|
|
1391
|
+
this.nativeCropHwFilter = null;
|
|
1392
|
+
this.nativeCropHwFilterKey = "";
|
|
890
1393
|
this.hwFilter?.close();
|
|
891
1394
|
this.hwFilter = null;
|
|
892
1395
|
this.hwFilterKey = "";
|