Initial Commit
This commit is contained in:
110
packages/media/audio/audio-codecs/src/stream/stream-encoder.md
Normal file
110
packages/media/audio/audio-codecs/src/stream/stream-encoder.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# `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 a `TransformStream<readonly Float32Array[], Uint8Array>`
|
||||
where each input `Float32Array[]` chunk represents one block of
|
||||
PCM (one `Float32Array` per channel, same length per channel) and
|
||||
each output `Uint8Array` carries encoded bytes from the codec's
|
||||
`StreamingEncoder`.
|
||||
- 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, then
|
||||
`write()` for every chunk, then `flush()` 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 calls `start()` 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 `registry` is omitted, the transform
|
||||
lazily loads `WavCodec` into a shared cached registry. Codecs
|
||||
beyond WAV that support encode (FLAC, MP3, Opus, QOA) must be
|
||||
passed explicitly via the `registry` parameter.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
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):
|
||||
|
||||
```ts
|
||||
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 with
|
||||
`AUDIO_CODECS.NOT_SUPPORTED` for unknown formats or
|
||||
`AUDIO_CODECS.STREAM_ERROR` for codecs without `createEncoder()`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The `started` flag ensures the encoder's `start(sampleRate,
|
||||
channels)` is invoked exactly once even when no chunks arrive
|
||||
(the `flush()` path also checks the flag and calls `start()`
|
||||
if needed before invoking `flush()`).
|
||||
- WAV's `start()` returns an empty `Uint8Array` because 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 the `byteLength > 0` check) so downstream
|
||||
consumers don't see empty data.
|
||||
- Codec-thrown `AudioCodecsError` instances pass through unchanged;
|
||||
non-API errors from `write` or `flush` are wrapped with
|
||||
`STREAM_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 calls
|
||||
`encoder.close()` to release any internal buffers — even
|
||||
half-streamed encodes leak no resources on cancellation.
|
||||
Reference in New Issue
Block a user