commit 29feeef5d399bb0613c8094f07d63a8e8cdeb8ec
parent a4d54b52ff25999a2e0a71a39b8fd2ec135a353c
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Sat, 19 Sep 2026 14:56:54 -0300
test: compare decodes without the padding that varies by ffmpeg
The reference disagreed with current ffmpeg by 767 trailing frames of
silence, and by no audio at all.
Diffstat:
1 file changed, 102 insertions(+), 5 deletions(-)
diff --git a/internal/test/audio_test.go b/internal/test/audio_test.go
@@ -57,7 +57,7 @@ func TestLoad(t *testing.T) {
if bytes.Equal(by, getUncompressed()) {
return
}
- if !equal(t, by, getUncompressed()) {
+ if !equal(t, by, getUncompressed(), f.Channels*f.BytesPerSample) {
t.Fatalf("native output does not match ffmpeg output")
}
}
@@ -84,7 +84,7 @@ func TestDecode(t *testing.T) {
if bytes.Equal(by, getUncompressed()) {
return
}
- if !equal(t, by, getUncompressed()) {
+ if !equal(t, by, getUncompressed(), f.Channels*f.BytesPerSample) {
t.Fatalf("native output does not match ffmpeg output")
}
}
@@ -151,16 +151,35 @@ func TestMemoryLeak(t *testing.T) {
// equal decodes the PCM samples and tests if they are "close enough"
// using a heuristic tolerance.
//
-// The two decoders must agree on length exactly. Sample values are
+// Silence at either end is trimmed first. How much padding surrounds the
+// audio is not a property of the audio: AAC carries encoder delay, and
+// how many priming and trailing frames survive the round trip differs
+// between decoder implementations and between ffmpeg releases. The
+// reference here was generated by one ffmpeg; a later one decodes the
+// same file to 767 fewer trailing frames of silence, and the audio
+// between the silent ends is unchanged. Comparing raw lengths turns that
+// into a failure and dates the fixture to whichever ffmpeg produced it.
+//
+// After trimming the two must agree on length exactly. Sample values are
// compared as signed integers by mean absolute difference, which must
// stay under one quantisation step (1 LSB). Different AAC decoders
// legitimately differ by rounding, so bit-exact output is not expected.
-func equal(t *testing.T, left, right []byte) bool {
+//
+// Trimming cannot hide a bad decode. Only frames that are entirely zero
+// are removed, so lost audio still reaches the comparison, and any
+// misalignment introduced would put the mean far above 1 rather than
+// passing quietly.
+func equal(t *testing.T, left, right []byte, align int) bool {
if len(left) == 0 || len(right) == 0 {
return false
}
+ left, right = trimSilence(left, align), trimSilence(right, align)
+ if len(left) == 0 && len(right) == 0 {
+ // Both sides decoded to nothing but silence, which is agreement.
+ return true
+ }
if len(left) != len(right) {
- t.Logf("length mismatch: native %d bytes, reference %d bytes", len(left), len(right))
+ t.Logf("length mismatch after trimming silence: native %d bytes, reference %d bytes", len(left), len(right))
return false
}
var (
@@ -189,6 +208,84 @@ func equal(t *testing.T, left, right []byte) bool {
return mean < 1.0
}
+// TestEqual guards the comparison itself. Trimming silence is what lets
+// the reference PCM outlive the ffmpeg that produced it, and the failure
+// mode of getting it wrong is a comparison that passes for good, so the
+// cases it must still reject are worth pinning down.
+func TestEqual(t *testing.T) {
+ reference := getUncompressed()
+ const align = 4 // the fixture is 16-bit stereo.
+
+ reject := func(name string, mutate func([]byte) []byte) {
+ t.Run(name, func(t *testing.T) {
+ if equal(t, mutate(append([]byte(nil), reference...)), reference, align) {
+ t.Errorf("expected the comparison to reject %s", name)
+ }
+ })
+ }
+
+ // Every sample louder by one step, which is the tolerance exactly.
+ reject("amplitude", func(b []byte) []byte {
+ for i := 0; i+1 < len(b); i += 2 {
+ v := int16(uint16(b[i]) | uint16(b[i+1])<<8)
+ if v < 32767 {
+ v++
+ }
+ b[i], b[i+1] = byte(v), byte(uint16(v)>>8)
+ }
+ return b
+ })
+
+ // Audio dropped from the middle, where no amount of trimming helps.
+ reject("truncated", func(b []byte) []byte {
+ mid := len(b) / 2
+ return append(b[:mid], b[mid+1000*align:]...)
+ })
+
+ // Shifted by a frame, so the channels land on each other.
+ reject("shifted", func(b []byte) []byte {
+ lead := 0
+ for lead+align <= len(b) && isZero(b[lead:lead+align]) {
+ lead += align
+ }
+ return append(b[:lead:lead], b[lead+align:]...)
+ })
+
+ t.Run("identity", func(t *testing.T) {
+ if !equal(t, append([]byte(nil), reference...), reference, align) {
+ t.Errorf("expected the comparison to accept identical input")
+ }
+ })
+}
+
+// trimSilence removes whole silent frames from both ends of the PCM.
+//
+// A frame at a time, rather than a byte at a time: the low byte of a
+// sample is zero whenever its value is a multiple of 256, so trimming
+// individual zero bytes would leave the buffer straddling a frame
+// boundary and compare the left channel against the right.
+func trimSilence(b []byte, align int) []byte {
+ if align <= 0 {
+ return b
+ }
+ for len(b) >= align && isZero(b[:align]) {
+ b = b[align:]
+ }
+ for len(b) >= align && isZero(b[len(b)-align:]) {
+ b = b[:len(b)-align]
+ }
+ return b
+}
+
+func isZero(b []byte) bool {
+ for _, c := range b {
+ if c != 0 {
+ return false
+ }
+ }
+ return true
+}
+
func abs(n int) int {
if n < 0 {
return -n