1

I am trying to achieve a multi-room audio setup in my house using Raspberry Pis. How can I get VLC playing simultaneously out the local headphone port while also streaming it to other devices via trx?

Background:

I have found a fantastic package called trx which allows low-latency streaming using the Opus codec across the LAN: http://www.pogo.org.uk/~mark/trx/streaming-desktop-audio.html

I have managed to successfully get trx installed and working with the following:

sudo modprobe snd-aloop

Transmission side ~/.asoundrc

# TX device catches played audio from a player (e.g. VLC)
# point vlc to this device:
#    cvlc --alsa-audio-device="tx" <file_or_stream>
pcm.tx {
    type plug
    slave.pcm {
        type dmix
        ipc_key 2867
        slave {
            pcm "hw:Loopback,0,0"
            rate 48000
            format S16_LE
            channels 2
            period_size 256
            buffer_size 8192
        }
    }
}

# Hubcap ensures 48000Hz sample rate (Opus compatible)
pcm.hubcap {
    type plug
    slave {
        pcm "hw:Loopback,1,0"
        rate 48000
        format S16_LE
    }
}

Transmission side:

tx -d hubcap -m 64 -h 239.0.0.99 &

cvlc --alsa-audio-device="tx" {source_file_path_or_url}

Receiving side:

rx -m 64 -h 239.0.0.99

There are occasional buffer underruns which are easily fixed by changing the niceness of the tx processes to -10.

renice -n -10 {process_id}

The problem:

I would like to be able to play audio from VLC to the devices across the network listening to the multicast 239.0.0.99, and also from the transmission device's headphone / line-out socket.

I cannot figure out how to set up a plug, route and multi in ~/.asoundrc so that there is one ALSA device cvlc can play to, where the audio is fed to both local hw:1 (headphone socket) and plug:tx (input for audio to be transmitted via tx).

The ALSA asound configuration documentation is abysmal. I have tried the following addition to .asoundrc with no luck:

pcm.headphones_dmix {
        type dmix
        slave {
                pcm "hw:Headphones"
        }
}

pcm.localandtx {
    type plug
    slave {
        format S16_LE
        pcm {
            type multi
            slaves.tx.pcm "tx"
            slaves.tx.channels 2
            slaves.local.pcm "headphones_dmix"
            slaves.local.channels 2
            bindings.0.slave tx
            bindings.0.channel 0
            bindings.1.slave tx
            bindings.1.channel 1
            bindings.2.slave local
            bindings.2.channel 0
            bindings.3.slave local
            bindings.3.channel 1
        }
    }
    route_policy duplicate
    ttable {
        0.0 1
        1.1 1
        0.2 1
        1.3 1
    }
    hint {
        show on
        description "Play both locally and via TX."
    }
}

With the above:

vlc will happily play to the local headphones with --alsa-audio-device="hw:Headphones"

vlc will happily play to devices running rx with --alsa-audio-device="tx"

But, vlc won't play to either with --alsa-audio-device="localandtx". I want it to play to both. (I am aware of the additional latency when sending audio via trx).

The vlc errors are:

ALSA lib pcm_direct.c:2031:(snd1_pcm_direct_parse_open_conf) Unique IPC key is not defined
[015a4ac8] alsa audio output error: cannot open ALSA device "localandtx": Invalid argument
[015a4ac8] main audio output error: Audio output failed
[015a4ac8] main audio output error: The audio device "localandtx" could not be used: Invalid argument.
[015a4ac8] main audio output error: module not functional
[71b7a980] main decoder error: failed to create audio output

Is there any useful (sane) tool for debugging an ALSA asound config file?

How do I determine which .asoundrc argument in localandtx is "invalid"?

How can I achieve audio routing to both hw:Headphones and tx in ALSA without using PulseAudio?

1 Answer 1

0

I've finally figured out the multiroom audio Raspberry Pi conundrum!

The solution was to bring in PulseAudio. ALSA cannot do it alone because of the Raspberry Pi's ALSA bcm2835 driver limitations. The driver cannot copy data from one stream to another using mmap for reasons I don't quite understand. This is the case even when mmap is specifically enabled and mmap emulation is used (mmap_emul) - see: https://blog.dowhile0.org/2013/04/27/mmap-support-for-raspberry-pi-bcm2835-alsa-driver/.

The complete solution to Raspberry Pi audio stream duplication locally and via trx:

  1. Transmission side - install packages:

     sudo su
     sudo apt install alsa-utils opus-tools lame vlc cmake libasound2-dev libortp-dev libopus-dev pulseaudio
     mkdir ~/Installers
     cd ~/Installers
     git clone http://www.pogo.org.uk/~mark/trx.git
     make
     sudo make install
    
  2. Transmission side - Add user to audio group:

     sudo usermod -a -G audio your_username
    
  3. Transmission side - activate the alsa loopback module:

     modprobe snd-aloop
     echo "snd-aloop" | sudo tee -a /etc/modules
    
  4. Transmission side - setup ALSA devices to support trx:

    You should edit ~/.asoundrc if you want to do this for just one user, or /etc/asound.conf to make it for all users.

     # nano /etc/asound.conf 
    

    OR

     # nano ~/asoundrc:
    

    File contents:

     # /etc/asound.conf OR ~/asoundrc:
    
     # tx_dmix ensures audio sent to tx gets resampled properly
     pcm.tx_dmix {
             type dmix
             ipc_key 2867
             ipc_perm 0666 # allow other users
             slave {
                 pcm "hw:Loopback,0,0"
                 rate 48000
                 format S16_LE
                 channels 2
                 period_size 256
                 buffer_size 8192
             }
     }
    
     # tx is the entry point for anything that wants to play down the TX link
     pcm.tx {
             type plug
             slave.pcm "tx_dmix"
             hint.description "Audio input for TX transmission."
     }
    
     # Hubcap is used by TX to resample audio into Opus-friendly sample rate
     pcm.hubcap {
             type plug
             slave {
                     pcm "hw:Loopback,1,0"
                     rate 48000
                     format S16_LE
             }
             hint.description "Internal loopback capture and resampler for TX. Only TX should use."
     }
    
     # Headphones (3.5mm jack) playback
     pcm.headphones_hw {
         type hw
         card Headphones
         sync_ptr_ioctl 1
     }
    
     pcm.headphones {
         type plug
         slave.pcm "headphones_hw"
     }
    
     pcm.!default {
         type plug
         slave.pcm "headphones"
     }
    
  5. Transmission side - set up PulseAudio connections

     # nano /etc/pulse/default.pa
    

    OR

     # nano ~/.pulse/default.pa:
    

    File contents:

     # PulseAudio config - duplicates audio for local playback and network playback
    
     .include /etc/pulse/default.pa
    
     # Set up Pulse sinks to connect to our ALSA devices we configured in .asoundrc
     load-module module-alsa-sink device="tx" sink_name=tx
     load-module module-alsa-sink device="headphones" sink_name=headphones
    
     # Create stream duplicator
     load-module module-null-sink sink_name=localandtx
     load-module module-loopback source=localandtx.monitor sink=tx
     load-module module-loopback source=localandtx.monitor sink=headphones
     set-default-sink localandtx
    

    Restart pulse after creating this file

     pulseaudio -k
    
  6. Transmission side - create tx initialising script:

     nano ~/run_tx.sh
    

    File contents

     #!/bin/bash
     # Fires up the TX transmission 
     # Usage:
     #     sudo run_tx.sh [ip] [latency in ms]
     #     sudo run_tx.sh 239.0.0.99 64
    
     # Defaults
     DEFAULT_TX_IP="239.0.0.99"
     DEFAULT_LATENCY_BUFFER_MS="64"
    
     # Resolve from args
     CHOSEN_TX_IP=${$1:-$DEFAULT_TX_IP}
     CHOSEN_LATENCY_BUFFER_MS=${$2:-$DEFAULT_LATENCY_BUFFER_MS}
    
     echo "Launching TX on ${CHOSEN_TX_IP} with ${CHOSEN_LATENCY_BUFFER_MS} ms buffer."
    
     # Start TX, using hubcap ALSA device
     tx -d hubcap -m $(($CHOSEN_LATENCY_BUFFER_MS)) -h $CHOSEN_TX_IP &
    
     # Boost priority of all TX threads (necessary to prevent buffer underruns)
     TX_PIDS=$(ps -L -C tx -o lwp=)
     for TX_PID in TX_PIDS
     do
       sudo renice -10 $(($TX_PID))
     done
    
  7. Transmission side - run the tx sender:

     chmod +x ~/run_tx.sh        
     sudo ~/run_tx.sh 
    
  8. Receiving side - install packages (just runs ALSA, no need for PulseAudio):

     sudo su
     sudo apt install alsa-utils opus-tools lame vlc cmake libasound2-dev libortp-dev libopus-dev
     mkdir ~/Installers
     cd ~/Installers
     git clone http://www.pogo.org.uk/~mark/trx.git
     make
     sudo make install
    
  9. Receiving side - Create rx initialising script:

     nano ~/run_rx.sh
    

    File contents

     #!/bin/bash
     # Fires up the RX Reception side
     # Usage:
     #     sudo run_rx.sh [ip] [latency in ms]
     #     sudo run_rx.sh 239.0.0.99 64
    
     # Defaults
     DEFAULT_RX_IP="239.0.0.99"
     DEFAULT_LATENCY_BUFFER_MS="64"
    
     # Resolve from args
     CHOSEN_RX_IP=${$1:-$DEFAULT_RX_IP}
     CHOSEN_LATENCY_BUFFER_MS=${$2:-$DEFAULT_LATENCY_BUFFER_MS}
    
     echo "Launching RX receiver, listening on ${CHOSEN_RX_IP} with ${CHOSEN_LATENCY_BUFFER_MS} ms buffer."
    
     # Start RX, using default ALSA device
     rx -m $(($CHOSEN_LATENCY_BUFFER_MS)) -h $CHOSEN_RX_IP &
    
     # Boost priority of all RX threads (necessary to prevent buffer underruns)
     RX_PIDS=$(ps -L -C rx -o lwp=)
     for RX_PID in RX_PIDS
     do
       sudo renice -10 $(($RX_PID))
     done
    
  10. Receiving side - run the rx listener:

    chmod +x ~/run_rx.sh        
    sudo ~/run_rx.sh 
    
  11. Play something on the tx side into PulseAudio default sink (localandtx). It will be played out from the rx side via the rx device's default audio output.

To specify a different audio device to playback in rx, add the -d "alsa_device_name" option to the rx -m $(($CHOSEN_LATENCY_BUFFER_MS)) -h $CHOSEN_RX_IP & line e.g. rx -m $(($CHOSEN_LATENCY_BUFFER_MS)) -h $CHOSEN_RX_IP -d Headphones &

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.