Page head image

Stereo to 2.1 "upmix" on Linux - part 3

(0 comments)

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:


pcm.stereo21 {
type route
slave {
pcm "iec958mux"
channels 6
}

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

hint {
description "Stereo to 2.1"
}

}

pcm.iec958mux {
type file
slave.pcm null
file "|/usr/local/bin/play-5.1"
format wav
}

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:


#!/bin/sh
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:


#!/bin/sh

WAITTIME=0.5
PIDDIR="$HOME/.cache/play-5.1"
LOCKFILE="$PIDDIR/lock"
PLAYERPID="$PIDDIR/player"

[ -e "$PIDDIR" ] || mkdir "$PIDDIR"
while [ -e "$LOCKFILE" ] ; do sleep $WAITTIME ; done
echo $$ >"$LOCKFILE"

(aften -v 0 - - ; sleep $WAITTIME ; kill -9 $(cat "$PLAYERPID")) | \
vlc-pipe "$PLAYERPID"

sleep $WAITTIME
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


pcm.!default {
type plug;
slave {
pcm stereo21;
rate 48000;
}
}

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.

Comments

There are currently no comments

New Comment

required

required (not published)

optional