@camstack/addon-pipeline 1.1.28 → 1.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio-analyzer/index.js +1 -1
- package/dist/audio-analyzer/index.mjs +1 -1
- package/dist/audio-codec-ffmpeg/index.js +1 -5
- package/dist/audio-codec-ffmpeg/index.mjs +1 -5
- package/dist/decoder-ffmpeg/index.js +102 -352
- package/dist/decoder-ffmpeg/index.mjs +82 -332
- package/dist/decoder-nodeav/index.js +6 -4
- package/dist/decoder-nodeav/index.mjs +6 -4
- package/dist/detection-pipeline/index.js +254 -186
- package/dist/detection-pipeline/index.mjs +254 -186
- package/dist/{dist-Biq62zt4.js → dist-Cwc0TUQr.js} +7 -42
- package/dist/{dist-C6_wgXqF.mjs → dist-DjuGmyG9.mjs} +7 -42
- package/dist/ffmpeg-args-C5GPp8Cw.mjs +323 -0
- package/dist/ffmpeg-args-D6h1edXK.js +418 -0
- package/dist/frame-dropper-AjheBGMG.mjs +22 -0
- package/dist/frame-dropper-DKLM6pMz.js +27 -0
- package/dist/{frame-handle-plane-BIoY6nRV.mjs → frame-handle-plane-Bkxz-TTD.mjs} +1 -1
- package/dist/{frame-handle-plane-D6BzyEgy.js → frame-handle-plane-DQNCTrpC.js} +1 -1
- package/dist/{frame-ring-sink-9J0wCdLF.js → frame-ring-sink-8LLV-cvH.js} +1 -1
- package/dist/{frame-ring-sink-Cs9vby6v.mjs → frame-ring-sink-ClEWjiRU.mjs} +1 -1
- package/dist/motion-wasm/index.js +1 -1
- package/dist/motion-wasm/index.mjs +1 -1
- package/dist/pipeline-runner/index.js +2 -2
- package/dist/pipeline-runner/index.mjs +2 -2
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.mjs +1 -1
- package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DoReAb4y.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D567z31g.mjs} +3 -3
- package/dist/stream-broker/{hostInit-C0SuwQhL.mjs → hostInit-B_b3PIZB.mjs} +3 -3
- package/dist/stream-broker/index.js +168 -121
- package/dist/stream-broker/index.mjs +159 -112
- package/dist/stream-broker/remoteEntry.js +1 -1
- package/embed-dist/assets/{MaskShapeCanvas-DI4BY7W2-Br2yi8ah.js → MaskShapeCanvas-DI4BY7W2-x-DZOuQL.js} +1 -1
- package/embed-dist/assets/{MotionZonesSettings-NcxxQN8r-BSCcPdPf.js → MotionZonesSettings-NcxxQN8r-DTeuDGCU.js} +1 -1
- package/embed-dist/assets/{PrivacyMaskSettings-APgPLF7p-ztxvxSjS.js → PrivacyMaskSettings-APgPLF7p-CirtxO3e.js} +1 -1
- package/embed-dist/assets/{index-CeZV1B-2.js → index-CpUy8OIE.js} +9 -9
- package/embed-dist/index.html +1 -1
- package/package.json +1 -1
- package/dist/frame-dropper-7RTo_YyG.js +0 -68
- package/dist/frame-dropper-CwkBTPGV.mjs +0 -51
|
@@ -3,9 +3,10 @@ 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-
|
|
7
|
-
const require_frame_handle_plane = require("../frame-handle-plane-
|
|
8
|
-
const
|
|
6
|
+
const require_dist = require("../dist-Cwc0TUQr.js");
|
|
7
|
+
const require_frame_handle_plane = require("../frame-handle-plane-DQNCTrpC.js");
|
|
8
|
+
const require_ffmpeg_args = require("../ffmpeg-args-D6h1edXK.js");
|
|
9
|
+
const require_frame_dropper = require("../frame-dropper-DKLM6pMz.js");
|
|
9
10
|
let node_crypto = require("node:crypto");
|
|
10
11
|
node_crypto = require_model_download_service_C_IHWnXx.__toESM(node_crypto);
|
|
11
12
|
let node_fs = require("node:fs");
|
|
@@ -2527,7 +2528,7 @@ function planAudioSilencePadPackets(input) {
|
|
|
2527
2528
|
* audio plane honours the profile literally.
|
|
2528
2529
|
*/
|
|
2529
2530
|
function buildTranscodeFfmpegArgs(inputFormat, profile, decodeHwAccel, sourceUrl) {
|
|
2530
|
-
const args = [...
|
|
2531
|
+
const args = [...require_ffmpeg_args.logBannerArgs("error")];
|
|
2531
2532
|
if (profile.inputArgs?.length) args.push(...profile.inputArgs);
|
|
2532
2533
|
if (decodeHwAccel && decodeHwAccel !== "none") args.push("-hwaccel", decodeHwAccel);
|
|
2533
2534
|
args.push("-fflags", "+discardcorrupt", "-rtsp_transport", "tcp", "-i", sourceUrl);
|
|
@@ -2549,7 +2550,7 @@ function buildTranscodeFfmpegArgs(inputFormat, profile, decodeHwAccel, sourceUrl
|
|
|
2549
2550
|
if (profile.audio === "passthrough") args.push("-an");
|
|
2550
2551
|
else {
|
|
2551
2552
|
const a = profile.audio;
|
|
2552
|
-
args.push(...
|
|
2553
|
+
args.push(...require_ffmpeg_args.audioEncoderArgs(a.codec, {
|
|
2553
2554
|
bitrateKbps: a.bitrateKbps,
|
|
2554
2555
|
sampleRateHz: a.sampleRateHz,
|
|
2555
2556
|
channels: a.channels
|
|
@@ -5912,24 +5913,6 @@ function toAudioSidecarCodec(audio) {
|
|
|
5912
5913
|
return audio === "aac" || audio === "opus" || audio === "pcmu" ? audio : null;
|
|
5913
5914
|
}
|
|
5914
5915
|
/**
|
|
5915
|
-
* Pick the decode `-hwaccel` backend, gated by what the configured ffmpeg
|
|
5916
|
-
* binary actually supports (`platformProbe.getHardwareDecodeAccels`).
|
|
5917
|
-
*
|
|
5918
|
-
* `preferred` entries are literal `-hwaccel` names (kernel-resolved), so they
|
|
5919
|
-
* intersect directly with the probed method list. Conservative by design:
|
|
5920
|
-
* - empty `preferred` → `null` (software decode);
|
|
5921
|
-
* - empty `supportedMethods` (probe miss / build reports none) → keep the top
|
|
5922
|
-
* preference and let the egress's per-stream software fallback cover failures;
|
|
5923
|
-
* - otherwise return the first preference the build supports, or `null` when it
|
|
5924
|
-
* supports none of them (positive evidence to skip a doomed hw-decode spawn).
|
|
5925
|
-
*/
|
|
5926
|
-
function pickDecodeHwAccel(preferred, supportedMethods) {
|
|
5927
|
-
if (preferred.length === 0) return null;
|
|
5928
|
-
if (supportedMethods.length === 0) return preferred[0] ?? null;
|
|
5929
|
-
const supported = new Set(supportedMethods.map((m) => m.toLowerCase()));
|
|
5930
|
-
return preferred.find((name) => supported.has(name.toLowerCase())) ?? null;
|
|
5931
|
-
}
|
|
5932
|
-
/**
|
|
5933
5916
|
* Pick the video encoder, honouring the `hwAccel` preference: `'none'`/`'copy'`
|
|
5934
5917
|
* (software-decode settings) force the software ENCODER too; otherwise the
|
|
5935
5918
|
* probe's default (hw when available) is used, falling back to software when no
|
|
@@ -5944,26 +5927,80 @@ function pickVideoEncoder(target, encoders, hwAccel) {
|
|
|
5944
5927
|
if (hwAccel === "none" || hwAccel === "copy" || !encoders) return software;
|
|
5945
5928
|
return target === "H264" ? encoders.defaultH264 : encoders.defaultH265;
|
|
5946
5929
|
}
|
|
5930
|
+
/**
|
|
5931
|
+
* The h264/hevc hardware ENCODER ids for a decode-hwaccel backend name — a
|
|
5932
|
+
* static, deterministic map (the same shape Scrypted uses: platform → encoder,
|
|
5933
|
+
* no probe). Consumed by {@link localHardwareEncoders}. `null` for a backend
|
|
5934
|
+
* with no hardware encoder (software / unknown).
|
|
5935
|
+
*/
|
|
5936
|
+
var ENCODER_IDS_BY_BACKEND = {
|
|
5937
|
+
videotoolbox: {
|
|
5938
|
+
h264: "h264_videotoolbox",
|
|
5939
|
+
h265: "hevc_videotoolbox"
|
|
5940
|
+
},
|
|
5941
|
+
vaapi: {
|
|
5942
|
+
h264: "h264_vaapi",
|
|
5943
|
+
h265: "hevc_vaapi"
|
|
5944
|
+
},
|
|
5945
|
+
cuda: {
|
|
5946
|
+
h264: "h264_nvenc",
|
|
5947
|
+
h265: "hevc_nvenc"
|
|
5948
|
+
},
|
|
5949
|
+
nvdec: {
|
|
5950
|
+
h264: "h264_nvenc",
|
|
5951
|
+
h265: "hevc_nvenc"
|
|
5952
|
+
},
|
|
5953
|
+
qsv: {
|
|
5954
|
+
h264: "h264_qsv",
|
|
5955
|
+
h265: "hevc_qsv"
|
|
5956
|
+
},
|
|
5957
|
+
amf: {
|
|
5958
|
+
h264: "h264_amf",
|
|
5959
|
+
h265: "hevc_amf"
|
|
5960
|
+
}
|
|
5961
|
+
};
|
|
5962
|
+
/**
|
|
5963
|
+
* Build the {@link HardwareEncoders} default pair for THIS node's decode-hwaccel
|
|
5964
|
+
* backend — resolved LOCALLY from `ctx.kernel.hwaccel` — WITHOUT probing
|
|
5965
|
+
* `ffmpeg -encoders` via the `platform-probe` singleton (whose per-node
|
|
5966
|
+
* `nodePin` does not survive a forked agent child's link chain → it answers with
|
|
5967
|
+
* the HUB's encoders). Mirrors how Scrypted derives encoders: a static
|
|
5968
|
+
* platform→encoder map. Returns `null` for software / an unmapped backend, which
|
|
5969
|
+
* makes {@link pickVideoEncoder} fall back to the software encoder. Only
|
|
5970
|
+
* `defaultH264`/`defaultH265` are consumed downstream, so `encoders` is left
|
|
5971
|
+
* empty (the per-encoder capability list was never read here).
|
|
5972
|
+
*/
|
|
5973
|
+
function localHardwareEncoders(backend) {
|
|
5974
|
+
if (backend === null) return null;
|
|
5975
|
+
const ids = ENCODER_IDS_BY_BACKEND[backend.toLowerCase()];
|
|
5976
|
+
if (!ids) return null;
|
|
5977
|
+
return {
|
|
5978
|
+
encoders: [],
|
|
5979
|
+
defaultH264: ids.h264,
|
|
5980
|
+
defaultH265: ids.h265,
|
|
5981
|
+
probedAt: Date.now()
|
|
5982
|
+
};
|
|
5983
|
+
}
|
|
5947
5984
|
/** Audio encoder args for a transcode target (preset values over the shared low-level builder). */
|
|
5948
5985
|
function pickAudioEncoderArgs(audio) {
|
|
5949
5986
|
switch (audio) {
|
|
5950
|
-
case "aac": return
|
|
5987
|
+
case "aac": return require_ffmpeg_args.audioEncoderArgs("aac", {
|
|
5951
5988
|
bitrateKbps: 128,
|
|
5952
5989
|
sampleRateHz: 48e3,
|
|
5953
5990
|
channels: 2
|
|
5954
5991
|
});
|
|
5955
|
-
case "opus": return
|
|
5992
|
+
case "opus": return require_ffmpeg_args.audioEncoderArgs("opus", {
|
|
5956
5993
|
bitrateKbps: 64,
|
|
5957
5994
|
sampleRateHz: 48e3,
|
|
5958
5995
|
channels: 2
|
|
5959
5996
|
});
|
|
5960
|
-
case "pcmu": return
|
|
5997
|
+
case "pcmu": return require_ffmpeg_args.audioEncoderArgs("pcmu", {
|
|
5961
5998
|
sampleRateHz: 8e3,
|
|
5962
5999
|
channels: 1
|
|
5963
6000
|
});
|
|
5964
|
-
case "copy": return
|
|
6001
|
+
case "copy": return require_ffmpeg_args.audioEncoderArgs("copy");
|
|
5965
6002
|
case "none": return ["-an"];
|
|
5966
|
-
default: return
|
|
6003
|
+
default: return require_ffmpeg_args.audioEncoderArgs("aac", { bitrateKbps: 128 });
|
|
5967
6004
|
}
|
|
5968
6005
|
}
|
|
5969
6006
|
/**
|
|
@@ -6028,7 +6065,7 @@ function buildFfmpegArgs$1(inv) {
|
|
|
6028
6065
|
const threadArgs = inv.threadCount > 0 ? ["-threads", String(inv.threadCount)] : [];
|
|
6029
6066
|
const audioArgs = inv.sink.kind === "stdout" && (inv.sink.container === "h264" || inv.sink.container === "hevc") ? ["-an"] : pickAudioEncoderArgs(inv.audio);
|
|
6030
6067
|
return [
|
|
6031
|
-
...
|
|
6068
|
+
...require_ffmpeg_args.logBannerArgs("warning"),
|
|
6032
6069
|
...decodeArgs,
|
|
6033
6070
|
"-rtsp_transport",
|
|
6034
6071
|
"tcp",
|
|
@@ -11364,8 +11401,7 @@ var TranscodePipelineManager = class {
|
|
|
11364
11401
|
* async egress start so concurrent acquires share one egress. */
|
|
11365
11402
|
pending = /* @__PURE__ */ new Map();
|
|
11366
11403
|
logger;
|
|
11367
|
-
|
|
11368
|
-
localNodeId;
|
|
11404
|
+
hwaccel;
|
|
11369
11405
|
cameraStreamLookup;
|
|
11370
11406
|
rtspEntryLookup;
|
|
11371
11407
|
resolveProfileSource;
|
|
@@ -11376,8 +11412,7 @@ var TranscodePipelineManager = class {
|
|
|
11376
11412
|
acquireChannel;
|
|
11377
11413
|
constructor(opts) {
|
|
11378
11414
|
this.logger = opts.logger;
|
|
11379
|
-
this.
|
|
11380
|
-
this.localNodeId = opts.localNodeId ?? null;
|
|
11415
|
+
this.hwaccel = opts.hwaccel ?? null;
|
|
11381
11416
|
this.cameraStreamLookup = opts.cameraStreamLookup;
|
|
11382
11417
|
this.rtspEntryLookup = opts.rtspEntryLookup;
|
|
11383
11418
|
this.resolveProfileSource = opts.resolveProfileSource ?? null;
|
|
@@ -11579,21 +11614,20 @@ var TranscodePipelineManager = class {
|
|
|
11579
11614
|
return stream;
|
|
11580
11615
|
}
|
|
11581
11616
|
/**
|
|
11582
|
-
*
|
|
11583
|
-
*
|
|
11584
|
-
*
|
|
11585
|
-
*
|
|
11617
|
+
* The h264/hevc hardware ENCODER defaults for THIS node — derived locally
|
|
11618
|
+
* from `ctx.kernel.hwaccel` (os.platform + GPU-vendor probe) via a static
|
|
11619
|
+
* backend→encoder map, NOT the `platform-probe` singleton's `ffmpeg -encoders`
|
|
11620
|
+
* probe (whose per-node `nodePin` answers with the HUB's encoders). `null` →
|
|
11621
|
+
* software encode (also the outcome when no local resolver is wired). vaapi is
|
|
11622
|
+
* ranked above qsv (qsv is broken on the Intel stack) so an Intel node
|
|
11623
|
+
* transcodes with `h264_vaapi`.
|
|
11586
11624
|
*/
|
|
11587
|
-
probePin() {
|
|
11588
|
-
return this.localNodeId ? require_dist.nodePin(this.localNodeId) : void 0;
|
|
11589
|
-
}
|
|
11590
|
-
/** Query the typed, cached hardware-encoder probe; null on any failure. */
|
|
11591
11625
|
async resolveHardwareEncoders() {
|
|
11592
|
-
if (!this.
|
|
11626
|
+
if (!this.hwaccel) return null;
|
|
11593
11627
|
try {
|
|
11594
|
-
return await this.
|
|
11628
|
+
return localHardwareEncoders(require_ffmpeg_args.rankDecodeHwAccels((await this.hwaccel.resolve(null)).preferred, [])[0] ?? null);
|
|
11595
11629
|
} catch (err) {
|
|
11596
|
-
this.logger.warn("
|
|
11630
|
+
this.logger.warn("local hardware-encoder resolve failed — software encode fallback", { meta: { error: require_dist.errMsg(err) } });
|
|
11597
11631
|
return null;
|
|
11598
11632
|
}
|
|
11599
11633
|
}
|
|
@@ -11621,35 +11655,20 @@ var TranscodePipelineManager = class {
|
|
|
11621
11655
|
if (strategy.kind === "software") return null;
|
|
11622
11656
|
return this.resolveSpecificDecodeHwAccel(strategy.prefer);
|
|
11623
11657
|
}
|
|
11624
|
-
/** Gate a specific backend against the probed support list; null on miss/error. */
|
|
11625
|
-
async resolveSpecificDecodeHwAccel(prefer) {
|
|
11626
|
-
if (!this.api) return null;
|
|
11627
|
-
try {
|
|
11628
|
-
const res = await this.api.platformProbe.resolveHwAccel.query({ prefer }, this.probePin());
|
|
11629
|
-
const supported = await this.resolveDecodeAccelMethods();
|
|
11630
|
-
const picked = pickDecodeHwAccel(res.preferred, supported);
|
|
11631
|
-
if (picked === null && res.preferred.length > 0) this.logger.info("decode hw-accel: configured ffmpeg supports none of the preferred backends — software decode", { meta: {
|
|
11632
|
-
preferred: res.preferred,
|
|
11633
|
-
supported
|
|
11634
|
-
} });
|
|
11635
|
-
return picked;
|
|
11636
|
-
} catch (err) {
|
|
11637
|
-
this.logger.warn("platformProbe.resolveHwAccel failed — software decode fallback", { meta: { error: require_dist.errMsg(err) } });
|
|
11638
|
-
return null;
|
|
11639
|
-
}
|
|
11640
|
-
}
|
|
11641
11658
|
/**
|
|
11642
|
-
*
|
|
11643
|
-
*
|
|
11644
|
-
*
|
|
11659
|
+
* Resolve a specific operator-forced decode backend against THIS node's local
|
|
11660
|
+
* hwaccel resolver. `ctx.kernel.hwaccel.resolve(prefer)` echoes the forced
|
|
11661
|
+
* backend (re-ranked so vaapi precedes qsv); the egress's per-stream software
|
|
11662
|
+
* fallback still catches a backend the actual ffmpeg build can't drive. Null
|
|
11663
|
+
* when no local resolver is wired.
|
|
11645
11664
|
*/
|
|
11646
|
-
async
|
|
11647
|
-
if (!this.
|
|
11665
|
+
async resolveSpecificDecodeHwAccel(prefer) {
|
|
11666
|
+
if (!this.hwaccel) return null;
|
|
11648
11667
|
try {
|
|
11649
|
-
return (await this.
|
|
11668
|
+
return require_ffmpeg_args.rankDecodeHwAccels((await this.hwaccel.resolve(prefer)).preferred, [])[0] ?? null;
|
|
11650
11669
|
} catch (err) {
|
|
11651
|
-
this.logger.warn("
|
|
11652
|
-
return
|
|
11670
|
+
this.logger.warn("local decode hw-accel resolve failed — software decode fallback", { meta: { error: require_dist.errMsg(err) } });
|
|
11671
|
+
return null;
|
|
11653
11672
|
}
|
|
11654
11673
|
}
|
|
11655
11674
|
shareEntry(entry) {
|
|
@@ -11967,6 +11986,14 @@ var StreamBrokerManager = class {
|
|
|
11967
11986
|
*/
|
|
11968
11987
|
api = null;
|
|
11969
11988
|
/**
|
|
11989
|
+
* This node's LOCAL hwaccel resolver (`ctx.kernel.hwaccel`), wired by
|
|
11990
|
+
* `setApiAccess`. Drives the transcode manager's decode/encode backend picks
|
|
11991
|
+
* and `getHwAccelOptions` from THIS node's hardware in-process — never the
|
|
11992
|
+
* `platform-probe` singleton (whose per-node `nodePin` answers with the hub's
|
|
11993
|
+
* hardware from a forked agent child).
|
|
11994
|
+
*/
|
|
11995
|
+
hwaccel = null;
|
|
11996
|
+
/**
|
|
11970
11997
|
* This broker node's own Moleculer nodeID (`ctx.kernel.localNodeId`). The
|
|
11971
11998
|
* shm frame plane's decoder sessions MUST be co-located on this node — an
|
|
11972
11999
|
* shm frame ring is node-local, so a decoder placed on any OTHER node can
|
|
@@ -12008,8 +12035,7 @@ var StreamBrokerManager = class {
|
|
|
12008
12035
|
getTranscodeManager() {
|
|
12009
12036
|
if (!this.transcodeManager) this.transcodeManager = new TranscodePipelineManager({
|
|
12010
12037
|
logger: this.logger.child("transcode"),
|
|
12011
|
-
|
|
12012
|
-
localNodeId: this.localNodeId ?? void 0,
|
|
12038
|
+
hwaccel: this.hwaccel ?? void 0,
|
|
12013
12039
|
cameraStreamLookup: (deviceId) => this.getCameraStreamsForDevice(deviceId),
|
|
12014
12040
|
rtspEntryLookup: (brokerId) => {
|
|
12015
12041
|
const entry = this.rtspProvider.getEntry(brokerId);
|
|
@@ -12215,9 +12241,10 @@ var StreamBrokerManager = class {
|
|
|
12215
12241
|
* resolve the `decoder` capability via tRPC — see
|
|
12216
12242
|
* `buildDecoderCapApi` for the full rationale.
|
|
12217
12243
|
*/
|
|
12218
|
-
setApiAccess(api, localNodeId) {
|
|
12244
|
+
setApiAccess(api, localNodeId, hwaccel) {
|
|
12219
12245
|
this.api = api;
|
|
12220
12246
|
if (localNodeId !== void 0) this.localNodeId = localNodeId;
|
|
12247
|
+
if (hwaccel !== void 0) this.hwaccel = hwaccel;
|
|
12221
12248
|
}
|
|
12222
12249
|
/** Update the system `ffmpeg` config snapshot used by the transcode egress. */
|
|
12223
12250
|
setFfmpegConfig(cfg) {
|
|
@@ -12225,11 +12252,11 @@ var StreamBrokerManager = class {
|
|
|
12225
12252
|
}
|
|
12226
12253
|
/**
|
|
12227
12254
|
* The decode `hwaccel` options surfaceable in a (future) UI: the always-valid
|
|
12228
|
-
* settings (`copy`, `auto`, `none`) plus the concrete backends
|
|
12229
|
-
*
|
|
12230
|
-
*
|
|
12231
|
-
*
|
|
12232
|
-
*
|
|
12255
|
+
* settings (`copy`, `auto`, `none`) plus the concrete hardware backends THIS
|
|
12256
|
+
* node can use, resolved from the LOCAL `ctx.kernel.hwaccel` (os.platform +
|
|
12257
|
+
* GPU-vendor probe), ranked so vaapi precedes qsv. Node-local — never the
|
|
12258
|
+
* `platform-probe` singleton (which answers with the hub's methods from a
|
|
12259
|
+
* forked agent broker). Best-effort: no resolver / error → always-valid only.
|
|
12233
12260
|
*/
|
|
12234
12261
|
async getHwAccelOptions() {
|
|
12235
12262
|
const always = [
|
|
@@ -12237,13 +12264,12 @@ var StreamBrokerManager = class {
|
|
|
12237
12264
|
"auto",
|
|
12238
12265
|
"none"
|
|
12239
12266
|
];
|
|
12240
|
-
if (!this.
|
|
12267
|
+
if (!this.hwaccel) return always;
|
|
12241
12268
|
try {
|
|
12242
|
-
const
|
|
12243
|
-
const probed = (await this.api.platformProbe.getHardwareDecodeAccels.query(void 0, pin)).methods.filter((m) => FFMPEG_HW_ACCEL_SETTINGS.includes(m));
|
|
12269
|
+
const probed = require_ffmpeg_args.rankDecodeHwAccels((await this.hwaccel.resolve(null)).preferred, []).filter((m) => FFMPEG_HW_ACCEL_SETTINGS.includes(m));
|
|
12244
12270
|
return [...always, ...probed];
|
|
12245
12271
|
} catch (err) {
|
|
12246
|
-
this.logger.warn("getHwAccelOptions:
|
|
12272
|
+
this.logger.warn("getHwAccelOptions: local hwaccel resolve failed — static options only", { meta: { error: require_dist.errMsg(err) } });
|
|
12247
12273
|
return always;
|
|
12248
12274
|
}
|
|
12249
12275
|
}
|
|
@@ -17074,9 +17100,6 @@ var WebrtcSessionProvider = class WebrtcSessionProvider {
|
|
|
17074
17100
|
};
|
|
17075
17101
|
//#endregion
|
|
17076
17102
|
//#region src/stream-broker/addon.ts
|
|
17077
|
-
function isFfmpegHwAccel(v) {
|
|
17078
|
-
return typeof v === "string" && FFMPEG_HW_ACCEL_SETTINGS.includes(v);
|
|
17079
|
-
}
|
|
17080
17103
|
/**
|
|
17081
17104
|
* Hydrate the persisted RTSP restream state (`rtspTokens` / `rtspEnabled`)
|
|
17082
17105
|
* through the durable `state()` handles — the sanctioned settings accessor —
|
|
@@ -17160,15 +17183,6 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
|
|
|
17160
17183
|
/** Handle for the embed SPA data-plane listener (disposed on shutdown). */
|
|
17161
17184
|
embedDataPlane = null;
|
|
17162
17185
|
/**
|
|
17163
|
-
* This broker's bare cluster node id (`ctx.kernel.localNodeId` with any
|
|
17164
|
-
* `node/addon` fork suffix stripped), computed once in onInitialize.
|
|
17165
|
-
* `platform-probe` is a `singleton` cap, so every probe read (transcode
|
|
17166
|
-
* decode hw-accel) MUST be pinned here — an unpinned call from an agent
|
|
17167
|
-
* child resolves to the HUB's probe (the wrong hardware). `undefined` when
|
|
17168
|
-
* the kernel id is unknown → legacy unpinned behaviour.
|
|
17169
|
-
*/
|
|
17170
|
-
localNodeId;
|
|
17171
|
-
/**
|
|
17172
17186
|
* Snapshot-equality cache for the broker-metrics emit. Each
|
|
17173
17187
|
* broker emits a stats snapshot every BROKER_METRICS_SNAPSHOT_-
|
|
17174
17188
|
* INTERVAL_MS; for an idle broker (status=streaming, fps=0,
|
|
@@ -17194,39 +17208,54 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
|
|
|
17194
17208
|
maxDecodeFps: 5,
|
|
17195
17209
|
initialReconnectDelayMs: 1e3,
|
|
17196
17210
|
maxReconnectDelayMs: 3e4,
|
|
17197
|
-
catalogReconcileIntervalSec: 30
|
|
17211
|
+
catalogReconcileIntervalSec: 30,
|
|
17212
|
+
binaryPath: "",
|
|
17213
|
+
hwaccel: "copy"
|
|
17198
17214
|
});
|
|
17199
17215
|
}
|
|
17200
17216
|
/**
|
|
17201
|
-
*
|
|
17202
|
-
*
|
|
17203
|
-
*
|
|
17204
|
-
*
|
|
17205
|
-
*
|
|
17206
|
-
|
|
17217
|
+
* Resolve the ffmpeg binary for THIS node: a per-node `binaryPath` override
|
|
17218
|
+
* (`binaryPath@<node>`) → else `ensureFfmpeg()` which provisions a guaranteed
|
|
17219
|
+
* portable static binary locally (hub / agent / Mac). Replaces the retired
|
|
17220
|
+
* global `ffmpeg` system section (node-local/empty on agents → a cross-node
|
|
17221
|
+
* footgun). Falls back to PATH `ffmpeg` only if the provision genuinely fails.
|
|
17222
|
+
*/
|
|
17223
|
+
async resolveFfmpegBinaryPath() {
|
|
17224
|
+
const override = this.config.binaryPath;
|
|
17225
|
+
if (typeof override === "string" && override.trim().length > 0) return override;
|
|
17226
|
+
try {
|
|
17227
|
+
return await this.ctx.deps.ensureFfmpeg();
|
|
17228
|
+
} catch (err) {
|
|
17229
|
+
this.ctx.logger.error("stream-broker: ensureFfmpeg() failed to provision ffmpeg — falling back to PATH \"ffmpeg\"", { meta: { error: require_dist.errMsg(err) } });
|
|
17230
|
+
return "ffmpeg";
|
|
17231
|
+
}
|
|
17232
|
+
}
|
|
17233
|
+
/**
|
|
17234
|
+
* Push THIS node's ffmpeg config into the broker — all node-local now, NOT the
|
|
17235
|
+
* retired global `ffmpeg` section. `binaryPath` via {@link resolveFfmpegBinaryPath}
|
|
17236
|
+
* (per-node override → ensureFfmpeg); `hwAccel` = the per-node `hwaccel`
|
|
17237
|
+
* preference (default `'copy'` = software, the safe HEVC-compatible choice; the
|
|
17238
|
+
* concrete backend is resolved node-locally in `resolveTranscodeDecodeHwAccel`
|
|
17239
|
+
* / the transcode manager). `threadCount` is always `0` (auto — ffmpeg uses the
|
|
17240
|
+
* node's own cores; the manual knob was removed).
|
|
17207
17241
|
*/
|
|
17208
17242
|
async loadFfmpegConfig() {
|
|
17209
17243
|
if (!this.brokerManager) return;
|
|
17210
17244
|
try {
|
|
17211
|
-
const
|
|
17212
|
-
const
|
|
17213
|
-
const binaryPath = typeof binaryPathRaw === "string" && binaryPathRaw.trim().length > 0 ? binaryPathRaw : "ffmpeg";
|
|
17214
|
-
const hwAccel = isFfmpegHwAccel(section["hwAccel"]) ? section["hwAccel"] : "copy";
|
|
17215
|
-
const threadCountRaw = section["threadCount"];
|
|
17216
|
-
const threadCount = typeof threadCountRaw === "number" && Number.isFinite(threadCountRaw) && threadCountRaw >= 0 ? Math.floor(threadCountRaw) : 0;
|
|
17245
|
+
const binaryPath = await this.resolveFfmpegBinaryPath();
|
|
17246
|
+
const hwAccel = this.config.hwaccel;
|
|
17217
17247
|
this.brokerManager.setFfmpegConfig({
|
|
17218
17248
|
binaryPath,
|
|
17219
17249
|
hwAccel,
|
|
17220
|
-
threadCount
|
|
17250
|
+
threadCount: 0
|
|
17221
17251
|
});
|
|
17222
17252
|
this.ffmpegBinaryPath = binaryPath;
|
|
17223
17253
|
this.ctx.logger.info("Loaded ffmpeg config", { meta: {
|
|
17224
17254
|
binaryPath,
|
|
17225
|
-
hwAccel
|
|
17226
|
-
threadCount
|
|
17255
|
+
hwAccel
|
|
17227
17256
|
} });
|
|
17228
17257
|
} catch (err) {
|
|
17229
|
-
this.ctx.logger.warn("Failed to
|
|
17258
|
+
this.ctx.logger.warn("Failed to load ffmpeg config — using defaults", { meta: { error: require_dist.errMsg(err) } });
|
|
17230
17259
|
}
|
|
17231
17260
|
}
|
|
17232
17261
|
/**
|
|
@@ -17234,21 +17263,19 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
|
|
|
17234
17263
|
* configured `ffmpeg.hwAccel` preference (default `'copy'` = software):
|
|
17235
17264
|
* - `'auto'` → the literal `'auto'` (ffmpeg self-selects + software-fallbacks).
|
|
17236
17265
|
* - `'copy'`/`'none'` → `null` (software decode).
|
|
17237
|
-
* - a specific backend →
|
|
17238
|
-
* (
|
|
17266
|
+
* - a specific backend → resolved from THIS node's LOCAL `ctx.kernel.hwaccel`
|
|
17267
|
+
* (ranked so vaapi precedes qsv); the feed still falls back to software on a
|
|
17239
17268
|
* per-stream miss.
|
|
17240
17269
|
* Mirrors the transcode egress's resolution.
|
|
17241
17270
|
*/
|
|
17242
17271
|
async resolveTranscodeDecodeHwAccel() {
|
|
17243
17272
|
try {
|
|
17244
|
-
const
|
|
17245
|
-
const strategy = classifyDecodeHwAccel(isFfmpegHwAccel(section["hwAccel"]) ? section["hwAccel"] : "copy");
|
|
17273
|
+
const strategy = classifyDecodeHwAccel(this.config.hwaccel);
|
|
17246
17274
|
if (strategy.kind === "auto") return "auto";
|
|
17247
17275
|
if (strategy.kind === "software") return null;
|
|
17248
|
-
const
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
return pickDecodeHwAccel(res.preferred, supported.methods);
|
|
17276
|
+
const resolver = this.ctx.kernel.hwaccel;
|
|
17277
|
+
if (!resolver) return null;
|
|
17278
|
+
return require_ffmpeg_args.rankDecodeHwAccels((await resolver.resolve(strategy.prefer)).preferred, [])[0] ?? null;
|
|
17252
17279
|
} catch (err) {
|
|
17253
17280
|
this.ctx.logger.warn("resolveTranscodeDecodeHwAccel failed — software decode", { meta: { error: require_dist.errMsg(err) } });
|
|
17254
17281
|
return null;
|
|
@@ -17260,8 +17287,7 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
|
|
|
17260
17287
|
this.brokerManager.setEventBus(this.ctx.eventBus);
|
|
17261
17288
|
const localNode = this.ctx.kernel.localNodeId;
|
|
17262
17289
|
const localNodeId = localNode ? localNode.includes("/") ? localNode.split("/")[0] : localNode : void 0;
|
|
17263
|
-
this.localNodeId
|
|
17264
|
-
this.brokerManager.setApiAccess(this.ctx.api, localNodeId);
|
|
17290
|
+
this.brokerManager.setApiAccess(this.ctx.api, localNodeId, this.ctx.kernel.hwaccel);
|
|
17265
17291
|
if (this.capabilities) this.brokerManager.setCapabilitiesAccess(this.capabilities);
|
|
17266
17292
|
await this.loadFfmpegConfig();
|
|
17267
17293
|
const rtspProvider = this.brokerManager.getRtspRestreamProvider();
|
|
@@ -17720,6 +17746,27 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
|
|
|
17720
17746
|
step: 1,
|
|
17721
17747
|
default: 5
|
|
17722
17748
|
},
|
|
17749
|
+
{
|
|
17750
|
+
type: "select",
|
|
17751
|
+
key: "hwaccel",
|
|
17752
|
+
label: "Transcode hardware acceleration",
|
|
17753
|
+
description: "Per-node decode/encode hwaccel for the WebRTC/restream transcode path. \"Software (copy)\" is the safe default (decodes every HEVC source); a concrete backend is resolved from this node’s own hardware. Per-node because hardware differs per node.",
|
|
17754
|
+
options: [...FFMPEG_HW_ACCEL_SETTINGS].map((s) => ({
|
|
17755
|
+
value: s,
|
|
17756
|
+
label: hwAccelOptionLabel(s)
|
|
17757
|
+
})),
|
|
17758
|
+
default: "copy",
|
|
17759
|
+
perNode: true
|
|
17760
|
+
},
|
|
17761
|
+
{
|
|
17762
|
+
type: "text",
|
|
17763
|
+
key: "binaryPath",
|
|
17764
|
+
label: "ffmpeg binary path (override)",
|
|
17765
|
+
description: "Optional per-node override for the ffmpeg binary. Leave empty to auto-provision a portable static ffmpeg on this node (recommended).",
|
|
17766
|
+
placeholder: "ffmpeg",
|
|
17767
|
+
default: "",
|
|
17768
|
+
perNode: true
|
|
17769
|
+
},
|
|
17723
17770
|
{
|
|
17724
17771
|
type: "number",
|
|
17725
17772
|
key: "catalogReconcileIntervalSec",
|
|
@@ -17789,7 +17836,7 @@ function codecToInputFormat(codec) {
|
|
|
17789
17836
|
function buildFfmpegArgs(config) {
|
|
17790
17837
|
const inputFormat = codecToInputFormat(config.codec);
|
|
17791
17838
|
const args = [
|
|
17792
|
-
...
|
|
17839
|
+
...require_ffmpeg_args.logBannerArgs("error"),
|
|
17793
17840
|
"-fflags",
|
|
17794
17841
|
"+nobuffer+flush_packets",
|
|
17795
17842
|
"-flags",
|