@camstack/addon-pipeline 1.1.31 → 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 +74 -9
- package/dist/decoder-ffmpeg/index.mjs +74 -9
- package/dist/decoder-nodeav/index.js +218 -16
- package/dist/decoder-nodeav/index.mjs +218 -16
- package/dist/detection-pipeline/index.js +34 -3
- package/dist/detection-pipeline/index.mjs +34 -3
- 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-8LLV-cvH.js → frame-ring-sink-B-DxHelU.js} +175 -22
- package/dist/{frame-ring-sink-ClEWjiRU.mjs → frame-ring-sink-B_WjDX0x.mjs} +164 -23
- 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
|
|
@@ -955,14 +1154,16 @@ var DecoderNodeAvAddon = class extends require_dist.BaseAddon {
|
|
|
955
1154
|
}];
|
|
956
1155
|
}
|
|
957
1156
|
/**
|
|
958
|
-
* Resolve the effective hwaccel backend for a new session. Reads
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
* is
|
|
1157
|
+
* Resolve the effective hwaccel backend for a new session. Reads the SHARED
|
|
1158
|
+
* per-node override from the `decoder-ffmpeg` OWNER — the single value the
|
|
1159
|
+
* Pipeline UI edits — exactly as `resolveDecoderBackend` reads the owner's
|
|
1160
|
+
* `backend`. This makes a manual hwaccel override take effect even when
|
|
1161
|
+
* node-av is the active backend (its OWN `hwaccel@node` is never surfaced by
|
|
1162
|
+
* the UI). `'auto'` defers to the session's local resolver
|
|
1163
|
+
* (`ctx.kernel.hwaccel`). Fails safe to `'auto'`.
|
|
963
1164
|
*/
|
|
964
1165
|
resolveHwAccelPref() {
|
|
965
|
-
return this.
|
|
1166
|
+
return require_frame_ring_sink.resolveDecoderHwAccel(this.ctx.api, this.resolveLocalNodeId(), this.ctx.logger);
|
|
966
1167
|
}
|
|
967
1168
|
/**
|
|
968
1169
|
* Re-run the platform probe on this host and persist the detected
|
|
@@ -1003,7 +1204,7 @@ var DecoderNodeAvAddon = class extends require_dist.BaseAddon {
|
|
|
1003
1204
|
return {
|
|
1004
1205
|
id: "decoder-nodeav",
|
|
1005
1206
|
name: "Decoder (node-av)",
|
|
1006
|
-
isPullMode:
|
|
1207
|
+
isPullMode: true,
|
|
1007
1208
|
priority: 10
|
|
1008
1209
|
};
|
|
1009
1210
|
}
|
|
@@ -1021,7 +1222,7 @@ var DecoderNodeAvAddon = class extends require_dist.BaseAddon {
|
|
|
1021
1222
|
}
|
|
1022
1223
|
async createSession(config) {
|
|
1023
1224
|
const sessionId = (0, node_crypto.randomUUID)();
|
|
1024
|
-
const hwaccel = this.resolveHwAccelPref();
|
|
1225
|
+
const hwaccel = await this.resolveHwAccelPref();
|
|
1025
1226
|
const { frameSink } = config;
|
|
1026
1227
|
const nodeId = this.resolveLocalNodeId();
|
|
1027
1228
|
const session = new NodeAvDecoderSession(config, this.ctx.logger, {
|
|
@@ -1121,8 +1322,9 @@ var DecoderNodeAvAddon = class extends require_dist.BaseAddon {
|
|
|
1121
1322
|
});
|
|
1122
1323
|
}
|
|
1123
1324
|
async openStream(input) {
|
|
1124
|
-
|
|
1125
|
-
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);
|
|
1126
1328
|
}
|
|
1127
1329
|
async pullFrames(input) {
|
|
1128
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, 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
|
|
@@ -951,14 +1150,16 @@ var DecoderNodeAvAddon = class extends BaseAddon {
|
|
|
951
1150
|
}];
|
|
952
1151
|
}
|
|
953
1152
|
/**
|
|
954
|
-
* Resolve the effective hwaccel backend for a new session. Reads
|
|
955
|
-
*
|
|
956
|
-
*
|
|
957
|
-
*
|
|
958
|
-
* is
|
|
1153
|
+
* Resolve the effective hwaccel backend for a new session. Reads the SHARED
|
|
1154
|
+
* per-node override from the `decoder-ffmpeg` OWNER — the single value the
|
|
1155
|
+
* Pipeline UI edits — exactly as `resolveDecoderBackend` reads the owner's
|
|
1156
|
+
* `backend`. This makes a manual hwaccel override take effect even when
|
|
1157
|
+
* node-av is the active backend (its OWN `hwaccel@node` is never surfaced by
|
|
1158
|
+
* the UI). `'auto'` defers to the session's local resolver
|
|
1159
|
+
* (`ctx.kernel.hwaccel`). Fails safe to `'auto'`.
|
|
959
1160
|
*/
|
|
960
1161
|
resolveHwAccelPref() {
|
|
961
|
-
return this.
|
|
1162
|
+
return resolveDecoderHwAccel(this.ctx.api, this.resolveLocalNodeId(), this.ctx.logger);
|
|
962
1163
|
}
|
|
963
1164
|
/**
|
|
964
1165
|
* Re-run the platform probe on this host and persist the detected
|
|
@@ -999,7 +1200,7 @@ var DecoderNodeAvAddon = class extends BaseAddon {
|
|
|
999
1200
|
return {
|
|
1000
1201
|
id: "decoder-nodeav",
|
|
1001
1202
|
name: "Decoder (node-av)",
|
|
1002
|
-
isPullMode:
|
|
1203
|
+
isPullMode: true,
|
|
1003
1204
|
priority: 10
|
|
1004
1205
|
};
|
|
1005
1206
|
}
|
|
@@ -1017,7 +1218,7 @@ var DecoderNodeAvAddon = class extends BaseAddon {
|
|
|
1017
1218
|
}
|
|
1018
1219
|
async createSession(config) {
|
|
1019
1220
|
const sessionId = randomUUID();
|
|
1020
|
-
const hwaccel = this.resolveHwAccelPref();
|
|
1221
|
+
const hwaccel = await this.resolveHwAccelPref();
|
|
1021
1222
|
const { frameSink } = config;
|
|
1022
1223
|
const nodeId = this.resolveLocalNodeId();
|
|
1023
1224
|
const session = new NodeAvDecoderSession(config, this.ctx.logger, {
|
|
@@ -1117,8 +1318,9 @@ var DecoderNodeAvAddon = class extends BaseAddon {
|
|
|
1117
1318
|
});
|
|
1118
1319
|
}
|
|
1119
1320
|
async openStream(input) {
|
|
1120
|
-
|
|
1121
|
-
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);
|
|
1122
1324
|
}
|
|
1123
1325
|
async pullFrames(input) {
|
|
1124
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");
|
|
@@ -185,6 +185,35 @@ function pickBestRuntime(env, bestBackendHint) {
|
|
|
185
185
|
device: defaultDeviceFor(chosen)
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* The ordered device candidates a runtime will try for an `'auto'`/`'all'`
|
|
190
|
+
* device pick — mirrors what the python inference pool actually resolves
|
|
191
|
+
* (openvino `_resolve_ov_devices`: NPU>GPU>CPU; coreml compute-units `all`:
|
|
192
|
+
* ANE>GPU>CPU). Surfaced in `probedBestEngine` so the UI shows the real
|
|
193
|
+
* accelerator order (`openvino/auto [NPU, GPU, CPU]`) instead of a bare `auto`
|
|
194
|
+
* tag. Returns `[]` for runtimes with a single fixed device (onnx → CPU) — the
|
|
195
|
+
* caller then keeps the plain `backend/device` label.
|
|
196
|
+
*/
|
|
197
|
+
function engineDeviceSequence(runtimeId, env) {
|
|
198
|
+
const hw = env.hardware;
|
|
199
|
+
switch (runtimeId) {
|
|
200
|
+
case "openvino": {
|
|
201
|
+
const seq = [];
|
|
202
|
+
if (hw?.npu?.type === "intel-npu") seq.push("NPU");
|
|
203
|
+
if (hw?.gpu?.type === "intel") seq.push("GPU");
|
|
204
|
+
seq.push("CPU");
|
|
205
|
+
return seq;
|
|
206
|
+
}
|
|
207
|
+
case "coreml": {
|
|
208
|
+
const seq = [];
|
|
209
|
+
if (hw?.npu?.type === "apple-ane") seq.push("ANE");
|
|
210
|
+
if (hw?.gpu != null) seq.push("GPU");
|
|
211
|
+
seq.push("CPU");
|
|
212
|
+
return seq;
|
|
213
|
+
}
|
|
214
|
+
default: return [];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
188
217
|
//#endregion
|
|
189
218
|
//#region src/detection-pipeline/engine-store-keys.ts
|
|
190
219
|
/**
|
|
@@ -6423,14 +6452,16 @@ var DetectionPipelineProvider = class DetectionPipelineProvider {
|
|
|
6423
6452
|
try {
|
|
6424
6453
|
hardware = await this.addonCtx?.kernel?.inferenceEngine?.resolveHardware() ?? null;
|
|
6425
6454
|
} catch {}
|
|
6426
|
-
const
|
|
6455
|
+
const env = runtimeEnvFromProcess(hardware ? toProbedHardware(hardware) : null);
|
|
6456
|
+
const pick = pickBestRuntime(env, null);
|
|
6427
6457
|
const best = {
|
|
6428
6458
|
runtime: "python",
|
|
6429
6459
|
backend: pick.runtimeId,
|
|
6430
6460
|
format: modelFormatFor(pick.runtimeId),
|
|
6431
6461
|
device: pick.device
|
|
6432
6462
|
};
|
|
6433
|
-
const
|
|
6463
|
+
const deviceSeq = engineDeviceSequence(pick.runtimeId, env);
|
|
6464
|
+
const probedLabel = deviceSeq.length > 1 ? `${best.backend}/${best.device ?? "default"} [${deviceSeq.join(", ")}]` : `${best.backend}/${best.device ?? "default"}`;
|
|
6434
6465
|
const rpNode = this.localProbeNodeId();
|
|
6435
6466
|
if (pick.runtimeId !== "onnx" || hardware !== null) {
|
|
6436
6467
|
await this.writeStore({ [nodeEngineKey("probedBestEngine", rpNode)]: probedLabel });
|