Skip to content

6.32.0 - A mode is not an encoder

Latest

Choose a tag to compare

@superuser404notfound superuser404notfound released this 18 Aug 09:04

Drop-in from 6.31.0. No API change, but the default audio carriage changes for one class of source, which is why this is a minor: a bridged source of two channels or fewer is now delivered as FLAC instead of E-AC-3.

A mode is not an encoder

AE#395: a live MPEG-TS channel was silent on an AirPlay 2 speaker route, played over HDMI, and played through a different engine entirely. The PMT carried H.264 plus three audio tracks: MP2 stereo, AC-3 5.1, and a second MP2 stereo. "No sound on any of them, stereo included" read like an argument against a codec cause.

It was not, because a stereo source is not a stereo track by the time AVPlayer sees it. Rebuilt as a fixture and read back off the served init.mp4:

source track what reached AVPlayer
MP2 stereo 48 kHz ec-3, E-AC-3 stereo at 256 kbps
AC-3 5.1 ac-3, 6 channels, stream-copied

MP2 is not fMP4-legal, so it always went through AudioBridge, and the default audioBridgeMode of .surroundCompat chose its encoder from the mode alone. All three selectable tracks therefore arrived as a Dolby bitstream. The reporter's own A/B then split on exactly that line: on the affected receiver the stream-copied AC-3 5.1 played and the bridged E-AC-3 stereo did not.

.surroundCompat exists to carry SURROUND across a route that cannot take multichannel LPCM. A source of two channels or fewer has none to carry, so on such a source its encoder bought nothing and cost twice: 256 kbps lossy where the FLAC encoder in the same build is lossless, and a Dolby bitstream handed to every output route, including the ones that can only pass one through rather than decode it.

The encoder is now resolved from the mode AND the source's channel count:

AudioBridge.bridgeEncoder(for: .surroundCompat, sourceChannels: 2)  // AV_CODEC_ID_FLAC
AudioBridge.bridgeEncoder(for: .surroundCompat, sourceChannels: 6)  // AV_CODEC_ID_EAC3

Route-blind by construction: the input is the source, never the current output route. #34 measured the route-dependent version wrong (AVPlayer downmixes E-AC-3+JOC natively over A2DP) and it was removed then; this is a static property of the source, so a route change cannot make it disagree with itself mid-session.

What moves and what does not

Measured with aetherctl against the fixture:

source 6.31.0 6.32.0
MP2 stereo (bridged) ec-3, 2 ch, 256 kbps fLaC, 2 ch, VBR
5.1 PCM (bridged) ec-3, 6 ch, 768 kbps unchanged
AC-3 5.1 in MPEG-TS ac-3, stream-copy unchanged

The tapped loopback PCM is identical in level before and after (mean -21.1 dB either way), so this changes the carriage and not the content. .lossless is untouched, and so is every stream-copy path.

Three things follow the encoder now rather than the mode: the channel cap and the per-channel bitrate (maxEncodedChannels(for:) and encoderBitRate(for:) take an AVCodecID), the PCM intermediate format the resampler and FIFO run in, and the master playlist's CODECS attribute plus the pipeline label, which come from the encoder the bridge actually opened.

The #165 cascade is now an encoder cascade

#165 exists so that an FFmpeg build missing one bridge encoder falls through to the other instead of dropping to silent video-only. It was ordered over MODES, and a mode no longer names an encoder on its own: on a stereo source both modes now resolve to FLAC, so a mode list would have retried the absent encoder against itself and landed on exactly the silent fallback #165 removed.

AudioBridgeError.encoderNotFound now carries the codec id it looked for, and the retry is derived from that.

What this release does not do

It does not close AE#395. The report is still consistent with two causes, because "HDMI was fine" may itself have been a run that landed on the AC-3 track: which track av_find_best_stream picks on that PMT varies between sessions. One pair of runs separates them (same channel, stereo track explicitly selected, HDMI against AirPlay), and if the bridge rather than the route turns out to be the axis, 6.31.0's per-arm counters name the stage and this change does not help. AE#395 stays open for that measurement.

Reported by @Simpendaal.

Upgrading

.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "6.32.0")