diff --git a/crates/audio/README.md b/crates/audio/README.md index 5dd33f5f..dbca8c25 100644 --- a/crates/audio/README.md +++ b/crates/audio/README.md @@ -261,9 +261,12 @@ match craft_audio::decode(&bytes, None) { - AIFF with more than four channels decodes with an unordered layout, in the file's order. - A FLAC layout that decoders would not assume for the channel count depends on its - `WAVEFORMATEXTENSIBLE_CHANNEL_MASK` comment, and RFC 9639 §8.6.2 calls such a file not - streamable. This includes the usual 7-channel layout: without the comment, its back left, back - right and back centre are read as back centre, side left and side right. + `WAVEFORMATEXTENSIBLE_CHANNEL_MASK` comment. This includes the usual layouts for 5 and 6 channels: + without the comment, back surrounds are read as side surrounds. It includes the usual 7-channel + layout too: without the comment, its back left, back right and back centre are read as back + centre, side left and side right. +- Outside RFC 9639's streamable subset (§7), which some decoders require: FLAC at 32 bits, at a + sample rate the frame header cannot name, or with a layout that needs the mask comment. - `ChannelLayout::unordered` is written to FLAC as mask `0x0000`, as RFC 9639 §8.6.2 allows, but ffmpeg and `flac -d` ignore that mask and use their default layout for the channel count. - Not decoded at all: AC-3, E-AC-3, Opus and Speex. diff --git a/crates/audio/src/write/flac/encoder.rs b/crates/audio/src/write/flac/encoder.rs index 6ad7545d..c401c746 100644 --- a/crates/audio/src/write/flac/encoder.rs +++ b/crates/audio/src/write/flac/encoder.rs @@ -234,3 +234,20 @@ impl Encoder { data } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn streaminfo_holds_a_36_bit_total_and_leaves_a_larger_one_unknown() { + let enc = Encoder::new(EncoderConfig::new(48_000, 1, 16)).unwrap(); + let total = |t| { + let si = enc.streaminfo_with(0, 0, t, [0; 16]); + (u64::from(si[13] & 0x0F) << 32) | u64::from(u32::from_be_bytes([si[14], si[15], si[16], si[17]])) + }; + assert_eq!(total((1 << 36) - 1), (1 << 36) - 1); + assert_eq!(total((1 << 35) + 5), (1 << 35) + 5); + assert_eq!(total(1 << 36), 0); + } +} diff --git a/crates/audio/src/write/flac/frame.rs b/crates/audio/src/write/flac/frame.rs index cc644ddf..c8903836 100644 --- a/crates/audio/src/write/flac/frame.rs +++ b/crates/audio/src/write/flac/frame.rs @@ -59,7 +59,8 @@ fn sample_size_code(bps: u8) -> u8 { 20 => 5, 24 => 6, // 32 bits has code 7 since RFC 9639, which earlier decoders (symphonia 0.5, FilmCraft's own - // FLAC import) reject as reserved; 0 ("as STREAMINFO says") decodes everywhere + // FLAC import) reject as reserved; 0 ("as STREAMINFO says") decodes in those as well, but is + // outside the streamable subset (§7) _ => 0, } } diff --git a/crates/audio/src/write/flac/oracle_tests.rs b/crates/audio/src/write/flac/oracle_tests.rs index a258579f..f7ba676c 100644 --- a/crates/audio/src/write/flac/oracle_tests.rs +++ b/crates/audio/src/write/flac/oracle_tests.rs @@ -1,4 +1,4 @@ -//! ffmpeg as an external decoder oracle (skipped when absent, see `tests/common`): every stream must +//! ffmpeg as an external decoder oracle (skipped when absent, see `craft_media_testkit`): every stream must //! decode without an error to exactly the samples that went in, with the STREAMINFO MD5 matching //! ffmpeg's hash of the decoded audio. From FilmCraft's `filmcraft-flac` tests.