latency - fcorthay/RPi-multiroom-audio GitHub Wiki

Table of Contents

Synchronicity vs latency

The important point about a multiroom system is that the receivers playing the same music have to be synchronous. The delay between two sounds in two neighbouring rooms have to be low enough not to be distinguished by the human ear.

Yet if a RPi has to play the sound output of a PC or a media player, a low latency (delay between the audio source and the loudspeaker sound) is of importance.

Latency measurements

Setup

In order to measure the latency between a sound input to a RPi based system and the loudspeaker output one needs an Ananlog to Digital Converter (ADC) and an amplifier. This can be built with a HiFiBerry DAC and ADC board together with its companion power amplifier.

As a test file, one can use a single tone .wav file generated with:

$AUDIO_BASE_DIR/LoudspeakerTest/buildSingleFrequency.py -r 0.01

Shortest path

The shortest path is from analog in to analog out.

Check who is driving the soundcard:

$AUDIO_BASE_DIR/Management/showInstallation.bash

Stop this service (adapt):

sudo service camilladsp stop

Now transfer audio in to audio out:

alsaloop -C hw:sndrpihifiberry -P plughw:sndrpihifiberry >/dev/null 2>&1 &

The plughw: prefix lets the system adapt the samples format from input to output.

Play the sound file to the analog input and measure de delay from input to output, e.g. with an oscilloscope. With a RPi 3, I have had a delay of 12.5 ms.

End the audio transfer:

pkill alsaloop

Loopback device

The path from analog in to the Loopback device and from there to the analog out will later allow more flexibility.

Setup the path:

alsaloop -C hw:sndrpihifiberry -P plughw:Loopback,0,7 2>/dev/null &
alsaloop -C hw:Loopback,1,7 -P plughw:sndrpihifiberry 2>/dev/null &

Play the sound to the analog input and measure de delay anew. With a RPi 3, I have had a delay of 25.3 ms.

Close the audio path:

pkill alsaloop

CamillaDSP

CamillaDSP allows to implement some processing on the audio being played.

Passthrough

A processing path from input to output can be established by editing $CAMILLA_CONFIGURATIONS_DIR/passthroughFromRecord.yaml:

---
devices:
  samplerate: 48000
  chunksize: 256
  capture:
    type: Alsa
    channels: 2
    device: "hw:sndrpihifiberry"
    format: S32LE
  playback:
    type: Alsa
    channels: 2
    device: "dmix:sndrpihifiberry"
    format: S32LE
mixers:
  passthrough:
    channels:
      in: 2
      out: 2
    mapping:
      - dest: 0
        sources:
          - channel: 0
            gain: 0
            inverted: false
      - dest: 1
        sources:
          - channel: 1
            gain: 0
            inverted: false
pipeline:
  - type: Mixer
    name: passthrough

As the DSP sends its output to a dmix device, one can reestablish the previous full functional path, but set the snapcast volume to zero:

sudo service camilladsp restart
SNAPCAST_SERVER=`cat /etc/default/snapclient | grep ^SNAPCLIENT_OPTS | sed "s/.*--host\s*//" | sed "s/[ \"].*//"`
$AUDIO_BASE_DIR/Snapcast/setVolume.py -s $SNAPCAST_SERVER $(hostname) 0

Run the DSP:

/opt/camilladsp $CAMILLA_CONFIGURATIONS_DIR/passthroughFromRecord.yaml

Playing a sound and measuring the delay gives:

  • 12.7 ms with chunksize: 256
  • 45.2 ms with chunksize: 1024
  • 88.1 ms with chunksize: 2048
The run with chunksize: 256 issues a warning suggesting to use a period of 1024 samples.

IIR

Changing the passthrough to an 8th order IIR gives:

  • 23.6 ms with chunksize: 256
  • 46.1 ms with chunksize: 1024
  • 89.4 ms with chunksize: 2048

FIR

Changing to a FIR of order 1001 gives:

  • 40.0 ms with chunksize: 256
  • 60.3 ms with chunksize: 1024
  • 100.5 ms with chunksize: 2048

Streaming

Streaming over Ethernet can induce latency due to the buffering of the samples in order to build packets and to fill gaps in case of the late arrival of some of them.

From computer to RPi

Sound Exchange SoX allows many different audio manipulations, including low latency (?) streaming. It can interact with ALSA devices.

Install it both on the computer and the RPi:

sudo apt install -y sox

On the RPi, stream a sound to an ALSA device:

amixer -D hw:$AMPLIFIER_SOUNDCARD get Digital
amixer -D hw:$AMPLIFIER_SOUNDCARD sset Digital 90%
sox /usr/share/sounds/alsa/Front_Center.wav -t alsa dmix:$AMPLIFIER_SOUNDCARD

On the computer, install non-interactive ssh password authentication:

sudo apt install sshpass

Provide RPi ssh password (adapt):

sudo apt install sshpass
echo amp > ~/.passwords/audioAmps
chmod 600 ~/.passwords/audioAmps

With this, stream sound to the RPi over Ethernet (adapt host and soundcard):

HOST='[email protected]'
sshpass -f ~/.passwords/audioAmps  ssh $HOST aplay -l | grep ^card

AMPLIFIER_SOUNDCARD='sndrpijustboomd'
sox /usr/share/sounds/alsa/Front_Center.wav -p | sshpass -f ~/.passwords/audioAmps ssh $HOST sox -p -t alsa dmix:$AMPLIFIER_SOUNDCARD

From VLC to RPi

VideoLAN media player (VLC) allows to play audio and video using the Graphical User Interface (GUI).

As we are going to stream from ALSA on the PC to ALSA on the RPi, make sure to have installed ALSA loopback on the PC:

sudo bash -c 'cat << EOF > /etc/modules-load.d/aloop.conf
# alsa loopback
snd
snd-timer
snd-pcm
snd-aloop
EOF'
sudo modprobe snd-aloop

On the PC, launch VLC with its audio output sent to the ALSA loopbcak device:

LOOPBACK_SUBDEVICE='7'
vlc -A alsa --alsa-audio-device dmix:Loopback,0,$LOOPBACK_SUBDEVICE >/dev/null 2>&1 &

Choose any music or video and play it.

Stream the audio to the RPi (adapt soundcard and host):

HOST='[email protected]'
ssh $HOST aplay -l | grep ^card

AMPLIFIER_SOUNDCARD='sndrpijustboomd'
sox -t alsa plughw:Loopback,1,$LOOPBACK_SUBDEVICE -p 2>/dev/null | sshpass -f ~/.passwords/audioAmps ssh $HOST sox -p -t alsa dmix:$AMPLIFIER_SOUNDCARD 2>/dev/null &

Latency

The audio can show some lagging from the video. If this is the case, play with the keyboard keys j and k to change the audio delay. It is also possible to add --delay-time -100 in the invocation of VLC.

Terminate the streaming:

pkill sox

From RPi to RPi

Active loudspeakers

For a room hosting a pair of actively driven loudspeakers, one can have a pair of RPi audio amplifiers, each driving one of the loudspeakers. In this situation, one can stream the audio to one of the RPi's and have it both forward it to the other RPi and split it for the two local loudspeaker drivers.

Streaming audio over Ethernet is not done directly to the loudspeaker outputs but to the RPi's Loopback device, as the DSP takes the audio from this device and splits the audio frequencies to the different loudspeaker drivers [1].

Stream a sound to one of the RPi's (adapt host and loopback device):

HOST='[email protected]'
sshpass -f ~/.passwords/audioAmps ssh $HOST /home/amp/RPi-multiroom-audio/Management/showInstallation.bash

LOOPBACK='dmix:Loopback,0,0'
sox /usr/share/sounds/alsa/Front_Center.wav -p | sshpass -f ~/.passwords/audioAmps ssh $HOST sox -p -t alsa $LOOPBACK

My experience is that I have to either pause mopidy or mute snapclient on the RPi in order to be able to stream audio to the loopback device from sox.

Notes

  1. ^ In fact, several options are available. Instead of streaming to the same loopback entry as the (snapclient) multiroom audio, one could wish to stream to a free loopback entry and start another CamillaDSP instance with a different audio processing, as for example a crossover with a lower latency (IIR vs FIR).
⚠️ **GitHub.com Fallback** ⚠️