Files
skills/docs/media/audio/audio-codec-ffmpeg-native/src/stream/stream-encoder.md
2026-05-29 08:20:56 +01:00

2.9 KiB

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

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

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.