# AC-3 decoded nothing from streams other encoders wrote - #200
Merged
Conversation
A libavcodec-encoded stream came back as zero samples, or a fraction of one frame: the reader desynchronised inside the first audio block and every frame after it was dropped. Our own encoder never sets the block-reuse, coupling, rematrixing or delta-allocation fields, so none of those paths had ever run. Root causes, all against ATSC A/52:2012: - Exponents (7.1.3): the absolute exponent covers one bin, not a whole D25/D45 group, and the coupling channel's reference exponent is doubled and occupies no bin at all. - Audio block syntax (Table 5.3): a phase-flag bit was read that the syntax does not have; the rematrixing band count was one short whenever coupling was on; the delta bit-allocation codes and their segment lists were read interleaved rather than as the two separate loops the table specifies. - Mantissas: the coupling channel's mantissas belong immediately after the first coupled channel, and the reconstruction of every later coupled channel was being cleared again right after it was written. - Bit allocation (7.2.2): the excitation function added the gain terms instead of subtracting them and had neither the band 0..6 bootstrap, the dbknee compensation, nor the coupling leak initialisation; delta allocation used the wrong offset encoding and gain mapping. - Tables: the hearing threshold, the log-add table and the transform window were approximations rather than Tables 7.15, 7.14 and 7.33. - Transform (7.9.4): the IMDCT ran at twice the intended frequency and dropped the factor of two the overlap-add step applies. Decoded PCM now comes out in the ITU/WAVE interleave order rather than the bit stream's own channel order, so multichannel output lands where consumers expect it; the encoder follows the corrected transform, exponent layout and input channel order. Verified against ffmpeg n9.0.1 over 35 streams (mono through 5.1, 32/44.1/48 kHz, 96-640 kbit/s, tones, noise, chirps, squares, transients, silence): all decode at full length, error rms is below one LSB except on coupled stereo, where what remains is the zero-bit dither A/52 7.3.4 leaves to the decoder.
Hawkynt
force-pushed
the
fix/ac3-sync
branch
from
September 6, 2026 15:44
1ab09b0 to
b2cc7ee
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A libavcodec AC-3 stream desynchronised inside the first audio block: 17 of 18 returned zero bytes, the 18th 3.1% coverage. The earlier diagnosis blamed the block-reuse paths — that was a symptom. The arithmetic core was wrong, and our encoder was wrong in the matching way, so the round trip passed.
Faults
exp[0] = absexpcovers one bin; the code filled a whole D25/D45 group with it, shifting the envelope by 1 or 3 bins. The coupling channel's reference exponent is also doubled and occupies no bin.phsflginubit was read that Table 5.3 does not have — one spurious bit per coupled block.deltbaecodes and their segment lists were read interleaved; Table 5.3 has two separate loops.fgain/sgaininstead of subtracting, and lacked the band 0–6 bootstrap, thedbkneecompensation and the coupling leak init (§7.2.2.4/5).latabwas truncated to 193 of 256 entries with0x01where the spec has0.Output is also now in ITU/WAVE channel order rather than the bit stream's own, derived empirically per layout.
Measured, against ffmpeg's decode of the same bytes
The coupled-stereo residual is the zero-bit dither §7.3.4 leaves to the implementation. Proved rather than assumed: disabling our dither drops the error by ≈√2, exactly what independent noise of equal power predicts.
The encoder shared the broken transform, so it moved too — ffmpeg now decodes its output at 57–74 dB SNR tonal, 60 dB mono, 53 dB 5.1.
Tests
Ac3ForeignStreamTestsembeds ffmpeg-produced streams and ffmpeg's decode of them, asserting sample by sample. Four original faults were re-introduced one at a time and each is caught: exponent expansion, coupling order, the phase-flag bit, the channel map. Two needed better fixtures before they would fail — true-stereo noise, and a mono tone at 1411 kbit/s that reaches the fine quantiser steps — and those fixtures are committed.64 passed, 0 failed for
~Ac3. Full suite on the source tree: 29030 passed, 0 failed, 1355 skipped.Ac3CodecTestshad a test pinning the exponent bug, andAudioRoutingOracleTestsasserted the decoder refuses ffmpeg AC-3; both now assert it decodes.Flagged, not fixed
E-AC-3 stereo now returns nothing where it previously returned full-length garbage — a behaviour change I caused.
Ac3EnhancedFrameDecoderfeeds AHThebappointers (> 15) straight intoAc3Mantissas.Next, which now throws, andRejectSilentDecodeturns that into an explicit refusal, so no silent silence escapes. E-AC-3 mono went the other way: 6.2% coverage of garbage to 100% coverage at peak 10 LSB.Ac3Codec.Encodestill claims to be a "managed adaptation of FFmpeg's LGPL ac3enc.c" without the vendoring records AGENTS.md requires. Worth a look.