@camstack/addon-pipeline 1.2.237 → 1.2.239
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.
|
@@ -5791,19 +5791,27 @@ var PipelineRunner = class {
|
|
|
5791
5791
|
if (entry.frame.frameRef) this.config.releaseFrameRef?.(entry.frame.frameRef, "drop");
|
|
5792
5792
|
});
|
|
5793
5793
|
const initialPhase = registration.detectionMode === "disabled" ? "idle" : registration.detectionMode === "always-on" ? "active" : "watching";
|
|
5794
|
+
const preRollQueue = new PreRollLane({
|
|
5795
|
+
capacity: this.config.maxPreRollQueueDepth ?? 12,
|
|
5796
|
+
clone: ownDetectionEntry,
|
|
5797
|
+
dispose: (entry) => {
|
|
5798
|
+
if (entry.handle) this.config.releaseRetention?.(entry.handle, "drop");
|
|
5799
|
+
if (entry.frame.frameRef) this.config.releaseFrameRef?.(entry.frame.frameRef, "drop");
|
|
5800
|
+
}
|
|
5801
|
+
});
|
|
5802
|
+
this.logger?.info("camera registered: root detectors", {
|
|
5803
|
+
tags: { deviceId },
|
|
5804
|
+
meta: {
|
|
5805
|
+
detectionSources: registration.detectionSources ?? null,
|
|
5806
|
+
...planDetectionSources(registration.detectionSources)
|
|
5807
|
+
}
|
|
5808
|
+
});
|
|
5794
5809
|
const state = {
|
|
5795
5810
|
registration,
|
|
5796
5811
|
onboardSlot: null,
|
|
5797
5812
|
motionQueue,
|
|
5798
5813
|
detectionQueue,
|
|
5799
|
-
preRollQueue
|
|
5800
|
-
capacity: this.config.maxPreRollQueueDepth ?? 12,
|
|
5801
|
-
clone: ownDetectionEntry,
|
|
5802
|
-
dispose: (entry) => {
|
|
5803
|
-
if (entry.handle) this.config.releaseRetention?.(entry.handle, "drop");
|
|
5804
|
-
if (entry.frame.frameRef) this.config.releaseFrameRef?.(entry.frame.frameRef, "drop");
|
|
5805
|
-
}
|
|
5806
|
-
}),
|
|
5814
|
+
preRollQueue,
|
|
5807
5815
|
inferenceTimes: [],
|
|
5808
5816
|
shedFrames: 0,
|
|
5809
5817
|
inflight: 0,
|
|
@@ -5783,19 +5783,27 @@ var PipelineRunner = class {
|
|
|
5783
5783
|
if (entry.frame.frameRef) this.config.releaseFrameRef?.(entry.frame.frameRef, "drop");
|
|
5784
5784
|
});
|
|
5785
5785
|
const initialPhase = registration.detectionMode === "disabled" ? "idle" : registration.detectionMode === "always-on" ? "active" : "watching";
|
|
5786
|
+
const preRollQueue = new PreRollLane({
|
|
5787
|
+
capacity: this.config.maxPreRollQueueDepth ?? 12,
|
|
5788
|
+
clone: ownDetectionEntry,
|
|
5789
|
+
dispose: (entry) => {
|
|
5790
|
+
if (entry.handle) this.config.releaseRetention?.(entry.handle, "drop");
|
|
5791
|
+
if (entry.frame.frameRef) this.config.releaseFrameRef?.(entry.frame.frameRef, "drop");
|
|
5792
|
+
}
|
|
5793
|
+
});
|
|
5794
|
+
this.logger?.info("camera registered: root detectors", {
|
|
5795
|
+
tags: { deviceId },
|
|
5796
|
+
meta: {
|
|
5797
|
+
detectionSources: registration.detectionSources ?? null,
|
|
5798
|
+
...planDetectionSources(registration.detectionSources)
|
|
5799
|
+
}
|
|
5800
|
+
});
|
|
5786
5801
|
const state = {
|
|
5787
5802
|
registration,
|
|
5788
5803
|
onboardSlot: null,
|
|
5789
5804
|
motionQueue,
|
|
5790
5805
|
detectionQueue,
|
|
5791
|
-
preRollQueue
|
|
5792
|
-
capacity: this.config.maxPreRollQueueDepth ?? 12,
|
|
5793
|
-
clone: ownDetectionEntry,
|
|
5794
|
-
dispose: (entry) => {
|
|
5795
|
-
if (entry.handle) this.config.releaseRetention?.(entry.handle, "drop");
|
|
5796
|
-
if (entry.frame.frameRef) this.config.releaseFrameRef?.(entry.frame.frameRef, "drop");
|
|
5797
|
-
}
|
|
5798
|
-
}),
|
|
5806
|
+
preRollQueue,
|
|
5799
5807
|
inferenceTimes: [],
|
|
5800
5808
|
shedFrames: 0,
|
|
5801
5809
|
inflight: 0,
|
|
@@ -1323,6 +1323,126 @@ function resolveRgbCropSource(plane, planeStride, region) {
|
|
|
1323
1323
|
stride: tightStride
|
|
1324
1324
|
};
|
|
1325
1325
|
}
|
|
1326
|
+
var ADMITTED = { admitted: true };
|
|
1327
|
+
/**
|
|
1328
|
+
* Choose the media stride for one burst.
|
|
1329
|
+
*
|
|
1330
|
+
* `plannedFrames` is what the stride actually yields, so a caller can log the
|
|
1331
|
+
* intent next to the outcome — a budget that is never reached and a budget that
|
|
1332
|
+
* is always hit are different problems and used to read the same.
|
|
1333
|
+
*/
|
|
1334
|
+
function planPreRollBurst(input) {
|
|
1335
|
+
const span = Number.isFinite(input.spanMs) && input.spanMs > 0 ? input.spanMs : 0;
|
|
1336
|
+
const fps = Number.isFinite(input.detectionFps) && input.detectionFps > 0 ? input.detectionFps : 1;
|
|
1337
|
+
const maxFrames = Number.isFinite(input.maxFrames) && input.maxFrames > 0 ? input.maxFrames : 0;
|
|
1338
|
+
if (span === 0 || maxFrames === 0) return {
|
|
1339
|
+
strideMs: Number.POSITIVE_INFINITY,
|
|
1340
|
+
plannedFrames: 0,
|
|
1341
|
+
subsampled: false
|
|
1342
|
+
};
|
|
1343
|
+
const cadenceMs = 1e3 / fps;
|
|
1344
|
+
const budgetStrideMs = span / maxFrames;
|
|
1345
|
+
const subsampled = budgetStrideMs > cadenceMs;
|
|
1346
|
+
const strideMs = subsampled ? budgetStrideMs : cadenceMs;
|
|
1347
|
+
return {
|
|
1348
|
+
strideMs,
|
|
1349
|
+
plannedFrames: Math.min(maxFrames, Math.max(1, Math.floor(span / strideMs))),
|
|
1350
|
+
subsampled
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* The live, per-session budget. Stateful by necessity — a stride is a decision
|
|
1355
|
+
* about the PREVIOUS admission — and deliberately free of any clock of its own:
|
|
1356
|
+
* both times are passed in, so a test can drive a catch-up burst without
|
|
1357
|
+
* pretending 4 s of media takes 4 s to decode.
|
|
1358
|
+
*/
|
|
1359
|
+
var PreRollBurst = class {
|
|
1360
|
+
budget;
|
|
1361
|
+
plan;
|
|
1362
|
+
startedAtWall;
|
|
1363
|
+
/** Widest lead a frame's moment took over the wall clock — the SERVED span. */
|
|
1364
|
+
maxLeadMs = 0;
|
|
1365
|
+
lastAdmittedMediaWall = null;
|
|
1366
|
+
offered = 0;
|
|
1367
|
+
inferred = 0;
|
|
1368
|
+
done = false;
|
|
1369
|
+
drops = {
|
|
1370
|
+
stride: 0,
|
|
1371
|
+
"budget-frames": 0,
|
|
1372
|
+
"budget-wall-ms": 0,
|
|
1373
|
+
"live-edge": 0
|
|
1374
|
+
};
|
|
1375
|
+
constructor(input) {
|
|
1376
|
+
this.budget = input.budget;
|
|
1377
|
+
this.plan = input.plan;
|
|
1378
|
+
this.startedAtWall = input.startedAtWall;
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* How much history this burst actually carried, in ms of media — the number
|
|
1382
|
+
* the broker knows as `preRollSpanMs` and that never reached this process.
|
|
1383
|
+
*
|
|
1384
|
+
* `null` until a frame has led the wall clock: a burst that saw none did not
|
|
1385
|
+
* measure a short span, it measured NOTHING, and the difference is the whole
|
|
1386
|
+
* reason the anchor was left alone rather than fabricated (D393).
|
|
1387
|
+
*/
|
|
1388
|
+
servedSpanMs() {
|
|
1389
|
+
return this.maxLeadMs > 0 ? this.maxLeadMs : null;
|
|
1390
|
+
}
|
|
1391
|
+
/** True once the burst has ended — by budget, or by reaching the live edge. */
|
|
1392
|
+
get finished() {
|
|
1393
|
+
return this.done;
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Offer one decoded history frame.
|
|
1397
|
+
*
|
|
1398
|
+
* `mediaWall` is the frame's OWN moment (see `media-clock.ts`); `nowWall` is
|
|
1399
|
+
* the wall clock of the offer. The two are different by construction during
|
|
1400
|
+
* catch-up, which is the whole reason this class exists.
|
|
1401
|
+
*/
|
|
1402
|
+
offer(mediaWall, nowWall) {
|
|
1403
|
+
this.offered++;
|
|
1404
|
+
if (this.done) return this.drop("budget-frames");
|
|
1405
|
+
const leadMs = mediaWall - nowWall;
|
|
1406
|
+
if (this.maxLeadMs > 0 && leadMs <= 100) {
|
|
1407
|
+
this.done = true;
|
|
1408
|
+
return this.drop("live-edge");
|
|
1409
|
+
}
|
|
1410
|
+
if (leadMs > this.maxLeadMs) this.maxLeadMs = leadMs;
|
|
1411
|
+
if (nowWall - this.startedAtWall >= this.budget.maxWallMs) {
|
|
1412
|
+
this.done = true;
|
|
1413
|
+
return this.drop("budget-wall-ms");
|
|
1414
|
+
}
|
|
1415
|
+
if (this.inferred >= this.budget.maxFrames) {
|
|
1416
|
+
this.done = true;
|
|
1417
|
+
return this.drop("budget-frames");
|
|
1418
|
+
}
|
|
1419
|
+
if (this.lastAdmittedMediaWall !== null && mediaWall - this.lastAdmittedMediaWall < this.plan.strideMs) return this.drop("stride");
|
|
1420
|
+
this.lastAdmittedMediaWall = mediaWall;
|
|
1421
|
+
this.inferred++;
|
|
1422
|
+
return ADMITTED;
|
|
1423
|
+
}
|
|
1424
|
+
/** End the burst without offering a frame (session torn down, decode ended). */
|
|
1425
|
+
finish() {
|
|
1426
|
+
this.done = true;
|
|
1427
|
+
}
|
|
1428
|
+
report() {
|
|
1429
|
+
return {
|
|
1430
|
+
offered: this.offered,
|
|
1431
|
+
inferred: this.inferred,
|
|
1432
|
+
dropped: this.offered - this.inferred,
|
|
1433
|
+
droppedByReason: { ...this.drops },
|
|
1434
|
+
strideMs: Math.round(this.plan.strideMs),
|
|
1435
|
+
subsampled: this.plan.subsampled
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
drop(reason) {
|
|
1439
|
+
this.drops[reason]++;
|
|
1440
|
+
return {
|
|
1441
|
+
admitted: false,
|
|
1442
|
+
reason
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1326
1446
|
//#endregion
|
|
1327
1447
|
//#region src/session-decode/pull-demuxer-open.ts
|
|
1328
1448
|
/**
|
|
@@ -1577,6 +1697,18 @@ var REDIAL_MS = 3e3;
|
|
|
1577
1697
|
/** How often the worker emits its `framesDecoded/framesSkipped/deliveredFps` line. */
|
|
1578
1698
|
var METRICS_INTERVAL_MS = 1e4;
|
|
1579
1699
|
/**
|
|
1700
|
+
* Wall-clock ceiling on one pre-roll catch-up burst.
|
|
1701
|
+
*
|
|
1702
|
+
* A burst is a race against the live edge it is trying to get ahead of. Two
|
|
1703
|
+
* seconds in, the decoder has either caught up (the burst ended on its own) or
|
|
1704
|
+
* something is wrong — a cold GPU, a node under load, a camera whose GOP is
|
|
1705
|
+
* longer than its history — and continuing to spend inference on footage that
|
|
1706
|
+
* is now three or four seconds stale buys an operator nothing while a live
|
|
1707
|
+
* frame waits. The frame budget bounds the WORK; this bounds the WINDOW in
|
|
1708
|
+
* which that work is still worth doing.
|
|
1709
|
+
*/
|
|
1710
|
+
var PRE_ROLL_BURST_MAX_WALL_MS = 2e3;
|
|
1711
|
+
/**
|
|
1580
1712
|
* Hard cap on the native-frame retention ring (native-res crop feature). Kept
|
|
1581
1713
|
* DELIBERATELY TINY — its only job is to cover the detection→crop latency
|
|
1582
1714
|
* (typically 1-4 frames), NOT the full shm ring depth. On the HW path every
|
|
@@ -3290,13 +3422,26 @@ var DecodeWorkerChild = class {
|
|
|
3290
3422
|
const ptsMs = this.frameMediaPtsMs(frame);
|
|
3291
3423
|
if (ptsMs === null) return now;
|
|
3292
3424
|
if (this.mediaClock === null || this.mediaClock.dialSeq !== this.dialSeq) {
|
|
3293
|
-
const servedPreRollMs = 0;
|
|
3294
|
-
if (this.preRollMs > 0) this.emitStderr(`[decode] pre-roll requested ${this.preRollMs}ms but the SERVED span does not reach this worker — burst suppressed, clock anchored at now (D411 follow-up)`);
|
|
3295
3425
|
this.mediaClock = mediaClockForDial(this.mediaClock, this.dialSeq, {
|
|
3296
3426
|
firstPtsMs: ptsMs,
|
|
3297
|
-
preRollMs:
|
|
3427
|
+
preRollMs: 0,
|
|
3298
3428
|
nowWall: now
|
|
3299
3429
|
});
|
|
3430
|
+
if (this.preRollMs > 0 && this.preRollMaxFrames > 0) {
|
|
3431
|
+
const preRollCadenceMs = this.minIntervalMs > 0 ? this.minIntervalMs : 1e3;
|
|
3432
|
+
this.preRollBurst = new PreRollBurst({
|
|
3433
|
+
budget: {
|
|
3434
|
+
maxFrames: this.preRollMaxFrames,
|
|
3435
|
+
maxWallMs: PRE_ROLL_BURST_MAX_WALL_MS
|
|
3436
|
+
},
|
|
3437
|
+
plan: planPreRollBurst({
|
|
3438
|
+
spanMs: this.preRollMaxFrames * preRollCadenceMs,
|
|
3439
|
+
detectionFps: 1e3 / preRollCadenceMs,
|
|
3440
|
+
maxFrames: this.preRollMaxFrames
|
|
3441
|
+
}),
|
|
3442
|
+
startedAtWall: now
|
|
3443
|
+
});
|
|
3444
|
+
}
|
|
3300
3445
|
}
|
|
3301
3446
|
return mediaWallClockOf(this.mediaClock.anchor, ptsMs, now);
|
|
3302
3447
|
}
|
|
@@ -1322,6 +1322,126 @@ function resolveRgbCropSource(plane, planeStride, region) {
|
|
|
1322
1322
|
stride: tightStride
|
|
1323
1323
|
};
|
|
1324
1324
|
}
|
|
1325
|
+
var ADMITTED = { admitted: true };
|
|
1326
|
+
/**
|
|
1327
|
+
* Choose the media stride for one burst.
|
|
1328
|
+
*
|
|
1329
|
+
* `plannedFrames` is what the stride actually yields, so a caller can log the
|
|
1330
|
+
* intent next to the outcome — a budget that is never reached and a budget that
|
|
1331
|
+
* is always hit are different problems and used to read the same.
|
|
1332
|
+
*/
|
|
1333
|
+
function planPreRollBurst(input) {
|
|
1334
|
+
const span = Number.isFinite(input.spanMs) && input.spanMs > 0 ? input.spanMs : 0;
|
|
1335
|
+
const fps = Number.isFinite(input.detectionFps) && input.detectionFps > 0 ? input.detectionFps : 1;
|
|
1336
|
+
const maxFrames = Number.isFinite(input.maxFrames) && input.maxFrames > 0 ? input.maxFrames : 0;
|
|
1337
|
+
if (span === 0 || maxFrames === 0) return {
|
|
1338
|
+
strideMs: Number.POSITIVE_INFINITY,
|
|
1339
|
+
plannedFrames: 0,
|
|
1340
|
+
subsampled: false
|
|
1341
|
+
};
|
|
1342
|
+
const cadenceMs = 1e3 / fps;
|
|
1343
|
+
const budgetStrideMs = span / maxFrames;
|
|
1344
|
+
const subsampled = budgetStrideMs > cadenceMs;
|
|
1345
|
+
const strideMs = subsampled ? budgetStrideMs : cadenceMs;
|
|
1346
|
+
return {
|
|
1347
|
+
strideMs,
|
|
1348
|
+
plannedFrames: Math.min(maxFrames, Math.max(1, Math.floor(span / strideMs))),
|
|
1349
|
+
subsampled
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* The live, per-session budget. Stateful by necessity — a stride is a decision
|
|
1354
|
+
* about the PREVIOUS admission — and deliberately free of any clock of its own:
|
|
1355
|
+
* both times are passed in, so a test can drive a catch-up burst without
|
|
1356
|
+
* pretending 4 s of media takes 4 s to decode.
|
|
1357
|
+
*/
|
|
1358
|
+
var PreRollBurst = class {
|
|
1359
|
+
budget;
|
|
1360
|
+
plan;
|
|
1361
|
+
startedAtWall;
|
|
1362
|
+
/** Widest lead a frame's moment took over the wall clock — the SERVED span. */
|
|
1363
|
+
maxLeadMs = 0;
|
|
1364
|
+
lastAdmittedMediaWall = null;
|
|
1365
|
+
offered = 0;
|
|
1366
|
+
inferred = 0;
|
|
1367
|
+
done = false;
|
|
1368
|
+
drops = {
|
|
1369
|
+
stride: 0,
|
|
1370
|
+
"budget-frames": 0,
|
|
1371
|
+
"budget-wall-ms": 0,
|
|
1372
|
+
"live-edge": 0
|
|
1373
|
+
};
|
|
1374
|
+
constructor(input) {
|
|
1375
|
+
this.budget = input.budget;
|
|
1376
|
+
this.plan = input.plan;
|
|
1377
|
+
this.startedAtWall = input.startedAtWall;
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* How much history this burst actually carried, in ms of media — the number
|
|
1381
|
+
* the broker knows as `preRollSpanMs` and that never reached this process.
|
|
1382
|
+
*
|
|
1383
|
+
* `null` until a frame has led the wall clock: a burst that saw none did not
|
|
1384
|
+
* measure a short span, it measured NOTHING, and the difference is the whole
|
|
1385
|
+
* reason the anchor was left alone rather than fabricated (D393).
|
|
1386
|
+
*/
|
|
1387
|
+
servedSpanMs() {
|
|
1388
|
+
return this.maxLeadMs > 0 ? this.maxLeadMs : null;
|
|
1389
|
+
}
|
|
1390
|
+
/** True once the burst has ended — by budget, or by reaching the live edge. */
|
|
1391
|
+
get finished() {
|
|
1392
|
+
return this.done;
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Offer one decoded history frame.
|
|
1396
|
+
*
|
|
1397
|
+
* `mediaWall` is the frame's OWN moment (see `media-clock.ts`); `nowWall` is
|
|
1398
|
+
* the wall clock of the offer. The two are different by construction during
|
|
1399
|
+
* catch-up, which is the whole reason this class exists.
|
|
1400
|
+
*/
|
|
1401
|
+
offer(mediaWall, nowWall) {
|
|
1402
|
+
this.offered++;
|
|
1403
|
+
if (this.done) return this.drop("budget-frames");
|
|
1404
|
+
const leadMs = mediaWall - nowWall;
|
|
1405
|
+
if (this.maxLeadMs > 0 && leadMs <= 100) {
|
|
1406
|
+
this.done = true;
|
|
1407
|
+
return this.drop("live-edge");
|
|
1408
|
+
}
|
|
1409
|
+
if (leadMs > this.maxLeadMs) this.maxLeadMs = leadMs;
|
|
1410
|
+
if (nowWall - this.startedAtWall >= this.budget.maxWallMs) {
|
|
1411
|
+
this.done = true;
|
|
1412
|
+
return this.drop("budget-wall-ms");
|
|
1413
|
+
}
|
|
1414
|
+
if (this.inferred >= this.budget.maxFrames) {
|
|
1415
|
+
this.done = true;
|
|
1416
|
+
return this.drop("budget-frames");
|
|
1417
|
+
}
|
|
1418
|
+
if (this.lastAdmittedMediaWall !== null && mediaWall - this.lastAdmittedMediaWall < this.plan.strideMs) return this.drop("stride");
|
|
1419
|
+
this.lastAdmittedMediaWall = mediaWall;
|
|
1420
|
+
this.inferred++;
|
|
1421
|
+
return ADMITTED;
|
|
1422
|
+
}
|
|
1423
|
+
/** End the burst without offering a frame (session torn down, decode ended). */
|
|
1424
|
+
finish() {
|
|
1425
|
+
this.done = true;
|
|
1426
|
+
}
|
|
1427
|
+
report() {
|
|
1428
|
+
return {
|
|
1429
|
+
offered: this.offered,
|
|
1430
|
+
inferred: this.inferred,
|
|
1431
|
+
dropped: this.offered - this.inferred,
|
|
1432
|
+
droppedByReason: { ...this.drops },
|
|
1433
|
+
strideMs: Math.round(this.plan.strideMs),
|
|
1434
|
+
subsampled: this.plan.subsampled
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
drop(reason) {
|
|
1438
|
+
this.drops[reason]++;
|
|
1439
|
+
return {
|
|
1440
|
+
admitted: false,
|
|
1441
|
+
reason
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1325
1445
|
//#endregion
|
|
1326
1446
|
//#region src/session-decode/pull-demuxer-open.ts
|
|
1327
1447
|
/**
|
|
@@ -1576,6 +1696,18 @@ var REDIAL_MS = 3e3;
|
|
|
1576
1696
|
/** How often the worker emits its `framesDecoded/framesSkipped/deliveredFps` line. */
|
|
1577
1697
|
var METRICS_INTERVAL_MS = 1e4;
|
|
1578
1698
|
/**
|
|
1699
|
+
* Wall-clock ceiling on one pre-roll catch-up burst.
|
|
1700
|
+
*
|
|
1701
|
+
* A burst is a race against the live edge it is trying to get ahead of. Two
|
|
1702
|
+
* seconds in, the decoder has either caught up (the burst ended on its own) or
|
|
1703
|
+
* something is wrong — a cold GPU, a node under load, a camera whose GOP is
|
|
1704
|
+
* longer than its history — and continuing to spend inference on footage that
|
|
1705
|
+
* is now three or four seconds stale buys an operator nothing while a live
|
|
1706
|
+
* frame waits. The frame budget bounds the WORK; this bounds the WINDOW in
|
|
1707
|
+
* which that work is still worth doing.
|
|
1708
|
+
*/
|
|
1709
|
+
var PRE_ROLL_BURST_MAX_WALL_MS = 2e3;
|
|
1710
|
+
/**
|
|
1579
1711
|
* Hard cap on the native-frame retention ring (native-res crop feature). Kept
|
|
1580
1712
|
* DELIBERATELY TINY — its only job is to cover the detection→crop latency
|
|
1581
1713
|
* (typically 1-4 frames), NOT the full shm ring depth. On the HW path every
|
|
@@ -3289,13 +3421,26 @@ var DecodeWorkerChild = class {
|
|
|
3289
3421
|
const ptsMs = this.frameMediaPtsMs(frame);
|
|
3290
3422
|
if (ptsMs === null) return now;
|
|
3291
3423
|
if (this.mediaClock === null || this.mediaClock.dialSeq !== this.dialSeq) {
|
|
3292
|
-
const servedPreRollMs = 0;
|
|
3293
|
-
if (this.preRollMs > 0) this.emitStderr(`[decode] pre-roll requested ${this.preRollMs}ms but the SERVED span does not reach this worker — burst suppressed, clock anchored at now (D411 follow-up)`);
|
|
3294
3424
|
this.mediaClock = mediaClockForDial(this.mediaClock, this.dialSeq, {
|
|
3295
3425
|
firstPtsMs: ptsMs,
|
|
3296
|
-
preRollMs:
|
|
3426
|
+
preRollMs: 0,
|
|
3297
3427
|
nowWall: now
|
|
3298
3428
|
});
|
|
3429
|
+
if (this.preRollMs > 0 && this.preRollMaxFrames > 0) {
|
|
3430
|
+
const preRollCadenceMs = this.minIntervalMs > 0 ? this.minIntervalMs : 1e3;
|
|
3431
|
+
this.preRollBurst = new PreRollBurst({
|
|
3432
|
+
budget: {
|
|
3433
|
+
maxFrames: this.preRollMaxFrames,
|
|
3434
|
+
maxWallMs: PRE_ROLL_BURST_MAX_WALL_MS
|
|
3435
|
+
},
|
|
3436
|
+
plan: planPreRollBurst({
|
|
3437
|
+
spanMs: this.preRollMaxFrames * preRollCadenceMs,
|
|
3438
|
+
detectionFps: 1e3 / preRollCadenceMs,
|
|
3439
|
+
maxFrames: this.preRollMaxFrames
|
|
3440
|
+
}),
|
|
3441
|
+
startedAtWall: now
|
|
3442
|
+
});
|
|
3443
|
+
}
|
|
3299
3444
|
}
|
|
3300
3445
|
return mediaWallClockOf(this.mediaClock.anchor, ptsMs, now);
|
|
3301
3446
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.239",
|
|
4
4
|
"description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|