first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user