Fixing an issue with PipeWire and Spotify

2022-04-21 updated 2023-01-20

2023 update: This seems to be working fine with newer versions of PipeWire.

I'd notice that Spotify was dropping out and stuttering when I received notifications from Slack. I suspected an issue with sample rates or buffer size.

A search for pipewire in NixOS options led to pipewire.conf.in, which includes the defaults.

I found PulseAudio & PipeWire documentation, by searching for "pulse.min.quantum", which introduced the meaning of the settings.

I also searched for "spotify stutter pipewire", which turned up some issues, which suggested configuring some PipeWire options (rather than PipeWire-PulseAudio options). This also suggested using pw-top for debugging the issue.

I saw the following output:

S   ID  QUANT   RATE    WAIT    BUSY   W/Q   B/Q  ERR  NAME
    55   1024  48000 250.6µs  34.6µs  0.01  0.00    0  alsa_output.usb-Schiit_Audio_I_m_Fulla_Schiit-00.analog-stereo
    89   8192  44100  96.7µs 698.2µs  0.00  0.03    0   + spotify
    84   1024  48000  99.0µs  55.8µs  0.00  0.00    0   + Chromium

From this, I noticed that the quantum value ("QUANT") and the sample rate ("RATE") were mismatched between spotify and Chromium (since, of course, Slack is a web app).

Suspecting a sample rate mismatch, I read through the PipeWire options and found a way to set the sample rate from the command-line, as well as a way to read the current settings, which let me see what my random experiments with writing configuration files was actually doing.

$ pw-metadata -n settings
Found "settings" metadata 30
update: id:0 key:'log.level' value:'2' type:''
update: id:0 key:'clock.rate' value:'48000' type:''
update: id:0 key:'clock.allowed-rates' value:'[ 48000 ]' type:''
update: id:0 key:'clock.quantum' value:'1024' type:''
update: id:0 key:'clock.min-quantum' value:'32' type:''
update: id:0 key:'clock.max-quantum' value:'8096' type:''
update: id:0 key:'clock.force-quantum' value:'0' type:''
update: id:0 key:'clock.force-rate' value:'0' type:''

I set the clock rate and observed that it changed in the settings and pw-top, but this didn't fix the issue.

pw-metadata -n settings 0 clock.allowed-rates '[ 44100 ]'
pw-metadata -n settings 0 clock.rate 44100

I then stared at pw-top for a while to see if I could identify the point at which audio dropouts occurred, and noticed that it happened when the "QUANT" value for the output device jumped from 512 to 8192:

S   ID  QUANT   RATE    WAIT    BUSY   W/Q   B/Q  ERR  NAME
    55    512  44100 269.7µs  57.5µs  0.02  0.00    0  alsa_output.usb-Schiit_Audio_I_m_Fulla_Schiit-00.analog-stereo
    89   8192  44100  99.1µs  43.3µs  0.01  0.00    0   + spotify
    87   1024  48000 135.2µs  61.3µs  0.01  0.01    0   + Chromium

And after Chromium stopped:

    55   8192  44100 205.4µs  34.5µs  0.01  0.00    0  alsa_output.usb-Schiit_Audio_I_m_Fulla_Schiit-00.analog-stereo
    89   8192  44100  54.8µs  61.9µs  0.00  0.00    0   + spotify

So I adjusted the value of clock.max-quantum using pw-metadata:

pw-metadata -n settings 0 clock.max-quantum 1024

I noted that the quantum value in pw-top changed immediately, and I could no longer hear audio dropouts when the Chromium process exited. I could also manually trigger the stutter by setting the value between 512 and 8192!

So I patched this in my NixOS configuration with the following:

  services.pipewire = {
    enable = true;
    alsa.enable = true;
    pulse.enable = true;
    config.pipewire = {
      "context.properties" = {
        "default.clock.max-quantum" = 1024;  # avoid stutter when Spotify raises quantum suddenly
      };
    };
  };

I might also decide to leave the sample rate fixed to 44.1k in the future, but it's not like I can hear resampling noise.

Appendix 1: Summary of failed attempts

Appendix 2: Retrospective

If I'd done only the following, I wouldn't have needed to do anything else (the "critical path"):

As it was, this took me 1 hour to diagnose and fix, and I spent 45 minutes writing up this document.