@camstack/addon-pipeline 1.1.32 → 1.1.33
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 +2 -2
- package/dist/audio-analyzer/index.mjs +2 -2
- package/dist/audio-codec-ffmpeg/index.js +118 -3
- package/dist/audio-codec-ffmpeg/index.mjs +118 -3
- package/dist/decoder-ffmpeg/index.js +71 -6
- package/dist/decoder-ffmpeg/index.mjs +71 -6
- package/dist/decoder-nodeav/index.js +209 -9
- package/dist/decoder-nodeav/index.mjs +209 -9
- package/dist/detection-pipeline/index.js +1 -1
- package/dist/detection-pipeline/index.mjs +1 -1
- package/dist/{dist-Cwc0TUQr.js → dist-BrIE85BG.js} +38 -1
- package/dist/{dist-DjuGmyG9.mjs → dist-CfSORkv2.mjs} +38 -1
- package/dist/{ffmpeg-args-C5GPp8Cw.mjs → ffmpeg-args-CpeRWwL4.mjs} +7 -4
- package/dist/{ffmpeg-args-D6h1edXK.js → ffmpeg-args-Dl9mLcOC.js} +7 -4
- package/dist/{frame-handle-plane-Bkxz-TTD.mjs → frame-handle-plane-C05rq9Zs.mjs} +108 -17
- package/dist/{frame-handle-plane-DQNCTrpC.js → frame-handle-plane-zMju9vmk.js} +108 -17
- package/dist/{frame-ring-sink-DvdhCFr3.js → frame-ring-sink-B-DxHelU.js} +74 -25
- package/dist/{frame-ring-sink-sY-8XUsH.mjs → frame-ring-sink-B_WjDX0x.mjs} +69 -26
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +86 -16
- package/dist/pipeline-runner/index.mjs +85 -17
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.mjs +1 -1
- package/dist/stream-broker/_stub.js +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D567z31g.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DPFAbcUo.mjs} +3 -3
- package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DtoYGpTp.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BsrrdOBU.mjs} +1 -1
- package/dist/stream-broker/{hostInit-B_b3PIZB.mjs → hostInit-C8slrxw1.mjs} +3 -3
- package/dist/stream-broker/index.js +165 -27
- package/dist/stream-broker/index.mjs +165 -27
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-x-DZOuQL.js → MaskShapeCanvas-DI4BY7W2-WUVfFzT-.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-DTeuDGCU.js → MotionZonesSettings-NcxxQN8r-CF7FXXLs.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-CirtxO3e.js → PrivacyMaskSettings-APgPLF7p-qQCpVob3.js} +1 -1
- package/embed-dist/assets/{index-CpUy8OIE.js → index-BNhisVUZ.js} +4 -4
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
- package/python/__pycache__/inference_pool.cpython-313.pyc +0 -0
- package/python/__pycache__/inference_pool.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_backpressure.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_coreml_cache.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_device_selection.cpython-314.pyc +0 -0
- package/python/__pycache__/yamnet_audio.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/_safety.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/clip.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/test_clip.cpython-313-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_saliency.cpython-313-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-313.pyc +0 -0
- package/dist/constants-CAqYVigN.js +0 -6615
- package/dist/constants-oYgW36Ty.mjs +0 -3587
- package/dist/dist-D3Ytt-_Y.js +0 -41111
- package/dist/dist-DluJuxV5.mjs +0 -37976
|
@@ -2,9 +2,9 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
6
|
-
const require_frame_ring_sink = require("../frame-ring-sink-
|
|
7
|
-
const require_ffmpeg_args = require("../ffmpeg-args-
|
|
5
|
+
const require_dist = require("../dist-BrIE85BG.js");
|
|
6
|
+
const require_frame_ring_sink = require("../frame-ring-sink-B-DxHelU.js");
|
|
7
|
+
const require_ffmpeg_args = require("../ffmpeg-args-Dl9mLcOC.js");
|
|
8
8
|
let _camstack_shm_ring = require("@camstack/shm-ring");
|
|
9
9
|
let node_crypto = require("node:crypto");
|
|
10
10
|
//#region src/decoder-nodeav/scaler-geometry.ts
|
|
@@ -22,6 +22,13 @@ function resolveScalerAction(current, incoming) {
|
|
|
22
22
|
}
|
|
23
23
|
//#endregion
|
|
24
24
|
//#region src/decoder-nodeav/nodeav-decoder-session.ts
|
|
25
|
+
/**
|
|
26
|
+
* Pull-mode re-dial backoff. On a pull input error/EOF the loop waits this long
|
|
27
|
+
* before re-opening the URL — mirrors the ffmpeg pull decoder's 3s re-dial so a
|
|
28
|
+
* transient RTSP restream blip (source suspended on idle, keyframe wait) does
|
|
29
|
+
* not hot-loop `Demuxer.open`.
|
|
30
|
+
*/
|
|
31
|
+
var PULL_REDIAL_MS = 3e3;
|
|
25
32
|
/** Map our canonical backend name to the node-av `AV_HWDEVICE_TYPE_*` constant. */
|
|
26
33
|
function backendToHwDeviceConst(backend, consts) {
|
|
27
34
|
switch (backend) {
|
|
@@ -58,11 +65,11 @@ var _nav = null;
|
|
|
58
65
|
var _consts = null;
|
|
59
66
|
var _sharp = null;
|
|
60
67
|
async function getNodeAv() {
|
|
61
|
-
if (!_nav) _nav = await
|
|
68
|
+
if (!_nav) _nav = await import("node-av");
|
|
62
69
|
return _nav;
|
|
63
70
|
}
|
|
64
71
|
async function getConstants() {
|
|
65
|
-
if (!_consts) _consts = await
|
|
72
|
+
if (!_consts) _consts = await import("node-av/constants");
|
|
66
73
|
return _consts;
|
|
67
74
|
}
|
|
68
75
|
async function getSharp() {
|
|
@@ -196,6 +203,20 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
|
|
|
196
203
|
activeHwAccel = "none";
|
|
197
204
|
hwDevice = null;
|
|
198
205
|
swTransferFrame = null;
|
|
206
|
+
/** True once `openStream` has armed the pull loop; drives `isPullMode`. */
|
|
207
|
+
pullActive = false;
|
|
208
|
+
/** One-shot guard so a stray push into a pull session logs once, not per-packet. */
|
|
209
|
+
pullStrayPushLogged = false;
|
|
210
|
+
/** The URL the pull loop (re)dials — updatable while active. */
|
|
211
|
+
pullUrl = null;
|
|
212
|
+
/** Pending re-dial timer + its resolver, so `destroy` can wake it instantly. */
|
|
213
|
+
pullRedialTimer = null;
|
|
214
|
+
pullRedialResolve = null;
|
|
215
|
+
/** High-level demuxer/decoder for the CURRENT dial — recreated per re-dial. */
|
|
216
|
+
pullDemuxer = null;
|
|
217
|
+
pullDecoder = null;
|
|
218
|
+
/** HW context — resolved once, reused across re-dials, freed in `destroy`. */
|
|
219
|
+
pullHwContext = null;
|
|
199
220
|
constructor(config, logger = noopLogger, options) {
|
|
200
221
|
this.config = { ...config };
|
|
201
222
|
const sessionTags = {};
|
|
@@ -465,8 +486,171 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
|
|
|
465
486
|
this.scalerInitializing = false;
|
|
466
487
|
}
|
|
467
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* Start PULL mode: libav dials `url` and demuxes it in-process, decoding
|
|
491
|
+
* every frame through the same emit path as push mode. Idempotent while
|
|
492
|
+
* active (updates the re-dial target). Throws if the session already took a
|
|
493
|
+
* push packet — a session is push-mode or pull-mode for its whole life.
|
|
494
|
+
*/
|
|
495
|
+
openStream(url) {
|
|
496
|
+
if (this.destroyed) return Promise.resolve();
|
|
497
|
+
if (this.inputPackets > 0 || this.initialized || this.initializing) throw new Error("node-av decoder: openStream on a push-fed session — a session is push-mode or pull-mode, never both");
|
|
498
|
+
if (this.pullActive) {
|
|
499
|
+
this.pullUrl = url;
|
|
500
|
+
return Promise.resolve();
|
|
501
|
+
}
|
|
502
|
+
this.pullActive = true;
|
|
503
|
+
this.pullUrl = url;
|
|
504
|
+
this.logger.info("node-av decoder: pull-mode openStream — dialing input URL", { meta: { url } });
|
|
505
|
+
this.runPullLoop().catch((err) => {
|
|
506
|
+
this.logger.error("node-av decoder: pull loop crashed", { meta: { error: require_dist.errMsg(err) } });
|
|
507
|
+
});
|
|
508
|
+
return Promise.resolve();
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* The pull-mode supervisor loop: resolve HW once, then repeatedly dial +
|
|
512
|
+
* decode the URL, re-dialing after {@link PULL_REDIAL_MS} on any exit/error
|
|
513
|
+
* until the session is destroyed. Each iteration rebuilds a fresh
|
|
514
|
+
* demuxer/decoder (the previous one is disposed in `closePullInput`).
|
|
515
|
+
*/
|
|
516
|
+
async runPullLoop() {
|
|
517
|
+
const nav = await getNodeAv();
|
|
518
|
+
const C = await getConstants();
|
|
519
|
+
this.EAGAIN = C.AVERROR_EAGAIN;
|
|
520
|
+
this.PIX_FMT_GRAY8 = C.AV_PIX_FMT_GRAY8;
|
|
521
|
+
this.PIX_FMT_RGB24 = C.AV_PIX_FMT_RGB24;
|
|
522
|
+
this.SWS_FAST_BILINEAR = C.SWS_FAST_BILINEAR;
|
|
523
|
+
nav.Log.setLevel(C.AV_LOG_FATAL);
|
|
524
|
+
if (this.outputMode === "jpeg" && !this.sharpFn) this.sharpFn = await getSharp();
|
|
525
|
+
await this.ensurePullHwContext(nav, C);
|
|
526
|
+
while (!this.destroyed && this.pullActive) {
|
|
527
|
+
const url = this.pullUrl;
|
|
528
|
+
if (!url) break;
|
|
529
|
+
try {
|
|
530
|
+
await this.pullDialAndDecode(nav, C, url);
|
|
531
|
+
} catch (err) {
|
|
532
|
+
if (this.destroyed) break;
|
|
533
|
+
this.logger.warn("node-av decoder: pull-mode dial ended — re-dial scheduled", { meta: {
|
|
534
|
+
error: require_dist.errMsg(err),
|
|
535
|
+
redialInMs: PULL_REDIAL_MS
|
|
536
|
+
} });
|
|
537
|
+
} finally {
|
|
538
|
+
this.closePullInput();
|
|
539
|
+
}
|
|
540
|
+
if (this.destroyed || !this.pullActive) break;
|
|
541
|
+
await this.pullSleep(PULL_REDIAL_MS);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* One dial: open the demuxer, pick the video stream, create a HW/SW decoder
|
|
546
|
+
* that downloads + normalises frames to YUV420P (so the shared scaler in
|
|
547
|
+
* `emitDecodedFrame` reads plain software planes regardless of hwaccel), and
|
|
548
|
+
* pump `demuxer.packets(video) → decoder.frames → emitDecodedFrame` until the
|
|
549
|
+
* stream ends or the session is torn down.
|
|
550
|
+
*/
|
|
551
|
+
async pullDialAndDecode(nav, C, url) {
|
|
552
|
+
const demuxer = await nav.Demuxer.open(url, { options: { rtsp_transport: "tcp" } });
|
|
553
|
+
if (this.destroyed || !this.pullActive) {
|
|
554
|
+
demuxer[Symbol.dispose]?.();
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
this.pullDemuxer = demuxer;
|
|
558
|
+
const videoStream = demuxer.video();
|
|
559
|
+
if (!videoStream) throw new Error("node-av decoder: pull input has no video stream");
|
|
560
|
+
const decoder = await nav.Decoder.create(videoStream, {
|
|
561
|
+
...this.pullHwContext ? { hardware: this.pullHwContext } : {},
|
|
562
|
+
rescale: { pixelFormat: C.AV_PIX_FMT_YUV420P }
|
|
563
|
+
});
|
|
564
|
+
if (this.destroyed || !this.pullActive) {
|
|
565
|
+
decoder[Symbol.dispose]?.();
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
this.pullDecoder = decoder;
|
|
569
|
+
this.logger.info("node-av pull decoder initialized", { meta: {
|
|
570
|
+
codec: this.config.codec,
|
|
571
|
+
output: this.outputMode,
|
|
572
|
+
hwAccel: this.activeHwAccel,
|
|
573
|
+
streamIndex: videoStream.index
|
|
574
|
+
} });
|
|
575
|
+
for await (const frame of decoder.frames(demuxer.packets(videoStream.index))) {
|
|
576
|
+
if (this.destroyed || !this.pullActive) break;
|
|
577
|
+
if (!frame) continue;
|
|
578
|
+
this.inputPackets++;
|
|
579
|
+
try {
|
|
580
|
+
this.emitDecodedFrame(frame);
|
|
581
|
+
} finally {
|
|
582
|
+
frame.free();
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Resolve and build the HW context for pull mode ONCE. Explicit backend →
|
|
588
|
+
* single try; `'auto'` → the kernel resolver's ordered list; no resolver →
|
|
589
|
+
* software. The first `HardwareContext.create` that succeeds wins; all
|
|
590
|
+
* failures fall through to software decode (`hardware: null`).
|
|
591
|
+
*/
|
|
592
|
+
async ensurePullHwContext(nav, C) {
|
|
593
|
+
if (this.hwaccelPref === "none") {
|
|
594
|
+
this.activeHwAccel = "none";
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const explicit = this.hwaccelPref === "auto" ? null : this.hwaccelPref;
|
|
598
|
+
const resolution = this.hwaccelResolver ? await this.hwaccelResolver.resolve(explicit) : explicit ? {
|
|
599
|
+
preferred: [explicit],
|
|
600
|
+
rationale: "explicit (no resolver)"
|
|
601
|
+
} : {
|
|
602
|
+
preferred: [],
|
|
603
|
+
rationale: "auto + no resolver → software"
|
|
604
|
+
};
|
|
605
|
+
for (const backend of resolution.preferred) {
|
|
606
|
+
const deviceType = backendToHwDeviceConst(backend, C);
|
|
607
|
+
if (!deviceType) continue;
|
|
608
|
+
const hw = nav.HardwareContext.create(deviceType);
|
|
609
|
+
if (!hw) {
|
|
610
|
+
this.logger.warn("node-av: pull hwaccel context create failed — trying next", { meta: { backend } });
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
this.pullHwContext = hw;
|
|
614
|
+
this.activeHwAccel = backend;
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
this.logger.warn("node-av: no pull hwaccel backend initialised — using software", { meta: {
|
|
618
|
+
attempted: resolution.preferred.join(","),
|
|
619
|
+
rationale: resolution.rationale
|
|
620
|
+
} });
|
|
621
|
+
this.activeHwAccel = "none";
|
|
622
|
+
}
|
|
623
|
+
/** Dispose the CURRENT dial's demuxer + decoder (HW context is reused). */
|
|
624
|
+
closePullInput() {
|
|
625
|
+
if (this.pullDecoder) {
|
|
626
|
+
this.pullDecoder[Symbol.dispose]?.();
|
|
627
|
+
this.pullDecoder = null;
|
|
628
|
+
}
|
|
629
|
+
if (this.pullDemuxer) {
|
|
630
|
+
this.pullDemuxer[Symbol.dispose]?.();
|
|
631
|
+
this.pullDemuxer = null;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
/** Interruptible sleep for the re-dial backoff — `destroy` resolves it early. */
|
|
635
|
+
pullSleep(ms) {
|
|
636
|
+
return new Promise((resolve) => {
|
|
637
|
+
this.pullRedialResolve = resolve;
|
|
638
|
+
this.pullRedialTimer = setTimeout(() => {
|
|
639
|
+
this.pullRedialTimer = null;
|
|
640
|
+
this.pullRedialResolve = null;
|
|
641
|
+
resolve();
|
|
642
|
+
}, ms);
|
|
643
|
+
});
|
|
644
|
+
}
|
|
468
645
|
pushPacket(packet) {
|
|
469
646
|
if (this.destroyed) return;
|
|
647
|
+
if (this.pullActive) {
|
|
648
|
+
if (!this.pullStrayPushLogged) {
|
|
649
|
+
this.pullStrayPushLogged = true;
|
|
650
|
+
this.logger.debug("node-av decoder: pushPacket on a pull-mode session — ignored");
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
470
654
|
this.inputPackets++;
|
|
471
655
|
if (!this.initialized && !this.initializing && packet.keyframe) {
|
|
472
656
|
this.initDecoder().then(() => {
|
|
@@ -847,6 +1031,21 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
|
|
|
847
1031
|
this.destroyed = true;
|
|
848
1032
|
this.frameCallbacks.clear();
|
|
849
1033
|
this.handleCallbacks.clear();
|
|
1034
|
+
this.pullActive = false;
|
|
1035
|
+
if (this.pullRedialTimer) {
|
|
1036
|
+
clearTimeout(this.pullRedialTimer);
|
|
1037
|
+
this.pullRedialTimer = null;
|
|
1038
|
+
}
|
|
1039
|
+
if (this.pullRedialResolve) {
|
|
1040
|
+
const resolve = this.pullRedialResolve;
|
|
1041
|
+
this.pullRedialResolve = null;
|
|
1042
|
+
resolve();
|
|
1043
|
+
}
|
|
1044
|
+
this.closePullInput();
|
|
1045
|
+
if (this.pullHwContext) {
|
|
1046
|
+
if (!this.pullHwContext.isDisposed) this.pullHwContext.dispose();
|
|
1047
|
+
this.pullHwContext = null;
|
|
1048
|
+
}
|
|
850
1049
|
this.frameRingSink?.destroy();
|
|
851
1050
|
this.frameRingSink = null;
|
|
852
1051
|
this.dstFrame?.[Symbol.dispose]?.();
|
|
@@ -876,7 +1075,7 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
|
|
|
876
1075
|
};
|
|
877
1076
|
}
|
|
878
1077
|
get isPullMode() {
|
|
879
|
-
return
|
|
1078
|
+
return this.pullActive;
|
|
880
1079
|
}
|
|
881
1080
|
};
|
|
882
1081
|
//#endregion
|
|
@@ -1005,7 +1204,7 @@ var DecoderNodeAvAddon = class extends require_dist.BaseAddon {
|
|
|
1005
1204
|
return {
|
|
1006
1205
|
id: "decoder-nodeav",
|
|
1007
1206
|
name: "Decoder (node-av)",
|
|
1008
|
-
isPullMode:
|
|
1207
|
+
isPullMode: true,
|
|
1009
1208
|
priority: 10
|
|
1010
1209
|
};
|
|
1011
1210
|
}
|
|
@@ -1123,8 +1322,9 @@ var DecoderNodeAvAddon = class extends require_dist.BaseAddon {
|
|
|
1123
1322
|
});
|
|
1124
1323
|
}
|
|
1125
1324
|
async openStream(input) {
|
|
1126
|
-
|
|
1127
|
-
input.
|
|
1325
|
+
const session = this.sessions.get(input.sessionId);
|
|
1326
|
+
if (!session) throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`);
|
|
1327
|
+
await session.openStream(input.url);
|
|
1128
1328
|
}
|
|
1129
1329
|
async pullFrames(input) {
|
|
1130
1330
|
if (!this.sessions.has(input.sessionId)) throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { B as errMsg, V as BaseAddon, d as HWACCEL_OPTIONS, s as DEFAULT_DECODER_HWACCEL_CONFIG, x as decoderCapability } from "../dist-
|
|
2
|
-
import { a as resolveDecoderBackend, i as NotifyingRingBuffer, n as RING_BUDGET_MB, o as resolveDecoderHwAccel, r as makeSegmentName, t as DecoderFrameRingSink } from "../frame-ring-sink-
|
|
3
|
-
import { f as rankDecodeHwAccels } from "../ffmpeg-args-
|
|
1
|
+
import { B as errMsg, V as BaseAddon, d as HWACCEL_OPTIONS, s as DEFAULT_DECODER_HWACCEL_CONFIG, x as decoderCapability } from "../dist-CfSORkv2.mjs";
|
|
2
|
+
import { a as resolveDecoderBackend, i as NotifyingRingBuffer, n as RING_BUDGET_MB, o as resolveDecoderHwAccel, r as makeSegmentName, t as DecoderFrameRingSink } from "../frame-ring-sink-B_WjDX0x.mjs";
|
|
3
|
+
import { f as rankDecodeHwAccels } from "../ffmpeg-args-CpeRWwL4.mjs";
|
|
4
4
|
import { FrameRingReaderCache } from "@camstack/shm-ring";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
//#region src/decoder-nodeav/scaler-geometry.ts
|
|
@@ -18,6 +18,13 @@ function resolveScalerAction(current, incoming) {
|
|
|
18
18
|
}
|
|
19
19
|
//#endregion
|
|
20
20
|
//#region src/decoder-nodeav/nodeav-decoder-session.ts
|
|
21
|
+
/**
|
|
22
|
+
* Pull-mode re-dial backoff. On a pull input error/EOF the loop waits this long
|
|
23
|
+
* before re-opening the URL — mirrors the ffmpeg pull decoder's 3s re-dial so a
|
|
24
|
+
* transient RTSP restream blip (source suspended on idle, keyframe wait) does
|
|
25
|
+
* not hot-loop `Demuxer.open`.
|
|
26
|
+
*/
|
|
27
|
+
var PULL_REDIAL_MS = 3e3;
|
|
21
28
|
/** Map our canonical backend name to the node-av `AV_HWDEVICE_TYPE_*` constant. */
|
|
22
29
|
function backendToHwDeviceConst(backend, consts) {
|
|
23
30
|
switch (backend) {
|
|
@@ -54,11 +61,11 @@ var _nav = null;
|
|
|
54
61
|
var _consts = null;
|
|
55
62
|
var _sharp = null;
|
|
56
63
|
async function getNodeAv() {
|
|
57
|
-
if (!_nav) _nav = await import("
|
|
64
|
+
if (!_nav) _nav = await import("node-av");
|
|
58
65
|
return _nav;
|
|
59
66
|
}
|
|
60
67
|
async function getConstants() {
|
|
61
|
-
if (!_consts) _consts = await import("
|
|
68
|
+
if (!_consts) _consts = await import("node-av/constants");
|
|
62
69
|
return _consts;
|
|
63
70
|
}
|
|
64
71
|
async function getSharp() {
|
|
@@ -192,6 +199,20 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
|
|
|
192
199
|
activeHwAccel = "none";
|
|
193
200
|
hwDevice = null;
|
|
194
201
|
swTransferFrame = null;
|
|
202
|
+
/** True once `openStream` has armed the pull loop; drives `isPullMode`. */
|
|
203
|
+
pullActive = false;
|
|
204
|
+
/** One-shot guard so a stray push into a pull session logs once, not per-packet. */
|
|
205
|
+
pullStrayPushLogged = false;
|
|
206
|
+
/** The URL the pull loop (re)dials — updatable while active. */
|
|
207
|
+
pullUrl = null;
|
|
208
|
+
/** Pending re-dial timer + its resolver, so `destroy` can wake it instantly. */
|
|
209
|
+
pullRedialTimer = null;
|
|
210
|
+
pullRedialResolve = null;
|
|
211
|
+
/** High-level demuxer/decoder for the CURRENT dial — recreated per re-dial. */
|
|
212
|
+
pullDemuxer = null;
|
|
213
|
+
pullDecoder = null;
|
|
214
|
+
/** HW context — resolved once, reused across re-dials, freed in `destroy`. */
|
|
215
|
+
pullHwContext = null;
|
|
195
216
|
constructor(config, logger = noopLogger, options) {
|
|
196
217
|
this.config = { ...config };
|
|
197
218
|
const sessionTags = {};
|
|
@@ -461,8 +482,171 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
|
|
|
461
482
|
this.scalerInitializing = false;
|
|
462
483
|
}
|
|
463
484
|
}
|
|
485
|
+
/**
|
|
486
|
+
* Start PULL mode: libav dials `url` and demuxes it in-process, decoding
|
|
487
|
+
* every frame through the same emit path as push mode. Idempotent while
|
|
488
|
+
* active (updates the re-dial target). Throws if the session already took a
|
|
489
|
+
* push packet — a session is push-mode or pull-mode for its whole life.
|
|
490
|
+
*/
|
|
491
|
+
openStream(url) {
|
|
492
|
+
if (this.destroyed) return Promise.resolve();
|
|
493
|
+
if (this.inputPackets > 0 || this.initialized || this.initializing) throw new Error("node-av decoder: openStream on a push-fed session — a session is push-mode or pull-mode, never both");
|
|
494
|
+
if (this.pullActive) {
|
|
495
|
+
this.pullUrl = url;
|
|
496
|
+
return Promise.resolve();
|
|
497
|
+
}
|
|
498
|
+
this.pullActive = true;
|
|
499
|
+
this.pullUrl = url;
|
|
500
|
+
this.logger.info("node-av decoder: pull-mode openStream — dialing input URL", { meta: { url } });
|
|
501
|
+
this.runPullLoop().catch((err) => {
|
|
502
|
+
this.logger.error("node-av decoder: pull loop crashed", { meta: { error: errMsg(err) } });
|
|
503
|
+
});
|
|
504
|
+
return Promise.resolve();
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* The pull-mode supervisor loop: resolve HW once, then repeatedly dial +
|
|
508
|
+
* decode the URL, re-dialing after {@link PULL_REDIAL_MS} on any exit/error
|
|
509
|
+
* until the session is destroyed. Each iteration rebuilds a fresh
|
|
510
|
+
* demuxer/decoder (the previous one is disposed in `closePullInput`).
|
|
511
|
+
*/
|
|
512
|
+
async runPullLoop() {
|
|
513
|
+
const nav = await getNodeAv();
|
|
514
|
+
const C = await getConstants();
|
|
515
|
+
this.EAGAIN = C.AVERROR_EAGAIN;
|
|
516
|
+
this.PIX_FMT_GRAY8 = C.AV_PIX_FMT_GRAY8;
|
|
517
|
+
this.PIX_FMT_RGB24 = C.AV_PIX_FMT_RGB24;
|
|
518
|
+
this.SWS_FAST_BILINEAR = C.SWS_FAST_BILINEAR;
|
|
519
|
+
nav.Log.setLevel(C.AV_LOG_FATAL);
|
|
520
|
+
if (this.outputMode === "jpeg" && !this.sharpFn) this.sharpFn = await getSharp();
|
|
521
|
+
await this.ensurePullHwContext(nav, C);
|
|
522
|
+
while (!this.destroyed && this.pullActive) {
|
|
523
|
+
const url = this.pullUrl;
|
|
524
|
+
if (!url) break;
|
|
525
|
+
try {
|
|
526
|
+
await this.pullDialAndDecode(nav, C, url);
|
|
527
|
+
} catch (err) {
|
|
528
|
+
if (this.destroyed) break;
|
|
529
|
+
this.logger.warn("node-av decoder: pull-mode dial ended — re-dial scheduled", { meta: {
|
|
530
|
+
error: errMsg(err),
|
|
531
|
+
redialInMs: PULL_REDIAL_MS
|
|
532
|
+
} });
|
|
533
|
+
} finally {
|
|
534
|
+
this.closePullInput();
|
|
535
|
+
}
|
|
536
|
+
if (this.destroyed || !this.pullActive) break;
|
|
537
|
+
await this.pullSleep(PULL_REDIAL_MS);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* One dial: open the demuxer, pick the video stream, create a HW/SW decoder
|
|
542
|
+
* that downloads + normalises frames to YUV420P (so the shared scaler in
|
|
543
|
+
* `emitDecodedFrame` reads plain software planes regardless of hwaccel), and
|
|
544
|
+
* pump `demuxer.packets(video) → decoder.frames → emitDecodedFrame` until the
|
|
545
|
+
* stream ends or the session is torn down.
|
|
546
|
+
*/
|
|
547
|
+
async pullDialAndDecode(nav, C, url) {
|
|
548
|
+
const demuxer = await nav.Demuxer.open(url, { options: { rtsp_transport: "tcp" } });
|
|
549
|
+
if (this.destroyed || !this.pullActive) {
|
|
550
|
+
demuxer[Symbol.dispose]?.();
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
this.pullDemuxer = demuxer;
|
|
554
|
+
const videoStream = demuxer.video();
|
|
555
|
+
if (!videoStream) throw new Error("node-av decoder: pull input has no video stream");
|
|
556
|
+
const decoder = await nav.Decoder.create(videoStream, {
|
|
557
|
+
...this.pullHwContext ? { hardware: this.pullHwContext } : {},
|
|
558
|
+
rescale: { pixelFormat: C.AV_PIX_FMT_YUV420P }
|
|
559
|
+
});
|
|
560
|
+
if (this.destroyed || !this.pullActive) {
|
|
561
|
+
decoder[Symbol.dispose]?.();
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
this.pullDecoder = decoder;
|
|
565
|
+
this.logger.info("node-av pull decoder initialized", { meta: {
|
|
566
|
+
codec: this.config.codec,
|
|
567
|
+
output: this.outputMode,
|
|
568
|
+
hwAccel: this.activeHwAccel,
|
|
569
|
+
streamIndex: videoStream.index
|
|
570
|
+
} });
|
|
571
|
+
for await (const frame of decoder.frames(demuxer.packets(videoStream.index))) {
|
|
572
|
+
if (this.destroyed || !this.pullActive) break;
|
|
573
|
+
if (!frame) continue;
|
|
574
|
+
this.inputPackets++;
|
|
575
|
+
try {
|
|
576
|
+
this.emitDecodedFrame(frame);
|
|
577
|
+
} finally {
|
|
578
|
+
frame.free();
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Resolve and build the HW context for pull mode ONCE. Explicit backend →
|
|
584
|
+
* single try; `'auto'` → the kernel resolver's ordered list; no resolver →
|
|
585
|
+
* software. The first `HardwareContext.create` that succeeds wins; all
|
|
586
|
+
* failures fall through to software decode (`hardware: null`).
|
|
587
|
+
*/
|
|
588
|
+
async ensurePullHwContext(nav, C) {
|
|
589
|
+
if (this.hwaccelPref === "none") {
|
|
590
|
+
this.activeHwAccel = "none";
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const explicit = this.hwaccelPref === "auto" ? null : this.hwaccelPref;
|
|
594
|
+
const resolution = this.hwaccelResolver ? await this.hwaccelResolver.resolve(explicit) : explicit ? {
|
|
595
|
+
preferred: [explicit],
|
|
596
|
+
rationale: "explicit (no resolver)"
|
|
597
|
+
} : {
|
|
598
|
+
preferred: [],
|
|
599
|
+
rationale: "auto + no resolver → software"
|
|
600
|
+
};
|
|
601
|
+
for (const backend of resolution.preferred) {
|
|
602
|
+
const deviceType = backendToHwDeviceConst(backend, C);
|
|
603
|
+
if (!deviceType) continue;
|
|
604
|
+
const hw = nav.HardwareContext.create(deviceType);
|
|
605
|
+
if (!hw) {
|
|
606
|
+
this.logger.warn("node-av: pull hwaccel context create failed — trying next", { meta: { backend } });
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
this.pullHwContext = hw;
|
|
610
|
+
this.activeHwAccel = backend;
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
this.logger.warn("node-av: no pull hwaccel backend initialised — using software", { meta: {
|
|
614
|
+
attempted: resolution.preferred.join(","),
|
|
615
|
+
rationale: resolution.rationale
|
|
616
|
+
} });
|
|
617
|
+
this.activeHwAccel = "none";
|
|
618
|
+
}
|
|
619
|
+
/** Dispose the CURRENT dial's demuxer + decoder (HW context is reused). */
|
|
620
|
+
closePullInput() {
|
|
621
|
+
if (this.pullDecoder) {
|
|
622
|
+
this.pullDecoder[Symbol.dispose]?.();
|
|
623
|
+
this.pullDecoder = null;
|
|
624
|
+
}
|
|
625
|
+
if (this.pullDemuxer) {
|
|
626
|
+
this.pullDemuxer[Symbol.dispose]?.();
|
|
627
|
+
this.pullDemuxer = null;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
/** Interruptible sleep for the re-dial backoff — `destroy` resolves it early. */
|
|
631
|
+
pullSleep(ms) {
|
|
632
|
+
return new Promise((resolve) => {
|
|
633
|
+
this.pullRedialResolve = resolve;
|
|
634
|
+
this.pullRedialTimer = setTimeout(() => {
|
|
635
|
+
this.pullRedialTimer = null;
|
|
636
|
+
this.pullRedialResolve = null;
|
|
637
|
+
resolve();
|
|
638
|
+
}, ms);
|
|
639
|
+
});
|
|
640
|
+
}
|
|
464
641
|
pushPacket(packet) {
|
|
465
642
|
if (this.destroyed) return;
|
|
643
|
+
if (this.pullActive) {
|
|
644
|
+
if (!this.pullStrayPushLogged) {
|
|
645
|
+
this.pullStrayPushLogged = true;
|
|
646
|
+
this.logger.debug("node-av decoder: pushPacket on a pull-mode session — ignored");
|
|
647
|
+
}
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
466
650
|
this.inputPackets++;
|
|
467
651
|
if (!this.initialized && !this.initializing && packet.keyframe) {
|
|
468
652
|
this.initDecoder().then(() => {
|
|
@@ -843,6 +1027,21 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
|
|
|
843
1027
|
this.destroyed = true;
|
|
844
1028
|
this.frameCallbacks.clear();
|
|
845
1029
|
this.handleCallbacks.clear();
|
|
1030
|
+
this.pullActive = false;
|
|
1031
|
+
if (this.pullRedialTimer) {
|
|
1032
|
+
clearTimeout(this.pullRedialTimer);
|
|
1033
|
+
this.pullRedialTimer = null;
|
|
1034
|
+
}
|
|
1035
|
+
if (this.pullRedialResolve) {
|
|
1036
|
+
const resolve = this.pullRedialResolve;
|
|
1037
|
+
this.pullRedialResolve = null;
|
|
1038
|
+
resolve();
|
|
1039
|
+
}
|
|
1040
|
+
this.closePullInput();
|
|
1041
|
+
if (this.pullHwContext) {
|
|
1042
|
+
if (!this.pullHwContext.isDisposed) this.pullHwContext.dispose();
|
|
1043
|
+
this.pullHwContext = null;
|
|
1044
|
+
}
|
|
846
1045
|
this.frameRingSink?.destroy();
|
|
847
1046
|
this.frameRingSink = null;
|
|
848
1047
|
this.dstFrame?.[Symbol.dispose]?.();
|
|
@@ -872,7 +1071,7 @@ var NodeAvDecoderSession = class NodeAvDecoderSession {
|
|
|
872
1071
|
};
|
|
873
1072
|
}
|
|
874
1073
|
get isPullMode() {
|
|
875
|
-
return
|
|
1074
|
+
return this.pullActive;
|
|
876
1075
|
}
|
|
877
1076
|
};
|
|
878
1077
|
//#endregion
|
|
@@ -1001,7 +1200,7 @@ var DecoderNodeAvAddon = class extends BaseAddon {
|
|
|
1001
1200
|
return {
|
|
1002
1201
|
id: "decoder-nodeav",
|
|
1003
1202
|
name: "Decoder (node-av)",
|
|
1004
|
-
isPullMode:
|
|
1203
|
+
isPullMode: true,
|
|
1005
1204
|
priority: 10
|
|
1006
1205
|
};
|
|
1007
1206
|
}
|
|
@@ -1119,8 +1318,9 @@ var DecoderNodeAvAddon = class extends BaseAddon {
|
|
|
1119
1318
|
});
|
|
1120
1319
|
}
|
|
1121
1320
|
async openStream(input) {
|
|
1122
|
-
|
|
1123
|
-
input.
|
|
1321
|
+
const session = this.sessions.get(input.sessionId);
|
|
1322
|
+
if (!session) throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`);
|
|
1323
|
+
await session.openStream(input.url);
|
|
1124
1324
|
}
|
|
1125
1325
|
async pullFrames(input) {
|
|
1126
1326
|
if (!this.sessions.has(input.sessionId)) throw new Error(`decoder-nodeav: unknown sessionId ${input.sessionId}`);
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_model_download_service_C_IHWnXx = require("../model-download-service-C-IHWnXx-DxM2DSns.js");
|
|
6
|
-
const require_dist = require("../dist-
|
|
6
|
+
const require_dist = require("../dist-BrIE85BG.js");
|
|
7
7
|
const require_node_topology_platform = require("../node-topology-platform-CFZ7F4xW.js");
|
|
8
8
|
let _camstack_shm_ring = require("@camstack/shm-ring");
|
|
9
9
|
let node_fs = require("node:fs");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
|
-
import { $ as sleep, B as errMsg, E as hfModelUrl, F as runtimeDevices$1, G as EventCategory, K as createEvent, M as pipelineExecutorCapability, R as supportedRuntimes$1, S as defaultDeviceFor$1, T as evaluateZoneRules, V as BaseAddon, X as parseJsonUnknown, a as COCO_TO_MACRO, i as COCO_80_LABELS, m as YAMNET_TO_MACRO, q as hydrateSchema, r as AUDIO_MACRO_LABELS, t as APPLE_SA_TO_MACRO, w as detectionPipelineCapability } from "../dist-
|
|
2
|
+
import { $ as sleep, B as errMsg, E as hfModelUrl, F as runtimeDevices$1, G as EventCategory, K as createEvent, M as pipelineExecutorCapability, R as supportedRuntimes$1, S as defaultDeviceFor$1, T as evaluateZoneRules, V as BaseAddon, X as parseJsonUnknown, a as COCO_TO_MACRO, i as COCO_80_LABELS, m as YAMNET_TO_MACRO, q as hydrateSchema, r as AUDIO_MACRO_LABELS, t as APPLE_SA_TO_MACRO, w as detectionPipelineCapability } from "../dist-CfSORkv2.mjs";
|
|
3
3
|
import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
|
|
4
4
|
import { a as isModelDownloaded, i as ensureModel, n as deleteModelFromDisk } from "../model-download-service-C-IHWnXx-BPy6aoAx.mjs";
|
|
5
5
|
import { FrameRingReaderCache } from "@camstack/shm-ring";
|
|
@@ -16328,7 +16328,17 @@ var AgentAddonConfigSchema = object({
|
|
|
16328
16328
|
});
|
|
16329
16329
|
var AgentPipelineSettingsSchema = object({
|
|
16330
16330
|
addonDefaults: record(string(), AgentAddonConfigSchema).readonly(),
|
|
16331
|
-
maxCameras: number().int().nonnegative().nullable().default(null)
|
|
16331
|
+
maxCameras: number().int().nonnegative().nullable().default(null),
|
|
16332
|
+
/** Per-node detection weight (relative share for the quota balancer). */
|
|
16333
|
+
detectWeight: number().positive().optional(),
|
|
16334
|
+
/** Node is eligible to run the detection pipeline (decode + inference). */
|
|
16335
|
+
detect: boolean().optional(),
|
|
16336
|
+
/** Node is eligible to host decoder sessions. */
|
|
16337
|
+
decode: boolean().optional(),
|
|
16338
|
+
/** Node is eligible to run audio-analyzer sessions. */
|
|
16339
|
+
audio: boolean().optional(),
|
|
16340
|
+
/** Node is eligible to be the ingest / source-owner (serve the restream). */
|
|
16341
|
+
ingest: boolean().optional()
|
|
16332
16342
|
});
|
|
16333
16343
|
var CameraPipelineForAgentSchema = object({
|
|
16334
16344
|
steps: array(PipelineStepInputSchema).readonly(),
|
|
@@ -16634,6 +16644,21 @@ method(object({
|
|
|
16634
16644
|
}), object({ success: literal(true) }), {
|
|
16635
16645
|
kind: "mutation",
|
|
16636
16646
|
auth: "admin"
|
|
16647
|
+
}), method(object({
|
|
16648
|
+
agentNodeId: string(),
|
|
16649
|
+
detectWeight: number().positive().nullable()
|
|
16650
|
+
}), object({ success: literal(true) }), {
|
|
16651
|
+
kind: "mutation",
|
|
16652
|
+
auth: "admin"
|
|
16653
|
+
}), method(object({
|
|
16654
|
+
agentNodeId: string(),
|
|
16655
|
+
detect: boolean().nullable().optional(),
|
|
16656
|
+
decode: boolean().nullable().optional(),
|
|
16657
|
+
audio: boolean().nullable().optional(),
|
|
16658
|
+
ingest: boolean().nullable().optional()
|
|
16659
|
+
}), object({ success: literal(true) }), {
|
|
16660
|
+
kind: "mutation",
|
|
16661
|
+
auth: "admin"
|
|
16637
16662
|
}), method(object({ deviceId: number() }), CameraPipelineSettingsSchema.nullable()), method(object({
|
|
16638
16663
|
deviceId: number(),
|
|
16639
16664
|
addonId: string(),
|
|
@@ -22621,6 +22646,18 @@ Object.freeze({
|
|
|
22621
22646
|
addonId: null,
|
|
22622
22647
|
access: "create"
|
|
22623
22648
|
},
|
|
22649
|
+
"pipelineOrchestrator.setAgentCapabilities": {
|
|
22650
|
+
capName: "pipeline-orchestrator",
|
|
22651
|
+
capScope: "system",
|
|
22652
|
+
addonId: null,
|
|
22653
|
+
access: "create"
|
|
22654
|
+
},
|
|
22655
|
+
"pipelineOrchestrator.setAgentDetectWeight": {
|
|
22656
|
+
capName: "pipeline-orchestrator",
|
|
22657
|
+
capScope: "system",
|
|
22658
|
+
addonId: null,
|
|
22659
|
+
access: "create"
|
|
22660
|
+
},
|
|
22624
22661
|
"pipelineOrchestrator.setAgentMaxCameras": {
|
|
22625
22662
|
capName: "pipeline-orchestrator",
|
|
22626
22663
|
capScope: "system",
|