PipeWire PulseAudio Integration

📋 PipeWire Audio Guide Series

🔹 You are here: Part 3 of 4 - PulseAudio compatibility and application routing

Complete Series:

  1. PipeWire Setup and Fundamentals - Foundation setup and verification
  2. PipeWire Virtual Devices - Create virtual sinks, sources, and loopbacks
  3. PipeWire PulseAudio IntegrationPulseAudio compatibility and application routing
  4. PipeWire VST Stacks using Carla - Professional audio processing with plugins

Prerequisites

This guide assumes you have completed:

PulseAudio Integration and Configuration

PipeWire includes a complete PulseAudio server implementation via libpipewire-module-protocol-pulse, providing high compatibility with existing PulseAudio applications. This section covers comprehensive configuration and routing for applications that use the PulseAudio API.

How PulseAudio Integration Works

Based on the module-protocol-pulse.c implementation:

Server Implementation:

  • Complete PulseAudio server: Full PA protocol compatibility on top of PipeWire
  • Client library compatibility: Existing applications use the original PulseAudio client library unchanged
  • Tool compatibility: pavucontrol, pactl, pamon, paplay work normally
  • Sample cache: Implements PA sample cache functionality not available in native PipeWire

Process Architecture:

  • Standalone process: Usually runs as pipewire-pulse with pipewire-pulse.conf config
  • Protocol translation: Converts PulseAudio API calls to PipeWire operations
  • Stream management: PA streams become PipeWire streams with automatic routing

Configuration File Structure

PulseAudio configuration uses a hierarchical file system based on pipewire-pulse.conf.5.md:

Configuration Locations (in order of precedence):

$XDG_CONFIG_HOME/pipewire/pipewire-pulse.conf.d/     # User overrides (highest priority)
$XDG_CONFIG_HOME/pipewire/pipewire-pulse.conf        # User main config
$(PIPEWIRE_CONFIG_DIR)/pipewire-pulse.conf.d/        # System overrides  
$(PIPEWIRE_CONFIG_DIR)/pipewire-pulse.conf           # System main config
$(PIPEWIRE_CONFDATADIR)/pipewire-pulse.conf.d/       # Distribution overrides
$(PIPEWIRE_CONFDATADIR)/pipewire-pulse.conf          # Distribution defaults (lowest priority)

Recommended approach: Use drop-in files in ~/.config/pipewire/pipewire-pulse.conf.d/ for user customizations.

Complete Configuration Sections Reference

Based on pipewire-pulse.conf.5.md source analysis:

stream.properties Section

Purpose: Configure streams created by the PipeWire PulseAudio server Required: No - uses PipeWire defaults if not specified

Reference: pipewire-pulse.conf.5.md:59-63

Available Properties (from pipewire-pulse.conf.5.md:76-100):

Property Description Default Optional Example Values
node.latency Target latency for streams, expressed as samples/rate. For playback, this is derived from buffer attributes (tlength, minreq). For capture, it’s based on fragsize. Note: This property is deprecated; use stream.latency for new configurations. Auto-calculated 1024/48000 (21ms), 512/48000 (10ms)
node.autoconnect Auto-connect streams to devices true true, false
resample.disable Disable automatic resampling false true, false
resample.quality Resampling algorithm quality 4 0-10 (higher = better quality)
channelmix.disable Disable channel mixing false true, false
channelmix.normalize Normalize volume during mixing false true, false
channelmix.mix-lfe Include LFE in channel mixing true true, false
channelmix.upmix Enable stereo to surround upmixing true true, false
channelmix.upmix-method Upmixing algorithm psd none, simple, psd
channelmix.lfe-cutoff LFE crossover frequency (Hz) 150.0 80.0, 120.0, 200.0
channelmix.fc-cutoff Front center cutoff (Hz) 12000.0 8000.0, 15000.0
channelmix.rear-delay Rear channel delay (ms) 12.0 0.0, 15.0, 20.0
channelmix.stereo-widen Stereo field widening 0.0 0.0-1.0 (0=none, 1=max)
channelmix.hilbert-taps Rear channel phase shift taps 0 0 (off), 63, 127
dither.noise Dithering noise level 0 0 (off), 1 (triangular)
dither.method Dithering algorithm none rectangular, triangular, shaped5

PulseAudio Compatibility Mapping (from pipewire-pulse.conf.5.md:65-72):

PulseAudio Setting PipeWire Property Notes
remixing-use-all-sink-channels channelmix.upmix Direct mapping
remixing-produce-lfe channelmix.lfe-cutoff Set > 0 to enable
remixing-consume-lfe channelmix.mix-lfe Direct mapping
lfe-crossover-freq channelmix.lfe-cutoff Frequency in Hz

pulse.properties Section

Purpose: Configure the PulseAudio server behavior Required: No - uses built-in defaults

Reference: module-protocol-pulse.c:46-91

Connection Properties:

Property Description Default Optional Example Values Source Reference
server.address Server listening addresses ["unix:native"] Array of connection strings Line 54-66
server.dbus-name DBus service name "org.pulseaudio.Server" Custom DBus name for multiple servers Line 68
pulse.allow-module-loading Allow clients to load/unload modules true true, false Line 69

Connection Address Examples (from module-protocol-pulse.c:54-66):

server.address = [
    "unix:native"                          # Default Unix socket
    "tcp:4713"                            # IPv4/IPv6 on all addresses  
    "tcp:[::]:9999"                       # IPv6 specific on all addresses
    "tcp:127.0.0.1:8888"                  # IPv4 on specific address
    # Extended syntax with additional options:
    { address = "tcp:4713"                # Connection address
      max-clients = 64                    # Maximum concurrent clients
      listen-backlog = 32                 # Server listen queue size  
      client.access = "restricted" }      # Client permission level
]

Buffering Properties (from module-protocol-pulse.c:70-87):

Property Description Default Optional Example Values Source Reference
pulse.min.req Minimum request size for playback 128/48000 (2.7ms) 256/48000, 512/48000 Line 70
pulse.default.req Default request size 960/48000 (20ms) 480/48000, 1920/48000 Line 71
pulse.min.frag Minimum fragment size for capture 128/48000 (2.7ms) 256/48000, 512/48000 Line 72
pulse.default.frag Default fragment size 96000/48000 (2s) 48000/48000, 192000/48000 Line 73
pulse.default.tlength Target buffer length 96000/48000 (2s) 48000/48000, 144000/48000 Line 74
pulse.min.quantum Minimum quantum size 128/48000 (2.7ms) 256/48000, 1024/48000 Line 75

Format Properties (from module-protocol-pulse.c:76-78):

Property Description Default Optional Example Values Source Reference
pulse.default.format Default sample format F32 F32, S16, S24, S32 Line 76
pulse.default.position Default channel layout [ FL FR ] [ FL FR ], [ FL FR FC LFE RL RR ] Line 77

Quirk Properties (from module-protocol-pulse.c:217-242):

Property Description Default Optional Example Values Source Reference
pulse.fix.format Force format for FIX_FORMAT streams None "S16LE", "F32LE" Line 217-222
pulse.fix.rate Force rate for FIX_RATE streams None 48000, 44100 Line 225-230
pulse.fix.position Force channels for FIX_CHANNELS streams None "[ FL FR ]", "[ MONO ]" Line 233-238
pulse.idle.timeout Auto-pause underrunning clients (seconds) 0 (disabled) 5, 10, 0 (off) Line 241-248

pulse.rules Section

Purpose: Apply rules and quirks to specific PulseAudio applications Required: No - no rules applied if not specified

Reference: module-protocol-pulse.c:300-329

Rule Structure:

pulse.rules = [
    {
        matches = [ { property.name = "value" } ]    # Application matching
        actions = { 
            quirks = [ "quirk-name" ]                # Apply behavior quirks
            update-props = { property = value }      # Override client properties
        }
    }
]

Available Quirks (from module-protocol-pulse.c:332-346):

Quirk Description Use Case Source Reference
force-s16-info Pretend device uses S16 format Apps that refuse non-S16 devices Line 335-337
remove-capture-dont-move Remove DONT_MOVE flag from capture streams Allow moving “locked” streams Line 338-340
block-source-volume Block client from changing source volumes Disable automatic gain control Line 341-342
block-sink-volume Block client from changing sink volumes Prevent volume override Line 343
block-record-stream Block client from creating record streams Disable recording capability Line 344
block-playback-stream Block client from creating playback streams Disable playback capability Line 345

pulse.cmd Section

Purpose: Execute commands during PulseAudio server startup Required: No - no commands executed if not specified

Reference: module-protocol-pulse.c:251-267

Available Commands:

Command Description Arguments Optional Flags Source Reference
load-module Load PulseAudio module "module-name module-args" ["nofail"] Line 176-178

Real-World PulseAudio Configuration Examples

Example 1: Desktop Content Creation Setup

Based on typical content creation configuration patterns:

# ~/.config/pipewire/pipewire-pulse.conf.d/desktop-content-creation.conf

# Optimize stream properties for content creation
stream.properties = {
    # Lower latency for real-time work
    node.latency = 512/48000              # 10.7ms - good balance for content creation
    resample.disable = false              # Enable resampling - needed when mixing sources with different sample rates
    resample.quality = 6                  # Higher quality resampling (vs default 4)
    
    # Audio processing optimized for mixing multiple sources
    channelmix.disable = false            # Enable mixing capabilities
    channelmix.normalize = true           # Prevent clipping when mixing
    channelmix.mix-lfe = false           # Preserve LFE channel integrity
    monitor.channel-volumes = true        # Per-channel control for monitoring
}

# Route specific applications to virtual devices for processing
pulse.rules = [
    {
        # Route Spotify to music processing sink for EQ/effects
        matches = [ { application.process.binary = "spotify" } ]
        actions = {
            update-props = {
                target.object = "music-null-sink"    # References null sink node.name
            }
        }
    }
    {
        # Fix Teams audio format issues  
        matches = [
            { application.process.binary = "teams" }
            { application.process.binary = "teams-insiders" }
        ]
        actions = { 
            quirks = [ "force-s16-info" ]            # Teams requires S16 format reporting
        }
    }
    {
        # Give OBS exclusive microphone access for streaming
        matches = [ { application.process.binary = "obs" } ]
        actions = {
            update-props = {
                pulse.min.req = 256/48000            # 5.3ms - lower latency for streaming
                pulse.min.quantum = 256/48000
            }
        }
    }
]

Example 2: Gaming and Communication Setup

# ~/.config/pipewire/pipewire-pulse.conf.d/gaming-communications.conf

# Optimized for low-latency gaming and voice chat
stream.properties = {
    node.latency = 256/48000              # 5.3ms - ultra-low latency for gaming
    resample.quality = 2                  # Lower quality but faster processing
    channelmix.upmix = true              # Games benefit from surround upmixing
    channelmix.upmix-method = "simple"   # Fast upmixing algorithm
    channelmix.rear-delay = 8.0          # Shorter delay for responsive gaming
}

pulse.rules = [
    {
        # Route games to dedicated gaming sink with surround processing
        matches = [
            { application.process.binary = "~.*game.*" }
            { application.process.binary = "steam" }
            { application.name = "~.*Counter-Strike.*" }
        ]
        actions = {
            update-props = {
                target.object = "game-audio-sink"
                pulse.min.quantum = 128/48000    # Ultra-low latency for competitive gaming
            }
        }
    }
    {
        # Route voice chat to separate device with echo cancellation
        matches = [
            { application.process.binary = "discord" }
            { application.process.binary = "teamspeak3" }
            { application.name = "~.*Voice.*" }
        ]
        actions = {
            update-props = {
                target.object = "voice-chat-sink"
                pulse.min.req = 512/48000        # Balanced latency for voice quality
            }
        }
    }
    {
        # Block microphone access from games (prevent accidental voice leaks)
        matches = [
            { application.process.binary = "~.*game.*" }
            { application.name = "~.*Steam.*" }
        ]
        actions = { 
            quirks = [ "block-record-stream" ]   # Disable microphone for games
        }
    }
]

Example 3: Professional Audio Production

# ~/.config/pipewire/pipewire-pulse.conf.d/professional-audio.conf

pulse.properties = {
    # Pro audio interface usually requires specific settings
    pulse.min.req = 64/48000              # 1.3ms - professional low latency
    pulse.default.req = 128/48000         # 2.7ms default
    pulse.min.quantum = 64/48000          # Match JACK workflows
    pulse.default.format = F32            # 32-bit float for maximum quality
    pulse.idle.timeout = 0                # Never pause streams in pro environment
}

stream.properties = {
    node.latency = 64/48000               # Ultra-low latency
    resample.disable = true               # No resampling in pro workflows  
    resample.quality = 10                 # Maximum quality if resampling needed
    channelmix.disable = true             # Preserve exact channel routing
    dither.method = "triangular"          # Professional dithering
    monitor.channel-volumes = true        # Detailed monitoring control
}

pulse.rules = [
    {
        # DAW applications get ultra-low latency settings
        matches = [
            { application.process.binary = "ardour" }
            { application.process.binary = "reaper" }
            { application.process.binary = "bitwig-studio" }
        ]
        actions = {
            update-props = {
                pulse.min.req = 32/48000          # 0.67ms - extreme low latency
                pulse.min.quantum = 32/48000      # Match req for consistent timing
            }
        }
    }
    {
        # Prevent consumer apps from disrupting pro workflow
        matches = [
            { application.process.binary = "firefox" }
            { application.process.binary = "chrome" }
            { media.role = "Notification" }
        ]
        actions = {
            update-props = {
                target.object = "consumer-audio-sink"   # Route to separate device
                pulse.min.quantum = 1024/48000          # Higher latency for efficiency
            }
        }
    }
]

PulseAudio Routing Behavior

Stream Routing Priority

Based on module-protocol-pulse.c implementation, PulseAudio applications route in this order:

  1. Explicit target.object: If pulse.rules specifies target.object = "device-name"
  2. Application preference: If app specifies a preferred device
  3. WirePlumber default: Session manager assigns based on policy
  4. System default: Fallback to system default audio device

Integration with Virtual Devices

Null Sinks: PulseAudio applications can target null sinks created with support.null-audio-sink:

# Route PulseAudio app to null sink for VST processing
pulse.rules = [
    {
        matches = [ { application.process.binary = "spotify" } ]
        actions = {
            update-props = {
                target.object = "music-vst-sink"    # References node.name from null sink config
            }
        }
    }
]

Loopback Devices: PulseAudio applications see loopback sinks as regular selectable devices when configured with proper device.description properties.

Configuration Validation and Troubleshooting

Check configuration syntax:

# Test configuration without applying
pipewire-pulse --version  # Should complete quickly if config is valid

# View PulseAudio server startup logs
journalctl --user -u pipewire-pulse --since "1 minute ago"

Verify PulseAudio application routing:

# List all PulseAudio streams and their routing
pactl list short sink-inputs
pactl list short source-outputs

# Monitor real-time stream changes
pactl subscribe

Debug pulse.rules matching:

# Show application properties that pulse.rules can match on
wpctl inspect STREAM_ID | grep -E "(application\.|media\.)"

# Example output shows matchable properties:
# * application.process.binary = "spotify"  
# * application.name = "Spotify"
# * media.role = "Music"

Common Issues:

  • Rules not applying: Check application property matching with exact binary names
  • Device not found: Verify target.object matches exact node.name from virtual device
  • Routing conflicts: Higher priority rules override lower priority ones
  • Latency issues: Balance pulse.min.quantum vs pulse.min.req for your workflow

References:


Next in this series: PipeWire VST Stacks using Carla - Professional audio processing with plugins

Related guides: