Redirect PulseAudio/PipeWire over TCP/IP network

If you have two Linux hosts, host1 and host2, both running PulseAudio (or Pipewire with PulseAudio backwards compatibility) and you want host2 to use the speakers on host1, then you can use a PulseAudio TCP server on host1 and a „tunnel sink“ on host2 to do that.

In the following setup examples, the following is assumed:

  • host1 is the host with the speakers attached.
  • A user myuser on host1 is currently running a PulseAudio session.
  • host1, having an IPv4 address 192.168.1.166, will act as a TCP server
  • host2, having an IPv4 address 192.168.1.167, will be the client that establishes the connection.

Configuration

Some variables are set on host2 as a preparation for the shell commands demonstrated below:

host1_ip=192.168.1.166
host1_user=myuser
host2_ip=192.168.1.167

Prerequisites: Transparent SSH and PulseAudio Cookie

The following preparations are required for both scenarios below.

On host2, password-less SSH to the given desktop user on host1 will be established:

ssh-copy-id "$host1_user"@"$host1_ip"

Note: Password-less SSH is not really a requirement for the „Setup without Encryption“ demonstrated below, but it is convenient for synchronizing the PulseAudio cookie (which is a requirement in any case); for the „Setup with Encryption“ as proposed further below, an SSH tunnel that can be established without password is the proposed solution. Also note that the direction of SSH could be changed so that host1 logs in to host2, this change is not very difficult and left as an exercise to the reader. 🙂

The PulseAudio cookie of host1, stored in the file .config/pulse/cookie will be transferred to host2:

scp -q \
   "$host1_user"@"$host1_ip":.config/pulse/cookie \
   .config/pulse/cookie

Setup without Encryption

On host2 the following commands start a PulseAudio TCP server on host1 that allows connections from host2. host2 then establishes a PulseAudio tunnel sink, directly connected to host1. PulseAudio on host2 is the set up to use that tunnel sink as default for all subsequent output.

ssh "$host1_user"@"$host1_ip" \
   pactl load-module module-native-protocol-tcp \
      listen="$host1_ip" \
      auth-ip-acl="$host2_ip"

pactl load-module module-tunnel-sink \
   server=tcp:"$host1_ip"
       
pactl set-default-sink tunnel-sink.tcp:"$host1_ip"

Setup with Encryption

The following setup proposes to use SSH port forwarding to encrypt the audio as it is transferred over the network. On host1, the TCP server only allows connections from the loopback address. host2 dials into host1 establishing port forwarding of the PulseAudio TCP port 4713:

ssh "$host1_user"@"$host1_ip" \
   pactl load-module module-native-protocol-tcp \
      listen=127.0.0.1 \
      auth-ip-acl=127.0.0.1

ssh -fNT \
   -L 127.0.0.1:4713:"$host1_ip":4713 \
   "$host1_user"@"$host1_ip"

pactl load-module module-tunnel-sink \
   server=tcp:127.0.0.1

pactl set-default-sink tunnel-sink.tcp:127.0.0.1

Note: This is the setup i run myself and the one which i recommend.

Implementation

A more complete implementation, useful as management utility and/or service worker script can be found here:

Gitea project pulseaudio-tcpLast 3 commits: by tilman: 8680811b typo in documentation by Tilman Kranz: 731c4825 documentation imporvements (definitions, requirements) by Tilman Kranz: 09c91bad update documentation to bidirectional operation