# `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` 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`. - `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 ` flag — once ffmpeg is running, the channel count cannot change.