first commit
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# `stream-encoder`
|
||||
|
||||
## Purpose
|
||||
|
||||
`createStreamingEncoder` returns a `FfmpegStreamingEncoder` — the
|
||||
`@suaveplan/audio-codecs` `StreamingEncoder` plus an additive Promise-returning
|
||||
`flushAsync()` companion. It backs the encoder with a single long-running
|
||||
ffmpeg subprocess that consumes interleaved f32le PCM on stdin and emits the
|
||||
selected container format on stdout, lazily spawned on the first `start()` so
|
||||
that an encoder constructed but never used pays no process cost.
|
||||
|
||||
## Features
|
||||
|
||||
- Single persistent ffmpeg subprocess per encoder.
|
||||
- Lazy spawn on first `start()`; channel count chosen by the caller.
|
||||
- Strict `validatePcm` on every `write` raises `INVALID_PCM` on bad input.
|
||||
- Sync `start` / `write` / `flush` honour the base contract; additive
|
||||
`flushAsync(): Promise<Uint8Array>` waits for ffmpeg to drain.
|
||||
- `AbortSignal` and `timeoutMs` integrated — both raise canonical
|
||||
`ABORTED` / `TIMEOUT` codes on the next `flushAsync`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { FfmpegNativeCodec } from "@suaveplan/audio-codec-ffmpeg-native";
|
||||
|
||||
const encoder = new FfmpegNativeCodec().createEncoder({
|
||||
sampleRate: 48_000,
|
||||
format: "flac",
|
||||
});
|
||||
encoder.start(48_000, channelData.length);
|
||||
encoder.write(channelData);
|
||||
const tail = await encoder.flushAsync();
|
||||
process.stdout.write(tail);
|
||||
encoder.close();
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { FfmpegNativeCodec } from "@suaveplan/audio-codec-ffmpeg-native";
|
||||
|
||||
const codec = new FfmpegNativeCodec({ timeoutMs: 10_000 });
|
||||
const encoder = codec.createEncoder({ format: "opus" });
|
||||
encoder.start(48_000, 2);
|
||||
for (const block of pcmBlocks()) encoder.write(block);
|
||||
const finalBytes = await encoder.flushAsync();
|
||||
encoder.close();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createStreamingEncoder(opts: StreamingEncoderOptions): FfmpegStreamingEncoder`
|
||||
- `FfmpegStreamingEncoder` — extends `StreamingEncoder` with
|
||||
`flushAsync(): Promise<Uint8Array>`.
|
||||
- `StreamingEncoderOptions` — `binaryProvider`, `argsFor(channels)`,
|
||||
optional `signal`, `timeoutMs`, `spawn`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The base `StreamingEncoder.start()` is documented as returning header bytes;
|
||||
ffmpeg emits headers on stdout as part of the normal encode flow, so we
|
||||
return an empty `Uint8Array` from `start()` and let the headers fall out of
|
||||
the first `write` / `flushAsync` call. This is consistent with how all the
|
||||
real-world container formats (FLAC, Opus, MP3) actually framing their headers
|
||||
on the same byte stream as their data.
|
||||
|
||||
`write` returns synchronously with whatever bytes ffmpeg has already pushed
|
||||
out of stdout. For most encoders the first synchronous return after a single
|
||||
`write` is empty because ffmpeg buffers frames; callers MUST await
|
||||
`flushAsync` to retrieve trailing bytes.
|
||||
|
||||
`chosenChannels` is captured in `start()` (or, defensively, the length of
|
||||
the first `write` call's `channelData`) because the encode argv embeds the
|
||||
`-ac <n>` flag — once ffmpeg is running, the channel count cannot change.
|
||||
Reference in New Issue
Block a user