The upmix pipe is nice, but quite complex and limited to applications which have piped output. Maybe we can get the latency down and probably can get sox out of the mix... ALSA can remix channels with the "route" plugin, and it can output to a pipe. Let's put the following in the ~/.asoundrc:
ttable.0.0 1; # front left
ttable.1.1 1; # front right
ttable.0.2 0; # rear left
ttable.1.3 0; # rear right
ttable.0.4 0; # center left (muted)
ttable.1.4 0; # center right (muted)
ttable.0.5 0.5; # sub left
ttable.1.5 0.5; # sub right
description "Stereo to 2.1"
This is exactly the "sox" part of the pipe, and every audio player that can output to an ALSA "PCM" device can use it:
aplay -Dstereo21 test-stereo-48k.wav
The stereo21 PCM performs the upmix and sends the resulting stream to the iec958mux PCM, which will pipe it to /usr/local/bin/play-5.1
And here comes the surprisingly tricky part. In a first iteration it looked like this:
aften -v 0 - - | \
mplayer -rawaudio format=0x2000 -demuxer rawaudio -ao alsa:device=iec958 -ac hwac3 -really-quiet -
We're using aften because of lower latency and that I couldn't find a way to tell ffmpeg to STFU — it keeps printing stuff to console whatever I do. As you can imagine, this only almost works. Mplayer complains about underruns. As "aften" is just a converter without being able to output to ALSA, it leaves us no choice than abusing vlc for the task:
aften -v 0 - - | vlc -I dummy stream:///dev/stdin
But this results in a hanging vlc because it does not catch end of file of the pipe (it blocks SIGPIPE). We need to kill it explicitly. With SIGKILL. As vlc also blocks SIGCHLD, it is not enough to kill the script. Since the "dummy" interface of vlc isn't capable of writing the PID (only the interactive interfaces are), we need to write a wrapper vlc-pipe:
echo $$ >$1
exec vlc -I dummy stream:///dev/stdin
The argument of the script is the file name to write the PID of vlc to. With some additional armor around it, our play-5.1 script looks like this:
[ -e "$PIDDIR" ] || mkdir "$PIDDIR"
while [ -e "$LOCKFILE" ] ; do sleep $WAITTIME ; done
echo $$ >"$LOCKFILE"
(aften -v 0 - - ; sleep $WAITTIME ; kill -9 $(cat "$PLAYERPID")) | \
rm "$LOCKFILE" "$PLAYERPID"
We assume a maximum latency of half a second, which is indeed enough. Extremely ugly, but at first, it seems to work reliably. Thus, let's see what happens if we make it the default PCM. And of course add the automatic rate conversion to 48 kHz sampling rate. Just add the following few lines to ~/.asoundrc
Yes! That works! Until you start firefox and play flash animations.
We end up with hanging instances vlc again, because opposed to vlc the play-5.1 script does catch signals, and if a process closes the ALSA device before the half-second sleep is over, we'll have a hanging vlc again, and stale lock files on top of it. Ugh. Okay, we might be able to catch most races eventually, but I'd very much prefer to avoid using vlc altogether.