PipeWire: Difference between revisions

From NixOS Wiki
imported>Iopq
more details on how to increase log level
Writer (talk | contribs)
Explain how to disable sink suspension in wireplumber to avoid sound pops.
 
(42 intermediate revisions by 23 users not shown)
Line 1: Line 1:
PipeWire is a new low-level multimedia framework. It aims to offer capture and playback for both audio and video with minimal latency and support for PulseAudio-, JACK-, ALSA- and GStreamer-based applications. PipeWire has a great bluetooth support: because Pulseaudio was [https://github.com/NixOS/nixpkgs/issues/123784 reported to have troubles with bluetooth], PipeWire can be a good alternative.  
 
[https://www.pipewire.org/ PipeWire] is a relatively new (first release in 2017) low-level multimedia framework. It aims to offer capture and playback for both audio and video with minimal latency and support for [[PulseAudio]]-, [[JACK]]-, [[ALSA]]- and [[GStreamer]]-based applications. PipeWire has a great [[Bluetooth|bluetooth]] support: because Pulseaudio was [https://github.com/NixOS/nixpkgs/issues/123784 reported to have troubles with bluetooth], PipeWire can be a good alternative.


The daemon based on the framework can be configured to be both an audio server (with PulseAudio and JACK features) and a video capture server.
The daemon based on the framework can be configured to be both an audio server (with PulseAudio and JACK features) and a video capture server.


PipeWire also supports containers like Flatpak and does not rely on audio and video user groups but rather it uses a Polkit-like security model asking Flatpak or Wayland for permission to record screen or audio.  
PipeWire also supports containers like [[Flatpak]] and does not rely on <code>audio</code> and <code>video</code> user groups, but rather it uses a [[Polkit]]-like security model, asking Flatpak or [[Wayland]] for permission to record screen or audio.


==Enabling PipeWire==
==Enabling PipeWire==
Add to your configuration:
PipeWire can be enabled with the following configuration:


<syntaxHighlight lang="nix">
<syntaxhighlight lang="nix">
# Remove sound.enable or turn it off if you had it set previously, it seems to cause conflicts with pipewire
  # On NixOS 24.05 or older, this option must be set:
#sound.enable = false;
  #sound.enable = false;


# rtkit is optional but recommended
  # rtkit is optional but recommended
security.rtkit.enable = true;
  security.rtkit.enable = true;
services.pipewire = {
  services.pipewire = {
  enable = true;
    enable = true;
  alsa.enable = true;
    alsa.enable = true;
  alsa.support32Bit = true;
    alsa.support32Bit = true;
  pulse.enable = true;
    pulse.enable = true;
  # If you want to use JACK applications, uncomment this
    # If you want to use JACK applications, uncomment this
  #jack.enable = true;
    #jack.enable = true;
};
  };
</syntaxHighlight>
</syntaxhighlight>


The option definition <code>services.pipewire.config</code> no longer has any effect; overriding default Pipewire configuration through NixOS options never worked correctly and is no longer supported. Please create drop-in files in <code>/etc/pipewire/pipewire.conf.d/</code> to make the desired setting changes instead. For example, you can create a file <code>/etc/pipewire/pipewire.conf.d/pipewire.conf</code> with the contents
It is possible to use the <code>services.pipewire.extraConfig</code> option hierarchy in NixOS (available from 24.05 onwards) to create drop-in configuration files, if needed.
 
<syntaxHighlight>
context.properties = {
    log.level = 5
}
</syntaxHighlight>
 
this will increase the log level to 5


==Bluetooth Configuration==
==Bluetooth Configuration==
PipeWire can be configured to use specific codecs. The mSBC codec provides slightly better sound quality in calls than regular HFP/HSP, while the SBC-XQ provides better sound quality for audio listening. For more information [https://www.guyrutenberg.com/2021/03/11/replacing-pulseaudio-with-pipewire/ see this link].
PipeWire can be configured to use specific codecs, by default all codecs and most connection modes are enabled, see [https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration/bluetooth.html#monitor-properties this link] for precise details of which connections modes are enabled by default. The mSBC codec provides slightly better sound quality in calls than regular HFP/HSP, while the SBC-XQ provides better sound quality for audio listening. For more information [https://www.guyrutenberg.com/2021/03/11/replacing-pulseaudio-with-pipewire/ see this link].


Wireplumber (<code>services.pipewire.wireplumber</code>) is the default modular session / policy manager for PipeWire in unstable, however there is currently no way to configure the advanced settings using the module. However, you can just drop the Lua files directly in the expected path (<code>/etc/wireplumber/bluetooth.lua.d</code>). For example:
Wireplumber (<code>services.pipewire.wireplumber</code>) is the default modular session / policy manager for PipeWire in unstable. To add custom configuration in NixOS 24.05 you can use <code>services.pipewire.wireplumber.extraConfig</code> directly. For example:


<syntaxHighlight lang="nix">
<syntaxhighlight lang="nix">
environment.etc = {
  services.pipewire.wireplumber.extraConfig."10-bluez" = {
"wireplumber/bluetooth.lua.d/51-bluez-config.lua".text = ''
    "monitor.bluez.properties" = {
bluez_monitor.properties = {
      "bluez5.enable-sbc-xq" = true;
["bluez5.enable-sbc-xq"] = true,
      "bluez5.enable-msbc" = true;
["bluez5.enable-msbc"] = true,
      "bluez5.enable-hw-volume" = true;
["bluez5.enable-hw-volume"] = true,
      "bluez5.roles" = [
["bluez5.headset-roles"] = "[ hsp_hs hsp_ag hfp_hf hfp_ag ]"
        "hsp_hs"
}
        "hsp_ag"
'';
        "hfp_hf"
};
        "hfp_ag"
</syntaxHighlight>
      ];
    };
  };
</syntaxhighlight>


If you want to change in your particular user instead of system-wide, you can add this to <code>~/.config/wireplumber/bluetooth.lua.d</code> instead, manually or using Home-Manager.
Or, to disable automatic HSP/HFP and A2DP mode switching, which is part of the <code>11-bluetooth-policy</code> configuration:<syntaxhighlight lang="nix">
  services.pipewire.wireplumber.extraConfig."11-bluetooth-policy" = {
    "wireplumber.settings" = {
      "bluetooth.autoswitch-to-headset-profile" = false;
    };
  };
</syntaxhighlight>Alternatively you can set <code>services.pipewire.wireplumber.configPackages</code> as well, adding derivations that output wireplumber config files in <code>$out/share/wireplumber/wireplumber.conf.d/*.conf</code>:


If you're on 21.11 or enabled <code>pipewire-media-session</code> manually (by setting <code>services.pipewire.media-session.enable = true</code>), them you can use the module to configure it:
<syntaxhighlight lang="nix">
  services.pipewire.wireplumber.configPackages = [
    (pkgs.writeTextDir "share/wireplumber/wireplumber.conf.d/10-bluez.conf" ''
      monitor.bluez.properties = {
        bluez5.enable-sbc-xq = true
        bluez5.enable-msbc = true
        bluez5.enable-hw-volume = true
        bluez5.roles = [hsp_hs hsp_ag hfp_hf hfp_ag]
      }
    '')
  ];
</syntaxhighlight>


<syntaxHighlight lang="nix">
It is possible change a particular user instead of system-wide, with adding this to <code>~/.config/wireplumber/bluetooth.conf.d</code> (<code>~/.config/wireplumber/bluetooth.lua.d</code> for wireplumber 4.X and below) instead, manually or using [[Home Manager]]. Refer to [https://wiki.archlinux.org/title/PipeWire ArchWiki] for possible configurations, as well as the [https://docs.pipewire.org/ Full Documentation].
services.pipewire  = {
  media-session.config.bluez-monitor.rules = [
    {
      # Matches all cards
      matches = [ { "device.name" = "~bluez_card.*"; } ];
      actions = {
        "update-props" = {
          "bluez5.reconnect-profiles" = [ "hfp_hf" "hsp_hs" "a2dp_sink" ];
          # mSBC is not expected to work on all headset + adapter combinations.
          "bluez5.msbc-support" = true;
          # SBC-XQ is not expected to work on all headset + adapter combinations.
          "bluez5.sbc-xq-support" = true;
        };
      };
    }
    {
      matches = [
        # Matches all sources
        { "node.name" = "~bluez_input.*"; }
        # Matches all outputs
        { "node.name" = "~bluez_output.*"; }
      ];
    }
  ];
};
</syntaxHighlight>


==Graphical tools==
==Graphical tools==
Line 89: Line 76:


* pavucontrol: controls the volume (per-sink and per-app basis), the default outputs/inputs, the different profiles (for HDMI outputs/bluetooth devices), routes each application to a different input/output, etc.
* pavucontrol: controls the volume (per-sink and per-app basis), the default outputs/inputs, the different profiles (for HDMI outputs/bluetooth devices), routes each application to a different input/output, etc.
* plasma-pa: a Plasma applet to change volume directly from the systray. Also deals with volume keys.
* plasma-pa: a [[KDE|Plasma]] applet to change volume directly from the systray. Also deals with volume keys.
* qjackctl: with JACK emulation, provides a patchbay (to connect applications together). Note that JACK does not provide any way to change the volume of a single application; use Pulseaudio tools for that purpose.
* qjackctl: with JACK emulation, provides a patchbay (to connect applications together). Note that JACK does not provide any way to change the volume of a single application; use Pulseaudio tools for that purpose.
* carla: with JACK emulation, provides a patchbay (make sure to go to "Patchbay" tab and check "Canvas > Show External").
* carla: with JACK emulation, provides a patchbay (make sure to go to "Patchbay" tab and check "Canvas > Show External").
* catia/patchage: similar to qjackctl and carla.
* catia/patchage: similar to qjackctl and carla.
* [https://gitlab.freedesktop.org/ryuukyu/helvum/ Helvum]: GTK-based patchbay for PipeWire (uses the PipeWire protocol). Volume control is planned for later.
* [https://gitlab.freedesktop.org/pipewire/helvum Helvum]: GTK-based patchbay for PipeWire (uses the PipeWire protocol). Volume control is planned for later.


==Advanced Configuration==
==Advanced Configuration==
Line 102: Line 89:
<strong>Note</strong>: those cards can be set to the "Pro Audio" profile with <code>pavucontrol</code> so PipeWire doesn't try to guess a wrong channel layout for them.
<strong>Note</strong>: those cards can be set to the "Pro Audio" profile with <code>pavucontrol</code> so PipeWire doesn't try to guess a wrong channel layout for them.


<syntaxHighlight lang="nix">
<syntaxHighlight lang=nix>
services.pipewire = {
  services.pipewire.extraConfig.pipewire."91-null-sinks" = {
  config.pipewire = {
     "context.objects" = [
     "context.objects" = [
       {
       {
Line 111: Line 97:
         factory = "spa-node-factory";
         factory = "spa-node-factory";
         args = {
         args = {
           "factory.name"     = "support.node.driver";
           "factory.name" = "support.node.driver";
           "node.name"       = "Dummy-Driver";
           "node.name" = "Dummy-Driver";
           "priority.driver" = 8000;
           "priority.driver" = 8000;
         };
         };
       }
       }
Line 119: Line 105:
         factory = "adapter";
         factory = "adapter";
         args = {
         args = {
           "factory.name"     = "support.null-audio-sink";
           "factory.name" = "support.null-audio-sink";
           "node.name"       = "Microphone-Proxy";
           "node.name" = "Microphone-Proxy";
           "node.description" = "Microphone";
           "node.description" = "Microphone";
           "media.class"     = "Audio/Source/Virtual";
           "media.class" = "Audio/Source/Virtual";
           "audio.position"   = "MONO";
           "audio.position" = "MONO";
         };
         };
       }
       }
Line 129: Line 115:
         factory = "adapter";
         factory = "adapter";
         args = {
         args = {
           "factory.name"     = "support.null-audio-sink";
           "factory.name" = "support.null-audio-sink";
           "node.name"       = "Main-Output-Proxy";
           "node.name" = "Main-Output-Proxy";
           "node.description" = "Main Output";
           "node.description" = "Main Output";
           "media.class"     = "Audio/Sink";
           "media.class" = "Audio/Sink";
           "audio.position"   = "FL,FR";
           "audio.position" = "FL,FR";
         };
         };
       }
       }
     ];
     ];
   };
   };
};
</syntaxHighlight>
</syntaxHighlight>


===Linking nodes===
===Linking nodes===
The config does not currently cover linking nodes together, but this can be fixed with a script. Soundcard names and ports should be replaced with the ones from the user's configuration:
The config does not currently cover linking nodes together, but this can be fixed with a script. Soundcard names and ports should be replaced with the ones from the user's configuration:
<syntaxHighlight lang="bash">
<syntaxHighlight lang=bash>
#!/usr/bin/env bash
#!/usr/bin/env bash


Line 159: Line 144:
Audio production and rhythm games require lower latency audio than general applications. PipeWire can achieve the required latency with much less CPU usage compared to PulseAudio, with the appropriate configuration.
Audio production and rhythm games require lower latency audio than general applications. PipeWire can achieve the required latency with much less CPU usage compared to PulseAudio, with the appropriate configuration.
The minimum period size controls how small a buffer can be. The lower it is, the less latency there is. PipeWire has a value of 32/48000 by default, which amounts to 0.667ms. It can be brought lower if needed:
The minimum period size controls how small a buffer can be. The lower it is, the less latency there is. PipeWire has a value of 32/48000 by default, which amounts to 0.667ms. It can be brought lower if needed:
<syntaxHighlight lang="nix">
 
services.pipewire = {
<syntaxHighlight lang=nix>
  config.pipewire = {
  services.pipewire.extraConfig.pipewire."92-low-latency" = {
     "context.properties" = {
     "context.properties" = {
      "link.max-buffers" = 16;
      "log.level" = 2;
       "default.clock.rate" = 48000;
       "default.clock.rate" = 48000;
       "default.clock.quantum" = 32;
       "default.clock.quantum" = 32;
       "default.clock.min-quantum" = 32;
       "default.clock.min-quantum" = 32;
       "default.clock.max-quantum" = 32;
       "default.clock.max-quantum" = 32;
      "core.daemon" = true;
      "core.name" = "pipewire-0";
     };
     };
    "context.modules" = [
      {
        name = "libpipewire-module-rtkit";
        args = {
          "nice.level" = -15;
          "rt.prio" = 88;
          "rt.time.soft" = 200000;
          "rt.time.hard" = 200000;
        };
        flags = [ "ifexists" "nofail" ];
      }
      { name = "libpipewire-module-protocol-native"; }
      { name = "libpipewire-module-profiler"; }
      { name = "libpipewire-module-metadata"; }
      { name = "libpipewire-module-spa-device-factory"; }
      { name = "libpipewire-module-spa-node-factory"; }
      { name = "libpipewire-module-client-node"; }
      { name = "libpipewire-module-client-device"; }
      {
        name = "libpipewire-module-portal";
        flags = [ "ifexists" "nofail" ];
      }
      {
        name = "libpipewire-module-access";
        args = {};
      }
      { name = "libpipewire-module-adapter"; }
      { name = "libpipewire-module-link-factory"; }
      { name = "libpipewire-module-session-manager"; }
    ];
   };
   };
};
</syntaxHighlight>
</syntaxHighlight>


Line 210: Line 160:
===PulseAudio backend===
===PulseAudio backend===
Applications using the Pulse backend have a separate configuration. The default minimum value is 1024, so it needs to be tweaked if low-latency audio is desired.
Applications using the Pulse backend have a separate configuration. The default minimum value is 1024, so it needs to be tweaked if low-latency audio is desired.
<syntaxHighlight lang="nix">
 
services.pipewire = {
<syntaxhighlight lang="nix">
  config.pipewire-pulse = {
  services.pipewire.extraConfig.pipewire-pulse."92-low-latency" = {
    "context.properties" = {
    "context.properties" = [
      "log.level" = 2;
    };
    "context.modules" = [
      {
        name = "libpipewire-module-rtkit";
        args = {
          "nice.level" = -15;
          "rt.prio" = 88;
          "rt.time.soft" = 200000;
          "rt.time.hard" = 200000;
        };
        flags = [ "ifexists" "nofail" ];
      }
      { name = "libpipewire-module-protocol-native"; }
      { name = "libpipewire-module-client-node"; }
      { name = "libpipewire-module-adapter"; }
      { name = "libpipewire-module-metadata"; }
       {
       {
         name = "libpipewire-module-protocol-pulse";
         name = "libpipewire-module-protocol-pulse";
         args = {
         args = { };
          "pulse.min.req" = "32/48000";
          "pulse.default.req" = "32/48000";
          "pulse.max.req" = "32/48000";
          "pulse.min.quantum" = "32/48000";
          "pulse.max.quantum" = "32/48000";
          "server.address" = [ "unix:native" ];
        };
       }
       }
     ];
     ];
    "pulse.properties" = {
      "pulse.min.req" = "32/48000";
      "pulse.default.req" = "32/48000";
      "pulse.max.req" = "32/48000";
      "pulse.min.quantum" = "32/48000";
      "pulse.max.quantum" = "32/48000";
    };
     "stream.properties" = {
     "stream.properties" = {
       "node.latency" = "32/48000";
       "node.latency" = "32/48000";
Line 248: Line 181:
     };
     };
   };
   };
};
</syntaxhighlight>
</syntaxHighlight>
 
As a general rule, the values in <code>pipewire-pulse</code> should not be lower than the ones in <code>pipewire</code>.
As a general rule, the values in <code>pipewire-pulse</code> should not be lower than the ones in <code>pipewire</code>.
===Controlling the ALSA devices===
===Controlling the ALSA devices===
{{outdated|Pipewire now uses Wireplumber by default, instead of media-session}}
It is possible to configure various aspects of soundcards through PipeWire, including format, period size and batch mode:
It is possible to configure various aspects of soundcards through PipeWire, including format, period size and batch mode:
<syntaxHighlight lang="nix">
 
services.pipewire = {
<syntaxHighlight lang=nix>
   media-session.config.alsa-monitor = {
  services.pipewire.wireplumber.configPackages = [
     rules = [
    (pkgs.writeTextDir "share/wireplumber/main.lua.d/99-alsa-lowlatency.lua" ''
      alsa_monitor.rules = {
        {
          matches = {{{ "node.name", "matches", "alsa_output.*" }}};
          apply_properties = {
            ["audio.format"] = "S32LE",
            ["audio.rate"] = "96000", -- for USB soundcards it should be twice your desired rate
            ["api.alsa.period-size"] = 2, -- defaults to 1024, tweak by trial-and-error
            -- ["api.alsa.disable-batch"] = true, -- generally, USB soundcards use the batch mode
          },
        },
      }
    '')
  ];
</syntaxHighlight>
 
The <code>matches</code> attribute applies the <code>actions</code> to the devices/properties listed there. It is usually used with soundcard names, like shown in the config above. <code><matches></code> can match any of the outputs of
<syntaxHighlight lang=console>
$ pw-dump | grep node.name | grep alsa
</syntaxHighlight>
 
==Headless operation==
PipeWire can run on a headless device (without a GUI) such as a Raspberry Pi connected to a speaker. In that case, it may be preferable to start PipeWire on boot and keep it running rather than only running during an interactive login session. Among other things, this helps prevent a race condition that may occur when socket activation fails to initialize  audio devices in time for their first use, leading to one-time errors after reboots. The following additional configuration facilitates this:
 
<syntaxhighlight lang="nix">
  # Socket activation too slow for headless; start at boot instead.
  services.pipewire.socketActivation = false;
  # Start WirePlumber (with PipeWire) at boot.
  systemd.user.services.wireplumber.wantedBy = [ "default.target" ];
  users.users.<name>.linger = true; # keep user services running
   users.users.<name>.extraGroups = [ ... "audio" ];
</syntaxhighlight>
 
Despite early activation, you may still experience a race condition that prevents audio from working if you play media immediately after a new login such as running an SSH command. If this occurs, try introducing a short delay (e.g. <code>sleep 5</code>) before invoking the media player application.
 
==Troubleshooting==
 
===pactl not found===
 
The <code>pactl</code> functionality is superseded in PipeWire with the native <code>pw-cli</code>, <code>pw-mon</code> and <code>pw-top</code> CLI tools.
When using WirePlumber (which is enabled by default), you can also use <code>wpctl</code> as a <code>pactl</code> alternative with similar high level subcommands.
 
=== No signal detected in audio application from external audio interface ===
This issue was seen when attempting to use a Roland STUDIO-CAPTURE 16x10 Audio Interface to record audio in Ardour8 on NixOS. One possible solution:
 
* Install {{nixos:package|pavucontrol}}.
* Run {{ic|pavucontrol}}, navigate to the configuration tab, and set the Profile for STUDIO-CAPTURE (or your audio interface) to "Pro Audio".
* Ensure the audio interface and Ardour are set to the same sample rate. Ardour8 will accept a configuration where its sample rate does not match the audio interface's sample rate, when using PipeWire, but will not actually be able to record audio unless they match. If you are unable to successfully activate a track's record button, this may be the issue.
 
=== Sound pops a few seconds after playback stops ===
By default Wireplumber suspends a sink after 5 seconds of no playback which can create a sound pop on sensitive audio equipment. You can disable this by providing extra configuration to wireplumber but first you need to find out the name of the problematic sink in the wireplumber namespace.
 
<ol>
<li>'''Find out the name of the sink'''
<ol>
<li>Run <code>pw-top</code> to monitor pipewire processes</li>
<li>Play and pause sound on the problematic device. After 5 seconds the numeric columns of at least one process will disappear. This is the suspension in action. You need the value of the NAME column to refer to this sink in the configuration below. Since the display updates each second, you can't copy the value easily. Rerun <code>pw-top -b</code> and abort with <kbd>CTRL</kbd>+<kbd>C</kbd> to get persistent output.</li>
</ol>
</li>
 
<li>'''Add configuration''':
Assuming the value of the NAME column was <code>alsa_output.usb-ASUSTeK_Xonar_SoundCard-00.iec958-stereo</code>, add the following Nix configuration:
<syntaxhighlight lang="nix">
# Disable suspend of Toslink output to prevent audio popping.
  services.pipewire.wireplumber.extraConfig."99-disable-suspend" = {
     "monitor.alsa.rules" = [
       {
       {
         matches = [ { "node.name" = "alsa_output.*"; } ];
         matches = [
          {
            "node.name" = "alsa_output.usb-ASUSTeK_Xonar_SoundCard-00.iec958-stereo";
          }
        ];
         actions = {
         actions = {
           update-props = {
           update-props = {
             "audio.format" = "S32LE";
             "session.suspend-timeout-seconds" = 0;
            "audio.rate" = 96000; # for USB soundcards it should be twice your desired rate
            "api.alsa.period-size" = 32; # defaults to 1024, tweak by trial-and-error
            #"api.alsa.disable-batch" = true; # generally, USB soundcards use the batch mode
           };
           };
         };
         };
Line 271: Line 270:
     ];
     ];
   };
   };
};
</syntaxhighlight>
</syntaxHighlight>
The value 0 disables suspension entirely. You could also set it to a higher value. 5 (seconds) is the default value.
The <code>matches</code> attribute applies the <code>actions</code> to the devices/properties listed there. It is usually used with soundcard names, like shown in the config above. <code><alsa_device></code> can be one of the outputs of
</li>
<syntaxHighlight lang="bash">
 
$ pw-dump | grep node.name | grep alsa
<li>'''Verify resolution''':
</syntaxHighlight>
After switching to the new configuration you need to also run <code>systemctl --user restart wireplumber</code> as your non-root user to apply the new wireplumber configuration, since wireplumber runs as your non-root user - <code>nixos-rebuild switch</code> is not sufficient. Play and pause sound again and verify that no sound pops occur anymore and observe in <code>pw-top</code> that the numeric columns for that sink do not disappear after 5 seconds.
</li>
 
</ol>


==Troubleshooting==


===Volume keys do not work in KDE, and sound applet is gone===


If volume keys do not work in KDE and/or you don't have the sound applet, make sure to install `plasma-pa` (you may need to restart the session for it to apply). Now added automatically thanks to [https://github.com/NixOS/nixpkgs/pull/123211 this PR (22/05/2021)].


===pactl not found===


If you want to use some Pulseaudio commands like <code>pactl</code>, you can safely install (either systemwide or in a nix-shell) the `pulseaudio` package (do not enable it in the system configuration). The <code>pactl</code> functionality is superseded in PipeWire with the native <code>pw-cli</code>, <code>pw-mon</code> and <code>pw-top</code> CLI tools. When using WirePlumber (which is enabled by default), you can also use <code>wpctl</code> as a <code>pactl</code> alternative with similar high level subcommands.


==See also==
==See also==

Latest revision as of 19:31, 16 November 2024

PipeWire is a relatively new (first release in 2017) low-level multimedia framework. It aims to offer capture and playback for both audio and video with minimal latency and support for PulseAudio-, JACK-, ALSA- and GStreamer-based applications. PipeWire has a great bluetooth support: because Pulseaudio was reported to have troubles with bluetooth, PipeWire can be a good alternative.

The daemon based on the framework can be configured to be both an audio server (with PulseAudio and JACK features) and a video capture server.

PipeWire also supports containers like Flatpak and does not rely on audio and video user groups, but rather it uses a Polkit-like security model, asking Flatpak or Wayland for permission to record screen or audio.

Enabling PipeWire

PipeWire can be enabled with the following configuration:

  # On NixOS 24.05 or older, this option must be set:
  #sound.enable = false;

  # rtkit is optional but recommended
  security.rtkit.enable = true;
  services.pipewire = {
    enable = true;
    alsa.enable = true;
    alsa.support32Bit = true;
    pulse.enable = true;
    # If you want to use JACK applications, uncomment this
    #jack.enable = true;
  };

It is possible to use the services.pipewire.extraConfig option hierarchy in NixOS (available from 24.05 onwards) to create drop-in configuration files, if needed.

Bluetooth Configuration

PipeWire can be configured to use specific codecs, by default all codecs and most connection modes are enabled, see this link for precise details of which connections modes are enabled by default. The mSBC codec provides slightly better sound quality in calls than regular HFP/HSP, while the SBC-XQ provides better sound quality for audio listening. For more information see this link.

Wireplumber (services.pipewire.wireplumber) is the default modular session / policy manager for PipeWire in unstable. To add custom configuration in NixOS 24.05 you can use services.pipewire.wireplumber.extraConfig directly. For example:

  services.pipewire.wireplumber.extraConfig."10-bluez" = {
    "monitor.bluez.properties" = {
      "bluez5.enable-sbc-xq" = true;
      "bluez5.enable-msbc" = true;
      "bluez5.enable-hw-volume" = true;
      "bluez5.roles" = [
        "hsp_hs"
        "hsp_ag"
        "hfp_hf"
        "hfp_ag"
      ];
    };
  };

Or, to disable automatic HSP/HFP and A2DP mode switching, which is part of the 11-bluetooth-policy configuration:

  services.pipewire.wireplumber.extraConfig."11-bluetooth-policy" = {
    "wireplumber.settings" = {
      "bluetooth.autoswitch-to-headset-profile" = false;
    };
  };

Alternatively you can set services.pipewire.wireplumber.configPackages as well, adding derivations that output wireplumber config files in $out/share/wireplumber/wireplumber.conf.d/*.conf:

  services.pipewire.wireplumber.configPackages = [
    (pkgs.writeTextDir "share/wireplumber/wireplumber.conf.d/10-bluez.conf" ''
      monitor.bluez.properties = {
        bluez5.enable-sbc-xq = true
        bluez5.enable-msbc = true
        bluez5.enable-hw-volume = true
        bluez5.roles = [hsp_hs hsp_ag hfp_hf hfp_ag]
      }
    '')
  ];

It is possible change a particular user instead of system-wide, with adding this to ~/.config/wireplumber/bluetooth.conf.d (~/.config/wireplumber/bluetooth.lua.d for wireplumber 4.X and below) instead, manually or using Home Manager. Refer to ArchWiki for possible configurations, as well as the Full Documentation.

Graphical tools

All protocols (Pulseaudio/JACK) are now talking to the PipeWire protocol and are managed by the PipeWire daemon (therefore, applications can be managed by both Pulseaudio and JACK tools). For that reason, all graphical tools used for these protocols can be used:

  • pavucontrol: controls the volume (per-sink and per-app basis), the default outputs/inputs, the different profiles (for HDMI outputs/bluetooth devices), routes each application to a different input/output, etc.
  • plasma-pa: a Plasma applet to change volume directly from the systray. Also deals with volume keys.
  • qjackctl: with JACK emulation, provides a patchbay (to connect applications together). Note that JACK does not provide any way to change the volume of a single application; use Pulseaudio tools for that purpose.
  • carla: with JACK emulation, provides a patchbay (make sure to go to "Patchbay" tab and check "Canvas > Show External").
  • catia/patchage: similar to qjackctl and carla.
  • Helvum: GTK-based patchbay for PipeWire (uses the PipeWire protocol). Volume control is planned for later.

Advanced Configuration

PipeWire can be extensively configured to fit the users' needs. Should the user want to do some fancy routing with null sinks, these can be defined directly in the config as shown below.

This is especially convenient if the user has a multi-channel (8+, or something "weird" like 2x2, 3x2) soundcard that keeps confusing applications with too many channels or a bad channel layout.

Note: those cards can be set to the "Pro Audio" profile with pavucontrol so PipeWire doesn't try to guess a wrong channel layout for them.

  services.pipewire.extraConfig.pipewire."91-null-sinks" = {
    "context.objects" = [
      {
        # A default dummy driver. This handles nodes marked with the "node.always-driver"
        # properyty when no other driver is currently active. JACK clients need this.
        factory = "spa-node-factory";
        args = {
          "factory.name" = "support.node.driver";
          "node.name" = "Dummy-Driver";
          "priority.driver" = 8000;
        };
      }
      {
        factory = "adapter";
        args = {
          "factory.name" = "support.null-audio-sink";
          "node.name" = "Microphone-Proxy";
          "node.description" = "Microphone";
          "media.class" = "Audio/Source/Virtual";
          "audio.position" = "MONO";
        };
      }
      {
        factory = "adapter";
        args = {
          "factory.name" = "support.null-audio-sink";
          "node.name" = "Main-Output-Proxy";
          "node.description" = "Main Output";
          "media.class" = "Audio/Sink";
          "audio.position" = "FL,FR";
        };
      }
    ];
  };

Linking nodes

The config does not currently cover linking nodes together, but this can be fixed with a script. Soundcard names and ports should be replaced with the ones from the user's configuration:

#!/usr/bin/env bash

# ports obtained from `pw-link -io`

pw-link "Main-Output-Proxy:monitor_FL" "alsa_output.usb-Native_Instruments_Komplete_Audio_6_69BC86B9-00.pro-audio:playback_1"
pw-link "Main-Output-Proxy:monitor_FR" "alsa_output.usb-Native_Instruments_Komplete_Audio_6_69BC86B9-00.pro-audio:playback_2"

pw-link "alsa_input.usb-M-Audio_Fast_Track-00.pro-audio:capture_1" "Microphone-Proxy:input_MONO"

In order to load the script on startup, it can be added to ~/.xprofile or the specific DE/WM autostart config. Similarly, a one-shot user service can be created that runs the script.

Low-latency setup

Audio production and rhythm games require lower latency audio than general applications. PipeWire can achieve the required latency with much less CPU usage compared to PulseAudio, with the appropriate configuration. The minimum period size controls how small a buffer can be. The lower it is, the less latency there is. PipeWire has a value of 32/48000 by default, which amounts to 0.667ms. It can be brought lower if needed:

  services.pipewire.extraConfig.pipewire."92-low-latency" = {
    "context.properties" = {
      "default.clock.rate" = 48000;
      "default.clock.quantum" = 32;
      "default.clock.min-quantum" = 32;
      "default.clock.max-quantum" = 32;
    };
  };

NOTE: Every setup is different, and a lot of factors determine your final latency, like CPU speed, RT/PREEMPTIVE kernels and soundcards supporting different audio formats. That's why 32/48000 isn't always a value that's going to work for everyone. The best way to get everything working is to keep increasing the quant value until you get no crackles (underruns) or until you get audio again (in case there wasn't any). This won't guarantee the lowest possible latency, but will provide a decent one paired with stable audio.

PulseAudio backend

Applications using the Pulse backend have a separate configuration. The default minimum value is 1024, so it needs to be tweaked if low-latency audio is desired.

  services.pipewire.extraConfig.pipewire-pulse."92-low-latency" = {
    "context.properties" = [
      {
        name = "libpipewire-module-protocol-pulse";
        args = { };
      }
    ];
    "pulse.properties" = {
      "pulse.min.req" = "32/48000";
      "pulse.default.req" = "32/48000";
      "pulse.max.req" = "32/48000";
      "pulse.min.quantum" = "32/48000";
      "pulse.max.quantum" = "32/48000";
    };
    "stream.properties" = {
      "node.latency" = "32/48000";
      "resample.quality" = 1;
    };
  };

As a general rule, the values in pipewire-pulse should not be lower than the ones in pipewire.

Controlling the ALSA devices

It is possible to configure various aspects of soundcards through PipeWire, including format, period size and batch mode:

  services.pipewire.wireplumber.configPackages = [
    (pkgs.writeTextDir "share/wireplumber/main.lua.d/99-alsa-lowlatency.lua" ''
      alsa_monitor.rules = {
        {
          matches = {{{ "node.name", "matches", "alsa_output.*" }}};
          apply_properties = {
            ["audio.format"] = "S32LE",
            ["audio.rate"] = "96000", -- for USB soundcards it should be twice your desired rate
            ["api.alsa.period-size"] = 2, -- defaults to 1024, tweak by trial-and-error
            -- ["api.alsa.disable-batch"] = true, -- generally, USB soundcards use the batch mode
          },
        },
      }
    '')
  ];

The matches attribute applies the actions to the devices/properties listed there. It is usually used with soundcard names, like shown in the config above. <matches> can match any of the outputs of

$ pw-dump | grep node.name | grep alsa

Headless operation

PipeWire can run on a headless device (without a GUI) such as a Raspberry Pi connected to a speaker. In that case, it may be preferable to start PipeWire on boot and keep it running rather than only running during an interactive login session. Among other things, this helps prevent a race condition that may occur when socket activation fails to initialize audio devices in time for their first use, leading to one-time errors after reboots. The following additional configuration facilitates this:

  # Socket activation too slow for headless; start at boot instead.
  services.pipewire.socketActivation = false; 
  # Start WirePlumber (with PipeWire) at boot.
  systemd.user.services.wireplumber.wantedBy = [ "default.target" ];
  users.users.<name>.linger = true; # keep user services running
  users.users.<name>.extraGroups = [ ... "audio" ];

Despite early activation, you may still experience a race condition that prevents audio from working if you play media immediately after a new login such as running an SSH command. If this occurs, try introducing a short delay (e.g. sleep 5) before invoking the media player application.

Troubleshooting

pactl not found

The pactl functionality is superseded in PipeWire with the native pw-cli, pw-mon and pw-top CLI tools. When using WirePlumber (which is enabled by default), you can also use wpctl as a pactl alternative with similar high level subcommands.

No signal detected in audio application from external audio interface

This issue was seen when attempting to use a Roland STUDIO-CAPTURE 16x10 Audio Interface to record audio in Ardour8 on NixOS. One possible solution:

  • Install pavucontrol.
  • Run pavucontrol, navigate to the configuration tab, and set the Profile for STUDIO-CAPTURE (or your audio interface) to "Pro Audio".
  • Ensure the audio interface and Ardour are set to the same sample rate. Ardour8 will accept a configuration where its sample rate does not match the audio interface's sample rate, when using PipeWire, but will not actually be able to record audio unless they match. If you are unable to successfully activate a track's record button, this may be the issue.

Sound pops a few seconds after playback stops

By default Wireplumber suspends a sink after 5 seconds of no playback which can create a sound pop on sensitive audio equipment. You can disable this by providing extra configuration to wireplumber but first you need to find out the name of the problematic sink in the wireplumber namespace.

  1. Find out the name of the sink
    1. Run pw-top to monitor pipewire processes
    2. Play and pause sound on the problematic device. After 5 seconds the numeric columns of at least one process will disappear. This is the suspension in action. You need the value of the NAME column to refer to this sink in the configuration below. Since the display updates each second, you can't copy the value easily. Rerun pw-top -b and abort with CTRL+C to get persistent output.
  2. Add configuration: Assuming the value of the NAME column was alsa_output.usb-ASUSTeK_Xonar_SoundCard-00.iec958-stereo, add the following Nix configuration:
     # Disable suspend of Toslink output to prevent audio popping.
      services.pipewire.wireplumber.extraConfig."99-disable-suspend" = {
        "monitor.alsa.rules" = [
          {
            matches = [
              {
                "node.name" = "alsa_output.usb-ASUSTeK_Xonar_SoundCard-00.iec958-stereo";
              }
            ];
            actions = {
              update-props = {
                "session.suspend-timeout-seconds" = 0;
              };
            };
          }
        ];
      };
    

    The value 0 disables suspension entirely. You could also set it to a higher value. 5 (seconds) is the default value.

  3. Verify resolution: After switching to the new configuration you need to also run systemctl --user restart wireplumber as your non-root user to apply the new wireplumber configuration, since wireplumber runs as your non-root user - nixos-rebuild switch is not sufficient. Play and pause sound again and verify that no sound pops occur anymore and observe in pw-top that the numeric columns for that sink do not disappear after 5 seconds.




See also