first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
# `codec`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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<Uint8Array>` 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.