# `codec` _(no submodules)_ ## Purpose `FfmpegNativeCodec` is the server-tier concrete `AudioCodec` implementation that backs `@suaveplan/audio-codec-ffmpeg-native`. It satisfies the `@suaveplan/audio-codecs` plugin contract — `canDecode`, `decode`, `encode`, `createDecoder`, `createEncoder` — by spawning a child `ffmpeg` process per operation, piping bytes through stdin and reading streamed PCM out of stdout without ever buffering the full input. The class also owns the per-instance concurrency limit (a tiny FIFO semaphore) so that multi-tenant servers cannot launch unbounded ffmpeg children, and threads the caller's `AbortSignal` through to the `runner` module so that cancellation reliably reaps the subprocess. ## Features - Full `AudioCodec` surface: `canDecode`, `decode`, `encode`, plus the optional `createDecoder` / `createEncoder` streaming factories. - Configurable concurrency (`concurrencyLimit`, default 4) enforced through an in-instance FIFO waiter queue. - Per-operation `AbortSignal` and per-instance `timeoutMs` plumbed through the runner; both escalate SIGTERM → SIGKILL. - Strict PCM validation on encode (`validatePcm` raises `INVALID_PCM`). - Strict encode-format allowlist (`UNSUPPORTED_CODEC` for anything outside `wav | mp3 | ogg | opus | flac | aac | m4a | webm | matroska | wma`). - f32le interleaving on encode + de-interleaving on decode (see `pcm.ts`). ## Usage: Basic ```ts import { FfmpegNativeCodec } from "@suaveplan/audio-codec-ffmpeg-native/codec/codec"; const codec = new FfmpegNativeCodec(); const decoded = await codec.decode(mp3Bytes); const wav = await codec.encode(decoded.channelData, { sampleRate: decoded.sampleRate, format: "wav", }); ``` ## Usage: Advanced ```ts import { FfmpegNativeCodec } from "@suaveplan/audio-codec-ffmpeg-native/codec/codec"; const codec = new FfmpegNativeCodec({ binaryProvider: () => process.env.FFMPEG_PATH ?? "", concurrencyLimit: 8, timeoutMs: 15_000, channels: 1, sampleRate: 16_000, defaultEncodeFormat: "opus", }); const controller = new AbortController(); setTimeout(() => controller.abort(), 5_000); const encoded = await codec.encode([Float32Array.from([0.1, -0.1])], { signal: controller.signal, format: "flac", }); ``` ## API Reference - `new FfmpegNativeCodec(options?, spawn?)` — `options` matches `FfmpegNativeCodecOptions`; the second positional `spawn` argument is the test-only `Spawn` injector that bypasses `node:child_process.spawn`. - `name`, `extensions`, `mimeTypes` — read-only metadata for registry dispatch. - `decode(bytes, opts?)` / `encode(channelData, opts?)` — full-buffer paths. - `createDecoder(opts?)` / `createEncoder(opts?)` — streaming-factory returning the additive companion shapes from `stream/*.ts`. ## Implementation Notes The semaphore is intentionally inline (one private counter + one waiter array) rather than a separate module because it never leaves this class — extracting it would inflate the public surface for zero callers. The same reasoning applies to the singleton `singleChunk` async generator used by `encode`: it exists to feed `runEncode`'s `AsyncIterable` contract from a single buffered byte string. Decode and encode share argv templates (`decodeArgs` / `encodeArgs`) so the streaming factories can reuse the exact same ffmpeg argv as the full-buffer paths. This keeps the on-the-wire ffmpeg behaviour stable across the four public methods. `canDecode` deliberately leans on the magic-byte sniffer in `pcm.ts` rather than running ffmpeg in a probe mode; we want `canDecode` to be cheap and synchronous because the `CodecRegistry` calls it on every dispatch.