@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
|
@@ -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 node_fs = require("node:fs");
|
|
9
9
|
node_fs = require_model_download_service_C_IHWnXx.__toESM(node_fs);
|
|
@@ -508,7 +508,7 @@ var AudioAnalyzerProvider = class {
|
|
|
508
508
|
}
|
|
509
509
|
this.classifyCallCount++;
|
|
510
510
|
const meaningful = result.labels.filter((l) => l.score >= .15 && l.className.toLowerCase() !== "silence");
|
|
511
|
-
if (meaningful.length > 0) this.log.
|
|
511
|
+
if (meaningful.length > 0) this.log.debug("audio classification", {
|
|
512
512
|
tags: chunk.deviceId !== void 0 ? { deviceId: chunk.deviceId } : void 0,
|
|
513
513
|
meta: {
|
|
514
514
|
top: meaningful.slice(0, 4).map((l) => `${l.className}(${(l.score * 100).toFixed(0)}%)`).join(", "),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __require } from "../chunk-BdkLduGY.mjs";
|
|
2
|
-
import { B as errMsg, D as mapAudioLabelToMacro, V as BaseAddon, _ as audioAnalyzerCapability, g as audioAnalysisCapability, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, q as hydrateSchema, u as HF_BASE_URL } from "../dist-
|
|
2
|
+
import { B as errMsg, D as mapAudioLabelToMacro, V as BaseAddon, _ as audioAnalyzerCapability, g as audioAnalysisCapability, n as AUDIO_BACKEND_CHOICES, o as DEFAULT_AUDIO_ANALYZER_CONFIG, q as hydrateSchema, u as HF_BASE_URL } from "../dist-CfSORkv2.mjs";
|
|
3
3
|
import { t as pickNodePlatformArch } from "../node-topology-platform-BkR_k6WT.mjs";
|
|
4
4
|
import { r as downloadFile } from "../model-download-service-C-IHWnXx-BPy6aoAx.mjs";
|
|
5
5
|
import * as fs from "node:fs";
|
|
@@ -503,7 +503,7 @@ var AudioAnalyzerProvider = class {
|
|
|
503
503
|
}
|
|
504
504
|
this.classifyCallCount++;
|
|
505
505
|
const meaningful = result.labels.filter((l) => l.score >= .15 && l.className.toLowerCase() !== "silence");
|
|
506
|
-
if (meaningful.length > 0) this.log.
|
|
506
|
+
if (meaningful.length > 0) this.log.debug("audio classification", {
|
|
507
507
|
tags: chunk.deviceId !== void 0 ? { deviceId: chunk.deviceId } : void 0,
|
|
508
508
|
meta: {
|
|
509
509
|
top: meaningful.slice(0, 4).map((l) => `${l.className}(${(l.score * 100).toFixed(0)}%)`).join(", "),
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-BrIE85BG.js");
|
|
6
6
|
let node_crypto = require("node:crypto");
|
|
7
7
|
let node_child_process = require("node:child_process");
|
|
8
8
|
//#region src/audio-codec-ffmpeg/ffmpeg-audio-process.ts
|
|
@@ -615,6 +615,82 @@ var OggOpusFramer = class {
|
|
|
615
615
|
}
|
|
616
616
|
};
|
|
617
617
|
//#endregion
|
|
618
|
+
//#region src/audio-codec-ffmpeg/adts.ts
|
|
619
|
+
/**
|
|
620
|
+
* ADTS framing for raw AAC access units.
|
|
621
|
+
*
|
|
622
|
+
* The audio decode path runs `ffmpeg -f aac -i pipe:0` — the **ADTS** demuxer,
|
|
623
|
+
* which requires each frame to begin with a 7-byte ADTS header (0xFFF sync).
|
|
624
|
+
* But every AAC source that reaches the decoder delivers **raw** AAC access
|
|
625
|
+
* units with no ADTS header: push sources (Reolink Baichuan) emit one bare
|
|
626
|
+
* codec frame per packet, and rfc3640 (RTP AAC) depacketization yields bare
|
|
627
|
+
* AUs too. Piping those straight into `-f aac` fails with
|
|
628
|
+
* `Invalid data found when processing input` → no PCM → a silent WebRTC audio
|
|
629
|
+
* track. Wrapping each raw AU in an ADTS header (built from the source
|
|
630
|
+
* sample-rate / channel-count / AAC object type) makes the demuxer accept it.
|
|
631
|
+
*
|
|
632
|
+
* Opus is handled separately (Ogg encapsulation); this module is AAC-only.
|
|
633
|
+
*/
|
|
634
|
+
/** MPEG-4 AAC sampling-frequency index table (ISO/IEC 14496-3). */
|
|
635
|
+
var AAC_SAMPLE_RATES = [
|
|
636
|
+
96e3,
|
|
637
|
+
88200,
|
|
638
|
+
64e3,
|
|
639
|
+
48e3,
|
|
640
|
+
44100,
|
|
641
|
+
32e3,
|
|
642
|
+
24e3,
|
|
643
|
+
22050,
|
|
644
|
+
16e3,
|
|
645
|
+
12e3,
|
|
646
|
+
11025,
|
|
647
|
+
8e3,
|
|
648
|
+
7350
|
|
649
|
+
];
|
|
650
|
+
/** AAC-LC is object type 2; the ADTS `profile` field is objectType - 1. */
|
|
651
|
+
var DEFAULT_AAC_OBJECT_TYPE = 2;
|
|
652
|
+
/** Resolve the ADTS sampling-frequency index for a sample rate (default 16 kHz → 8). */
|
|
653
|
+
function aacSampleRateIndex(sampleRate) {
|
|
654
|
+
const idx = AAC_SAMPLE_RATES.indexOf(sampleRate);
|
|
655
|
+
return idx >= 0 ? idx : AAC_SAMPLE_RATES.indexOf(16e3);
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* True when `buf` already begins with an ADTS syncword (0xFFF) + MPEG layer 0.
|
|
659
|
+
* Raw AAC AUs never do; ADTS-framed frames always do. Lets the wrapper pass
|
|
660
|
+
* already-framed input through untouched.
|
|
661
|
+
*/
|
|
662
|
+
function hasAdtsSync(buf) {
|
|
663
|
+
return buf.length >= 2 && buf[0] === 255 && (buf[1] & 246) === 240;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Build the 7-byte ADTS header (no CRC) for a payload of `payloadLength` bytes.
|
|
667
|
+
*/
|
|
668
|
+
function buildAdtsHeader(config, payloadLength) {
|
|
669
|
+
const objectType = config.aacObjectType ?? DEFAULT_AAC_OBJECT_TYPE;
|
|
670
|
+
const profile = Math.max(0, objectType - 1) & 3;
|
|
671
|
+
const freqIdx = aacSampleRateIndex(config.sampleRate) & 15;
|
|
672
|
+
const chanCfg = Math.max(1, config.channels) & 7;
|
|
673
|
+
const frameLength = payloadLength + 7;
|
|
674
|
+
const h = Buffer.alloc(7);
|
|
675
|
+
h[0] = 255;
|
|
676
|
+
h[1] = 241;
|
|
677
|
+
h[2] = profile << 6 | freqIdx << 2 | chanCfg >> 2 & 1;
|
|
678
|
+
h[3] = (chanCfg & 3) << 6 | frameLength >> 11 & 3;
|
|
679
|
+
h[4] = frameLength >> 3 & 255;
|
|
680
|
+
h[5] = (frameLength & 7) << 5 | 31;
|
|
681
|
+
h[6] = 252;
|
|
682
|
+
return h;
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Return `frame` framed as ADTS: unchanged when it already carries an ADTS
|
|
686
|
+
* sync, otherwise the 7-byte header prepended. Pure; the input is never mutated.
|
|
687
|
+
*/
|
|
688
|
+
function wrapAacAsAdts(frame, config) {
|
|
689
|
+
if (hasAdtsSync(frame)) return Buffer.from(frame.buffer, frame.byteOffset, frame.byteLength);
|
|
690
|
+
const payload = Buffer.from(frame.buffer, frame.byteOffset, frame.byteLength);
|
|
691
|
+
return Buffer.concat([buildAdtsHeader(config, payload.length), payload]);
|
|
692
|
+
}
|
|
693
|
+
//#endregion
|
|
618
694
|
//#region src/audio-codec-ffmpeg/ffmpeg-audio-decode-session.ts
|
|
619
695
|
var FfmpegAudioDecodeSession = class {
|
|
620
696
|
logger;
|
|
@@ -632,6 +708,14 @@ var FfmpegAudioDecodeSession = class {
|
|
|
632
708
|
* which pipe their encoded frames straight through.
|
|
633
709
|
*/
|
|
634
710
|
oggFramer;
|
|
711
|
+
/**
|
|
712
|
+
* ADTS framing config, present ONLY when the codec is AAC. `ffmpeg -f aac`
|
|
713
|
+
* is the ADTS demuxer, but every AAC source delivers raw AAC access units
|
|
714
|
+
* (push sources emit bare frames; rfc3640 depacketization yields bare AUs) —
|
|
715
|
+
* piping those in raw fails `Invalid data found when processing input` → no
|
|
716
|
+
* PCM → silent WebRTC audio. Each frame is ADTS-wrapped before ffmpeg.
|
|
717
|
+
*/
|
|
718
|
+
adtsConfig;
|
|
635
719
|
residual = Buffer.alloc(0);
|
|
636
720
|
nextPts = 0;
|
|
637
721
|
destroyed = false;
|
|
@@ -646,6 +730,10 @@ var FfmpegAudioDecodeSession = class {
|
|
|
646
730
|
channelCount: Math.max(1, params.sourceChannels),
|
|
647
731
|
serial: serialFromSeed(params.oggSerialSeed ?? "audio-codec-ffmpeg-opus")
|
|
648
732
|
}) : null;
|
|
733
|
+
this.adtsConfig = resolveAudioCodecAlias(params.codec) === "aac" ? {
|
|
734
|
+
sampleRate: params.sourceSampleRate,
|
|
735
|
+
channels: Math.max(1, params.sourceChannels)
|
|
736
|
+
} : null;
|
|
649
737
|
const args = buildAudioDecodeArgs(params);
|
|
650
738
|
this.process = new FfmpegAudioProcess({
|
|
651
739
|
ffmpegPath: params.ffmpegPath,
|
|
@@ -669,6 +757,10 @@ var FfmpegAudioDecodeSession = class {
|
|
|
669
757
|
this.process.write(this.oggFramer.framePacket(packet));
|
|
670
758
|
return;
|
|
671
759
|
}
|
|
760
|
+
if (this.adtsConfig) {
|
|
761
|
+
this.process.write(wrapAacAsAdts(data, this.adtsConfig));
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
672
764
|
this.process.write(data);
|
|
673
765
|
}
|
|
674
766
|
handleStdout(chunk) {
|
|
@@ -895,6 +987,29 @@ var AudioCodecFfmpegAddon = class extends require_dist.BaseAddon {
|
|
|
895
987
|
if (!entry) return false;
|
|
896
988
|
return input.kind === "decode" ? entry.canDecode : entry.canEncode;
|
|
897
989
|
}
|
|
990
|
+
/**
|
|
991
|
+
* The BARE cluster node id this addon runs on (e.g. `hub`) — stamped into a
|
|
992
|
+
* session's return so the caller pins its follow-up `pushEncodedFrame` /
|
|
993
|
+
* `pullPcm` / `closeSession` to the same node. `ctx.kernel.localNodeId` is
|
|
994
|
+
* the COMPOUND `<node>/<addon>` form (e.g. `hub/audio-codec-ffmpeg`), but the
|
|
995
|
+
* cap route resolver matches an explicit `nodeId` against the bare node id
|
|
996
|
+
* (`classifyExplicitNode`: `nodeId === hubNodeId`). Returning the compound
|
|
997
|
+
* form makes every pinned call fall through to a remote-Moleculer dispatch
|
|
998
|
+
* for a non-existent service → `Services waiting is timed out` → no WebRTC
|
|
999
|
+
* audio, cluster-wide. Strip the addon suffix. Mirrors `decoder-ffmpeg`'s
|
|
1000
|
+
* `resolveLocalNodeId`.
|
|
1001
|
+
*
|
|
1002
|
+
* TODO(arch): this bare-node normalization is duplicated across
|
|
1003
|
+
* `decoder-ffmpeg`, `decoder-nodeav`, `audio-codec-ffmpeg`, and
|
|
1004
|
+
* `decoder-backend-keys.normalizeDecoderNodeId`. It should live ONCE on the
|
|
1005
|
+
* shared surface — either a bare `ctx.kernel.nodeId` accessor alongside the
|
|
1006
|
+
* compound `localNodeId`, or a base-addon helper — so no addon can leak the
|
|
1007
|
+
* compound `<node>/<addon>` id into a routing `nodeId` again (this exact bug).
|
|
1008
|
+
*/
|
|
1009
|
+
resolveLocalNodeId() {
|
|
1010
|
+
const raw = this.ctx.kernel.localNodeId ?? "local";
|
|
1011
|
+
return raw.includes("/") ? raw.split("/")[0] : raw;
|
|
1012
|
+
}
|
|
898
1013
|
async createDecodeSession(input) {
|
|
899
1014
|
const codec = resolveAudioCodecAlias(input.codec);
|
|
900
1015
|
const entry = CODEC_CATALOG.find((e) => resolveAudioCodecAlias(e.codec) === codec);
|
|
@@ -926,7 +1041,7 @@ var AudioCodecFfmpegAddon = class extends require_dist.BaseAddon {
|
|
|
926
1041
|
});
|
|
927
1042
|
return {
|
|
928
1043
|
sessionId,
|
|
929
|
-
nodeId: this.
|
|
1044
|
+
nodeId: this.resolveLocalNodeId()
|
|
930
1045
|
};
|
|
931
1046
|
}
|
|
932
1047
|
async createEncodeSession(input) {
|
|
@@ -960,7 +1075,7 @@ var AudioCodecFfmpegAddon = class extends require_dist.BaseAddon {
|
|
|
960
1075
|
});
|
|
961
1076
|
return {
|
|
962
1077
|
sessionId,
|
|
963
|
-
nodeId: this.
|
|
1078
|
+
nodeId: this.resolveLocalNodeId()
|
|
964
1079
|
};
|
|
965
1080
|
}
|
|
966
1081
|
async closeSession(input) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as errMsg, V as BaseAddon, v as audioCodecCapability } from "../dist-
|
|
1
|
+
import { B as errMsg, V as BaseAddon, v as audioCodecCapability } from "../dist-CfSORkv2.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
//#region src/audio-codec-ffmpeg/ffmpeg-audio-process.ts
|
|
@@ -611,6 +611,82 @@ var OggOpusFramer = class {
|
|
|
611
611
|
}
|
|
612
612
|
};
|
|
613
613
|
//#endregion
|
|
614
|
+
//#region src/audio-codec-ffmpeg/adts.ts
|
|
615
|
+
/**
|
|
616
|
+
* ADTS framing for raw AAC access units.
|
|
617
|
+
*
|
|
618
|
+
* The audio decode path runs `ffmpeg -f aac -i pipe:0` — the **ADTS** demuxer,
|
|
619
|
+
* which requires each frame to begin with a 7-byte ADTS header (0xFFF sync).
|
|
620
|
+
* But every AAC source that reaches the decoder delivers **raw** AAC access
|
|
621
|
+
* units with no ADTS header: push sources (Reolink Baichuan) emit one bare
|
|
622
|
+
* codec frame per packet, and rfc3640 (RTP AAC) depacketization yields bare
|
|
623
|
+
* AUs too. Piping those straight into `-f aac` fails with
|
|
624
|
+
* `Invalid data found when processing input` → no PCM → a silent WebRTC audio
|
|
625
|
+
* track. Wrapping each raw AU in an ADTS header (built from the source
|
|
626
|
+
* sample-rate / channel-count / AAC object type) makes the demuxer accept it.
|
|
627
|
+
*
|
|
628
|
+
* Opus is handled separately (Ogg encapsulation); this module is AAC-only.
|
|
629
|
+
*/
|
|
630
|
+
/** MPEG-4 AAC sampling-frequency index table (ISO/IEC 14496-3). */
|
|
631
|
+
var AAC_SAMPLE_RATES = [
|
|
632
|
+
96e3,
|
|
633
|
+
88200,
|
|
634
|
+
64e3,
|
|
635
|
+
48e3,
|
|
636
|
+
44100,
|
|
637
|
+
32e3,
|
|
638
|
+
24e3,
|
|
639
|
+
22050,
|
|
640
|
+
16e3,
|
|
641
|
+
12e3,
|
|
642
|
+
11025,
|
|
643
|
+
8e3,
|
|
644
|
+
7350
|
|
645
|
+
];
|
|
646
|
+
/** AAC-LC is object type 2; the ADTS `profile` field is objectType - 1. */
|
|
647
|
+
var DEFAULT_AAC_OBJECT_TYPE = 2;
|
|
648
|
+
/** Resolve the ADTS sampling-frequency index for a sample rate (default 16 kHz → 8). */
|
|
649
|
+
function aacSampleRateIndex(sampleRate) {
|
|
650
|
+
const idx = AAC_SAMPLE_RATES.indexOf(sampleRate);
|
|
651
|
+
return idx >= 0 ? idx : AAC_SAMPLE_RATES.indexOf(16e3);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* True when `buf` already begins with an ADTS syncword (0xFFF) + MPEG layer 0.
|
|
655
|
+
* Raw AAC AUs never do; ADTS-framed frames always do. Lets the wrapper pass
|
|
656
|
+
* already-framed input through untouched.
|
|
657
|
+
*/
|
|
658
|
+
function hasAdtsSync(buf) {
|
|
659
|
+
return buf.length >= 2 && buf[0] === 255 && (buf[1] & 246) === 240;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Build the 7-byte ADTS header (no CRC) for a payload of `payloadLength` bytes.
|
|
663
|
+
*/
|
|
664
|
+
function buildAdtsHeader(config, payloadLength) {
|
|
665
|
+
const objectType = config.aacObjectType ?? DEFAULT_AAC_OBJECT_TYPE;
|
|
666
|
+
const profile = Math.max(0, objectType - 1) & 3;
|
|
667
|
+
const freqIdx = aacSampleRateIndex(config.sampleRate) & 15;
|
|
668
|
+
const chanCfg = Math.max(1, config.channels) & 7;
|
|
669
|
+
const frameLength = payloadLength + 7;
|
|
670
|
+
const h = Buffer.alloc(7);
|
|
671
|
+
h[0] = 255;
|
|
672
|
+
h[1] = 241;
|
|
673
|
+
h[2] = profile << 6 | freqIdx << 2 | chanCfg >> 2 & 1;
|
|
674
|
+
h[3] = (chanCfg & 3) << 6 | frameLength >> 11 & 3;
|
|
675
|
+
h[4] = frameLength >> 3 & 255;
|
|
676
|
+
h[5] = (frameLength & 7) << 5 | 31;
|
|
677
|
+
h[6] = 252;
|
|
678
|
+
return h;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Return `frame` framed as ADTS: unchanged when it already carries an ADTS
|
|
682
|
+
* sync, otherwise the 7-byte header prepended. Pure; the input is never mutated.
|
|
683
|
+
*/
|
|
684
|
+
function wrapAacAsAdts(frame, config) {
|
|
685
|
+
if (hasAdtsSync(frame)) return Buffer.from(frame.buffer, frame.byteOffset, frame.byteLength);
|
|
686
|
+
const payload = Buffer.from(frame.buffer, frame.byteOffset, frame.byteLength);
|
|
687
|
+
return Buffer.concat([buildAdtsHeader(config, payload.length), payload]);
|
|
688
|
+
}
|
|
689
|
+
//#endregion
|
|
614
690
|
//#region src/audio-codec-ffmpeg/ffmpeg-audio-decode-session.ts
|
|
615
691
|
var FfmpegAudioDecodeSession = class {
|
|
616
692
|
logger;
|
|
@@ -628,6 +704,14 @@ var FfmpegAudioDecodeSession = class {
|
|
|
628
704
|
* which pipe their encoded frames straight through.
|
|
629
705
|
*/
|
|
630
706
|
oggFramer;
|
|
707
|
+
/**
|
|
708
|
+
* ADTS framing config, present ONLY when the codec is AAC. `ffmpeg -f aac`
|
|
709
|
+
* is the ADTS demuxer, but every AAC source delivers raw AAC access units
|
|
710
|
+
* (push sources emit bare frames; rfc3640 depacketization yields bare AUs) —
|
|
711
|
+
* piping those in raw fails `Invalid data found when processing input` → no
|
|
712
|
+
* PCM → silent WebRTC audio. Each frame is ADTS-wrapped before ffmpeg.
|
|
713
|
+
*/
|
|
714
|
+
adtsConfig;
|
|
631
715
|
residual = Buffer.alloc(0);
|
|
632
716
|
nextPts = 0;
|
|
633
717
|
destroyed = false;
|
|
@@ -642,6 +726,10 @@ var FfmpegAudioDecodeSession = class {
|
|
|
642
726
|
channelCount: Math.max(1, params.sourceChannels),
|
|
643
727
|
serial: serialFromSeed(params.oggSerialSeed ?? "audio-codec-ffmpeg-opus")
|
|
644
728
|
}) : null;
|
|
729
|
+
this.adtsConfig = resolveAudioCodecAlias(params.codec) === "aac" ? {
|
|
730
|
+
sampleRate: params.sourceSampleRate,
|
|
731
|
+
channels: Math.max(1, params.sourceChannels)
|
|
732
|
+
} : null;
|
|
645
733
|
const args = buildAudioDecodeArgs(params);
|
|
646
734
|
this.process = new FfmpegAudioProcess({
|
|
647
735
|
ffmpegPath: params.ffmpegPath,
|
|
@@ -665,6 +753,10 @@ var FfmpegAudioDecodeSession = class {
|
|
|
665
753
|
this.process.write(this.oggFramer.framePacket(packet));
|
|
666
754
|
return;
|
|
667
755
|
}
|
|
756
|
+
if (this.adtsConfig) {
|
|
757
|
+
this.process.write(wrapAacAsAdts(data, this.adtsConfig));
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
668
760
|
this.process.write(data);
|
|
669
761
|
}
|
|
670
762
|
handleStdout(chunk) {
|
|
@@ -891,6 +983,29 @@ var AudioCodecFfmpegAddon = class extends BaseAddon {
|
|
|
891
983
|
if (!entry) return false;
|
|
892
984
|
return input.kind === "decode" ? entry.canDecode : entry.canEncode;
|
|
893
985
|
}
|
|
986
|
+
/**
|
|
987
|
+
* The BARE cluster node id this addon runs on (e.g. `hub`) — stamped into a
|
|
988
|
+
* session's return so the caller pins its follow-up `pushEncodedFrame` /
|
|
989
|
+
* `pullPcm` / `closeSession` to the same node. `ctx.kernel.localNodeId` is
|
|
990
|
+
* the COMPOUND `<node>/<addon>` form (e.g. `hub/audio-codec-ffmpeg`), but the
|
|
991
|
+
* cap route resolver matches an explicit `nodeId` against the bare node id
|
|
992
|
+
* (`classifyExplicitNode`: `nodeId === hubNodeId`). Returning the compound
|
|
993
|
+
* form makes every pinned call fall through to a remote-Moleculer dispatch
|
|
994
|
+
* for a non-existent service → `Services waiting is timed out` → no WebRTC
|
|
995
|
+
* audio, cluster-wide. Strip the addon suffix. Mirrors `decoder-ffmpeg`'s
|
|
996
|
+
* `resolveLocalNodeId`.
|
|
997
|
+
*
|
|
998
|
+
* TODO(arch): this bare-node normalization is duplicated across
|
|
999
|
+
* `decoder-ffmpeg`, `decoder-nodeav`, `audio-codec-ffmpeg`, and
|
|
1000
|
+
* `decoder-backend-keys.normalizeDecoderNodeId`. It should live ONCE on the
|
|
1001
|
+
* shared surface — either a bare `ctx.kernel.nodeId` accessor alongside the
|
|
1002
|
+
* compound `localNodeId`, or a base-addon helper — so no addon can leak the
|
|
1003
|
+
* compound `<node>/<addon>` id into a routing `nodeId` again (this exact bug).
|
|
1004
|
+
*/
|
|
1005
|
+
resolveLocalNodeId() {
|
|
1006
|
+
const raw = this.ctx.kernel.localNodeId ?? "local";
|
|
1007
|
+
return raw.includes("/") ? raw.split("/")[0] : raw;
|
|
1008
|
+
}
|
|
894
1009
|
async createDecodeSession(input) {
|
|
895
1010
|
const codec = resolveAudioCodecAlias(input.codec);
|
|
896
1011
|
const entry = CODEC_CATALOG.find((e) => resolveAudioCodecAlias(e.codec) === codec);
|
|
@@ -922,7 +1037,7 @@ var AudioCodecFfmpegAddon = class extends BaseAddon {
|
|
|
922
1037
|
});
|
|
923
1038
|
return {
|
|
924
1039
|
sessionId,
|
|
925
|
-
nodeId: this.
|
|
1040
|
+
nodeId: this.resolveLocalNodeId()
|
|
926
1041
|
};
|
|
927
1042
|
}
|
|
928
1043
|
async createEncodeSession(input) {
|
|
@@ -956,7 +1071,7 @@ var AudioCodecFfmpegAddon = class extends BaseAddon {
|
|
|
956
1071
|
});
|
|
957
1072
|
return {
|
|
958
1073
|
sessionId,
|
|
959
|
-
nodeId: this.
|
|
1074
|
+
nodeId: this.resolveLocalNodeId()
|
|
960
1075
|
};
|
|
961
1076
|
}
|
|
962
1077
|
async closeSession(input) {
|
|
@@ -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
|
const require_frame_dropper = require("../frame-dropper-DKLM6pMz.js");
|
|
9
9
|
let _camstack_shm_ring = require("@camstack/shm-ring");
|
|
10
10
|
let node_crypto = require("node:crypto");
|
|
@@ -114,6 +114,13 @@ var HWACCEL_FALLBACK_WINDOW_MS = 4e3;
|
|
|
114
114
|
* session down (`destroy`) cancels the pending re-dial.
|
|
115
115
|
*/
|
|
116
116
|
var PULL_REDIAL_DELAY_MS = 3e3;
|
|
117
|
+
/**
|
|
118
|
+
* Safety ceiling for the current-GOP replay buffer (push mode). A GOP resets on
|
|
119
|
+
* every keyframe so this is never reached by a well-behaved stream; it only
|
|
120
|
+
* bounds a pathological long/no-keyframe stream. When exceeded, only the
|
|
121
|
+
* keyframe is retained — enough to re-prime the decoder's SPS/PPS on a re-spawn.
|
|
122
|
+
*/
|
|
123
|
+
var MAX_GOP_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
117
124
|
var FfmpegDecoderSession = class {
|
|
118
125
|
config;
|
|
119
126
|
logger;
|
|
@@ -185,6 +192,18 @@ var FfmpegDecoderSession = class {
|
|
|
185
192
|
handleCallbacks = /* @__PURE__ */ new Set();
|
|
186
193
|
frameDropper;
|
|
187
194
|
inputPackets = 0;
|
|
195
|
+
/**
|
|
196
|
+
* Current-GOP Annex-B packet bytes (push mode), from the most recent keyframe
|
|
197
|
+
* forward. Replayed keyframe-first into every freshly-spawned child so a
|
|
198
|
+
* hwaccel cascade re-spawn (`vaapi → qsv → software`) or a config restart
|
|
199
|
+
* resumes on a keyframe instead of the live mid-GOP P-frame the dead child had
|
|
200
|
+
* already consumed — which the fallback child would otherwise choke on
|
|
201
|
+
* (`non-existing PPS 0`) until the next live keyframe. Mirrors the reference
|
|
202
|
+
* NVR libav decoder, which replays its buffered packet list to each hwaccel
|
|
203
|
+
* candidate. Reset on each keyframe; unused in pull mode.
|
|
204
|
+
*/
|
|
205
|
+
gopBuffer = [];
|
|
206
|
+
gopBufferBytes = 0;
|
|
188
207
|
outputFrames = 0;
|
|
189
208
|
droppedFrames = 0;
|
|
190
209
|
startTime = Date.now();
|
|
@@ -280,6 +299,7 @@ var FfmpegDecoderSession = class {
|
|
|
280
299
|
this.process = child;
|
|
281
300
|
this.spawnedAtMs = Date.now();
|
|
282
301
|
child.stdin?.on("error", () => {});
|
|
302
|
+
this.replayGopBuffer(child);
|
|
283
303
|
child.stdout?.on("data", (chunk) => {
|
|
284
304
|
if (child === this.process) this.handleStdout(chunk);
|
|
285
305
|
});
|
|
@@ -491,14 +511,50 @@ var FfmpegDecoderSession = class {
|
|
|
491
511
|
pushPacket(packet) {
|
|
492
512
|
if (this.destroyed) return;
|
|
493
513
|
if (this.pullUrl !== null) return;
|
|
494
|
-
|
|
514
|
+
this.recordGop(packet);
|
|
515
|
+
this.inputPackets++;
|
|
516
|
+
if (this.process === null && !this.spawnAttempted) {
|
|
517
|
+
this.spawnFfmpeg();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
495
520
|
const stdin = this.process?.stdin;
|
|
496
521
|
if (!stdin) return;
|
|
497
|
-
this.inputPackets++;
|
|
498
522
|
try {
|
|
499
523
|
stdin.write(packet.data);
|
|
500
524
|
} catch {}
|
|
501
525
|
}
|
|
526
|
+
/**
|
|
527
|
+
* Accumulate the current GOP for the keyframe-first re-spawn replay. Reset on
|
|
528
|
+
* each keyframe; P-frames before the first keyframe are ignored (useless to
|
|
529
|
+
* prime a decoder). A pathological long/no-keyframe GOP is capped to the
|
|
530
|
+
* keyframe alone (enough to re-prime SPS/PPS).
|
|
531
|
+
*/
|
|
532
|
+
recordGop(packet) {
|
|
533
|
+
if (packet.keyframe) {
|
|
534
|
+
this.gopBuffer = [packet.data];
|
|
535
|
+
this.gopBufferBytes = packet.data.length;
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (this.gopBuffer.length === 0) return;
|
|
539
|
+
this.gopBuffer.push(packet.data);
|
|
540
|
+
this.gopBufferBytes += packet.data.length;
|
|
541
|
+
if (this.gopBufferBytes > MAX_GOP_BUFFER_BYTES) {
|
|
542
|
+
const keyframe = this.gopBuffer[0];
|
|
543
|
+
this.gopBuffer = [keyframe];
|
|
544
|
+
this.gopBufferBytes = keyframe.length;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/** Replay the buffered GOP (keyframe-first) into a freshly-spawned push child. */
|
|
548
|
+
replayGopBuffer(child) {
|
|
549
|
+
if (this.pullUrl !== null || this.gopBuffer.length === 0) return;
|
|
550
|
+
const stdin = child.stdin;
|
|
551
|
+
if (!stdin) return;
|
|
552
|
+
for (const buf of this.gopBuffer) try {
|
|
553
|
+
stdin.write(buf);
|
|
554
|
+
} catch {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
502
558
|
onFrame(callback) {
|
|
503
559
|
this.frameCallbacks.add(callback);
|
|
504
560
|
return () => {
|
|
@@ -709,7 +765,16 @@ var DecoderFfmpegAddon = class extends require_dist.BaseAddon {
|
|
|
709
765
|
if (!this.config.probedBestHwaccel) this.reprobeHwaccel().catch((err) => {
|
|
710
766
|
this.ctx.logger.warn("ffmpeg: auto-reprobe hwaccel failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
711
767
|
});
|
|
712
|
-
|
|
768
|
+
let backend;
|
|
769
|
+
try {
|
|
770
|
+
backend = require_frame_ring_sink.resolveOwnDecoderBackend(await this.resolveGlobalStore(this.resolveLocalNodeId()));
|
|
771
|
+
} catch (err) {
|
|
772
|
+
this.ctx.logger.warn("decoder-ffmpeg: own-store backend read failed — using default", { meta: {
|
|
773
|
+
default: require_frame_ring_sink.DEFAULT_DECODER_BACKEND,
|
|
774
|
+
error: err instanceof Error ? err.message : String(err)
|
|
775
|
+
} });
|
|
776
|
+
backend = require_frame_ring_sink.DEFAULT_DECODER_BACKEND;
|
|
777
|
+
}
|
|
713
778
|
if (backend !== "ffmpeg") {
|
|
714
779
|
this.ctx.logger.info("ffmpeg decoder: this node selects a different decoder backend — standing down (no decoder provider registered)", { meta: { selectedBackend: backend } });
|
|
715
780
|
return [];
|
|
@@ -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 {
|
|
3
|
-
import { a as codecToInputFormat, c as maxDecodeWidth, d as pickDecodeHwAccel, f as rankDecodeHwAccels, i as channelsForPixel, l as parseAvailableGpuScaleFilters, m as resolveGpuScaleFilter, n as DEFAULT_VAAPI_RENDER_DEVICE, o as computeOutputGeometry, p as rawPixelForFormat, r as buildFfmpegDecodeArgs, s as isSoftwareHwAccel, t as DECODE_HWACCEL_RANK, u as parseFfmpegOutputDims } 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 { c as DEFAULT_DECODER_BACKEND, i as NotifyingRingBuffer, n as RING_BUDGET_MB, s as resolveOwnDecoderBackend, t as DecoderFrameRingSink } from "../frame-ring-sink-B_WjDX0x.mjs";
|
|
3
|
+
import { a as codecToInputFormat, c as maxDecodeWidth, d as pickDecodeHwAccel, f as rankDecodeHwAccels, i as channelsForPixel, l as parseAvailableGpuScaleFilters, m as resolveGpuScaleFilter, n as DEFAULT_VAAPI_RENDER_DEVICE, o as computeOutputGeometry, p as rawPixelForFormat, r as buildFfmpegDecodeArgs, s as isSoftwareHwAccel, t as DECODE_HWACCEL_RANK, u as parseFfmpegOutputDims } from "../ffmpeg-args-CpeRWwL4.mjs";
|
|
4
4
|
import { t as FrameDropper } from "../frame-dropper-AjheBGMG.mjs";
|
|
5
5
|
import { FrameRingReaderCache } from "@camstack/shm-ring";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
@@ -110,6 +110,13 @@ var HWACCEL_FALLBACK_WINDOW_MS = 4e3;
|
|
|
110
110
|
* session down (`destroy`) cancels the pending re-dial.
|
|
111
111
|
*/
|
|
112
112
|
var PULL_REDIAL_DELAY_MS = 3e3;
|
|
113
|
+
/**
|
|
114
|
+
* Safety ceiling for the current-GOP replay buffer (push mode). A GOP resets on
|
|
115
|
+
* every keyframe so this is never reached by a well-behaved stream; it only
|
|
116
|
+
* bounds a pathological long/no-keyframe stream. When exceeded, only the
|
|
117
|
+
* keyframe is retained — enough to re-prime the decoder's SPS/PPS on a re-spawn.
|
|
118
|
+
*/
|
|
119
|
+
var MAX_GOP_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
113
120
|
var FfmpegDecoderSession = class {
|
|
114
121
|
config;
|
|
115
122
|
logger;
|
|
@@ -181,6 +188,18 @@ var FfmpegDecoderSession = class {
|
|
|
181
188
|
handleCallbacks = /* @__PURE__ */ new Set();
|
|
182
189
|
frameDropper;
|
|
183
190
|
inputPackets = 0;
|
|
191
|
+
/**
|
|
192
|
+
* Current-GOP Annex-B packet bytes (push mode), from the most recent keyframe
|
|
193
|
+
* forward. Replayed keyframe-first into every freshly-spawned child so a
|
|
194
|
+
* hwaccel cascade re-spawn (`vaapi → qsv → software`) or a config restart
|
|
195
|
+
* resumes on a keyframe instead of the live mid-GOP P-frame the dead child had
|
|
196
|
+
* already consumed — which the fallback child would otherwise choke on
|
|
197
|
+
* (`non-existing PPS 0`) until the next live keyframe. Mirrors the reference
|
|
198
|
+
* NVR libav decoder, which replays its buffered packet list to each hwaccel
|
|
199
|
+
* candidate. Reset on each keyframe; unused in pull mode.
|
|
200
|
+
*/
|
|
201
|
+
gopBuffer = [];
|
|
202
|
+
gopBufferBytes = 0;
|
|
184
203
|
outputFrames = 0;
|
|
185
204
|
droppedFrames = 0;
|
|
186
205
|
startTime = Date.now();
|
|
@@ -276,6 +295,7 @@ var FfmpegDecoderSession = class {
|
|
|
276
295
|
this.process = child;
|
|
277
296
|
this.spawnedAtMs = Date.now();
|
|
278
297
|
child.stdin?.on("error", () => {});
|
|
298
|
+
this.replayGopBuffer(child);
|
|
279
299
|
child.stdout?.on("data", (chunk) => {
|
|
280
300
|
if (child === this.process) this.handleStdout(chunk);
|
|
281
301
|
});
|
|
@@ -487,14 +507,50 @@ var FfmpegDecoderSession = class {
|
|
|
487
507
|
pushPacket(packet) {
|
|
488
508
|
if (this.destroyed) return;
|
|
489
509
|
if (this.pullUrl !== null) return;
|
|
490
|
-
|
|
510
|
+
this.recordGop(packet);
|
|
511
|
+
this.inputPackets++;
|
|
512
|
+
if (this.process === null && !this.spawnAttempted) {
|
|
513
|
+
this.spawnFfmpeg();
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
491
516
|
const stdin = this.process?.stdin;
|
|
492
517
|
if (!stdin) return;
|
|
493
|
-
this.inputPackets++;
|
|
494
518
|
try {
|
|
495
519
|
stdin.write(packet.data);
|
|
496
520
|
} catch {}
|
|
497
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* Accumulate the current GOP for the keyframe-first re-spawn replay. Reset on
|
|
524
|
+
* each keyframe; P-frames before the first keyframe are ignored (useless to
|
|
525
|
+
* prime a decoder). A pathological long/no-keyframe GOP is capped to the
|
|
526
|
+
* keyframe alone (enough to re-prime SPS/PPS).
|
|
527
|
+
*/
|
|
528
|
+
recordGop(packet) {
|
|
529
|
+
if (packet.keyframe) {
|
|
530
|
+
this.gopBuffer = [packet.data];
|
|
531
|
+
this.gopBufferBytes = packet.data.length;
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
if (this.gopBuffer.length === 0) return;
|
|
535
|
+
this.gopBuffer.push(packet.data);
|
|
536
|
+
this.gopBufferBytes += packet.data.length;
|
|
537
|
+
if (this.gopBufferBytes > MAX_GOP_BUFFER_BYTES) {
|
|
538
|
+
const keyframe = this.gopBuffer[0];
|
|
539
|
+
this.gopBuffer = [keyframe];
|
|
540
|
+
this.gopBufferBytes = keyframe.length;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/** Replay the buffered GOP (keyframe-first) into a freshly-spawned push child. */
|
|
544
|
+
replayGopBuffer(child) {
|
|
545
|
+
if (this.pullUrl !== null || this.gopBuffer.length === 0) return;
|
|
546
|
+
const stdin = child.stdin;
|
|
547
|
+
if (!stdin) return;
|
|
548
|
+
for (const buf of this.gopBuffer) try {
|
|
549
|
+
stdin.write(buf);
|
|
550
|
+
} catch {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
498
554
|
onFrame(callback) {
|
|
499
555
|
this.frameCallbacks.add(callback);
|
|
500
556
|
return () => {
|
|
@@ -705,7 +761,16 @@ var DecoderFfmpegAddon = class extends BaseAddon {
|
|
|
705
761
|
if (!this.config.probedBestHwaccel) this.reprobeHwaccel().catch((err) => {
|
|
706
762
|
this.ctx.logger.warn("ffmpeg: auto-reprobe hwaccel failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
707
763
|
});
|
|
708
|
-
|
|
764
|
+
let backend;
|
|
765
|
+
try {
|
|
766
|
+
backend = resolveOwnDecoderBackend(await this.resolveGlobalStore(this.resolveLocalNodeId()));
|
|
767
|
+
} catch (err) {
|
|
768
|
+
this.ctx.logger.warn("decoder-ffmpeg: own-store backend read failed — using default", { meta: {
|
|
769
|
+
default: DEFAULT_DECODER_BACKEND,
|
|
770
|
+
error: err instanceof Error ? err.message : String(err)
|
|
771
|
+
} });
|
|
772
|
+
backend = DEFAULT_DECODER_BACKEND;
|
|
773
|
+
}
|
|
709
774
|
if (backend !== "ffmpeg") {
|
|
710
775
|
this.ctx.logger.info("ffmpeg decoder: this node selects a different decoder backend — standing down (no decoder provider registered)", { meta: { selectedBackend: backend } });
|
|
711
776
|
return [];
|