PipeWire/PulseAudio RTP network audio in October 2025: a configuration guide to the remote (time)…

3 min read Original article ↗

Jean-Marc Liotier

Setting up RTP network audio streaming between PipeWire/PulseAudio systems is surprisingly difficult due to broken or incomplete module implementations. We stepped on all the landmines so you don’t have to… This guide provides a tested, working configuration for streaming audio from a desktop Debian system to a headless Raspbian Raspberry Pi.

TL;DR: PulseAudio’s module-rtp-send (via pipewire-pulse) → GStreamer RTP receiver

Despite being “standard” solutions, PipeWire’s native RTP modules are not production-ready, and PulseAudio’s RTP receiver is unreliable for unicast streams. The combination of PulseAudio’s sender (via pipewire-pulse) and GStreamer’s receiver provides a robust, infrastructure-grade solution for network audio streaming.

Press enter or click to view image in full size

Current State of RTP Support (as of PipeWire 0.3.65)

What Should Work (according to documentation)

PipeWire Native:

  • libpipewire-module-rtp-sink and libpipewire-module-rtp-source claim to provide RTP streaming
  • Documented in PipeWire Wiki as functional modules

PulseAudio Compatibility:

  • module-rtp-send and module-rtp-recv are mature modules from PulseAudio
  • Should auto-discover streams via SAP (Session Announcement Protocol)
  • Documented as supporting both multicast and unicast

Known Issues (Prior to Testing)

  • PipeWire’s RTP modules are marked as experimental in some distributions
  • PulseAudio’s module-rtp-recv has long-standing issues with unicast detection
  • Format negotiation between sender and receiver can be problematic

Combinations That Don’t Work

1. Native PipeWire RTP Modules

Configuration Attempted:

# Sender
context.modules = [
{ name = libpipewire-module-rtp-sink
args = { destination.ip = "192.168.1.8" ... }}
]
# Receiver  
context.modules = [
{ name = libpipewire-module-rtp-source
args = { source.port = 46000 ... }}
]

Why It Fails:

  • libpipewire-module-rtp-sink cannot accept float32le audio from PipeWire’s null-audio-sink
  • Returns “Operation not supported” (error -95) when attempting to start
  • The null-audio-sink adapter ignores format specifications and always outputs float32le
  • RTP modules only accept integer formats (S16BE/S16LE)

2. PulseAudio module-rtp-recv

Configuration Attempted:

pactl load-module module-rtp-recv port=46000

Why It Fails:

  • Designed for multicast with SAP discovery
  • Fails to create audio sources for unicast streams
  • Even with multicast, often fails to detect valid RTP streams
  • No useful error messages when detection fails

3. Mixed native PipeWire configurations

Configuration Attempted:

  • Various combinations of PipeWire loopback modules to bridge format gaps
  • WirePlumber routing rules for automatic connections

Why It Fails:

  • PipeWire’s RTP modules create nodes with unpredictable names
  • Channel mapping issues (numbered outputs instead of FL/FR)
  • Complex module chains introduce multiple failure points

The Working Solution

Sender Configuration (desktop with PipeWire/PulseAudio)

Create ~/.config/pipewire/pipewire-pulse.conf.d/rtp.conf:

pulse.cmd = [
{
cmd = "load-module"
args = "module-null-sink sink_name=rtp_sink sink_properties=device.description=RTP_Network_Stream"
}
{
cmd = "load-module"
args = "module-rtp-send source=rtp_sink.monitor destination_ip=192.168.1.8 port=46000"
}
]

Restart services:

systemctl --user restart pipewire pipewire-pulse

Receiver Configuration (Raspberry Pi)

Install GStreamer:

sudo apt install gstreamer1.0-tools gstreamer1.0-plugins-good gstreamer1.0-alsa

Create /etc/systemd/user/rtp-receiver.service:

[Unit]
Description=RTP Audio Receiver
After=sound.target network-online.target
[Service]
Type=simple
Restart=always
RestartSec=5
ExecStart=/usr/bin/gst-launch-1.0 udpsrc port=46000 buffer-size=65536 ! \
application/x-rtp,media=audio,payload=127,clock-rate=48000,encoding-name=L16,channels=2 ! \
rtpjitterbuffer latency=200 ! \
rtpL16depay ! \
audioconvert ! \
alsasink device=hw:0,0 buffer-time=200000 latency-time=100000
[Install]
WantedBy=default.target

Enable service:

systemctl --user enable --now rtp-receiver

Critical configuration details

Why This Works

  1. PulseAudio’s module-rtp-send handles format conversion: Accepts float32le from the null sink monitor, converts to S16BE for transmission
  2. GStreamer properly decodes RTP: More robust than PulseAudio’s receiver modules
  3. Explicit payload type: module-rtp-send uses payload 127, not the commonly assumed 96
  4. Adequate buffering: Network audio requires larger buffers than local playback

Key Parameters

  • Payload type 127: PulseAudio uses dynamic payload types (96–127)
  • Buffer sizes: Essential for preventing audio dropouts on Raspberry Pi
  • UDP buffer: 65536 bytes
  • ALSA buffer-time: 200ms
  • RTP jitter buffer: 200ms
  • Device specification: Use hw:0,0 for direct ALSA access (adjust based on the specific audio interface on your hardware configuration)

Troubleshooting

Verify audio transmission network traffic

# On receiver
sudo tcpdump -i any -n port 46000 -c 5

Check audio format

# Capture and analyze RTP payload
timeout 5 gst-launch-1.0 udpsrc port=46000 ! filesink location=/tmp/rtp.dump
hexdump -C /tmp/rtp.dump | head -20

Test GStreamer pipeline

# Local audio test on receiver
gst-launch-1.0 audiotestsrc ! alsasink device=hw:0,0