-
Notifications
You must be signed in to change notification settings - Fork 3
kithara encode
Documentation reviewed from source revision 19ca073f2. This records the documented contract at that revision; it is not a new runtime validation. API and usage · All crates.
The crate owns three roles: portable continuous PCM/WAV encoding, streaming AAC-LC encoding for the live broadcast path, and offline encoding that kithara-integration-tests uses to generate encoded fixtures and packaged tracks. It consumes canonical AudioCodec, ContainerFormat, and MediaInfo from kithara-stream.
The portable PCM/WAV sessions are always compiled and need no native backend. Two optional native encoders also live here and the caller names the one it wants. ffmpeg links the system FFmpeg and owns byte encoding, FLAC, and the offline AAC-LC fixtures; fdk-aac builds from vendored sources into the binary and owns HE-AAC v1/v2 offline plus the streaming AAC-LC a live broadcast runs on. Both native backends are default features, but a build with neither still has the portable sessions.
StreamBackend follows kithara-decode's DecoderBackend: a variant exists only where its feature is compiled in, so asking for a backend this build does not carry is a compile error rather than a runtime miss, and a backend that fails is terminal — neither one stands in for the other. The offline routes answer per codec instead: a codec whose backend is configured out is UnsupportedCodec, and byte encoding without ffmpeg is InvalidInput.
EncodeConfig is the shared bon configuration for a continuous encoder/container pair. sample_rate and channels are required; codec defaults to Pcm, container defaults to Wav, and packet_frames defaults to 1024. The current portable profile is WAV with little-endian IEEE float32 samples. Other codec/container pairs return typed unsupported-profile errors and are never replaced with the default.
EncoderSession accepts complete interleaved f32 frames. It emits the same EncodedAccessUnit sequence for the same samples regardless of input chunking, and finish emits the final partial packet. Access-unit timestamps are PCM frame positions and start at zero.
ContainerSession accepts those access units in timestamp order and returns absolute ContainerWrite operations instead of owning a file or byte store. WAV payload writes start after the fixed header; finish returns the final header rewrite and exact committed length. A caller with a known frame count must call validate_frame_count before doing work. RIFF's hard size boundary returns ContainerLimitExceeded rather than truncating or switching formats.
StreamEncoder is the canonical AAC-LC encode path. Its builder names the backend, sample rate, channels, bit rate, and timescale and opens one encoder for one continuous stream; push takes interleaved f32 and returns the access units that audio completed; finish flushes the encoder and returns the rest. The instance lives for the whole stream — a per-segment encoder would restart the priming frame and click at every boundary.
-
pushtakes full frames: a slice whose length is not a multiple of the channel count is rejected. Samples are expected in[-1.0, 1.0];FFmpegtakes larger magnitudes through to the linked encoder, where the outcome depends on its sample format, and fdk clamps them onto full scale. - The filter graph holds up to
FRAME_SAMPLES - 1samples between calls and hands the encoder whole frames, so chunk size does not reach the encoder: the same audio pushed in any chunking yields byte-identical access units with identical timestamps.finishis what releases the tail. - A
pushthat returns a backend error leaves the encoder mid-frame — drop it and open a new one rather than pushing again. - Timestamps start at 0 and access-unit boundaries are what gets rescaled into
1/timescale, so durations tile the pts timeline exactly even when the ratio is fractional. Each access unit carriesFRAME_SAMPLES(1024) samples per channel and is a sync point, andpts == dts. - The encoder takes the source's own channel layout: finishing the builder fails when AAC-LC has no layout for the requested channel count rather than silently downmixing or upmixing. fdk carries 1 to 6 channels and audio from 8 kHz to 96 kHz.
- Priming is measured per backend, not assumed: past the pushed audio
FFmpeghands back oneFRAME_SAMPLESframe and fdk two. Whoever judges the decoded signal skips the larger of the two. - fdk takes the pushed f32 down to i16, which is the input libfdk reads;
FFmpegencodes the f32 as it comes. Its access units carry no transport header, and its stream ends on libfdk's own end-of-input signal — that signal is what puts the tail of a broadcast into the last segment.
PcmSource::read_pcm_at must yield interleaved packed i16 bytes; every backend derives its frame stride as channels * size_of::<i16>(). A source that hands back any other layout produces silent garbage, not an error. The AAC-LC offline path reads those bytes, scales them by 1/32768 into f32, and pushes them through StreamEncoder; the FLAC and bytes paths hand the i16 bytes to FFmpeg directly, and the HE-AAC path reads them into the in-tree fdk encoder.
PackagedEncodeRequest::validate() is shared by every packaged backend and rejects a zero timescale, a zero packets_per_segment, or a PcmSource without a finite total_byte_len() (packaged encoding is offline-only).
Routing inside OfflineEncoder:
-
AacLc—StreamEncoderon theFFmpegbackend fed the whole source, natural frame 1024 samples. -
AacHe/AacHeV2— in-tree fdk-aacAacHeEncoder(AOT SBR / PS), natural frame 2048 samples, stereo input only. -
Flac— FFmpeg FLAC, natural frame 4608 samples. - Any other codec —
EncodeError::UnsupportedCodec.
EncoderFactory::frame_samples(codec) returns those same natural frame sizes; segmenters must use it rather than assuming 1024.
EncoderFactory::encode_packaged takes the caller's typed PoolRegion and reuses its byte and sample pools for temporary byte and converted-sample storage. Production encoding must not construct a component-local region. A scratch lease ends inside the encode call and is returned before the resulting track can outlive the request.
-
EncodedAccessUnit::bytesis a long-lived ownedVec<u8>, not pooled scratch. It must not retain a pool lease: access units can outlive the encoder request and move independently through muxing, caching, and transport. -
codec_configis codec-dependent: FLAC yields the 34-byte STREAMINFO body, HE-AAC yields the AudioSpecificConfig, and AAC-LC yields an empty blob — the muxer has to synthesize the ASC itself. -
media_infois the request's info withcodec,sample_rate, andchannelsoverridden from the actual encode. The FLAC and HE-AAC paths additionally forcecontainer = Fmp4; the AAC-LC path leaves the requested container untouched. - FFmpeg backends rescale packets from
1/sample_rateto1/timescaleand normalize every timestamp against the first packet'smin(pts, dts), so a track always starts at 0;is_synccomes from the packet's key flag. The fdk HE-AAC path synthesizespts = dtsitself in timescale units from the encoder frame length and marks every access unitis_sync. -
encoder_delayandtrailing_delayare pass-through values from the request — this crate never measures priming or padding.
create_bytes covers Mp3, Flac, Aac, and M4a. BytesEncodeTarget owns the codec/container/extension mapping and the 128 kbps default for the lossy targets; the FFmpeg bytes backend adds the MIME type and forces compression_level=5 with no bit rate for FLAC. An explicit bit_rate on the request wins over the target default. Byte encoding muxes through a real file in a tempfile::tempdir(), so it needs a writable temp dir and is not usable from a read-only sandbox.
normalize_flac_codec_config is the public helper for callers holding a raw FLAC config blob: it accepts a bare 34-byte STREAMINFO, a metadata block carrying STREAMINFO, or fLaC-prefixed bytes, and errors otherwise.
FFmpeg is initialized once per process behind a OnceLock. On wasm32 the ffmpeg and fdk modules are not compiled at all and StreamEncoder is absent. EncodeConfig, EncoderSession, and ContainerSession still provide PCM/WAV float32; native-only EncoderFactory entry points return EncodeError::InvalidInput("encoding is not supported on wasm32").
libfdk's vendored FDK_archdef.h has no branch for MSVC on ARM64, so the fdk-aac feature does not build for aarch64-pc-windows-msvc.