@camstack/addon-pipeline 0.1.8
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 +723 -0
- package/dist/audio-analyzer/index.js.map +1 -0
- package/dist/audio-analyzer/index.mjs +683 -0
- package/dist/audio-analyzer/index.mjs.map +1 -0
- package/dist/audio-codec-nodeav/index.js +467 -0
- package/dist/audio-codec-nodeav/index.js.map +1 -0
- package/dist/audio-codec-nodeav/index.mjs +467 -0
- package/dist/audio-codec-nodeav/index.mjs.map +1 -0
- package/dist/decoder-nodeav/index.js +929 -0
- package/dist/decoder-nodeav/index.js.map +1 -0
- package/dist/decoder-nodeav/index.mjs +907 -0
- package/dist/decoder-nodeav/index.mjs.map +1 -0
- package/dist/detection-pipeline/index.js +5766 -0
- package/dist/detection-pipeline/index.js.map +1 -0
- package/dist/detection-pipeline/index.mjs +5725 -0
- package/dist/detection-pipeline/index.mjs.map +1 -0
- package/dist/index-D_cl0Qqb.js +5791 -0
- package/dist/index-D_cl0Qqb.js.map +1 -0
- package/dist/index-UbcdLS7a.mjs +5790 -0
- package/dist/index-UbcdLS7a.mjs.map +1 -0
- package/dist/motion-wasm/index.js +476 -0
- package/dist/motion-wasm/index.js.map +1 -0
- package/dist/motion-wasm/index.mjs +454 -0
- package/dist/motion-wasm/index.mjs.map +1 -0
- package/dist/pipeline-runner/index.js +1669 -0
- package/dist/pipeline-runner/index.js.map +1 -0
- package/dist/pipeline-runner/index.mjs +1647 -0
- package/dist/pipeline-runner/index.mjs.map +1 -0
- package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/StreamBrokerPanel.d.ts +21 -0
- package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/index.d.ts +13 -0
- package/dist/stream-broker/@mf-types/widgets.d.ts +2 -0
- package/dist/stream-broker/@mf-types.d.ts +3 -0
- package/dist/stream-broker/@mf-types.zip +0 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-h5aXOPSA.mjs +12 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-C-URP6DW.mjs +17 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-69eEmXwl.mjs +20 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.mjs-U1EUeEPs.mjs +104 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_trpc_mf_1_client__loadShare__.mjs-DeouEaSs.mjs +85 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.mjs-DHUwjbb9.mjs +62 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-DePVYdid.mjs +85 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-CBlCGyx5.mjs +29 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.mjs-gBEZsQrp.mjs +36 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs-DYEKzzY-.mjs +45 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-DZchZKbW.mjs +6 -0
- package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom_mf_1_client__loadShare__.mjs-DICOtMTl.mjs +34 -0
- package/dist/stream-broker/_stub.js +752 -0
- package/dist/stream-broker/_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-D6o1e2ed.mjs +156 -0
- package/dist/stream-broker/client-BK73l2KT.mjs +10063 -0
- package/dist/stream-broker/getErrorShape-BPSzUA7W-TlK8ipWe.mjs +211 -0
- package/dist/stream-broker/hostInit-RCeroTVY.mjs +168 -0
- package/dist/stream-broker/index-BYclbfM0.mjs +15806 -0
- package/dist/stream-broker/index-BhXZh4lQ.mjs +1617 -0
- package/dist/stream-broker/index-BxHaCH3N.mjs +725 -0
- package/dist/stream-broker/index-D2-K2YJ7.mjs +19268 -0
- package/dist/stream-broker/index-IUYKHbxX.mjs +185 -0
- package/dist/stream-broker/index-Ss9m7Jum.mjs +2603 -0
- package/dist/stream-broker/index-ns1fRD30.mjs +435 -0
- package/dist/stream-broker/index-xncRG7-x.mjs +2713 -0
- package/dist/stream-broker/index.js +11171 -0
- package/dist/stream-broker/index.js.map +1 -0
- package/dist/stream-broker/index.mjs +11130 -0
- package/dist/stream-broker/index.mjs.map +1 -0
- package/dist/stream-broker/jsx-runtime-ZdY5pIZz.mjs +55 -0
- package/dist/stream-broker/remoteEntry.js +2973 -0
- package/dist/stream-broker/virtualExposes-pCd777Rp.mjs +42 -0
- package/package.json +258 -0
- package/python/__pycache__/inference_pool.cpython-313.pyc +0 -0
- package/python/inference_pool.py +1088 -0
- package/python/postprocessors/__init__.py +24 -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__/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__/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/python/postprocessors/arcface.py +31 -0
- package/python/postprocessors/ctc.py +68 -0
- package/python/postprocessors/saliency.py +44 -0
- package/python/postprocessors/scrfd.py +212 -0
- package/python/postprocessors/softmax.py +43 -0
- package/python/postprocessors/yamnet.py +41 -0
- package/python/postprocessors/yolo.py +278 -0
- package/python/postprocessors/yolo_seg.py +247 -0
- package/python/requirements-coreml.txt +4 -0
- package/python/requirements-onnxruntime.txt +3 -0
- package/python/requirements-openvino.txt +3 -0
- package/python/requirements.txt +9 -0
- package/swift/audio-analyzer/apple-sound-classifier +0 -0
- package/swift/audio-analyzer/apple-sound-classifier.swift +213 -0
- package/swift/detection-pipeline/apple-sound-classifier +0 -0
- package/swift/detection-pipeline/apple-sound-classifier.swift +196 -0
- package/wasm/assembly/index.ts +290 -0
- package/wasm/assembly/tsconfig.json +4 -0
- package/wasm/motion.wasm +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../src/audio-codec-nodeav/addon/codec-runtime.ts","../../src/audio-codec-nodeav/addon/index.ts"],"sourcesContent":["/**\n * Thin libavcodec/libswresample wrapper backing one decode or encode\n * session in `audio-codec`. Holds:\n * - CodecContext (decoder or encoder)\n * - SoftwareResampleContext for input format → output format\n * - reusable Packet + input/output Frame\n *\n * Stateful — one runtime per session. The cap addon owns the lifecycle.\n */\nimport {\n Codec,\n CodecContext,\n Frame,\n Packet,\n SoftwareResampleContext,\n AV_CHANNEL_ORDER_NATIVE,\n AV_CH_LAYOUT_MONO,\n AV_CH_LAYOUT_STEREO,\n AV_CODEC_ID_AAC,\n AV_CODEC_ID_AAC_LATM,\n AV_CODEC_ID_OPUS,\n AV_CODEC_ID_PCM_ALAW,\n AV_CODEC_ID_PCM_MULAW,\n AV_SAMPLE_FMT_FLT,\n AV_SAMPLE_FMT_S16,\n AVERROR_EAGAIN,\n AVERROR_EOF,\n type AVCodecID,\n type AVSampleFormat,\n type ChannelLayout,\n} from 'node-av'\nimport type { PcmSampleFormat } from '@camstack/types'\n\n// ── Codec resolution ────────────────────────────────────────────────────────\n\nconst CODEC_ID_BY_NAME: Readonly<Record<string, AVCodecID>> = {\n aac: AV_CODEC_ID_AAC,\n aac_latm: AV_CODEC_ID_AAC_LATM,\n opus: AV_CODEC_ID_OPUS,\n pcm_alaw: AV_CODEC_ID_PCM_ALAW,\n pcm_mulaw: AV_CODEC_ID_PCM_MULAW,\n}\n\nexport function resolveCodecId(codec: string): AVCodecID | null {\n return CODEC_ID_BY_NAME[codec.toLowerCase()] ?? null\n}\n\n// ── Channel layout ──────────────────────────────────────────────────────────\n\nexport function buildChannelLayout(channels: number): ChannelLayout {\n if (channels === 1) {\n return { nbChannels: 1, order: AV_CHANNEL_ORDER_NATIVE, mask: AV_CH_LAYOUT_MONO }\n }\n if (channels === 2) {\n return { nbChannels: 2, order: AV_CHANNEL_ORDER_NATIVE, mask: AV_CH_LAYOUT_STEREO }\n }\n // For >2 channels fall back to mask=0 (unknown layout) — libav still\n // works with raw sample interleave; consumers requesting exotic layouts\n // can always pass through stereo or mono.\n return { nbChannels: channels, order: AV_CHANNEL_ORDER_NATIVE, mask: 0n }\n}\n\n// ── PCM format mapping ──────────────────────────────────────────────────────\n\nexport function pcmFormatToAv(format: PcmSampleFormat): AVSampleFormat {\n switch (format) {\n case 'f32le': return AV_SAMPLE_FMT_FLT\n case 's16le': return AV_SAMPLE_FMT_S16\n }\n}\n\nexport function bytesPerSample(format: PcmSampleFormat): number {\n switch (format) {\n case 'f32le': return 4\n case 's16le': return 2\n }\n}\n\n// ── DecodeRuntime ───────────────────────────────────────────────────────────\n\nexport interface DecodeRuntimeConfig {\n readonly codec: string\n readonly sourceSampleRate: number\n readonly sourceChannels: number\n readonly extraData?: Uint8Array\n readonly targetSampleRate: number\n readonly targetChannels: number\n readonly targetFormat: PcmSampleFormat\n}\n\nexport interface DecodedPcm {\n readonly data: Uint8Array<ArrayBuffer>\n readonly sampleRate: number\n readonly channels: number\n readonly format: PcmSampleFormat\n readonly pts: number\n}\n\nexport class DecodeRuntime {\n private readonly ctx: CodecContext\n private readonly swr: SoftwareResampleContext\n private readonly packet: Packet\n private readonly inFrame: Frame\n private readonly outFrame: Frame\n private readonly cfg: DecodeRuntimeConfig\n private readonly inLayout: ChannelLayout\n private readonly outLayout: ChannelLayout\n private readonly inSampleFmt: AVSampleFormat\n private readonly outSampleFmt: AVSampleFormat\n private readonly outBytesPerSample: number\n private nextPts = 0\n private closed = false\n\n static create(cfg: DecodeRuntimeConfig): DecodeRuntime {\n const codecId = resolveCodecId(cfg.codec)\n if (codecId === null) {\n throw new Error(`audio-codec: unknown codec '${cfg.codec}' for decode runtime`)\n }\n const codec = Codec.findDecoder(codecId)\n if (!codec) {\n throw new Error(`audio-codec: decoder not registered for codec '${cfg.codec}'`)\n }\n\n const ctx = new CodecContext()\n ctx.allocContext3(codec)\n ctx.sampleRate = cfg.sourceSampleRate\n ctx.channels = cfg.sourceChannels\n const inLayout = buildChannelLayout(cfg.sourceChannels)\n ctx.channelLayout = inLayout\n if (cfg.extraData && cfg.extraData.byteLength > 0) {\n ctx.extraData = Buffer.from(cfg.extraData)\n }\n const ret = ctx.open2Sync(codec, null)\n if (ret < 0) {\n ctx.freeContext()\n throw new Error(`audio-codec: open2 failed for '${cfg.codec}' (ret=${ret})`)\n }\n\n const inSampleFmt = ctx.sampleFormat ?? AV_SAMPLE_FMT_FLT\n const outSampleFmt = pcmFormatToAv(cfg.targetFormat)\n const outLayout = buildChannelLayout(cfg.targetChannels)\n\n const swr = new SoftwareResampleContext()\n const allocRet = swr.allocSetOpts2(\n outLayout, outSampleFmt, cfg.targetSampleRate,\n inLayout, inSampleFmt, cfg.sourceSampleRate,\n )\n if (allocRet < 0) {\n ctx.freeContext()\n throw new Error(`audio-codec: swr allocSetOpts2 failed (ret=${allocRet})`)\n }\n const initRet = swr.init()\n if (initRet < 0) {\n ctx.freeContext()\n throw new Error(`audio-codec: swr init failed (ret=${initRet})`)\n }\n\n const packet = new Packet()\n packet.alloc()\n const inFrame = new Frame()\n inFrame.alloc()\n const outFrame = new Frame()\n outFrame.alloc()\n\n return new DecodeRuntime(ctx, swr, packet, inFrame, outFrame, cfg, inLayout, outLayout, inSampleFmt, outSampleFmt)\n }\n\n private constructor(\n ctx: CodecContext,\n swr: SoftwareResampleContext,\n packet: Packet,\n inFrame: Frame,\n outFrame: Frame,\n cfg: DecodeRuntimeConfig,\n inLayout: ChannelLayout,\n outLayout: ChannelLayout,\n inSampleFmt: AVSampleFormat,\n outSampleFmt: AVSampleFormat,\n ) {\n this.ctx = ctx\n this.swr = swr\n this.packet = packet\n this.inFrame = inFrame\n this.outFrame = outFrame\n this.cfg = cfg\n this.inLayout = inLayout\n this.outLayout = outLayout\n this.inSampleFmt = inSampleFmt\n this.outSampleFmt = outSampleFmt\n this.outBytesPerSample = bytesPerSample(cfg.targetFormat)\n }\n\n decode(buf: Uint8Array, pts?: number): DecodedPcm[] {\n if (this.closed) return []\n this.packet.data = Buffer.from(buf)\n if (pts !== undefined) {\n this.packet.pts = BigInt(Math.round(pts))\n this.packet.dts = BigInt(Math.round(pts))\n }\n const sendRet = this.ctx.sendPacketSync(this.packet)\n if (sendRet < 0 && sendRet !== AVERROR_EAGAIN) {\n // Non-fatal — log via thrown error so the addon-level catch records it\n throw new Error(`audio-codec: sendPacket failed (ret=${sendRet})`)\n }\n\n const out: DecodedPcm[] = []\n while (true) {\n const recvRet = this.ctx.receiveFrameSync(this.inFrame)\n if (recvRet === AVERROR_EAGAIN || recvRet === AVERROR_EOF) break\n if (recvRet < 0) {\n throw new Error(`audio-codec: receiveFrame failed (ret=${recvRet})`)\n }\n const pcm = this.resampleFrame(this.inFrame)\n if (pcm) out.push(pcm)\n this.inFrame.unref()\n }\n return out\n }\n\n private resampleFrame(inFrame: Frame): DecodedPcm | null {\n // Compute the output sample count for this input frame. The simplest\n // path: ask swr for the worst-case output size and allocate the\n // output frame to that. swr_get_out_samples isn't bound; we use\n // ceil(in_samples * out_rate / in_rate) + small slack.\n const inSamples = inFrame.nbSamples\n const outSamples = Math.ceil((inSamples * this.cfg.targetSampleRate) / this.cfg.sourceSampleRate) + 32\n\n this.outFrame.unref()\n this.outFrame.format = this.outSampleFmt\n this.outFrame.sampleRate = this.cfg.targetSampleRate\n this.outFrame.channelLayout = this.outLayout\n this.outFrame.nbSamples = outSamples\n const allocRet = this.outFrame.getBuffer(0)\n if (allocRet < 0) {\n throw new Error(`audio-codec: outFrame.getBuffer failed (ret=${allocRet})`)\n }\n\n const convRet = this.swr.convertFrame(this.outFrame, inFrame)\n if (convRet < 0) {\n throw new Error(`audio-codec: swr.convertFrame failed (ret=${convRet})`)\n }\n\n const producedSamples = this.outFrame.nbSamples\n if (producedSamples <= 0) return null\n\n // Pack interleaved PCM bytes. AV_SAMPLE_FMT_FLT / AV_SAMPLE_FMT_S16\n // are interleaved (non-planar), so the entire payload sits in\n // extendedData[0].\n const planes = this.outFrame.extendedData\n if (!planes || planes.length === 0 || !planes[0]) return null\n const bytes = producedSamples * this.cfg.targetChannels * this.outBytesPerSample\n const src = planes[0]\n const out = new Uint8Array(new ArrayBuffer(bytes))\n out.set(src.subarray(0, bytes))\n\n const ptsMs = this.nextPts\n this.nextPts = ptsMs + Math.round((producedSamples * 1000) / this.cfg.targetSampleRate)\n return {\n data: out,\n sampleRate: this.cfg.targetSampleRate,\n channels: this.cfg.targetChannels,\n format: this.cfg.targetFormat,\n pts: ptsMs,\n }\n }\n\n close(): void {\n if (this.closed) return\n this.closed = true\n try { this.outFrame.free() } catch { /* noop */ }\n try { this.inFrame.free() } catch { /* noop */ }\n try { this.packet.free() } catch { /* noop */ }\n try { this.swr.free() } catch { /* noop */ }\n try { this.ctx.freeContext() } catch { /* noop */ }\n }\n\n /** Surfaced for assertion-style tests to inspect computed layouts. */\n describe(): {\n inLayoutMask: bigint\n outLayoutMask: bigint\n inSampleFormat: AVSampleFormat\n outSampleFormat: AVSampleFormat\n } {\n return {\n inLayoutMask: this.inLayout.mask,\n outLayoutMask: this.outLayout.mask,\n inSampleFormat: this.inSampleFmt,\n outSampleFormat: this.outSampleFmt,\n }\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type {\n IAudioCodecCapProvider,\n ProviderRegistration,\n IScopedLogger,\n AudioCodecInfo,\n AudioDecodeSessionConfig,\n AudioEncodeSessionConfig,\n AudioPcmChunk,\n AudioEncodedChunk,\n PcmSampleFormat,\n} from '@camstack/types'\nimport {\n BaseAddon,\n audioCodecCapability,\n errMsg,\n} from '@camstack/types'\nimport { DecodeRuntime } from './codec-runtime.js'\n\n// ── Codec catalogue ─────────────────────────────────────────────────────────\n//\n// Phase 1 scope: decode side covers the codec matrix Reolink + ONVIF cameras\n// announce in SDP (`pcm_mulaw`, `pcm_alaw`, `aac`, `aac_latm`, `opus`).\n// Encode side covers the codecs cameras commonly accept on the intercom\n// back-channel (`pcm_mulaw`, `pcm_alaw`, `aac`, `opus`). The catalogue is\n// declarative — `listSupportedCodecs` returns this list, runtime probing of\n// node-av availability happens lazily on first session create.\n\ninterface CodecCatalogEntry {\n readonly codec: string\n readonly canDecode: boolean\n readonly canEncode: boolean\n readonly label?: string\n}\n\nconst CODEC_CATALOG: ReadonlyArray<CodecCatalogEntry> = [\n { codec: 'pcm_mulaw', canDecode: true, canEncode: true, label: 'PCM µ-law (G.711)' },\n { codec: 'pcm_alaw', canDecode: true, canEncode: true, label: 'PCM A-law (G.711)' },\n { codec: 'aac', canDecode: true, canEncode: true, label: 'AAC' },\n { codec: 'aac_latm', canDecode: true, canEncode: false, label: 'AAC LATM' },\n { codec: 'mpeg4-generic', canDecode: true, canEncode: false, label: 'AAC (MPEG4-GENERIC)' },\n { codec: 'opus', canDecode: true, canEncode: true, label: 'Opus' },\n]\n\nfunction resolveCodecAlias(codec: string): string {\n const c = codec.toLowerCase()\n // SDP names often differ from libav names. Normalise here so callers can\n // pass the SDP-reported value verbatim.\n if (c === 'mpeg4-generic') return 'aac'\n if (c === 'l16') return 'pcm_s16be'\n return c\n}\n\n// ── Session state ───────────────────────────────────────────────────────────\n\ninterface DecodeSessionState {\n readonly sessionId: string\n readonly kind: 'decode'\n readonly config: AudioDecodeSessionConfig\n readonly tag?: string\n readonly createdAtMs: number\n lastActivityMs: number\n framesIn: number\n framesOut: number\n /** Pending decoded chunks ready to be pulled. */\n readonly pcmQueue: AudioPcmChunk[]\n /** libavcodec-backed decode runtime (lazy-init on first push). */\n runtime: DecodeRuntime | null\n}\n\ninterface EncodeSessionState {\n readonly sessionId: string\n readonly kind: 'encode'\n readonly config: AudioEncodeSessionConfig\n readonly tag?: string\n readonly createdAtMs: number\n lastActivityMs: number\n framesIn: number\n framesOut: number\n /** Pending encoded chunks ready to be pulled. */\n readonly encodedQueue: AudioEncodedChunk[]\n}\n\ntype SessionState = DecodeSessionState | EncodeSessionState\n\nconst DEFAULT_IDLE_MS = 30_000\nconst REAPER_INTERVAL_MS = 5_000\n\n// ── Addon ───────────────────────────────────────────────────────────────────\n\ninterface AudioCodecGlobalConfig {\n readonly defaultIdleMs: number\n}\n\nconst DEFAULT_GLOBAL_CONFIG: AudioCodecGlobalConfig = {\n defaultIdleMs: DEFAULT_IDLE_MS,\n}\n\n/**\n * Audio codec I/O box backed by node-av (libavcodec + libswresample).\n *\n * Each `createDecodeSession` / `createEncodeSession` allocates an\n * independent libav codec context + resampler so consumers don't share\n * resamplers — a 16kHz mono ASA subscriber and a 48kHz stereo WebRTC\n * subscriber on the same source codec each get their own session.\n *\n * Phase 1 scaffolds the cap surface + session bookkeeping. The actual\n * libav decode/encode wiring is filled in by follow-up commits — the\n * stub keeps the cap registered so wiring on the broker side can land\n * before the audio backend matures.\n */\nexport default class AudioCodecNodeAvAddon\n extends BaseAddon<AudioCodecGlobalConfig>\n implements IAudioCodecCapProvider\n{\n private readonly sessions = new Map<string, SessionState>()\n private reaperTimer: ReturnType<typeof setInterval> | null = null\n private logger: IScopedLogger | null = null\n\n constructor() {\n super(DEFAULT_GLOBAL_CONFIG)\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n this.logger = this.ctx.logger as IScopedLogger\n this.reaperTimer = setInterval(() => this.reapIdleSessions(), REAPER_INTERVAL_MS)\n if (typeof this.reaperTimer.unref === 'function') this.reaperTimer.unref()\n return [{ capability: audioCodecCapability, provider: this }]\n }\n\n protected async onShutdown(): Promise<void> {\n if (this.reaperTimer) {\n clearInterval(this.reaperTimer)\n this.reaperTimer = null\n }\n for (const s of [...this.sessions.values()]) {\n this.disposeSession(s)\n }\n this.sessions.clear()\n }\n\n // ── Discovery ─────────────────────────────────────────────────────────────\n\n async listSupportedCodecs(): Promise<AudioCodecInfo[]> {\n return CODEC_CATALOG.map(e => ({\n codec: e.codec,\n canDecode: e.canDecode,\n canEncode: e.canEncode,\n ...(e.label ? { label: e.label } : {}),\n }))\n }\n\n async canHandle(input: { codec: string; kind: 'decode' | 'encode' }): Promise<boolean> {\n const resolved = resolveCodecAlias(input.codec)\n const entry = CODEC_CATALOG.find(e => e.codec === resolved)\n if (!entry) return false\n return input.kind === 'decode' ? entry.canDecode : entry.canEncode\n }\n\n // ── Session lifecycle ─────────────────────────────────────────────────────\n\n async createDecodeSession(\n input: AudioDecodeSessionConfig,\n ): Promise<{ sessionId: string; nodeId: string }> {\n const codec = resolveCodecAlias(input.codec)\n const entry = CODEC_CATALOG.find(e => e.codec === codec)\n if (!entry || !entry.canDecode) {\n throw new Error(`audio-codec: decode unsupported for codec '${input.codec}'`)\n }\n const sessionId = `dec-${randomUUID()}`\n const state: DecodeSessionState = {\n sessionId,\n kind: 'decode',\n config: { ...input, codec },\n ...(input.tag ? { tag: input.tag } : {}),\n createdAtMs: Date.now(),\n lastActivityMs: Date.now(),\n framesIn: 0,\n framesOut: 0,\n pcmQueue: [],\n runtime: null,\n }\n this.sessions.set(sessionId, state)\n this.logger?.info('audio-codec: decode session created', {\n tags: { sessionId },\n meta: { codec, target: `${input.targetSampleRate}Hz×${input.targetChannels}` },\n })\n return { sessionId, nodeId: this.ctx.kernel.localNodeId ?? 'local' }\n }\n\n async createEncodeSession(\n input: AudioEncodeSessionConfig,\n ): Promise<{ sessionId: string; nodeId: string }> {\n const codec = resolveCodecAlias(input.codec)\n const entry = CODEC_CATALOG.find(e => e.codec === codec)\n if (!entry || !entry.canEncode) {\n throw new Error(`audio-codec: encode unsupported for codec '${input.codec}'`)\n }\n const sessionId = `enc-${randomUUID()}`\n const state: EncodeSessionState = {\n sessionId,\n kind: 'encode',\n config: { ...input, codec },\n ...(input.tag ? { tag: input.tag } : {}),\n createdAtMs: Date.now(),\n lastActivityMs: Date.now(),\n framesIn: 0,\n framesOut: 0,\n encodedQueue: [],\n }\n this.sessions.set(sessionId, state)\n this.logger?.info('audio-codec: encode session created', {\n tags: { sessionId },\n meta: { codec, target: `${input.targetSampleRate}Hz×${input.targetChannels}` },\n })\n return { sessionId, nodeId: this.ctx.kernel.localNodeId ?? 'local' }\n }\n\n async closeSession(input: { sessionId: string }): Promise<void> {\n const s = this.sessions.get(input.sessionId)\n if (!s) return\n this.disposeSession(s)\n this.sessions.delete(input.sessionId)\n }\n\n // ── Decode data plane ─────────────────────────────────────────────────────\n\n async pushEncodedFrame(input: {\n sessionId: string\n data: Uint8Array\n pts?: number\n }): Promise<void> {\n const s = this.sessions.get(input.sessionId)\n if (!s || s.kind !== 'decode') {\n throw new Error(`audio-codec: decode session '${input.sessionId}' not found`)\n }\n s.lastActivityMs = Date.now()\n s.framesIn++\n\n // Lazy-init the libav runtime on first push. Decode failures here\n // are non-fatal for the broker — we log and drop the frame so a\n // bad packet doesn't tear the session down.\n if (!s.runtime) {\n try {\n s.runtime = DecodeRuntime.create({\n codec: s.config.codec,\n sourceSampleRate: s.config.sourceSampleRate,\n sourceChannels: s.config.sourceChannels,\n ...(s.config.extraData ? { extraData: s.config.extraData } : {}),\n targetSampleRate: s.config.targetSampleRate,\n targetChannels: s.config.targetChannels,\n targetFormat: s.config.targetFormat ?? 'f32le',\n })\n } catch (err) {\n this.logger?.error('audio-codec: decode runtime init failed', {\n tags: { sessionId: input.sessionId },\n meta: { codec: s.config.codec, error: errMsg(err) },\n })\n return\n }\n }\n\n try {\n const pcms = s.runtime.decode(input.data, input.pts)\n for (const pcm of pcms) {\n s.pcmQueue.push(pcm)\n }\n } catch (err) {\n this.logger?.warn('audio-codec: decode frame failed', {\n tags: { sessionId: input.sessionId },\n meta: { codec: s.config.codec, error: errMsg(err) },\n })\n }\n }\n\n async pullPcm(input: {\n sessionId: string\n maxCount: number\n }): Promise<AudioPcmChunk[]> {\n const s = this.sessions.get(input.sessionId)\n if (!s || s.kind !== 'decode') {\n throw new Error(`audio-codec: decode session '${input.sessionId}' not found`)\n }\n s.lastActivityMs = Date.now()\n const out = s.pcmQueue.splice(0, input.maxCount)\n s.framesOut += out.length\n return out\n }\n\n // ── Encode data plane ─────────────────────────────────────────────────────\n\n async pushPcm(input: {\n sessionId: string\n data: Uint8Array\n pts?: number\n }): Promise<void> {\n const s = this.sessions.get(input.sessionId)\n if (!s || s.kind !== 'encode') {\n throw new Error(`audio-codec: encode session '${input.sessionId}' not found`)\n }\n s.lastActivityMs = Date.now()\n s.framesIn++\n void input.data\n void input.pts\n }\n\n async pullEncoded(input: {\n sessionId: string\n maxCount: number\n }): Promise<AudioEncodedChunk[]> {\n const s = this.sessions.get(input.sessionId)\n if (!s || s.kind !== 'encode') {\n throw new Error(`audio-codec: encode session '${input.sessionId}' not found`)\n }\n s.lastActivityMs = Date.now()\n const out = s.encodedQueue.splice(0, input.maxCount)\n s.framesOut += out.length\n return out\n }\n\n async flushEncode(input: {\n sessionId: string\n }): Promise<AudioEncodedChunk[]> {\n const s = this.sessions.get(input.sessionId)\n if (!s || s.kind !== 'encode') {\n throw new Error(`audio-codec: encode session '${input.sessionId}' not found`)\n }\n s.lastActivityMs = Date.now()\n const out = s.encodedQueue.splice(0)\n s.framesOut += out.length\n return out\n }\n\n // ── Inventory ─────────────────────────────────────────────────────────────\n\n async listActiveSessions() {\n return [...this.sessions.values()].map(s => ({\n sessionId: s.sessionId,\n kind: s.kind,\n codec: s.config.codec,\n sourceSampleRate: s.config.sourceSampleRate,\n sourceChannels: s.config.sourceChannels,\n targetSampleRate: s.config.targetSampleRate,\n targetChannels: s.config.targetChannels,\n format: this.resolveFormat(s),\n ...(s.tag ? { tag: s.tag } : {}),\n createdAtMs: s.createdAtMs,\n lastActivityMs: s.lastActivityMs,\n framesIn: s.framesIn,\n framesOut: s.framesOut,\n }))\n }\n\n // ── Internals ─────────────────────────────────────────────────────────────\n\n private resolveFormat(s: SessionState): PcmSampleFormat {\n if (s.kind === 'decode') return s.config.targetFormat ?? 'f32le'\n return s.config.sourceFormat ?? 'f32le'\n }\n\n private reapIdleSessions(): void {\n const now = Date.now()\n for (const [id, s] of this.sessions) {\n const limit = (s.config.idleMs ?? this.config.defaultIdleMs ?? DEFAULT_IDLE_MS)\n if (now - s.lastActivityMs > limit) {\n this.logger?.info('audio-codec: reaping idle session', {\n tags: { sessionId: id },\n meta: { kind: s.kind, idleMs: now - s.lastActivityMs, limit },\n })\n try { this.disposeSession(s) }\n catch (err) {\n this.logger?.warn('audio-codec: dispose failed during reap', {\n tags: { sessionId: id },\n meta: { error: errMsg(err) },\n })\n }\n this.sessions.delete(id)\n }\n }\n }\n\n private disposeSession(s: SessionState): void {\n if (s.kind === 'decode' && s.runtime) {\n try { s.runtime.close() }\n catch (err) {\n this.logger?.warn('audio-codec: decode runtime close failed', {\n tags: { sessionId: s.sessionId },\n meta: { error: errMsg(err) },\n })\n }\n s.runtime = null\n }\n // Encode runtime cleanup lands when the encode path is wired.\n }\n}\n"],"names":["AV_CODEC_ID_AAC","AV_CODEC_ID_AAC_LATM","AV_CODEC_ID_OPUS","AV_CODEC_ID_PCM_ALAW","AV_CODEC_ID_PCM_MULAW","AV_CHANNEL_ORDER_NATIVE","AV_CH_LAYOUT_MONO","AV_CH_LAYOUT_STEREO","AV_SAMPLE_FMT_FLT","AV_SAMPLE_FMT_S16","Codec","CodecContext","SoftwareResampleContext","Packet","Frame","AVERROR_EAGAIN","AVERROR_EOF","BaseAddon","audioCodecCapability","randomUUID","errMsg"],"mappings":"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"}
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { BaseAddon, audioCodecCapability, errMsg } from "@camstack/types";
|
|
3
|
+
import { Codec, CodecContext, AV_SAMPLE_FMT_FLT, SoftwareResampleContext, Packet, Frame, AVERROR_EAGAIN, AVERROR_EOF, AV_CODEC_ID_PCM_MULAW, AV_CODEC_ID_PCM_ALAW, AV_CODEC_ID_OPUS, AV_CODEC_ID_AAC_LATM, AV_CODEC_ID_AAC, AV_CH_LAYOUT_MONO, AV_CHANNEL_ORDER_NATIVE, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16 } from "node-av";
|
|
4
|
+
const CODEC_ID_BY_NAME = {
|
|
5
|
+
aac: AV_CODEC_ID_AAC,
|
|
6
|
+
aac_latm: AV_CODEC_ID_AAC_LATM,
|
|
7
|
+
opus: AV_CODEC_ID_OPUS,
|
|
8
|
+
pcm_alaw: AV_CODEC_ID_PCM_ALAW,
|
|
9
|
+
pcm_mulaw: AV_CODEC_ID_PCM_MULAW
|
|
10
|
+
};
|
|
11
|
+
function resolveCodecId(codec) {
|
|
12
|
+
return CODEC_ID_BY_NAME[codec.toLowerCase()] ?? null;
|
|
13
|
+
}
|
|
14
|
+
function buildChannelLayout(channels) {
|
|
15
|
+
if (channels === 1) {
|
|
16
|
+
return { nbChannels: 1, order: AV_CHANNEL_ORDER_NATIVE, mask: AV_CH_LAYOUT_MONO };
|
|
17
|
+
}
|
|
18
|
+
if (channels === 2) {
|
|
19
|
+
return { nbChannels: 2, order: AV_CHANNEL_ORDER_NATIVE, mask: AV_CH_LAYOUT_STEREO };
|
|
20
|
+
}
|
|
21
|
+
return { nbChannels: channels, order: AV_CHANNEL_ORDER_NATIVE, mask: 0n };
|
|
22
|
+
}
|
|
23
|
+
function pcmFormatToAv(format) {
|
|
24
|
+
switch (format) {
|
|
25
|
+
case "f32le":
|
|
26
|
+
return AV_SAMPLE_FMT_FLT;
|
|
27
|
+
case "s16le":
|
|
28
|
+
return AV_SAMPLE_FMT_S16;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function bytesPerSample(format) {
|
|
32
|
+
switch (format) {
|
|
33
|
+
case "f32le":
|
|
34
|
+
return 4;
|
|
35
|
+
case "s16le":
|
|
36
|
+
return 2;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
class DecodeRuntime {
|
|
40
|
+
ctx;
|
|
41
|
+
swr;
|
|
42
|
+
packet;
|
|
43
|
+
inFrame;
|
|
44
|
+
outFrame;
|
|
45
|
+
cfg;
|
|
46
|
+
inLayout;
|
|
47
|
+
outLayout;
|
|
48
|
+
inSampleFmt;
|
|
49
|
+
outSampleFmt;
|
|
50
|
+
outBytesPerSample;
|
|
51
|
+
nextPts = 0;
|
|
52
|
+
closed = false;
|
|
53
|
+
static create(cfg) {
|
|
54
|
+
const codecId = resolveCodecId(cfg.codec);
|
|
55
|
+
if (codecId === null) {
|
|
56
|
+
throw new Error(`audio-codec: unknown codec '${cfg.codec}' for decode runtime`);
|
|
57
|
+
}
|
|
58
|
+
const codec = Codec.findDecoder(codecId);
|
|
59
|
+
if (!codec) {
|
|
60
|
+
throw new Error(`audio-codec: decoder not registered for codec '${cfg.codec}'`);
|
|
61
|
+
}
|
|
62
|
+
const ctx = new CodecContext();
|
|
63
|
+
ctx.allocContext3(codec);
|
|
64
|
+
ctx.sampleRate = cfg.sourceSampleRate;
|
|
65
|
+
ctx.channels = cfg.sourceChannels;
|
|
66
|
+
const inLayout = buildChannelLayout(cfg.sourceChannels);
|
|
67
|
+
ctx.channelLayout = inLayout;
|
|
68
|
+
if (cfg.extraData && cfg.extraData.byteLength > 0) {
|
|
69
|
+
ctx.extraData = Buffer.from(cfg.extraData);
|
|
70
|
+
}
|
|
71
|
+
const ret = ctx.open2Sync(codec, null);
|
|
72
|
+
if (ret < 0) {
|
|
73
|
+
ctx.freeContext();
|
|
74
|
+
throw new Error(`audio-codec: open2 failed for '${cfg.codec}' (ret=${ret})`);
|
|
75
|
+
}
|
|
76
|
+
const inSampleFmt = ctx.sampleFormat ?? AV_SAMPLE_FMT_FLT;
|
|
77
|
+
const outSampleFmt = pcmFormatToAv(cfg.targetFormat);
|
|
78
|
+
const outLayout = buildChannelLayout(cfg.targetChannels);
|
|
79
|
+
const swr = new SoftwareResampleContext();
|
|
80
|
+
const allocRet = swr.allocSetOpts2(
|
|
81
|
+
outLayout,
|
|
82
|
+
outSampleFmt,
|
|
83
|
+
cfg.targetSampleRate,
|
|
84
|
+
inLayout,
|
|
85
|
+
inSampleFmt,
|
|
86
|
+
cfg.sourceSampleRate
|
|
87
|
+
);
|
|
88
|
+
if (allocRet < 0) {
|
|
89
|
+
ctx.freeContext();
|
|
90
|
+
throw new Error(`audio-codec: swr allocSetOpts2 failed (ret=${allocRet})`);
|
|
91
|
+
}
|
|
92
|
+
const initRet = swr.init();
|
|
93
|
+
if (initRet < 0) {
|
|
94
|
+
ctx.freeContext();
|
|
95
|
+
throw new Error(`audio-codec: swr init failed (ret=${initRet})`);
|
|
96
|
+
}
|
|
97
|
+
const packet = new Packet();
|
|
98
|
+
packet.alloc();
|
|
99
|
+
const inFrame = new Frame();
|
|
100
|
+
inFrame.alloc();
|
|
101
|
+
const outFrame = new Frame();
|
|
102
|
+
outFrame.alloc();
|
|
103
|
+
return new DecodeRuntime(ctx, swr, packet, inFrame, outFrame, cfg, inLayout, outLayout, inSampleFmt, outSampleFmt);
|
|
104
|
+
}
|
|
105
|
+
constructor(ctx, swr, packet, inFrame, outFrame, cfg, inLayout, outLayout, inSampleFmt, outSampleFmt) {
|
|
106
|
+
this.ctx = ctx;
|
|
107
|
+
this.swr = swr;
|
|
108
|
+
this.packet = packet;
|
|
109
|
+
this.inFrame = inFrame;
|
|
110
|
+
this.outFrame = outFrame;
|
|
111
|
+
this.cfg = cfg;
|
|
112
|
+
this.inLayout = inLayout;
|
|
113
|
+
this.outLayout = outLayout;
|
|
114
|
+
this.inSampleFmt = inSampleFmt;
|
|
115
|
+
this.outSampleFmt = outSampleFmt;
|
|
116
|
+
this.outBytesPerSample = bytesPerSample(cfg.targetFormat);
|
|
117
|
+
}
|
|
118
|
+
decode(buf, pts) {
|
|
119
|
+
if (this.closed) return [];
|
|
120
|
+
this.packet.data = Buffer.from(buf);
|
|
121
|
+
if (pts !== void 0) {
|
|
122
|
+
this.packet.pts = BigInt(Math.round(pts));
|
|
123
|
+
this.packet.dts = BigInt(Math.round(pts));
|
|
124
|
+
}
|
|
125
|
+
const sendRet = this.ctx.sendPacketSync(this.packet);
|
|
126
|
+
if (sendRet < 0 && sendRet !== AVERROR_EAGAIN) {
|
|
127
|
+
throw new Error(`audio-codec: sendPacket failed (ret=${sendRet})`);
|
|
128
|
+
}
|
|
129
|
+
const out = [];
|
|
130
|
+
while (true) {
|
|
131
|
+
const recvRet = this.ctx.receiveFrameSync(this.inFrame);
|
|
132
|
+
if (recvRet === AVERROR_EAGAIN || recvRet === AVERROR_EOF) break;
|
|
133
|
+
if (recvRet < 0) {
|
|
134
|
+
throw new Error(`audio-codec: receiveFrame failed (ret=${recvRet})`);
|
|
135
|
+
}
|
|
136
|
+
const pcm = this.resampleFrame(this.inFrame);
|
|
137
|
+
if (pcm) out.push(pcm);
|
|
138
|
+
this.inFrame.unref();
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
resampleFrame(inFrame) {
|
|
143
|
+
const inSamples = inFrame.nbSamples;
|
|
144
|
+
const outSamples = Math.ceil(inSamples * this.cfg.targetSampleRate / this.cfg.sourceSampleRate) + 32;
|
|
145
|
+
this.outFrame.unref();
|
|
146
|
+
this.outFrame.format = this.outSampleFmt;
|
|
147
|
+
this.outFrame.sampleRate = this.cfg.targetSampleRate;
|
|
148
|
+
this.outFrame.channelLayout = this.outLayout;
|
|
149
|
+
this.outFrame.nbSamples = outSamples;
|
|
150
|
+
const allocRet = this.outFrame.getBuffer(0);
|
|
151
|
+
if (allocRet < 0) {
|
|
152
|
+
throw new Error(`audio-codec: outFrame.getBuffer failed (ret=${allocRet})`);
|
|
153
|
+
}
|
|
154
|
+
const convRet = this.swr.convertFrame(this.outFrame, inFrame);
|
|
155
|
+
if (convRet < 0) {
|
|
156
|
+
throw new Error(`audio-codec: swr.convertFrame failed (ret=${convRet})`);
|
|
157
|
+
}
|
|
158
|
+
const producedSamples = this.outFrame.nbSamples;
|
|
159
|
+
if (producedSamples <= 0) return null;
|
|
160
|
+
const planes = this.outFrame.extendedData;
|
|
161
|
+
if (!planes || planes.length === 0 || !planes[0]) return null;
|
|
162
|
+
const bytes = producedSamples * this.cfg.targetChannels * this.outBytesPerSample;
|
|
163
|
+
const src = planes[0];
|
|
164
|
+
const out = new Uint8Array(new ArrayBuffer(bytes));
|
|
165
|
+
out.set(src.subarray(0, bytes));
|
|
166
|
+
const ptsMs = this.nextPts;
|
|
167
|
+
this.nextPts = ptsMs + Math.round(producedSamples * 1e3 / this.cfg.targetSampleRate);
|
|
168
|
+
return {
|
|
169
|
+
data: out,
|
|
170
|
+
sampleRate: this.cfg.targetSampleRate,
|
|
171
|
+
channels: this.cfg.targetChannels,
|
|
172
|
+
format: this.cfg.targetFormat,
|
|
173
|
+
pts: ptsMs
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
close() {
|
|
177
|
+
if (this.closed) return;
|
|
178
|
+
this.closed = true;
|
|
179
|
+
try {
|
|
180
|
+
this.outFrame.free();
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
this.inFrame.free();
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
this.packet.free();
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
this.swr.free();
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
this.ctx.freeContext();
|
|
197
|
+
} catch {
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/** Surfaced for assertion-style tests to inspect computed layouts. */
|
|
201
|
+
describe() {
|
|
202
|
+
return {
|
|
203
|
+
inLayoutMask: this.inLayout.mask,
|
|
204
|
+
outLayoutMask: this.outLayout.mask,
|
|
205
|
+
inSampleFormat: this.inSampleFmt,
|
|
206
|
+
outSampleFormat: this.outSampleFmt
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const CODEC_CATALOG = [
|
|
211
|
+
{ codec: "pcm_mulaw", canDecode: true, canEncode: true, label: "PCM µ-law (G.711)" },
|
|
212
|
+
{ codec: "pcm_alaw", canDecode: true, canEncode: true, label: "PCM A-law (G.711)" },
|
|
213
|
+
{ codec: "aac", canDecode: true, canEncode: true, label: "AAC" },
|
|
214
|
+
{ codec: "aac_latm", canDecode: true, canEncode: false, label: "AAC LATM" },
|
|
215
|
+
{ codec: "mpeg4-generic", canDecode: true, canEncode: false, label: "AAC (MPEG4-GENERIC)" },
|
|
216
|
+
{ codec: "opus", canDecode: true, canEncode: true, label: "Opus" }
|
|
217
|
+
];
|
|
218
|
+
function resolveCodecAlias(codec) {
|
|
219
|
+
const c = codec.toLowerCase();
|
|
220
|
+
if (c === "mpeg4-generic") return "aac";
|
|
221
|
+
if (c === "l16") return "pcm_s16be";
|
|
222
|
+
return c;
|
|
223
|
+
}
|
|
224
|
+
const DEFAULT_IDLE_MS = 3e4;
|
|
225
|
+
const REAPER_INTERVAL_MS = 5e3;
|
|
226
|
+
const DEFAULT_GLOBAL_CONFIG = {
|
|
227
|
+
defaultIdleMs: DEFAULT_IDLE_MS
|
|
228
|
+
};
|
|
229
|
+
class AudioCodecNodeAvAddon extends BaseAddon {
|
|
230
|
+
sessions = /* @__PURE__ */ new Map();
|
|
231
|
+
reaperTimer = null;
|
|
232
|
+
logger = null;
|
|
233
|
+
constructor() {
|
|
234
|
+
super(DEFAULT_GLOBAL_CONFIG);
|
|
235
|
+
}
|
|
236
|
+
async onInitialize() {
|
|
237
|
+
this.logger = this.ctx.logger;
|
|
238
|
+
this.reaperTimer = setInterval(() => this.reapIdleSessions(), REAPER_INTERVAL_MS);
|
|
239
|
+
if (typeof this.reaperTimer.unref === "function") this.reaperTimer.unref();
|
|
240
|
+
return [{ capability: audioCodecCapability, provider: this }];
|
|
241
|
+
}
|
|
242
|
+
async onShutdown() {
|
|
243
|
+
if (this.reaperTimer) {
|
|
244
|
+
clearInterval(this.reaperTimer);
|
|
245
|
+
this.reaperTimer = null;
|
|
246
|
+
}
|
|
247
|
+
for (const s of [...this.sessions.values()]) {
|
|
248
|
+
this.disposeSession(s);
|
|
249
|
+
}
|
|
250
|
+
this.sessions.clear();
|
|
251
|
+
}
|
|
252
|
+
// ── Discovery ─────────────────────────────────────────────────────────────
|
|
253
|
+
async listSupportedCodecs() {
|
|
254
|
+
return CODEC_CATALOG.map((e) => ({
|
|
255
|
+
codec: e.codec,
|
|
256
|
+
canDecode: e.canDecode,
|
|
257
|
+
canEncode: e.canEncode,
|
|
258
|
+
...e.label ? { label: e.label } : {}
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
async canHandle(input) {
|
|
262
|
+
const resolved = resolveCodecAlias(input.codec);
|
|
263
|
+
const entry = CODEC_CATALOG.find((e) => e.codec === resolved);
|
|
264
|
+
if (!entry) return false;
|
|
265
|
+
return input.kind === "decode" ? entry.canDecode : entry.canEncode;
|
|
266
|
+
}
|
|
267
|
+
// ── Session lifecycle ─────────────────────────────────────────────────────
|
|
268
|
+
async createDecodeSession(input) {
|
|
269
|
+
const codec = resolveCodecAlias(input.codec);
|
|
270
|
+
const entry = CODEC_CATALOG.find((e) => e.codec === codec);
|
|
271
|
+
if (!entry || !entry.canDecode) {
|
|
272
|
+
throw new Error(`audio-codec: decode unsupported for codec '${input.codec}'`);
|
|
273
|
+
}
|
|
274
|
+
const sessionId = `dec-${randomUUID()}`;
|
|
275
|
+
const state = {
|
|
276
|
+
sessionId,
|
|
277
|
+
kind: "decode",
|
|
278
|
+
config: { ...input, codec },
|
|
279
|
+
...input.tag ? { tag: input.tag } : {},
|
|
280
|
+
createdAtMs: Date.now(),
|
|
281
|
+
lastActivityMs: Date.now(),
|
|
282
|
+
framesIn: 0,
|
|
283
|
+
framesOut: 0,
|
|
284
|
+
pcmQueue: [],
|
|
285
|
+
runtime: null
|
|
286
|
+
};
|
|
287
|
+
this.sessions.set(sessionId, state);
|
|
288
|
+
this.logger?.info("audio-codec: decode session created", {
|
|
289
|
+
tags: { sessionId },
|
|
290
|
+
meta: { codec, target: `${input.targetSampleRate}Hz×${input.targetChannels}` }
|
|
291
|
+
});
|
|
292
|
+
return { sessionId, nodeId: this.ctx.kernel.localNodeId ?? "local" };
|
|
293
|
+
}
|
|
294
|
+
async createEncodeSession(input) {
|
|
295
|
+
const codec = resolveCodecAlias(input.codec);
|
|
296
|
+
const entry = CODEC_CATALOG.find((e) => e.codec === codec);
|
|
297
|
+
if (!entry || !entry.canEncode) {
|
|
298
|
+
throw new Error(`audio-codec: encode unsupported for codec '${input.codec}'`);
|
|
299
|
+
}
|
|
300
|
+
const sessionId = `enc-${randomUUID()}`;
|
|
301
|
+
const state = {
|
|
302
|
+
sessionId,
|
|
303
|
+
kind: "encode",
|
|
304
|
+
config: { ...input, codec },
|
|
305
|
+
...input.tag ? { tag: input.tag } : {},
|
|
306
|
+
createdAtMs: Date.now(),
|
|
307
|
+
lastActivityMs: Date.now(),
|
|
308
|
+
framesIn: 0,
|
|
309
|
+
framesOut: 0,
|
|
310
|
+
encodedQueue: []
|
|
311
|
+
};
|
|
312
|
+
this.sessions.set(sessionId, state);
|
|
313
|
+
this.logger?.info("audio-codec: encode session created", {
|
|
314
|
+
tags: { sessionId },
|
|
315
|
+
meta: { codec, target: `${input.targetSampleRate}Hz×${input.targetChannels}` }
|
|
316
|
+
});
|
|
317
|
+
return { sessionId, nodeId: this.ctx.kernel.localNodeId ?? "local" };
|
|
318
|
+
}
|
|
319
|
+
async closeSession(input) {
|
|
320
|
+
const s = this.sessions.get(input.sessionId);
|
|
321
|
+
if (!s) return;
|
|
322
|
+
this.disposeSession(s);
|
|
323
|
+
this.sessions.delete(input.sessionId);
|
|
324
|
+
}
|
|
325
|
+
// ── Decode data plane ─────────────────────────────────────────────────────
|
|
326
|
+
async pushEncodedFrame(input) {
|
|
327
|
+
const s = this.sessions.get(input.sessionId);
|
|
328
|
+
if (!s || s.kind !== "decode") {
|
|
329
|
+
throw new Error(`audio-codec: decode session '${input.sessionId}' not found`);
|
|
330
|
+
}
|
|
331
|
+
s.lastActivityMs = Date.now();
|
|
332
|
+
s.framesIn++;
|
|
333
|
+
if (!s.runtime) {
|
|
334
|
+
try {
|
|
335
|
+
s.runtime = DecodeRuntime.create({
|
|
336
|
+
codec: s.config.codec,
|
|
337
|
+
sourceSampleRate: s.config.sourceSampleRate,
|
|
338
|
+
sourceChannels: s.config.sourceChannels,
|
|
339
|
+
...s.config.extraData ? { extraData: s.config.extraData } : {},
|
|
340
|
+
targetSampleRate: s.config.targetSampleRate,
|
|
341
|
+
targetChannels: s.config.targetChannels,
|
|
342
|
+
targetFormat: s.config.targetFormat ?? "f32le"
|
|
343
|
+
});
|
|
344
|
+
} catch (err) {
|
|
345
|
+
this.logger?.error("audio-codec: decode runtime init failed", {
|
|
346
|
+
tags: { sessionId: input.sessionId },
|
|
347
|
+
meta: { codec: s.config.codec, error: errMsg(err) }
|
|
348
|
+
});
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
const pcms = s.runtime.decode(input.data, input.pts);
|
|
354
|
+
for (const pcm of pcms) {
|
|
355
|
+
s.pcmQueue.push(pcm);
|
|
356
|
+
}
|
|
357
|
+
} catch (err) {
|
|
358
|
+
this.logger?.warn("audio-codec: decode frame failed", {
|
|
359
|
+
tags: { sessionId: input.sessionId },
|
|
360
|
+
meta: { codec: s.config.codec, error: errMsg(err) }
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async pullPcm(input) {
|
|
365
|
+
const s = this.sessions.get(input.sessionId);
|
|
366
|
+
if (!s || s.kind !== "decode") {
|
|
367
|
+
throw new Error(`audio-codec: decode session '${input.sessionId}' not found`);
|
|
368
|
+
}
|
|
369
|
+
s.lastActivityMs = Date.now();
|
|
370
|
+
const out = s.pcmQueue.splice(0, input.maxCount);
|
|
371
|
+
s.framesOut += out.length;
|
|
372
|
+
return out;
|
|
373
|
+
}
|
|
374
|
+
// ── Encode data plane ─────────────────────────────────────────────────────
|
|
375
|
+
async pushPcm(input) {
|
|
376
|
+
const s = this.sessions.get(input.sessionId);
|
|
377
|
+
if (!s || s.kind !== "encode") {
|
|
378
|
+
throw new Error(`audio-codec: encode session '${input.sessionId}' not found`);
|
|
379
|
+
}
|
|
380
|
+
s.lastActivityMs = Date.now();
|
|
381
|
+
s.framesIn++;
|
|
382
|
+
void input.data;
|
|
383
|
+
void input.pts;
|
|
384
|
+
}
|
|
385
|
+
async pullEncoded(input) {
|
|
386
|
+
const s = this.sessions.get(input.sessionId);
|
|
387
|
+
if (!s || s.kind !== "encode") {
|
|
388
|
+
throw new Error(`audio-codec: encode session '${input.sessionId}' not found`);
|
|
389
|
+
}
|
|
390
|
+
s.lastActivityMs = Date.now();
|
|
391
|
+
const out = s.encodedQueue.splice(0, input.maxCount);
|
|
392
|
+
s.framesOut += out.length;
|
|
393
|
+
return out;
|
|
394
|
+
}
|
|
395
|
+
async flushEncode(input) {
|
|
396
|
+
const s = this.sessions.get(input.sessionId);
|
|
397
|
+
if (!s || s.kind !== "encode") {
|
|
398
|
+
throw new Error(`audio-codec: encode session '${input.sessionId}' not found`);
|
|
399
|
+
}
|
|
400
|
+
s.lastActivityMs = Date.now();
|
|
401
|
+
const out = s.encodedQueue.splice(0);
|
|
402
|
+
s.framesOut += out.length;
|
|
403
|
+
return out;
|
|
404
|
+
}
|
|
405
|
+
// ── Inventory ─────────────────────────────────────────────────────────────
|
|
406
|
+
async listActiveSessions() {
|
|
407
|
+
return [...this.sessions.values()].map((s) => ({
|
|
408
|
+
sessionId: s.sessionId,
|
|
409
|
+
kind: s.kind,
|
|
410
|
+
codec: s.config.codec,
|
|
411
|
+
sourceSampleRate: s.config.sourceSampleRate,
|
|
412
|
+
sourceChannels: s.config.sourceChannels,
|
|
413
|
+
targetSampleRate: s.config.targetSampleRate,
|
|
414
|
+
targetChannels: s.config.targetChannels,
|
|
415
|
+
format: this.resolveFormat(s),
|
|
416
|
+
...s.tag ? { tag: s.tag } : {},
|
|
417
|
+
createdAtMs: s.createdAtMs,
|
|
418
|
+
lastActivityMs: s.lastActivityMs,
|
|
419
|
+
framesIn: s.framesIn,
|
|
420
|
+
framesOut: s.framesOut
|
|
421
|
+
}));
|
|
422
|
+
}
|
|
423
|
+
// ── Internals ─────────────────────────────────────────────────────────────
|
|
424
|
+
resolveFormat(s) {
|
|
425
|
+
if (s.kind === "decode") return s.config.targetFormat ?? "f32le";
|
|
426
|
+
return s.config.sourceFormat ?? "f32le";
|
|
427
|
+
}
|
|
428
|
+
reapIdleSessions() {
|
|
429
|
+
const now = Date.now();
|
|
430
|
+
for (const [id, s] of this.sessions) {
|
|
431
|
+
const limit = s.config.idleMs ?? this.config.defaultIdleMs ?? DEFAULT_IDLE_MS;
|
|
432
|
+
if (now - s.lastActivityMs > limit) {
|
|
433
|
+
this.logger?.info("audio-codec: reaping idle session", {
|
|
434
|
+
tags: { sessionId: id },
|
|
435
|
+
meta: { kind: s.kind, idleMs: now - s.lastActivityMs, limit }
|
|
436
|
+
});
|
|
437
|
+
try {
|
|
438
|
+
this.disposeSession(s);
|
|
439
|
+
} catch (err) {
|
|
440
|
+
this.logger?.warn("audio-codec: dispose failed during reap", {
|
|
441
|
+
tags: { sessionId: id },
|
|
442
|
+
meta: { error: errMsg(err) }
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
this.sessions.delete(id);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
disposeSession(s) {
|
|
450
|
+
if (s.kind === "decode" && s.runtime) {
|
|
451
|
+
try {
|
|
452
|
+
s.runtime.close();
|
|
453
|
+
} catch (err) {
|
|
454
|
+
this.logger?.warn("audio-codec: decode runtime close failed", {
|
|
455
|
+
tags: { sessionId: s.sessionId },
|
|
456
|
+
meta: { error: errMsg(err) }
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
s.runtime = null;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
export {
|
|
464
|
+
AudioCodecNodeAvAddon,
|
|
465
|
+
AudioCodecNodeAvAddon as default
|
|
466
|
+
};
|
|
467
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../src/audio-codec-nodeav/addon/codec-runtime.ts","../../src/audio-codec-nodeav/addon/index.ts"],"sourcesContent":["/**\n * Thin libavcodec/libswresample wrapper backing one decode or encode\n * session in `audio-codec`. Holds:\n * - CodecContext (decoder or encoder)\n * - SoftwareResampleContext for input format → output format\n * - reusable Packet + input/output Frame\n *\n * Stateful — one runtime per session. The cap addon owns the lifecycle.\n */\nimport {\n Codec,\n CodecContext,\n Frame,\n Packet,\n SoftwareResampleContext,\n AV_CHANNEL_ORDER_NATIVE,\n AV_CH_LAYOUT_MONO,\n AV_CH_LAYOUT_STEREO,\n AV_CODEC_ID_AAC,\n AV_CODEC_ID_AAC_LATM,\n AV_CODEC_ID_OPUS,\n AV_CODEC_ID_PCM_ALAW,\n AV_CODEC_ID_PCM_MULAW,\n AV_SAMPLE_FMT_FLT,\n AV_SAMPLE_FMT_S16,\n AVERROR_EAGAIN,\n AVERROR_EOF,\n type AVCodecID,\n type AVSampleFormat,\n type ChannelLayout,\n} from 'node-av'\nimport type { PcmSampleFormat } from '@camstack/types'\n\n// ── Codec resolution ────────────────────────────────────────────────────────\n\nconst CODEC_ID_BY_NAME: Readonly<Record<string, AVCodecID>> = {\n aac: AV_CODEC_ID_AAC,\n aac_latm: AV_CODEC_ID_AAC_LATM,\n opus: AV_CODEC_ID_OPUS,\n pcm_alaw: AV_CODEC_ID_PCM_ALAW,\n pcm_mulaw: AV_CODEC_ID_PCM_MULAW,\n}\n\nexport function resolveCodecId(codec: string): AVCodecID | null {\n return CODEC_ID_BY_NAME[codec.toLowerCase()] ?? null\n}\n\n// ── Channel layout ──────────────────────────────────────────────────────────\n\nexport function buildChannelLayout(channels: number): ChannelLayout {\n if (channels === 1) {\n return { nbChannels: 1, order: AV_CHANNEL_ORDER_NATIVE, mask: AV_CH_LAYOUT_MONO }\n }\n if (channels === 2) {\n return { nbChannels: 2, order: AV_CHANNEL_ORDER_NATIVE, mask: AV_CH_LAYOUT_STEREO }\n }\n // For >2 channels fall back to mask=0 (unknown layout) — libav still\n // works with raw sample interleave; consumers requesting exotic layouts\n // can always pass through stereo or mono.\n return { nbChannels: channels, order: AV_CHANNEL_ORDER_NATIVE, mask: 0n }\n}\n\n// ── PCM format mapping ──────────────────────────────────────────────────────\n\nexport function pcmFormatToAv(format: PcmSampleFormat): AVSampleFormat {\n switch (format) {\n case 'f32le': return AV_SAMPLE_FMT_FLT\n case 's16le': return AV_SAMPLE_FMT_S16\n }\n}\n\nexport function bytesPerSample(format: PcmSampleFormat): number {\n switch (format) {\n case 'f32le': return 4\n case 's16le': return 2\n }\n}\n\n// ── DecodeRuntime ───────────────────────────────────────────────────────────\n\nexport interface DecodeRuntimeConfig {\n readonly codec: string\n readonly sourceSampleRate: number\n readonly sourceChannels: number\n readonly extraData?: Uint8Array\n readonly targetSampleRate: number\n readonly targetChannels: number\n readonly targetFormat: PcmSampleFormat\n}\n\nexport interface DecodedPcm {\n readonly data: Uint8Array<ArrayBuffer>\n readonly sampleRate: number\n readonly channels: number\n readonly format: PcmSampleFormat\n readonly pts: number\n}\n\nexport class DecodeRuntime {\n private readonly ctx: CodecContext\n private readonly swr: SoftwareResampleContext\n private readonly packet: Packet\n private readonly inFrame: Frame\n private readonly outFrame: Frame\n private readonly cfg: DecodeRuntimeConfig\n private readonly inLayout: ChannelLayout\n private readonly outLayout: ChannelLayout\n private readonly inSampleFmt: AVSampleFormat\n private readonly outSampleFmt: AVSampleFormat\n private readonly outBytesPerSample: number\n private nextPts = 0\n private closed = false\n\n static create(cfg: DecodeRuntimeConfig): DecodeRuntime {\n const codecId = resolveCodecId(cfg.codec)\n if (codecId === null) {\n throw new Error(`audio-codec: unknown codec '${cfg.codec}' for decode runtime`)\n }\n const codec = Codec.findDecoder(codecId)\n if (!codec) {\n throw new Error(`audio-codec: decoder not registered for codec '${cfg.codec}'`)\n }\n\n const ctx = new CodecContext()\n ctx.allocContext3(codec)\n ctx.sampleRate = cfg.sourceSampleRate\n ctx.channels = cfg.sourceChannels\n const inLayout = buildChannelLayout(cfg.sourceChannels)\n ctx.channelLayout = inLayout\n if (cfg.extraData && cfg.extraData.byteLength > 0) {\n ctx.extraData = Buffer.from(cfg.extraData)\n }\n const ret = ctx.open2Sync(codec, null)\n if (ret < 0) {\n ctx.freeContext()\n throw new Error(`audio-codec: open2 failed for '${cfg.codec}' (ret=${ret})`)\n }\n\n const inSampleFmt = ctx.sampleFormat ?? AV_SAMPLE_FMT_FLT\n const outSampleFmt = pcmFormatToAv(cfg.targetFormat)\n const outLayout = buildChannelLayout(cfg.targetChannels)\n\n const swr = new SoftwareResampleContext()\n const allocRet = swr.allocSetOpts2(\n outLayout, outSampleFmt, cfg.targetSampleRate,\n inLayout, inSampleFmt, cfg.sourceSampleRate,\n )\n if (allocRet < 0) {\n ctx.freeContext()\n throw new Error(`audio-codec: swr allocSetOpts2 failed (ret=${allocRet})`)\n }\n const initRet = swr.init()\n if (initRet < 0) {\n ctx.freeContext()\n throw new Error(`audio-codec: swr init failed (ret=${initRet})`)\n }\n\n const packet = new Packet()\n packet.alloc()\n const inFrame = new Frame()\n inFrame.alloc()\n const outFrame = new Frame()\n outFrame.alloc()\n\n return new DecodeRuntime(ctx, swr, packet, inFrame, outFrame, cfg, inLayout, outLayout, inSampleFmt, outSampleFmt)\n }\n\n private constructor(\n ctx: CodecContext,\n swr: SoftwareResampleContext,\n packet: Packet,\n inFrame: Frame,\n outFrame: Frame,\n cfg: DecodeRuntimeConfig,\n inLayout: ChannelLayout,\n outLayout: ChannelLayout,\n inSampleFmt: AVSampleFormat,\n outSampleFmt: AVSampleFormat,\n ) {\n this.ctx = ctx\n this.swr = swr\n this.packet = packet\n this.inFrame = inFrame\n this.outFrame = outFrame\n this.cfg = cfg\n this.inLayout = inLayout\n this.outLayout = outLayout\n this.inSampleFmt = inSampleFmt\n this.outSampleFmt = outSampleFmt\n this.outBytesPerSample = bytesPerSample(cfg.targetFormat)\n }\n\n decode(buf: Uint8Array, pts?: number): DecodedPcm[] {\n if (this.closed) return []\n this.packet.data = Buffer.from(buf)\n if (pts !== undefined) {\n this.packet.pts = BigInt(Math.round(pts))\n this.packet.dts = BigInt(Math.round(pts))\n }\n const sendRet = this.ctx.sendPacketSync(this.packet)\n if (sendRet < 0 && sendRet !== AVERROR_EAGAIN) {\n // Non-fatal — log via thrown error so the addon-level catch records it\n throw new Error(`audio-codec: sendPacket failed (ret=${sendRet})`)\n }\n\n const out: DecodedPcm[] = []\n while (true) {\n const recvRet = this.ctx.receiveFrameSync(this.inFrame)\n if (recvRet === AVERROR_EAGAIN || recvRet === AVERROR_EOF) break\n if (recvRet < 0) {\n throw new Error(`audio-codec: receiveFrame failed (ret=${recvRet})`)\n }\n const pcm = this.resampleFrame(this.inFrame)\n if (pcm) out.push(pcm)\n this.inFrame.unref()\n }\n return out\n }\n\n private resampleFrame(inFrame: Frame): DecodedPcm | null {\n // Compute the output sample count for this input frame. The simplest\n // path: ask swr for the worst-case output size and allocate the\n // output frame to that. swr_get_out_samples isn't bound; we use\n // ceil(in_samples * out_rate / in_rate) + small slack.\n const inSamples = inFrame.nbSamples\n const outSamples = Math.ceil((inSamples * this.cfg.targetSampleRate) / this.cfg.sourceSampleRate) + 32\n\n this.outFrame.unref()\n this.outFrame.format = this.outSampleFmt\n this.outFrame.sampleRate = this.cfg.targetSampleRate\n this.outFrame.channelLayout = this.outLayout\n this.outFrame.nbSamples = outSamples\n const allocRet = this.outFrame.getBuffer(0)\n if (allocRet < 0) {\n throw new Error(`audio-codec: outFrame.getBuffer failed (ret=${allocRet})`)\n }\n\n const convRet = this.swr.convertFrame(this.outFrame, inFrame)\n if (convRet < 0) {\n throw new Error(`audio-codec: swr.convertFrame failed (ret=${convRet})`)\n }\n\n const producedSamples = this.outFrame.nbSamples\n if (producedSamples <= 0) return null\n\n // Pack interleaved PCM bytes. AV_SAMPLE_FMT_FLT / AV_SAMPLE_FMT_S16\n // are interleaved (non-planar), so the entire payload sits in\n // extendedData[0].\n const planes = this.outFrame.extendedData\n if (!planes || planes.length === 0 || !planes[0]) return null\n const bytes = producedSamples * this.cfg.targetChannels * this.outBytesPerSample\n const src = planes[0]\n const out = new Uint8Array(new ArrayBuffer(bytes))\n out.set(src.subarray(0, bytes))\n\n const ptsMs = this.nextPts\n this.nextPts = ptsMs + Math.round((producedSamples * 1000) / this.cfg.targetSampleRate)\n return {\n data: out,\n sampleRate: this.cfg.targetSampleRate,\n channels: this.cfg.targetChannels,\n format: this.cfg.targetFormat,\n pts: ptsMs,\n }\n }\n\n close(): void {\n if (this.closed) return\n this.closed = true\n try { this.outFrame.free() } catch { /* noop */ }\n try { this.inFrame.free() } catch { /* noop */ }\n try { this.packet.free() } catch { /* noop */ }\n try { this.swr.free() } catch { /* noop */ }\n try { this.ctx.freeContext() } catch { /* noop */ }\n }\n\n /** Surfaced for assertion-style tests to inspect computed layouts. */\n describe(): {\n inLayoutMask: bigint\n outLayoutMask: bigint\n inSampleFormat: AVSampleFormat\n outSampleFormat: AVSampleFormat\n } {\n return {\n inLayoutMask: this.inLayout.mask,\n outLayoutMask: this.outLayout.mask,\n inSampleFormat: this.inSampleFmt,\n outSampleFormat: this.outSampleFmt,\n }\n }\n}\n","import { randomUUID } from 'node:crypto'\nimport type {\n IAudioCodecCapProvider,\n ProviderRegistration,\n IScopedLogger,\n AudioCodecInfo,\n AudioDecodeSessionConfig,\n AudioEncodeSessionConfig,\n AudioPcmChunk,\n AudioEncodedChunk,\n PcmSampleFormat,\n} from '@camstack/types'\nimport {\n BaseAddon,\n audioCodecCapability,\n errMsg,\n} from '@camstack/types'\nimport { DecodeRuntime } from './codec-runtime.js'\n\n// ── Codec catalogue ─────────────────────────────────────────────────────────\n//\n// Phase 1 scope: decode side covers the codec matrix Reolink + ONVIF cameras\n// announce in SDP (`pcm_mulaw`, `pcm_alaw`, `aac`, `aac_latm`, `opus`).\n// Encode side covers the codecs cameras commonly accept on the intercom\n// back-channel (`pcm_mulaw`, `pcm_alaw`, `aac`, `opus`). The catalogue is\n// declarative — `listSupportedCodecs` returns this list, runtime probing of\n// node-av availability happens lazily on first session create.\n\ninterface CodecCatalogEntry {\n readonly codec: string\n readonly canDecode: boolean\n readonly canEncode: boolean\n readonly label?: string\n}\n\nconst CODEC_CATALOG: ReadonlyArray<CodecCatalogEntry> = [\n { codec: 'pcm_mulaw', canDecode: true, canEncode: true, label: 'PCM µ-law (G.711)' },\n { codec: 'pcm_alaw', canDecode: true, canEncode: true, label: 'PCM A-law (G.711)' },\n { codec: 'aac', canDecode: true, canEncode: true, label: 'AAC' },\n { codec: 'aac_latm', canDecode: true, canEncode: false, label: 'AAC LATM' },\n { codec: 'mpeg4-generic', canDecode: true, canEncode: false, label: 'AAC (MPEG4-GENERIC)' },\n { codec: 'opus', canDecode: true, canEncode: true, label: 'Opus' },\n]\n\nfunction resolveCodecAlias(codec: string): string {\n const c = codec.toLowerCase()\n // SDP names often differ from libav names. Normalise here so callers can\n // pass the SDP-reported value verbatim.\n if (c === 'mpeg4-generic') return 'aac'\n if (c === 'l16') return 'pcm_s16be'\n return c\n}\n\n// ── Session state ───────────────────────────────────────────────────────────\n\ninterface DecodeSessionState {\n readonly sessionId: string\n readonly kind: 'decode'\n readonly config: AudioDecodeSessionConfig\n readonly tag?: string\n readonly createdAtMs: number\n lastActivityMs: number\n framesIn: number\n framesOut: number\n /** Pending decoded chunks ready to be pulled. */\n readonly pcmQueue: AudioPcmChunk[]\n /** libavcodec-backed decode runtime (lazy-init on first push). */\n runtime: DecodeRuntime | null\n}\n\ninterface EncodeSessionState {\n readonly sessionId: string\n readonly kind: 'encode'\n readonly config: AudioEncodeSessionConfig\n readonly tag?: string\n readonly createdAtMs: number\n lastActivityMs: number\n framesIn: number\n framesOut: number\n /** Pending encoded chunks ready to be pulled. */\n readonly encodedQueue: AudioEncodedChunk[]\n}\n\ntype SessionState = DecodeSessionState | EncodeSessionState\n\nconst DEFAULT_IDLE_MS = 30_000\nconst REAPER_INTERVAL_MS = 5_000\n\n// ── Addon ───────────────────────────────────────────────────────────────────\n\ninterface AudioCodecGlobalConfig {\n readonly defaultIdleMs: number\n}\n\nconst DEFAULT_GLOBAL_CONFIG: AudioCodecGlobalConfig = {\n defaultIdleMs: DEFAULT_IDLE_MS,\n}\n\n/**\n * Audio codec I/O box backed by node-av (libavcodec + libswresample).\n *\n * Each `createDecodeSession` / `createEncodeSession` allocates an\n * independent libav codec context + resampler so consumers don't share\n * resamplers — a 16kHz mono ASA subscriber and a 48kHz stereo WebRTC\n * subscriber on the same source codec each get their own session.\n *\n * Phase 1 scaffolds the cap surface + session bookkeeping. The actual\n * libav decode/encode wiring is filled in by follow-up commits — the\n * stub keeps the cap registered so wiring on the broker side can land\n * before the audio backend matures.\n */\nexport default class AudioCodecNodeAvAddon\n extends BaseAddon<AudioCodecGlobalConfig>\n implements IAudioCodecCapProvider\n{\n private readonly sessions = new Map<string, SessionState>()\n private reaperTimer: ReturnType<typeof setInterval> | null = null\n private logger: IScopedLogger | null = null\n\n constructor() {\n super(DEFAULT_GLOBAL_CONFIG)\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n this.logger = this.ctx.logger as IScopedLogger\n this.reaperTimer = setInterval(() => this.reapIdleSessions(), REAPER_INTERVAL_MS)\n if (typeof this.reaperTimer.unref === 'function') this.reaperTimer.unref()\n return [{ capability: audioCodecCapability, provider: this }]\n }\n\n protected async onShutdown(): Promise<void> {\n if (this.reaperTimer) {\n clearInterval(this.reaperTimer)\n this.reaperTimer = null\n }\n for (const s of [...this.sessions.values()]) {\n this.disposeSession(s)\n }\n this.sessions.clear()\n }\n\n // ── Discovery ─────────────────────────────────────────────────────────────\n\n async listSupportedCodecs(): Promise<AudioCodecInfo[]> {\n return CODEC_CATALOG.map(e => ({\n codec: e.codec,\n canDecode: e.canDecode,\n canEncode: e.canEncode,\n ...(e.label ? { label: e.label } : {}),\n }))\n }\n\n async canHandle(input: { codec: string; kind: 'decode' | 'encode' }): Promise<boolean> {\n const resolved = resolveCodecAlias(input.codec)\n const entry = CODEC_CATALOG.find(e => e.codec === resolved)\n if (!entry) return false\n return input.kind === 'decode' ? entry.canDecode : entry.canEncode\n }\n\n // ── Session lifecycle ─────────────────────────────────────────────────────\n\n async createDecodeSession(\n input: AudioDecodeSessionConfig,\n ): Promise<{ sessionId: string; nodeId: string }> {\n const codec = resolveCodecAlias(input.codec)\n const entry = CODEC_CATALOG.find(e => e.codec === codec)\n if (!entry || !entry.canDecode) {\n throw new Error(`audio-codec: decode unsupported for codec '${input.codec}'`)\n }\n const sessionId = `dec-${randomUUID()}`\n const state: DecodeSessionState = {\n sessionId,\n kind: 'decode',\n config: { ...input, codec },\n ...(input.tag ? { tag: input.tag } : {}),\n createdAtMs: Date.now(),\n lastActivityMs: Date.now(),\n framesIn: 0,\n framesOut: 0,\n pcmQueue: [],\n runtime: null,\n }\n this.sessions.set(sessionId, state)\n this.logger?.info('audio-codec: decode session created', {\n tags: { sessionId },\n meta: { codec, target: `${input.targetSampleRate}Hz×${input.targetChannels}` },\n })\n return { sessionId, nodeId: this.ctx.kernel.localNodeId ?? 'local' }\n }\n\n async createEncodeSession(\n input: AudioEncodeSessionConfig,\n ): Promise<{ sessionId: string; nodeId: string }> {\n const codec = resolveCodecAlias(input.codec)\n const entry = CODEC_CATALOG.find(e => e.codec === codec)\n if (!entry || !entry.canEncode) {\n throw new Error(`audio-codec: encode unsupported for codec '${input.codec}'`)\n }\n const sessionId = `enc-${randomUUID()}`\n const state: EncodeSessionState = {\n sessionId,\n kind: 'encode',\n config: { ...input, codec },\n ...(input.tag ? { tag: input.tag } : {}),\n createdAtMs: Date.now(),\n lastActivityMs: Date.now(),\n framesIn: 0,\n framesOut: 0,\n encodedQueue: [],\n }\n this.sessions.set(sessionId, state)\n this.logger?.info('audio-codec: encode session created', {\n tags: { sessionId },\n meta: { codec, target: `${input.targetSampleRate}Hz×${input.targetChannels}` },\n })\n return { sessionId, nodeId: this.ctx.kernel.localNodeId ?? 'local' }\n }\n\n async closeSession(input: { sessionId: string }): Promise<void> {\n const s = this.sessions.get(input.sessionId)\n if (!s) return\n this.disposeSession(s)\n this.sessions.delete(input.sessionId)\n }\n\n // ── Decode data plane ─────────────────────────────────────────────────────\n\n async pushEncodedFrame(input: {\n sessionId: string\n data: Uint8Array\n pts?: number\n }): Promise<void> {\n const s = this.sessions.get(input.sessionId)\n if (!s || s.kind !== 'decode') {\n throw new Error(`audio-codec: decode session '${input.sessionId}' not found`)\n }\n s.lastActivityMs = Date.now()\n s.framesIn++\n\n // Lazy-init the libav runtime on first push. Decode failures here\n // are non-fatal for the broker — we log and drop the frame so a\n // bad packet doesn't tear the session down.\n if (!s.runtime) {\n try {\n s.runtime = DecodeRuntime.create({\n codec: s.config.codec,\n sourceSampleRate: s.config.sourceSampleRate,\n sourceChannels: s.config.sourceChannels,\n ...(s.config.extraData ? { extraData: s.config.extraData } : {}),\n targetSampleRate: s.config.targetSampleRate,\n targetChannels: s.config.targetChannels,\n targetFormat: s.config.targetFormat ?? 'f32le',\n })\n } catch (err) {\n this.logger?.error('audio-codec: decode runtime init failed', {\n tags: { sessionId: input.sessionId },\n meta: { codec: s.config.codec, error: errMsg(err) },\n })\n return\n }\n }\n\n try {\n const pcms = s.runtime.decode(input.data, input.pts)\n for (const pcm of pcms) {\n s.pcmQueue.push(pcm)\n }\n } catch (err) {\n this.logger?.warn('audio-codec: decode frame failed', {\n tags: { sessionId: input.sessionId },\n meta: { codec: s.config.codec, error: errMsg(err) },\n })\n }\n }\n\n async pullPcm(input: {\n sessionId: string\n maxCount: number\n }): Promise<AudioPcmChunk[]> {\n const s = this.sessions.get(input.sessionId)\n if (!s || s.kind !== 'decode') {\n throw new Error(`audio-codec: decode session '${input.sessionId}' not found`)\n }\n s.lastActivityMs = Date.now()\n const out = s.pcmQueue.splice(0, input.maxCount)\n s.framesOut += out.length\n return out\n }\n\n // ── Encode data plane ─────────────────────────────────────────────────────\n\n async pushPcm(input: {\n sessionId: string\n data: Uint8Array\n pts?: number\n }): Promise<void> {\n const s = this.sessions.get(input.sessionId)\n if (!s || s.kind !== 'encode') {\n throw new Error(`audio-codec: encode session '${input.sessionId}' not found`)\n }\n s.lastActivityMs = Date.now()\n s.framesIn++\n void input.data\n void input.pts\n }\n\n async pullEncoded(input: {\n sessionId: string\n maxCount: number\n }): Promise<AudioEncodedChunk[]> {\n const s = this.sessions.get(input.sessionId)\n if (!s || s.kind !== 'encode') {\n throw new Error(`audio-codec: encode session '${input.sessionId}' not found`)\n }\n s.lastActivityMs = Date.now()\n const out = s.encodedQueue.splice(0, input.maxCount)\n s.framesOut += out.length\n return out\n }\n\n async flushEncode(input: {\n sessionId: string\n }): Promise<AudioEncodedChunk[]> {\n const s = this.sessions.get(input.sessionId)\n if (!s || s.kind !== 'encode') {\n throw new Error(`audio-codec: encode session '${input.sessionId}' not found`)\n }\n s.lastActivityMs = Date.now()\n const out = s.encodedQueue.splice(0)\n s.framesOut += out.length\n return out\n }\n\n // ── Inventory ─────────────────────────────────────────────────────────────\n\n async listActiveSessions() {\n return [...this.sessions.values()].map(s => ({\n sessionId: s.sessionId,\n kind: s.kind,\n codec: s.config.codec,\n sourceSampleRate: s.config.sourceSampleRate,\n sourceChannels: s.config.sourceChannels,\n targetSampleRate: s.config.targetSampleRate,\n targetChannels: s.config.targetChannels,\n format: this.resolveFormat(s),\n ...(s.tag ? { tag: s.tag } : {}),\n createdAtMs: s.createdAtMs,\n lastActivityMs: s.lastActivityMs,\n framesIn: s.framesIn,\n framesOut: s.framesOut,\n }))\n }\n\n // ── Internals ─────────────────────────────────────────────────────────────\n\n private resolveFormat(s: SessionState): PcmSampleFormat {\n if (s.kind === 'decode') return s.config.targetFormat ?? 'f32le'\n return s.config.sourceFormat ?? 'f32le'\n }\n\n private reapIdleSessions(): void {\n const now = Date.now()\n for (const [id, s] of this.sessions) {\n const limit = (s.config.idleMs ?? this.config.defaultIdleMs ?? DEFAULT_IDLE_MS)\n if (now - s.lastActivityMs > limit) {\n this.logger?.info('audio-codec: reaping idle session', {\n tags: { sessionId: id },\n meta: { kind: s.kind, idleMs: now - s.lastActivityMs, limit },\n })\n try { this.disposeSession(s) }\n catch (err) {\n this.logger?.warn('audio-codec: dispose failed during reap', {\n tags: { sessionId: id },\n meta: { error: errMsg(err) },\n })\n }\n this.sessions.delete(id)\n }\n }\n }\n\n private disposeSession(s: SessionState): void {\n if (s.kind === 'decode' && s.runtime) {\n try { s.runtime.close() }\n catch (err) {\n this.logger?.warn('audio-codec: decode runtime close failed', {\n tags: { sessionId: s.sessionId },\n meta: { error: errMsg(err) },\n })\n }\n s.runtime = null\n }\n // Encode runtime cleanup lands when the encode path is wired.\n }\n}\n"],"names":[],"mappings":"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"}
|