• Welcome to ASR. There are many reviews of audio hardware and expert members to help answer your questions. Click here to have your audio equipment measured for free!

Bit-Perfect Audio on Linux with PipeWire

@alpha_logic thanks for the write-up. I'd honestly never thought of PipeWire as a way to get bit-perfect - I always just went around it with ALSA directly on a Pi. Your post made me curious, so I built a little tool that checks a PipeWire setup for the usual bit-perfect blockers (rate switching, volume, resampling, EasyEffects, etc.) and can apply the fixes, with an off to undo.

Built with AI help and then tested by hand for a good while. It's safe by default — check is read-only, all changes are removable drop-in configs, and the one genuinely loud action (raising the sink to 100%) is opt-in behind --set-volume. Works well on my laptop, though that's locked to 48 kHz so I couldn't try it on a real multi-rate USB DAC — would love reports from anyone who can. The --dedicated-sink mode was inspired by @PurpaSmart 's bash script.

https://github.com/madenvel/pipewire-bitperfect

curl -fLo bitperfect.py https://raw.githubusercontent.com/madenvel/pipewire-bitperfect/main/bitperfect.py && chmod +x bitperfect.py

demo.gif
 
@MadEnvel : Kudos to your app. But if that animated gif was a static text dump of the final printout, the result could be read, unlike now. THanks for considering.
 
@MadEnvel : Kudos to your app. But if that animated gif was a static text dump of the final printout, the result could be read, unlike now. THanks for considering.

Good point - I've added a static screenshot and the text dump.

Screenshot From 2026-07-24 23-19-47.png


envel@envel-lenovo:~$ ./bitperfect.py
=== PipeWire bit-perfect audit ===

[INFO ] No bitperfect drop-in configs installed (stock configuration)
[INFO ] Native ALSA rates for hw:0,3 (HDMI 1): 32000-192000 Hz (range)
[INFO ] Native ALSA rates for hw:0,4 (HDMI 2): 32000-192000 Hz (range)
[INFO ] Native ALSA rates for hw:0,5 (HDMI 3): 32000-192000 Hz (range)
[INFO ] Native ALSA rates for hw:0,31 (Deepbuffer HDA Analog (*)): 48000 Hz
[PASS ] Single-rate hardware and graph are both fixed at 48000 Hz
This output path does not support rate switching; this is expected.
[FAIL ] Default sink volume is 38%, not 100%
PipeWire processes in float32; any attenuation rewrites every
sample, so the DAC never sees the original bits.
Expect a discrepancy on ALL playback until volume is 100%.
[FAIL ] Live mismatch: PipeWire ALSA [python3.14] plays at 44100 Hz but the device runs at 48000 Hz
This stream is being resampled RIGHT NOW.
[INFO ] Hardware is being fed: format=S32_LE rate=48000 Hz
(/proc/asound/card0/pcm0p/sub0/hw_params)
Note: 16-bit sources in an S24/S32 hardware format are still
bit-perfect — zero-padding depth is lossless. Only a RATE that
differs from the source indicates resampling.

--- summary ---
Auto-fixable (run: bitperfect.py fix): volume
 
Thanks for the guide! Works on NixOS with wayland and sabaj a10d dac which conveniently always displays sample rate :)

Btw an easy way to test is using speaker-test which is packaged in alsa utils on most distros
nix-shell -p alsa-utils
e.g. for 96kHz F32
speaker-test -r 96000 -F FLOAT_LE

For anyone else using nix and wanting to save a few minutes, here's the pipewire nix config copied from the OP, only changed device path and supported rates

services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
systemWide = true;
extraConfig.pipewire = {
"50-bitperfect-dac" = {
"context.objects" = [
{
"factory" = "adapter";
"args" = {
"factory.name" = "api.alsa.pcm.sink";
"node.name" = "DAC_Bit_Perfect";
"node.description" = "USB DAC (Bit-Perfect Mode)";
"media.class" = "Audio/Sink";
"api.alsa.path" = "hw:3,0";
"api.alsa.period-size" = 1024;
"api.alsa.headroom" = 0;
"audio.format" = "S32LE";
"audio.channels" = 2;
"node.suspend-on-idle" = false;
"priority.driver" = 9000;
"priority.session" = 9000;
"resample.disable" = true;
"monitor.channel-volumes" = false;
};
}
];
# Clock and rate settings for bit-perfect operation
"context.properties" = {
# Fallback rate when no stream is active.
# NOT the playback rate — with resample.disable and allowed-rates,
# the DAC will switch to match the source stream's native rate.
# Set to 44100 if most content is CD-quality, 48000 for video/gaming.
"default.clock.rate" = 44100;

# Rates PipeWire may negotiate with the DAC.
# MUST match your DAC's supported rates from stream0.
# Do NOT include rates your DAC doesn't support.
"default.clock.allowed-rates" = [
44100
48000
88200
96000
176400
192000
352800
384000
705600
768000
];

# Buffer size in frames. 1024 = ~23ms at 44100Hz.
# Lower = less latency, higher xrun risk.
# Higher = safer, more latency.
"default.clock.quantum" = 1024;

# Bounds for dynamic quantum adjustment.
"default.clock.min-quantum" = 32;
"default.clock.max-quantum" = 8192;
};
# PipeWire-Pulse tuning for bit-perfect playback
"pulse.properties" = {
# Internal processing format — F32 gives maximum headroom
"pulse.default.format" = "F32";
};

"stream.properties" = {
# Maximum SoXR quality for when resampling DOES occur.
# In a bit-perfect setup this should rarely fire — only when
# two streams at different rates must be mixed, or an app
# outputs a rate not in allowed-rates.
# This is a safety net, not a primary control.
"resample.quality" = 15;

# Disable channel mixing modifications
"channelmix.normalize" = false;
"channelmix.upmix" = false;
"channelmix.upmix-method" = "none";
"channelmix.mix-lfe" = false;
};
};
};
};
 
Back
Top Bottom