craft-audio: tests of the coded formats hold on ffmpeg 8 and without AAC

- ffmpeg 8 skips an AAC M4A's priming but plays the last packet's padding
  (ffmpeg 9 cuts it): the ffmpeg oracle test takes either length, and the
  README says which readers end the audio where.
- With alac-encode and no aac-encode, `m4a` names ALAC: the extension test
  compared it with AAC.

Refs #1230
This commit is contained in:
alehel committed 2026-11-14 08:31:05 +01:00
1 parent e5fa1ac2f9
commit 6f7c041a09
3 files changed
+11 -4

No files matched your search

+3
View File
@@ -468,6 +468,9 @@ match craft_audio::decode(&bytes, None) {
the length: the file decodes with 1057 (MP3) or 256 (AC-3, E-AC-3) frames of delay in front and
padded to whole frames at the end. MP3 gets no Xing / Info frame. `EncodeFormat::Aac` and `Alac`
(M4A) decode to the length written.
- An AAC M4A's length is in the edit list and the last packet's duration. This crate, Apple's
decoders and ffmpeg 9 end the audio there; ffmpeg 8 skips the priming but plays the last
packet's padding (under 1024 frames of near silence).
- `EncodeFormat::Aac` and `Alac` write the `moov` box after the audio (no fast start), and no
metadata. Raw ADTS AAC is not a file format of `encode`; `aac::EncoderConfig::with_adts` gives
the frames.
@@ -66,7 +66,9 @@ fn every_encode_format_has_an_extension_it_is_written_and_read_under() {
// A format whose feature is off has no name.
match craft_audio::encode_format_for(ext) {
Some(named) => {
assert!(named == format || (named, format) == (EncodeFormat::Aac, EncodeFormat::Alac), "{ext}");
let shares_m4a = |f| matches!(f, EncodeFormat::Aac | EncodeFormat::Alac);
assert!(named == format || (shares_m4a(named) && shares_m4a(format)), "{ext}");
assert!(named.is_built(), "{ext}");
assert!(craft_audio::ENCODE_EXTENSIONS.contains(&ext), "{ext}");
}
None => assert!(!matches!(format, EncodeFormat::Wav | EncodeFormat::Aiff | EncodeFormat::Flac), "{ext}"),
+5 -3
View File
@@ -418,8 +418,8 @@ fn ffmpeg_decode(ff: &std::path::Path, file: &[u8], name: &str, channels: usize)
planar
}
/// ffmpeg reads every format: the M4A files at the length written (it applies the edit list),
/// ALAC exactly, the raw streams behind their delay.
/// ffmpeg reads every format: the M4A files without their priming (it applies the edit list) and,
/// from ffmpeg 9, at the length written; ALAC exactly; the raw streams behind their delay.
#[test]
fn ffmpeg_reads_the_files() {
let ff = require_ffmpeg!();
@@ -428,7 +428,9 @@ fn ffmpeg_reads_the_files() {
let mut opts = EncodeOptions::new(EncodeFormat::Aac);
opts.layout = layout;
let out = ffmpeg_decode(&ff, &encode(&buf, &opts).unwrap(), &format!("aac_{channels}.m4a"), channels);
assert_eq!(out[0].len(), 20_000, "AAC, {channels} channels");
// The priming is skipped by every ffmpeg (the channels below line up). The padding of the
// last packet is cut by ffmpeg 9; ffmpeg 8 plays it, to the end of the 20th packet.
assert!([20_000, 20_480].contains(&out[0].len()), "AAC, {channels} channels: {} frames", out[0].len());
if channels <= 6 {
// ffmpeg also returns 5.1 in WAV order.
for (c, (want, got)) in buf.channels.iter().zip(&out).enumerate() {