4.3 KiB
4.3 KiB
stream/stream-encoder
TransformStream<readonly Float32Array[], Uint8Array> factory —
composes a PCM-block stream into encoded bytes by driving a codec's
StreamingEncoder.
Purpose
The mirror of stream-decoder for the encode direction. Callers
building a @suaveplan/streams pipeline (decode → DSP → encode)
need a TransformStream node for the encode stage; this module
provides it.
Features
createEncodeTransform(format, sampleRate, channels, registry?)— returns aTransformStream<readonly Float32Array[], Uint8Array>where each inputFloat32Array[]chunk represents one block of PCM (oneFloat32Arrayper channel, same length per channel) and each outputUint8Arraycarries encoded bytes from the codec'sStreamingEncoder.- Lazy codec resolution — the transform constructs the codec's
encoder on first chunk (or on flush if no chunks arrived). The
encoder's
start()is called once to emit header bytes, thenwrite()for every chunk, thenflush()to emit any trailer. - Sample rate + channel count are captured at construction so the
encoder's
start()can pre-compute its header without waiting for the first chunk. - Empty-source handling — even when no PCM chunks arrive,
flush()still callsstart()once and emits the codec's trailer (the 44-byte WAV header for the WAV codec). Downstream consumers always receive a structurally-valid file. - Default registry — when
registryis omitted, the transform lazily loadsWavCodecinto a shared cached registry. Codecs beyond WAV that support encode (FLAC, MP3, Opus, QOA) must be passed explicitly via theregistryparameter.
Usage: Basic Example
import { createEncodeTransform } from "@suaveplan/audio-codecs/stream/stream-encoder";
import { CodecRegistry, WavCodec } from "@suaveplan/audio-codecs";
const registry = new CodecRegistry({ codecs: [WavCodec] });
const transform = createEncodeTransform("wav", 44_100, 2, registry);
const pcmStream = makePcmBlockStream();
const wavBytes = pcmStream.pipeThrough(transform);
await wavBytes.pipeTo(myFileSink);
Usage: Advanced Example
Decode-then-encode pipeline (transcode FLAC → WAV via streaming):
import {
createDecodeTransform,
createEncodeTransform,
} from "@suaveplan/audio-codecs/stream/stream-encoder";
const decodeTx = createDecodeTransform("flac", registry);
const encodeTx = createEncodeTransform("wav", 44_100, 2, registry);
// The mid pipeline converts AudioPage into Float32Array[] form.
const pageToBlock = new TransformStream<AudioPage, readonly Float32Array[]>({
transform(page, controller) {
controller.enqueue(page.channelData);
},
});
await fetchAudio()
.pipeThrough(decodeTx)
.pipeThrough(pageToBlock)
.pipeThrough(encodeTx)
.pipeTo(myDestination);
API Reference
function createEncodeTransform(format: string, sampleRate: number, channels: number, registry?: CodecRegistry): TransformStream<readonly Float32Array[], Uint8Array>— produces a transform that emits encoded byte chunks; errors withAUDIO_CODECS.NOT_SUPPORTEDfor unknown formats orAUDIO_CODECS.STREAM_ERRORfor codecs withoutcreateEncoder().
Implementation Notes
- The
startedflag ensures the encoder'sstart(sampleRate, channels)is invoked exactly once even when no chunks arrive (theflush()path also checks the flag and callsstart()if needed before invokingflush()). - WAV's
start()returns an emptyUint8Arraybecause the WAV header includes the total data-chunk size which is only known at flush time; the transform handles that by emitting zero-byte chunks (skipped via thebyteLength > 0check) so downstream consumers don't see empty data. - Codec-thrown
AudioCodecsErrorinstances pass through unchanged; non-API errors fromwriteorflushare wrapped withSTREAM_ERROR. - The default registry registers only
WavCodec. Encoding via FLAC, MP3, Opus, or QOA requires the caller to construct a registry with those codecs and pass it as the fourth argument. This choice keeps the default-registry path tree-shakeable when callers don't use streaming. cancel()on the underlying transform stream callsencoder.close()to release any internal buffers — even half-streamed encodes leak no resources on cancellation.