Raspberry Pi 3 and 5.1 non-passthrough surround sound

So I’m trying to save space on my overburdened media disk by transcoding as much audio as possible from AC3 and DTS to Opus. For 2-channel sound this is trivial, or as trivial as ffmpeg incantations ever get, but the real space waste comes with DTS and 5-channel sound, so I’m particularly interested in converting that…

… and no matter what channel mapping I use, I can’t get all the channels to work at once! One channel is always missing (usually the centre channel, of course it would be the most important single channel of all). I expect figuring out channel mappings to be a complete nightmare: after all, Linux, DTS and AC3 all have different and incompatible mappings – but I don’t expect to lose a channel entirely!

My setup is simple enough: everything goes from a Raspberry Pi 3 running up-to-date OSMC (kernel 4.19.122-2-osmc) via HDMI to a Yamaha RX-V375 receiver and is then dispatched to perfectly ordinary wired speakers (video goes to a projector and works perfectly, refresh-rate-switching and all).

What does work: passthrough audio, 2-channel audio, 5.1 audio from other non-rasbpi-connected devices (like a bluray player, though that’s probably using passthrough again). From the Pi, I can emit sound on any 5 of the 6 speakers in the room by changing the ALSA channel mappings (and asking Kodi to emit through the ALSA device rather than ‘PI: HDMI’), but the centre channel is always missing. The wiring is (obviously) fine, and switching HDMI cables doesn’t help so it’s not that either.

This is obviously an ALSA problem because it fails with the same symptoms with MPD and other ALSA-using things as well, not just with Kodi: and it fails with Handbrake downmixing to Opus and also with ffmpeg transcoding existing audio via, say,

ffmpeg -i foo.mkv -acodec libopus -af aformat=channel_layouts=“5.1|stereo” -mapping_family 1 …

My current best guess ALSA config is:

pcm.!default {
type route;
slave.pcm “hw:0,1”;
slave.channels 6;
ttable {
0.0= 1;
1.1= 1;
2.2= 1;
3.5= 1;
4.4= 1;
5.3= 1;
}
}

ctl.!default {
type hw;
card 0;
}

but of course this doesn’t actually work because the centre channel is still missing, even though six channels are listed above and all are uniquely assigned.

Looking around the net, I find lots of people talking about this and lots of people saying oh yeah it works now, but zero people actually saying what the fix was. Does anyone know what the fix is? Is there a fix?

It shouldn’t be necessary to mess with alsa at all. What happens if you don’t include that route plugin? In theory, Kodi should be able to untangle the channels.

Are you sure the centre channel has made it to the opus re-encode? Can you play those re-encodes on another device (say, a PC with HDMI-out)?

My primary test case is actually not Opus encodes but DTS and AC-3 test files for surround-sound speaker setup, which generally send things through each speaker in turn, so I can get the channel order right for (non-passthrough) AC-3 and DTS before I add Opus to the mix. One example: https://www.demolandia.net/cinema/dolby-demo-trailers-hd.html. These work with passthrough and fail with non-passthrough. So the encoder can be ruled out as the cause of this.

Without the plugin, the channels in the above file are in the wrong order: the channel order of ALSA differs from the channel order used by basically everything else (SMPTE, “film order”, none of them are what ALSA uses). With no plugin, the right side channel comes out of right front, Ls out of L, and LFE is entirely inaudible. The routing above is worse: C comes out of Ls, Ls either disappears or possibly ends up in LFE (and thus usually inaudible, since my LFE is incapable of producing sounds above about 200Hz), LFE vanishes. There’s a lot of crackling, which is probably due to the bad LFE assignment.

So I tried using ALSA’s speaker-test instead, to at least get the channels assigned there coming out of the right speakers, which, again, they don’t with the default assignment (and note that this is on a system where passthrough works fine, so HDMI and the amp and actual speakers all agree on the channel assignment and it matches DTS/AC3/etc).

I can’t find a good assignment with that yet either, but it at least told me what ALSA’s crazy channel assignment is (L R Ls Rs C LFE). (For comparison, SMPTE is L C R Ls Rs LFE). I tried to use that to determine a mapping that worked, but so far it’s just got far worse, so I think I’m not properly grasping how the rather bizarre and almost totally undocumented ttable statement works (perhaps I have the meaning of source and target mixed up).

This is so hard that I am frankly astonished that anyone ever gets non-passthrough surround sound working acceptably on Linux systems at all.

Maybe I’m using the wrong audio target, but PI: HDMI and the ALSA targets both go wrong in different ways (PI: HDMI is just like ALSA with no route plugin, IIRC, though I haven’t tried that one in a while: I’ll try it later today).

Thanks for the explanation. I’ll have a look to see what happens here and whether the 5.x kernel is any better.

Testing with kernel 4.19.55-3 and Kodi 18.4. Everything works as expected with Pi:HDMI. All other options multi-channel is reduced (downmixed) to stereo.

Hmm! I’ll try again in a few hours with PI:HDMI and see if I was originally imagining things (I can’t remember what happened when I looked at that last).

You are entirely correct, and I am kicking myself. With the simplest possible config – the asoundrc removed entirely and the audio output switched to “PI: HDMI” – everything is working perfectly, all the speakers are correctly assigned, and I can’t hear any difference between passthrough DTS and non-passthrough Opus at all. Digging back through my old configuration logs, it looks like I added an /etc/asoundrc in the first place to try to get surround-sound tracks playing back properly in mpd, and then never removed it because why would I want to break mpd? (The solution is obviously to get that right in a new non-default alsa device that mpd can use, and then not use that device for Kodi.)

And the space saving, especially for DTS, is simply insane (far higher than going from AC3, even though AC3 is widely renowned as a simply dreadful codec). My testcase (the soundtrack for Soul) drops from 1085MiB to 203MiB, with, as far as I can tell, no loss of quality whatsoever. That’s about half as much space as the entire video track (after re-encoding to H.264), all completely wasted due to crappy codecs.

Thank you!

1 Like

FYI, in case other people want to do a huge batch re-encoding job, the ffmpeg command line to re-encode the audio in some input to Opus is horrific enough that I thought I might paste my current attempt in here.

This takes an input file (I used MKV but other containers should work, I guess) and an output file to generate, with >2-channel non-Opus audio converted to Opus with mapping family 1 (thus enabling surround masking and various LFE optimizations which save quite a lot of space), <=2-channel non-Opus audio converted to Opus with mapping family -1 (i.e. stereo or mono, as appropriate), and all audio channels already in Opus copied across without re-encoding.

Requires jq. Tested on precisely one file so far, may not work in all situations :slight_smile:

#!/bin/bash
set -e

if [[ ! -f $1 ]] || [[ -e $2 ]]; then
    echo "$1 must exist: $2 must not exist." >&2
    exit 1
fi

STREAMS="$(ffprobe -loglevel error -select_streams a -show_streams -show_entries stream=index,codec_name,channels:tags=:disposition= -of json "$1")"
ffmpeg -i "$1" -copyts -map 0 -map_metadata 0 -c:s copy -c:v copy $(for index in $(echo "$STREAMS" | jq '.streams[] | select(.codec_name != "opus" and .channels > 2).index'); do echo -c:$index libopus -filter:$index aformat=channel_layouts="5.1|stereo" -mapping_family:$index 1; done) $(for index in $(echo "$STREAMS" | jq '.streams[] | select(.codec_name != "opus" and .channels <= 2).index'); do echo -c:$index libopus -filter:$index aformat=channel_layouts="stereo" -mapping_family:$index -1; done) $(for index in $(echo "$STREAMS" | jq '.streams[] | select(.codec_name == "opus").index'); do echo -c:$index copy; done) $2

OK but thanks for sharing. Users should know that direct access to the sound device through ALSA for multi-channel (eg with mpd or speaker-test) does not work as no surround*.confs are involved to do the channel mapping. Anyone fluent in ALSA or others with a couple of days to spare might like to figure out how to correct that :stuck_out_tongue_winking_eye:

Yeah I think I gathered that :slight_smile: once I figure out what the right mapping is, I’ll see if I can write one (and contribute it upstream too natch).

I would start from the conf files on Vero, since we use ALSA for everything. Simplest way to find that if you don’t have a Vero is probably to download an install image and unzip/untar the filesystem.