* craft-audio: a program config element in the shape of a channel configuration gets its speakers
An AAC stream with channel configuration 0 whose program config element lists
a centre, a front pair and then exactly the elements of configuration 3 to 6
decoded as C L R Ls Rs LFE and was reported as an unordered layout, so a
caller reading it in WAV order sent the centre to the left speaker. Such a
stream now has the configuration's layout and WAV order. Any other element
list keeps the decoded order, as before.
Ported from storytold/filmcraft#4280 (aac_stream_order in filmcraft-codecs).
* craft-audio: the ADTS program config oracle expects 5.1 in WAV order
ffmpeg 9 writes its 5.1 program config element centre first, so the stream
now has 5.1's speakers: each decoded channel must match ffmpeg's channel of
the same index. An element list in another order keeps the old check.
---------
Co-authored-by: mpratsaki <110930793+mpratsaki@users.noreply.github.com>
wav_pcm and aiff_pcm (feature decode-native) return a file's integer PCM as stored
(RawPcm: little-endian interleaved bytes, 8-bit unsigned, with channels, rate and
bits), built on the existing WAV and AIFF chunk parsers. pcm_wav_file,
pcm_wav_header, pcm_aiff_file and pcm_aiff_header (feature pcm-encode) write the
44-byte canonical WAVE header and the plain AIFF header around such bytes.
For essence embedded in AAF and OMF files, which is moved bit for bit and whose
descriptors hold a bare header as a summary. The writers are FilmCraft's
(filmcraft-interchange wav.rs); the readers replace its second WAV/AIFF parser
with this crate's.
Moves filmcraft-mp3enc (storytold/filmcraft@4bec22b) into craft_audio::mp3,
on craft-media's BitWriter. `encode` turns the feature on, mp3::Error converts
into AudioError::Encode, and a test covers every sample rate and bit rate with
extreme signals.
The existing test sets the decoder's state by hand, so it also passes
with a mid-only flag kept from one frame to the next (the behaviour the
per-frame flag replaced). This one decodes a stereo frame that codes
the flag, then loses a frame: with a kept flag the side channel does
not restart and the test fails.
* craft-audio: AAC Huffman lookahead uses BitReader::peek
`BitReader::peek` reads from one 8-byte load since the bit reader was made
faster, which is what the AAC decoder's private `peek32` did. The private copy
goes; the lookahead is the same zero-padded 32 bits.
Ported from storytold/filmcraft#4232.
* craft-audio: a damaged FLAC keeps its length and timing
symphonia's FLAC demuxer drops a frame whose checksum fails and resyncs on the
next one, so the decoder never saw an error: the file decoded shorter and all
later audio came out one block early, with `damaged` at 0.
The jump in the packet timestamps is the audio that was lost. It is returned
as silence, in blocks of at most 65 536 frames, and counted once in
`AudioInfo::damaged`. It never passes the length the file declares; with no
declared length a jump of more than 2^20 frames is not believed. A seek into
the lost frames lands on silence at the right position.
Ported from storytold/soundcraft#556, onto the streaming decoder.
---------
Co-authored-by: vangel76 <vangel1976@googlemail.com>
Co-authored-by: Gert Pellin <gert@pellin.be>
DECODE_EXTENSIONS lists the file extensions of the formats decode reads.
EncodeFormat gains ALL, extension() and mime_type(). SoundCraft kept these
tables in its own FileFormat type; they are format knowledge, so they live
here.
A WAV with many empty data chunks parsed in quadratic time: whether an empty
data chunk is followed by chunks alone was decided for every one of them, by
walking the rest of the file. Only the first data chunk is used, so it is
decided once.
Tests SoundCraft added to audio-io after craft-audio was cut from it: Matroska
element sizes beyond the file, an AIFF channel count with the sign bit set,
probe and decode agreeing on the length of AAC in MP4, and one small file per
decodable extension (tests/fixtures).
* craft-audio: FilmCraft's FLAC encoder and MP1/MP2 decoder speed-ups
FLAC: per-order predictor kernels, merged Rice partition sums, the LPC
window kept per block length, and batches consumed by offset
(filmcraft 5eaff794, d9dad4a5). The output is unchanged: 64 golden cases
hash the same before and after.
MP1/MP2: the faster synthesis filterbank (filmcraft 51c755e5), with its
bit-exact test.
* craft-audio: FilmCraft's AAC and Opus decoder speed-ups and refactors
AAC: table-driven Huffman decoding, per-stage FFT twiddles and fused
stages, reused per-channel work buffers, and the encoder's memoised
per-band work (filmcraft f9627280, d587ceed, 6121635c, dc124ab7,
7a532eca).
Opus: radix-2/3/4 FFT kernels in the CELT IMDCT, the PVQ table built
once, one de-emphasis loop for both channels, the SILK resampler
without per-sample divisions and with eight partial sums, concealment
and FEC capped at 120 ms (filmcraft bf7ac420, 12e32026, c4dc64d1,
eb352ec8, 87b8e22d, 014b5e56, 78d77b72).
The per-frame mid-only flag this crate fixed in the SILK decoder is
kept in the refactored decode.
* craft-media: Ogg CRC-32 by slice-by-16
FilmCraft's 6763f9bf: sixteen independent lookups per 16 input bytes, so
opening a file is several times faster. Checked against a bit-at-a-time
reference on every short length and alignment, and across every split.
* craft-audio: SoundCraft's CAF cookie-size and FLAC vendor-length checks
A CAF magic cookie size above the signed range is refused instead of
passed to symphonia (soundcraft 3bd3965), and a Vorbis comment block
whose vendor string claims more than the block holds is refused
(soundcraft ddfe991). Each with its regression test.
* craft-audio: AC-3 reads bits through the shared BitReader
FilmCraft's 1c9c2e82: the AC-3 decoder's private bit-at-a-time reader is
replaced by craft-media's BitReader, with its two tests: the decoded
output of 2,000 synthetic and 12 real syncframes (and their damaged
variants) hashes the same as before, and the reader agrees with the old
one on random reads, skips and overruns. The three AC-3 features now
turn on craft-media's bitstream module.
* craft-audio: the quantiser rounds without a libm call
SoundCraft's d293336, the audio-io part: round-half-away by integer
arithmetic in the bounce's inner loop, with its test against f64::round
over the quantiser's range.
* craft-audio: flac::Encoder, the FLAC packet encoder, is public
FilmCraft muxes FLAC into its own outputs from integer samples, so it
needs the encoder below FileEncoder: planar samples in, frames out, and
the STREAMINFO to patch in at the end. The module is behind flac-encode,
its errors convert to AudioError::Encode, and pending_samples says how
long the final frame will be.
* craft-audio: FilmCraft's FLAC encoder and MP1/MP2 decoder speed-ups
FLAC: per-order predictor kernels, merged Rice partition sums, the LPC
window kept per block length, and batches consumed by offset
(filmcraft 5eaff794, d9dad4a5). The output is unchanged: 64 golden cases
hash the same before and after.
MP1/MP2: the faster synthesis filterbank (filmcraft 51c755e5), with its
bit-exact test.
* craft-audio: FilmCraft's AAC and Opus decoder speed-ups and refactors
AAC: table-driven Huffman decoding, per-stage FFT twiddles and fused
stages, reused per-channel work buffers, and the encoder's memoised
per-band work (filmcraft f9627280, d587ceed, 6121635c, dc124ab7,
7a532eca).
Opus: radix-2/3/4 FFT kernels in the CELT IMDCT, the PVQ table built
once, one de-emphasis loop for both channels, the SILK resampler
without per-sample divisions and with eight partial sums, concealment
and FEC capped at 120 ms (filmcraft bf7ac420, 12e32026, c4dc64d1,
eb352ec8, 87b8e22d, 014b5e56, 78d77b72).
The per-frame mid-only flag this crate fixed in the SILK decoder is
kept in the refactored decode.
* craft-media: Ogg CRC-32 by slice-by-16
FilmCraft's 6763f9bf: sixteen independent lookups per 16 input bytes, so
opening a file is several times faster. Checked against a bit-at-a-time
reference on every short length and alignment, and across every split.
* craft-audio: SoundCraft's CAF cookie-size and FLAC vendor-length checks
A CAF magic cookie size above the signed range is refused instead of
passed to symphonia (soundcraft 3bd3965), and a Vorbis comment block
whose vendor string claims more than the block holds is refused
(soundcraft ddfe991). Each with its regression test.
* craft-audio: AC-3 reads bits through the shared BitReader
FilmCraft's 1c9c2e82: the AC-3 decoder's private bit-at-a-time reader is
replaced by craft-media's BitReader, with its two tests: the decoded
output of 2,000 synthetic and 12 real syncframes (and their damaged
variants) hashes the same as before, and the reader agrees with the old
one on random reads, skips and overruns. The three AC-3 features now
turn on craft-media's bitstream module.
* craft-audio: the quantiser rounds without a libm call
SoundCraft's d293336, the audio-io part: round-half-away by integer
arithmetic in the bounce's inner loop, with its test against f64::round
over the quantiser's range.
* craft-audio: ALAC decoder and encoder, ported from Apple's reference
Add `craft_audio::alac`, a Rust port of Apple's ALAC reference
implementation (macosforge/alac@c38887c, Apache-2.0):
- `alac::Decoder`: packets to planar i32, bit depths 1-32, 1-8 channels,
cookies in MP4/Matroska (24-byte config), CAF and old QuickTime form.
Hostile input returns errors; arithmetic wraps where C would overflow.
- `alac::Encoder`: 16/20/24/32 bits, 1-8 channels, fast mode, any frame
length; same packets as the reference for the same input.
- `SpecificConfig` (cookie parse/write), `wav_order`/`alac_order`,
`channel_mask`.
- Features `alac-decode` (in `decode-native`) and `alac-encode` (in
`encode`).
`TrackDecoder` decodes ALAC natively, so ALAC in MP4 and Matroska no
longer goes through symphonia (CAF still does).
Apple's reference writes the cookie's channel layout tag in host byte
order; this writes it big-endian as its cookie description specifies and
reads either.
Tests: unit tests for each part, an MP4 decode-and-seek test without
symphonia, and ffmpeg oracles both ways (bit-exact, 1-8 channels).
NOTICE and each file's header record the Apache-2.0 origin.
* craft-audio: ALAC review fixes (#731)
- Decode the full-width residual escape 0xffffffff as i32::MIN; the
reference's `nd + 1` wraps it to 0.
- Require a whole element tag after the last channel (the end tag, after
any fill or data stream elements), so a packet that lost its last byte
is an error instead of decoding without damage. Other elements there
end the packet, as in the reference.
- Refuse an ALAC track with no sample rate in its cookie or container.
- Conceal a damaged packet with the length its header gives
(`alac::Decoder::packet_frames`), falling back to the last packet's.
- Matroska: place ALAC packet starts at multiples of the frame length
instead of the rounded block timestamps, so seeks land on the exact
frame. MP4 keeps its exact sample-table timing.
* craft-audio: exact Matroska ALAC seeks after a short packet (#731)
Packet starts were multiples of the frame length, so a short packet
before the end moved every later packet and seeks after it returned
the wrong frames. When a packet's timestamp is further from that
multiple than its rounding allows, the starts are now counted from the
earlier packets' headers (read once and kept). Other codecs with a
fixed packet length fall back to the timestamp, as before.
---------
Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
FilmCraft's MPEG audio Layer I/II decoder as `craft_audio::mpa` behind
`mpa-decode`, kept over symphonia's: the same quality where both
decode, and it also decodes the MP2 files ffmpeg writes by default
(mono at 384 kbit/s), which symphonia rejects. `TrackDecoder` and the
native read path take MP1/MP2 in MP4, Matroska and raw streams;
symphonia keeps MP3 only, so its `mp1` and `mp2` features are dropped.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
* craft-audio: AC-3 and E-AC-3 decoder
FilmCraft's AC-3 decoder as `craft_audio::ac3` behind `ac3-decode`:
every AC-3 channel mode and frame size, and E-AC-3 independent
substream 0 with spectral extension, in WAV order without downmix.
`TrackDecoder` and the native read path take AC-3 and E-AC-3 in MP4,
Matroska and raw streams.
Not bit-exact: zero-bit mantissas are filled with decoder-specific
dither (A/52 §7.3.4), so tests compare at 65-94 dB SNR on tones and
27-48 dB on white noise. 7.1 E-AC-3 decodes as its 5.1 core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 1 (#447)
An AC-3 / E-AC-3 packet is as long as all the primary syncframes in it.
Before the first decode, TrackDecoder::frames_per_packet gave 1536 for any
AC-3 track, and conceal used the first syncframe's header only. An E-AC-3
stream of one-, two- or three-block syncframes then got the wrong MP4 length
and seek positions and the wrong Matroska positions. A packet that groups
several such syncframes concealed at a fraction of its length.
Seeks in AC-3 and E-AC-3 now have to match the whole-file decode bit for bit,
as every other codec's do. The dither is seeded from each syncframe, so the
old 25 dB bound was looser than the decoder needs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 2 (#447)
- AC-3 / E-AC-3 packets that decode to different lengths (in the packets
read ahead, or any decoded since) have no frames_per_packet, so a
Matroska seek finds its packet by timestamp rather than by index times
the first packet's length.
- E-AC-3 syncframes of fewer than six blocks may reuse exponents from any
syncframe before them: once one is seen, a seek decodes from the start
of the stream instead of 1536 frames before the target.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 3 (#447)
- Matroska E-AC-3: the first seek reads every packet's syncframe headers (only the headers,
header stripping undone) and places packets by the frames their primary syncframes hold.
Packets that started to differ in length after the eight read ahead were placed as the first
packet's length times their index.
- A packet whose later syncframe fails no longer sets the length lost packets are concealed at,
nor marks the packets' lengths as differing: it decoded only in part.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge main; E-AC-3 in Matroska: read packet headers only up to the seek target, and count a packet of dependent syncframes as zero frames
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
* craft-audio: Opus decoder
FilmCraft's `filmcraft-opus` decoder as `craft_audio::opus` behind
`opus-decode`: SILK, hybrid and CELT, every frame size, PLC and FEC,
multistream (`Decoder`) and single stream (`StreamDecoder`), 8-48 kHz
output.
- `TrackDecoder` decodes Opus; mono and stereo without an `OpusHead`
(RTP, MPEG-TS) get a default one. Lost packets use Opus's own
concealment.
- The native read path takes Ogg Opus (pre-skip and end trimming per
RFC 7845), and Opus in Matroska/WebM and MP4, where packet starts
after a seek come from the packets' own lengths.
The RFC 8251 conformance test runs all 36 vectors.
`scripts/fetch-opus-vectors.sh` downloads them (SHA-256 checked) and
the CI test job caches them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 1 (#445)
- CELT concealment keeps its pitch in its own field. It reused postfilter_period_old, which the
next good frame's post-filter cross-fades from.
- Matroska Opus seeks: a failed packet read no longer caches a wrong start; a one-byte packet
is read as one byte.
- A malformed OpusHead in Ogg is reported as malformed instead of falling back.
- The libopus oracle tests skip through the testkit, so CRAFT_MEDIA_REQUIRE_ORACLES fails them.
- Decoder::reset documents that it re-arms pre-skip trimming.
- fetch-opus-vectors.sh retries its size probe.
- CI: the vectors are fetched after ffmpeg is installed, and apt's timeout runs as root so a
stalled apt-get is killed and frees the dpkg lock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 2 (#445)
- CELT: the noise-based concealment sets skip_plc, as libopus does, so
pitch-based concealment waits for two good frames in a row instead of
extending history that holds noise.
- SILK: decode_only_middle is per call, as in libopus. A lost or mono
frame no longer reuses the mid-only flag from an earlier frame.
- A damaged Opus packet is concealed at its own length (from its TOC and
framing) rather than the previous packet's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
* craft-audio: AAC decoder
FilmCraft's `filmcraft-aac` decoder as `craft_audio::aac` behind
`aac-decode`, kept over symphonia's: the same quality (about 135 dB
SNR against ffmpeg), and it decodes 5.1, which symphonia refuses.
HE-AAC decodes as its AAC-LC core, upsampled 2x to the stream's rate.
- `TrackDecoder` decodes AAC, including ADTS-framed packets without
extradata (MPEG-TS, raw `.aac`).
- The native read path takes AAC in MP4 and Matroska and raw ADTS
streams (frame sync over the file, with resync after damage).
- AAC errors convert into `AudioError`.
The AAC module's shared parts (Huffman, MDCT, TNS, ICS) arrive whole
as ported; their encoder half is unused until the encoder PR.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 1 (#443)
Raw ADTS and TrackDecoder:
- Channel configuration 0 (a PCE in the stream, as ffmpeg's -aac_pce and
AudioToolbox's 7.x write) decodes; it was refused.
- Frames with several raw data blocks are refused; only their first block
was decoded, silently losing the rest.
- A frame that changes the sample rate, profile or channel configuration is
concealed; it played at the wrong speed.
- A source that fails during the frame scan is AudioError::Io; it looked
like the end of the file.
- A stream after an ID3 tag longer than the file head is found.
- conceal counts all the ADTS frames of a packet, and a packet of more than
1024 frames is refused before it is decoded into memory.
AAC decoder:
- Indexed channel configurations place elements by arrival order, not tag,
as ffmpeg does; tagged differently, channels were swapped.
- A PCE in an access unit that fails no longer changes the layout, which
left every later packet failing.
- Non-finite samples are zeroed before the output and the overlap.
MP4: probe and decode agree: the samples' durations end the track when no
single edit does, and an edit cannot outlast the media.
The routing oracle fails on a fixture ffmpeg cannot write when oracles are
required. The README's Limits names HE-AAC v2 as mono, multi-block ADTS,
iTunSMPB, Matroska's tick precision and AAC in CAF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 2 (#443)
Corrects round 1:
- A multi-block ADTS frame after the stream has started is concealed at its
whole length; round 1's filter skipped it, shifting the audio after it.
- Sample durations only trim an MP4's last packet: wrong ones (some count a
HE-AAC packet's core frames) were cutting audio. Durations of 0 with an edit
use the edit.
Raw ADTS packets now keep their headers, so TrackDecoder alone handles format
changes, multi-block frames and concealment, for apps as for files, and nested
ADTS payloads are not split again. Headers come from the frame scan's reads,
not one read per frame. The stream's format is the one most of its first frames
share, so one damaged header does not silence the file. The ID3 walk and a
packet's ADTS split are bounded.
AAC:
- HE-AAC is delayed by the SBR filterbanks' 481 core samples; it played 19.5 ms
early against ffmpeg in every container.
- A failed access unit leaves the decoder's state as it was, so the next packet
decodes as if it had not been there.
- reset restarts the noise generator, so a seek into PNS repeats.
- A signalled rate that is not twice the core's plays the core; it reported the
signalled rate with the core's samples.
The decoder oracle's tolerance tightens from 1e-3 to 1e-5. Tests cover HE-AAC.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 3 (#443)
Corrects round 2:
- ADTS headers are compared with the stream's format only when the first header
gave it. With extradata the format comes from there, so ADTS in MP4 whose
headers say something else decodes again; every packet had been concealed.
- A stream's only program config element is found in its first frames even when
that frame's header is damaged.
- The test of wrong sample durations patched the second run's count, not the
delta, so "durations of one" changed nothing. It now patches the delta, and
checks a file was changed and that seeks land on the same audio.
MP4 seeks place a packet by its index when every packet decodes to the same
length, as decoding does; they used the durations round 2 had stopped trusting
for the length, and returned the wrong audio. A last delta of 0 is overridden too.
Docs: what TrackInfo says for raw ADTS, that opening one reads the whole file, and
which codecs symphonia reads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 4 (#443)
- TrackDecoder takes an ADTS stream's format from the one most of the lookahead
shares, as the file path did, so one damaged first header does not fail every
packet of an MPEG-TS stream. open_adts now just asks it.
- MP4 samples of several ADTS frames keep the sample table's timing; a packet
count of one frame each truncated them silently.
- Sample durations past what the packets decode to no longer over-report the
length.
- split_adts_upto is not behind aac-decode, as split_adts is not: an aac-encode
build in the next PR did not compile.
Removes an unreachable ID_END check and open_adts's unused core rate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 5 (#443)
No change in decoding. An aac-encode build in the next PR needs the
split_adts_upto re-export behind aac-decode, and ID_END only in tests.
Docs say the lookahead's headers give an ADTS stream's configuration, that AAC in
CAF still goes through symphonia, and that ProgramConfig is not #[non_exhaustive].
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 6 (#443)
Two tests from round 1 failed on ffmpeg before 9, including CI's 8.0. Neither was
a decoding fault:
- ffmpeg 9 ends an MP4 with no edit list at its sample durations and earlier
versions present the whole last packet, so the length is checked against the
file's known duration, not ffmpeg's decode.
- The order ffmpeg gives channels with -aac_pce differs by version, so each decoded
channel must match a different one of ffmpeg's, whatever the order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 7 (#443)
Reading:
- an element of another type or a tag a program config does not list is
refused, not written to another channel; only an LFE coded as an SCE
is still allowed, as ffmpeg allows it
- an explicit sampling rate of 0 is refused
- a packet's later ADTS frames are searched for SBR
- a multi-frame ADTS packet that fails leaves the decoder as it was, and
one whose frames change the channel count is an error
- the whole ADTS frames before a damaged end of a packet decode
- a failed read of a raw stream's first frames is an I/O error
- an MP4 sample of several ADTS frames past the lookahead keeps its length
- an Ogg file with leading bytes keeps its track order and mask comments
Writing:
- ProgramConfig::write, AudioSpecificConfig::to_bytes and
AdtsHeader::write refuse what their fields cannot hold
Tests:
- the routing test checks one alignment for the ffmpeg version under test
- a default decode past an Ogg stream symphonia has no track for
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 8 (#443)
From wanikhawar's review:
- AudioSpecificConfig::parse reads backward-compatible SBR and PS signalling
(a 0x2b7 sync extension after the config), which craft-media's Matroska
demuxer writes for legacy HE-AAC IDs. Without lookahead those streams decoded
as their core.
- An MP4 media edit after one empty edit is applied: its priming skip and its
end. The empty edit's delay is not added as silence.
- A packet lost whole is concealed as long as the last one decoded, so a stream
of multi-frame ADTS packets keeps its timing.
- ADTS-framed MP4 samples keep their sample table's timing, instead of every
sample being read at open to look for several frames in one.
- AdtsHeader::write refuses an object type, sampling frequency index or channel
configuration its fields cannot hold; it masked them into other values.
- AudioSpecificConfig::lc no longer panics for a sampling frequency index with no
rate, and the decoder refuses such a config.
- AudioSpecificConfig::to_bytes refuses channel configuration 0 without a program
config element, which would not parse back.
- Upsample2x starts each channel's history afresh when the channel count
changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 9 (#443)
From CodeRabbit's review of round 8:
- AudioSpecificConfig::parse consumes extensionFlag3 after the config when
extensionFlag is set, so the 0x2b7 sync extension after it is found.
- AudioSpecificConfig::to_bytes refuses a sampling rate of 0, which would not
parse back.
- ADTS-framed HE-AAC in MP4 whose sample durations count core frames is not cut
to the durations' half length: durations short of one frame per sample no
longer end an ADTS-framed track, and its length is unknown until decoded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 10 (#443)
ADTS-framed AAC in MP4: durations short of the frames in the samples read
ahead no longer end the track. With HE-AAC samples of two ADTS frames each,
timed as two core frames, the durations matched one output frame per sample,
so the undercount check passed and the track stopped at half its length,
which probe reported as known. The frames each read-ahead sample holds are
now counted, and durations short of them leave the length unknown until
decoded, as durations short of one frame per sample already did. Correctly
timed AAC-LC samples of several frames keep their end trim.
Reported by FredApps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 11 (#443)
Corrects round 10. A short ADTS-framed MP4 track, whose last sample is in
the read-ahead, lost that sample's trim when it cut more than one frame:
the trim counted as durations falling short, so the length was left to
decoding and its padding was presented. The track's last sample is now left
out of the read-ahead comparison.
Reported by CodeRabbit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: document the ADTS-in-MP4 seek and edit limits (#443)
AAC in MP4 stored as ADTS frames, with sample durations that count fewer
frames than the samples hold, decodes in full from the start, but seeks
place samples by those durations and land later than asked, and an edit
list measured in them can end the track early. The README's Limits section
now says so, and the seek example points to it.
Reported by FredApps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: say why the ADTS-in-MP4 seek limit is kept (#443)
The Limits entry from 79f5839 said what goes wrong but not why it is left.
It now says that such a file is outside the MP4 specification: an MP4 AAC
sample is one raw access unit with the configuration in the sample
description (ISO/IEC 14496-14, 14496-3), so ADTS headers do not belong in
it, and a sample's duration is the time it decodes to (ISO/IEC 14496-12).
It says why files within the specification are not affected, what still
works, and why seeks and edits in such files are not corrected: finding
where each sample's audio starts means counting the ADTS frames in every
sample before it, one read per sample, which would slow every ADTS-framed
MP4 at open to work around files outside the specification.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: AAC encoder
FilmCraft's AAC-LC encoder behind `aac-encode`: psychoacoustic model,
window switching, M/S, TNS, bit reservoir, CBR and VBR, 1-8 channels,
optional ADTS framing. `rayon` encodes channels in parallel.
`aac::aac_order` maps WAV order to the encoder's input order. Settings
the encoder cannot use are `aac::Error::InvalidSettings`, which
converts to `AudioError::Encode`. `encode` now turns on `aac-encode`.
Tests that need AAC streams written by our own encoder come with it:
the encoder oracle and robustness tests, packet concealment, and
`TrackDecoder` on ADTS without extradata.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 1 (#444)
From CodeRabbit's first review of the AAC encoder:
- CBR bitrate limits use the exact bits per frame (bitrate * 1024
against channels * sample_rate) instead of the truncated quotient, so
288001 b/s at 48 kHz mono is rejected.
- Rates below 48 bits per channel per frame are rejected. That is the
largest silent frame per channel measured across 1-8 channels; below
it the encoder overshot the requested rate (96 kHz mono at 1000 b/s
came out near 4500 b/s).
- Fill elements are capped so the byte-aligned access unit stays within
6144 bits per channel. write_fill can write 8 bits over its request,
which gave 769-byte mono units at the maximum rate and when the
reservoir was flushed on the last frame.
- encode() buffers one frame of input at a time. Appending the whole
slice and draining 1024 samples per frame copied the rest of the
input once per frame, quadratic in the slice length. Output is
byte-identical to feeding the input in chunks.
- README: 8 channels are taken in channel configuration 7's element
order; a WAV 7.1 buffer has no equivalent there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 2 (#444)
From CodeRabbit's review of round 1:
- The CBR minimum adds the program config's bits when ADTS is on. The
7-channel layout repeats it in every ADTS frame, and at full
bandwidth a silent frame took 344 bits against the 336 bits that
31,500 b/s gives at 96 kHz. Bandwidth alone stays within 48 bits
per channel.
- write_fill never writes more than it is asked for: each element's
size comes from the bits left, so the total ends 0-6 bits short.
The old version could pass the request by 8 bits and could also
stop early (asked for 14, it wrote 7).
- The encoder fills to the unit's target end, capped at
max_frame_bits, instead of keeping round 1's 8-bit margin. Frames at
the maximum rate were 767 bytes when there was little to code; they
are now exactly 768 bytes per channel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 3 (#444)
Encoder::new rejects a NaN VBR quality, the known issue ported from
FilmCraft and listed in the PR description.
NaN passes f32::clamp, so the rate-distortion multiplier became NaN.
The frame-size loop compares it with `<` and never ran, and no band
was skipped, so every frame was coded at full precision: mono units
reached 2465 bytes against the 768-byte limit, and 8-channel units of
up to 17024 bytes did not fit the ADTS length field, so they went out
without headers. Infinities and other out-of-range values are clamped
and stay within the limit; the test checks both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 4 (#444)
From CodeRabbit's review of round 3:
- The CBR fill target is rounded down to a whole byte. Round 2 filled
to an unaligned target, so after byte alignment a unit could end up
to 7 bits past the bits it had: in a sweep of silent CBR encodes,
1188 of 13200 frames overspent, all of them frames with fill, hidden
by the level clamp at 0. The unit now ends exactly at the rounded
target. The 0-7 bits rounded off stay in the reservoir and are left
unspent after the last frame; the comment says so instead of
claiming an exact total.
- The NaN-quality test checks that infinities and out-of-range values
encode exactly as the nearest end of the 0.5-6.0 range.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Limits entry from 79f5839 said what goes wrong but not why it is left.
It now says that such a file is outside the MP4 specification: an MP4 AAC
sample is one raw access unit with the configuration in the sample
description (ISO/IEC 14496-14, 14496-3), so ADTS headers do not belong in
it, and a sample's duration is the time it decodes to (ISO/IEC 14496-12).
It says why files within the specification are not affected, what still
works, and why seeks and edits in such files are not corrected: finding
where each sample's audio starts means counting the ADTS frames in every
sample before it, one read per sample, which would slow every ADTS-framed
MP4 at open to work around files outside the specification.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AAC in MP4 stored as ADTS frames, with sample durations that count fewer
frames than the samples hold, decodes in full from the start, but seeks
place samples by those durations and land later than asked, and an edit
list measured in them can end the track early. The README's Limits section
now says so, and the seek example points to it.
Reported by FredApps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Corrects round 10. A short ADTS-framed MP4 track, whose last sample is in
the read-ahead, lost that sample's trim when it cut more than one frame:
the trim counted as durations falling short, so the length was left to
decoding and its padding was presented. The track's last sample is now left
out of the read-ahead comparison.
Reported by CodeRabbit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ADTS-framed AAC in MP4: durations short of the frames in the samples read
ahead no longer end the track. With HE-AAC samples of two ADTS frames each,
timed as two core frames, the durations matched one output frame per sample,
so the undercount check passed and the track stopped at half its length,
which probe reported as known. The frames each read-ahead sample holds are
now counted, and durations short of them leave the length unknown until
decoded, as durations short of one frame per sample already did. Correctly
timed AAC-LC samples of several frames keep their end trim.
Reported by FredApps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From CodeRabbit's review of round 8:
- AudioSpecificConfig::parse consumes extensionFlag3 after the config when
extensionFlag is set, so the 0x2b7 sync extension after it is found.
- AudioSpecificConfig::to_bytes refuses a sampling rate of 0, which would not
parse back.
- ADTS-framed HE-AAC in MP4 whose sample durations count core frames is not cut
to the durations' half length: durations short of one frame per sample no
longer end an ADTS-framed track, and its length is unknown until decoded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From wanikhawar's review:
- AudioSpecificConfig::parse reads backward-compatible SBR and PS signalling
(a 0x2b7 sync extension after the config), which craft-media's Matroska
demuxer writes for legacy HE-AAC IDs. Without lookahead those streams decoded
as their core.
- An MP4 media edit after one empty edit is applied: its priming skip and its
end. The empty edit's delay is not added as silence.
- A packet lost whole is concealed as long as the last one decoded, so a stream
of multi-frame ADTS packets keeps its timing.
- ADTS-framed MP4 samples keep their sample table's timing, instead of every
sample being read at open to look for several frames in one.
- AdtsHeader::write refuses an object type, sampling frequency index or channel
configuration its fields cannot hold; it masked them into other values.
- AudioSpecificConfig::lc no longer panics for a sampling frequency index with no
rate, and the decoder refuses such a config.
- AudioSpecificConfig::to_bytes refuses channel configuration 0 without a program
config element, which would not parse back.
- Upsample2x starts each channel's history afresh when the channel count
changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reading:
- an element of another type or a tag a program config does not list is
refused, not written to another channel; only an LFE coded as an SCE
is still allowed, as ffmpeg allows it
- an explicit sampling rate of 0 is refused
- a packet's later ADTS frames are searched for SBR
- a multi-frame ADTS packet that fails leaves the decoder as it was, and
one whose frames change the channel count is an error
- the whole ADTS frames before a damaged end of a packet decode
- a failed read of a raw stream's first frames is an I/O error
- an MP4 sample of several ADTS frames past the lookahead keeps its length
- an Ogg file with leading bytes keeps its track order and mask comments
Writing:
- ProgramConfig::write, AudioSpecificConfig::to_bytes and
AdtsHeader::write refuse what their fields cannot hold
Tests:
- the routing test checks one alignment for the ffmpeg version under test
- a default decode past an Ogg stream symphonia has no track for
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two tests from round 1 failed on ffmpeg before 9, including CI's 8.0. Neither was
a decoding fault:
- ffmpeg 9 ends an MP4 with no edit list at its sample durations and earlier
versions present the whole last packet, so the length is checked against the
file's known duration, not ffmpeg's decode.
- The order ffmpeg gives channels with -aac_pce differs by version, so each decoded
channel must match a different one of ffmpeg's, whatever the order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No change in decoding. An aac-encode build in the next PR needs the
split_adts_upto re-export behind aac-decode, and ID_END only in tests.
Docs say the lookahead's headers give an ADTS stream's configuration, that AAC in
CAF still goes through symphonia, and that ProgramConfig is not #[non_exhaustive].
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- TrackDecoder takes an ADTS stream's format from the one most of the lookahead
shares, as the file path did, so one damaged first header does not fail every
packet of an MPEG-TS stream. open_adts now just asks it.
- MP4 samples of several ADTS frames keep the sample table's timing; a packet
count of one frame each truncated them silently.
- Sample durations past what the packets decode to no longer over-report the
length.
- split_adts_upto is not behind aac-decode, as split_adts is not: an aac-encode
build in the next PR did not compile.
Removes an unreachable ID_END check and open_adts's unused core rate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Corrects round 2:
- ADTS headers are compared with the stream's format only when the first header
gave it. With extradata the format comes from there, so ADTS in MP4 whose
headers say something else decodes again; every packet had been concealed.
- A stream's only program config element is found in its first frames even when
that frame's header is damaged.
- The test of wrong sample durations patched the second run's count, not the
delta, so "durations of one" changed nothing. It now patches the delta, and
checks a file was changed and that seeks land on the same audio.
MP4 seeks place a packet by its index when every packet decodes to the same
length, as decoding does; they used the durations round 2 had stopped trusting
for the length, and returned the wrong audio. A last delta of 0 is overridden too.
Docs: what TrackInfo says for raw ADTS, that opening one reads the whole file, and
which codecs symphonia reads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Corrects round 1:
- A multi-block ADTS frame after the stream has started is concealed at its
whole length; round 1's filter skipped it, shifting the audio after it.
- Sample durations only trim an MP4's last packet: wrong ones (some count a
HE-AAC packet's core frames) were cutting audio. Durations of 0 with an edit
use the edit.
Raw ADTS packets now keep their headers, so TrackDecoder alone handles format
changes, multi-block frames and concealment, for apps as for files, and nested
ADTS payloads are not split again. Headers come from the frame scan's reads,
not one read per frame. The stream's format is the one most of its first frames
share, so one damaged header does not silence the file. The ID3 walk and a
packet's ADTS split are bounded.
AAC:
- HE-AAC is delayed by the SBR filterbanks' 481 core samples; it played 19.5 ms
early against ffmpeg in every container.
- A failed access unit leaves the decoder's state as it was, so the next packet
decodes as if it had not been there.
- reset restarts the noise generator, so a seek into PNS repeats.
- A signalled rate that is not twice the core's plays the core; it reported the
signalled rate with the core's samples.
The decoder oracle's tolerance tightens from 1e-3 to 1e-5. Tests cover HE-AAC.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Raw ADTS and TrackDecoder:
- Channel configuration 0 (a PCE in the stream, as ffmpeg's -aac_pce and
AudioToolbox's 7.x write) decodes; it was refused.
- Frames with several raw data blocks are refused; only their first block
was decoded, silently losing the rest.
- A frame that changes the sample rate, profile or channel configuration is
concealed; it played at the wrong speed.
- A source that fails during the frame scan is AudioError::Io; it looked
like the end of the file.
- A stream after an ID3 tag longer than the file head is found.
- conceal counts all the ADTS frames of a packet, and a packet of more than
1024 frames is refused before it is decoded into memory.
AAC decoder:
- Indexed channel configurations place elements by arrival order, not tag,
as ffmpeg does; tagged differently, channels were swapped.
- A PCE in an access unit that fails no longer changes the layout, which
left every later packet failing.
- Non-finite samples are zeroed before the output and the overlap.
MP4: probe and decode agree: the samples' durations end the track when no
single edit does, and an edit cannot outlast the media.
The routing oracle fails on a fixture ffmpeg cannot write when oracles are
required. The README's Limits names HE-AAC v2 as mono, multi-block ADTS,
iTunSMPB, Matroska's tick precision and AAC in CAF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FilmCraft's `filmcraft-aac` decoder as `craft_audio::aac` behind
`aac-decode`, kept over symphonia's: the same quality (about 135 dB
SNR against ffmpeg), and it decodes 5.1, which symphonia refuses.
HE-AAC decodes as its AAC-LC core, upsampled 2x to the stream's rate.
- `TrackDecoder` decodes AAC, including ADTS-framed packets without
extradata (MPEG-TS, raw `.aac`).
- The native read path takes AAC in MP4 and Matroska and raw ADTS
streams (frame sync over the file, with resync after damage).
- AAC errors convert into `AudioError`.
The AAC module's shared parts (Huffman, MDCT, TNS, ICS) arrive whole
as ported; their encoder half is unused until the encoder PR.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Ogg track tests set their files' serial numbers instead of writing
the files until ffmpeg happens to pick the wanted order. They rewrite
each page's serial and checksum, so both orders run with one ffmpeg call
and do not depend on ffmpeg's serials being random.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Ogg track tests no longer depend on chance. Each writes its file
until the serial numbers are in the order it asks for, in both orders,
so the reversed order that told the old code apart always runs. A file
this ffmpeg cannot write goes through `skip`, which fails the test when
oracles are required.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A track index in an Ogg file means the same stream everywhere: the
file's order, as `tracks` lists them. Streams the native path does not
read fell back to symphonia with the same index, but symphonia orders
tracks by serial, which ffmpeg picks at random. A file could decode the
wrong track, or fail on a FLAC stream listed before an Opus one.
The tests write each file eight times, so both serial orders occur, and
cover a track with no mask comment next to one with a mask.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each FLAC stream in an Ogg file reads its own channel mask comment. The
tags symphonia's reader collects do not say which track they are from,
so a track could get another track's mask. The masks now come from
craft-media's Ogg demuxer, by serial, which is symphonia's track ID.
The Ogg layout test also covers 7 channels with no comment and a zero
mask.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FLAC in Ogg reads its layout the way a FLAC file does: from the mask
comment, or else the one decoders assume for the count. Without this, a
6-channel stream with no comment reported back surrounds in Ogg and side
surrounds in a FLAC file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- FLAC files read their layout from the channel mask comment, or else
the one decoders assume for the count, which is what the writer omits
the comment for
- an unbuilt format is Unsupported before the layout is checked
- FLAC encode no longer reserves half the PCM size up front
- flac-encode's lib tests stream a file and compare it with encode
- the seek test compares samples after the seek
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No change in output: the 74 oracle test streams are byte-identical.
- The Wasted case is handled in one function, without the unreachable arm
and the wrapper round 8 added.
- The bitstream writer uses predict::fold, the tested copy, not its own.
- STREAMINFO's MD5 may be 0 (RFC 9639 §8.2), so the comment no longer says
the RFC requires it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests only; each was confirmed to fail on a mutated encoder.
- Every sample rate code in the frame header table.
- Signals that reach the Rice parameter and LPC shift limits, where a
value past the limit gives a file decoders refuse.
- ENCODE_EXTENSIONS lists flac.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refuse a linear predictor whose prediction leaves the i32 range. Such a
stream is valid, but symphonia adds the prediction to the residual as an
i32, which panics in a debug build. The round 8 residual limit made this
reachable at 24 bits; without the override, 13 of 240 clipped 32-bit test
streams failed in a debug build and none do now, for 0.09% more bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Code wasted bits (RFC 9639 §9.2.2): low bits that are zero in every
sample of a subframe were stored anyway, so 16-bit audio exported at 24
or 32 bits grew by the padding.
- Allow residuals up to the 32-bit limit (§9.2.7.3). The limit was half
that, so at 32 bits a transient forced its whole block to verbatim.
- README: 32-bit FLAC codes stereo channels independently.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Test the high bits of STREAMINFO's total sample count, and that a total
past 36 bits is left unknown.
- README limits: the usual 5- and 6-channel layouts depend on the mask
comment too; say what is outside the streamable subset.
- Comments: 32-bit code 0 does not decode everywhere and leaves the
subset; the oracle test helper is in craft_media_testkit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests only, plus docs; the encoder does not change. Each new check was
confirmed to fail on a mutated encoder.
- Tests: frame numbers count up, LPC and Rice partitions and stereo
decorrelation save the space they should, FileEncoder records a FLAC
layout, the smallest frame anywhere reaches STREAMINFO, Writer::write
applies the frame limit, finish leaves the sink at the end, the exact
655350 Hz cap, the sample size codes.
- README limits: a FLAC layout decoders would not assume depends on its
mask comment, and ffmpeg and flac ignore a zero mask.
- The encode example's comment named no referent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- libFLAC and ffmpeg read a FLAC with 5 or 6 channels and no mask comment
as having side surrounds, so the comment is now written for anything
else, including the crate's usual back-surround layouts. The round 3
check treated back surrounds as FLAC's assignment, which lost them.
- Tests: the mask comment rules, opposite-polarity stereo, STREAMINFO
block and frame sizes.
- A stale comment said only WAV records the layout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PICTURE metadata test inserts its block without the last-block flag:
the encoder's VORBIS_COMMENT follows it, and the flagged version was a
broken metadata chain that only decoded by resyncing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FLAC files record the speaker layout when FLAC's channel assignment
does not already say it, as a WAVEFORMATEXTENSIBLE_CHANNEL_MASK comment.
FLAC's 7-channel order is 6.1 with side surrounds, so a buffer in the
crate's usual 7-channel layout (backs and a back centre) was decoded by
ffmpeg and libFLAC with three channels on the wrong speakers; explicit
layouts such as 5.1 with side surrounds were dropped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- check_room docs: FLAC holds at most 2^36 - 1 frames.
- The FLAC round trip covers every channel count from 1 to 8.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- FileEncoder/encode for FLAC hand the encoder one 4096-frame block at a
time: a whole-buffer encode drained the pending samples quadratically
(480 s of stereo took 19 s, now 5 s) and copied the whole input twice.
- Frame headers name sample rates outside the table (codes 12-14), so
streams at rates like 11025 or 37800 Hz stay in the streamable subset.
- The decode doctest needs symphonia to read FLAC at this point.
- README: FLAC output is verified against ffmpeg, not the flac tool.
- Tests: keep the AIFF force-RF64 check; exact 2^36 frame boundary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FilmCraft's FLAC encoder behind `flac-encode`, kept over SoundCraft's:
linear prediction, stereo decorrelation and the STREAMINFO MD5 give
0.1-37% smaller files on 12 test signals, and the output is bit-exact
through `flac` and ffmpeg. It is 3-5x slower than SoundCraft's, still
about 56x real time.
`lossless-encode` is now `pcm-encode` plus `flac-encode`, and
`EncodeFormat::Flac` writes it through `encode` and `FileEncoder`.
With only one of the two features, the other's formats are refused as
`Unsupported`.
The tests that use FLAC files as decode fixtures (truncation,
corruption, metadata bounds, zero-frame files, container preflight)
come with it, as do the FLAC oracle tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: core types, WAV/AIFF read and write, peaks, symphonia
The new crate without any codec module: what every later codec plugs
into.
- Shared types: `AudioBuffer`, `AudioInfo`, `ChannelLayout`/`Speaker`,
`AudioError`, `EncodeOptions` (built with `new` and `with_*`),
`EncodeFormat`, `BitDepth`, `BwfInfo`, `SampleFormat`.
- WAV/BWF/RF64 and AIFF/AIFF-C reading and writing from SoundCraft's
`soundcraft-audio-io`, kept over EffectCraft's and FilmCraft's
smaller writers (only it has BWF, dither, any speaker mask and
streaming without a known length). `encode` and `FileEncoder` behind
`pcm-encode`; `lossless-encode` and `encode` turn it on.
- `decode`, `probe`, `tracks` and the streaming, seekable `Decoder`
(feature `decode-native`). The native path reads WAV, AIFF, and PCM in
MP4/MOV and Matroska through craft-media's demuxers, packet by packet,
with edit lists and `CodecDelay`/`DiscardPadding` applied.
`TrackDecoder` decodes PCM; each codec PR adds its codec to it.
- symphonia 0.6 (MPL-2.0, used unmodified) behind the `symphonia`
feature, part of `decode`: the fallback for every format with no
native decoder yet. Its size fields are checked before it reads, and
its panics are caught where they unwind.
- `peaks`: the min/max/RMS pyramid for waveform drawing.
Workspace: craft-media is a workspace dependency without default
features, so an encode-only craft-audio does not build the MP4
demuxer, and `symphonia-bundle-flac` builds without overflow checks in
dev profiles (its 32-bit LPC restore wraps on valid input).
CI: `audio.yml`, with clippy per feature, rustdoc, WASM, MSRV, and the
tests with ffmpeg oracles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes (CodeRabbit on #441)
- Reading: AIFF that declares frames without an SSND chunk is Malformed; a Matroska PCM track's
length counts from its first frame; seek targets saturate; an empty FLAC has one track; a
failed header read is an Io error; deinterleave allocates fallibly; concealed silence is capped
like a decoded packet, and symphonia's uses the damaged packet's own duration when known.
- Peaks sum squares in f64, so loud float samples give a finite RMS.
- Writing: .aifc maps to AIFF; BWF text is cut at a character boundary; a WAV byte rate past
32 bits is an error; the FileEncoder docs say nothing may follow the sink's position.
- README: the Limits list says which codecs go through symphonia and which MP4 edit lists apply.
- Tests for each, alias and dither coverage, and a FLAC case in the routing oracle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, second round (CodeRabbit on #441)
- Every header read is checked: `Native::open`, `native::tracks` and the no-symphonia error read
it with `head_checked`. craft-media demuxer errors that are I/O failures (not a file ending
early) are returned as `AudioError::Io` instead of sending the file to the other path.
- A Matroska track's presented length runs from its first frame plus `CodecDelay` (unclamped,
in timestamp ticks with the delay rounded to one) to its end.
- symphonia's concealment allocates fallibly, and a damaged first packet is concealed when the
demuxer gives its duration. `deinterleave` reserves its channel list fallibly too.
- `TrackDecoder::conceal` documents its cap at the decode limit.
- Tests: an I/O failure past the first 64 KiB, codec delay with the first block at -20, 0 and
50 ms (checked with `probe`), a seek back after an overshoot, and the loud-sample RMS value.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: TrackDecoder::conceal returns a Result
Concealment allocates fallibly and fails with TooLarge when the silence would pass the 2^30-sample
decode limit, instead of returning less than the packet's length. The native path, the README,
the packets example and the doc example pass the error on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, third round (CodeRabbit on #441)
- Concealed silence is held to the decode limit inside pcm::silence, so symphonia's path checks
frames × channels too, and symphonia's decoded audio is copied into fallibly allocated planes.
- TrackDecoder::reset forgets the last packet's length.
- Without symphonia, a damaged MP4, Matroska or Ogg file (truncated, invalid, no moov) is
Malformed instead of Unsupported; a feature the demuxer does not read still names the build.
- Tests: an MP4 with no moov in a native-only build, conceal after a reset and past the limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, fourth round (CodeRabbit on #441)
- A file the native demuxers find damaged is Malformed with symphonia too, when symphonia calls
it unsupported; before, an ftyp-only MP4 was Unsupported in symphonia builds.
- symphonia's decoded blocks are held to the 2^30-sample decode limit before allocating.
- Symph::seek forgets the last packet's length, so a lost packet after a seek is not concealed
with the length of one before it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, fifth round (CodeRabbit on #441)
- The damaged-file test covers Matroska as well as MP4, and fails when either branch of
native::damage is removed.
- native::damage leaves out Ogg: ogg::open reports no damage (it skips damaged pages and calls a
file with none it can read not Ogg), and symphonia reports damaged Ogg as malformed itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: run the ffmpeg oracle tests on ubuntu-26.04
ubuntu-latest's apt ships ffmpeg 6.1.1, which writes MP2 and E-AC-3 in
Matroska with ends a few milliseconds early, so the routing tests on the
later codec PRs cannot pass on it. ubuntu-26.04 ships ffmpeg 8.0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>