PipeWire: Difference between revisions

Wimpy (talk | contribs)
Correct the PulseAudio backend configuration as per: https://docs.pipewire.org/page_module_protocol_pulse.html
My first edit on this wiki! I struggled quite a bit with configuring wireplumber to fix an issue that i've seen a ton of people deal with, about wireplumber switching modes when you'd least want it to, causing all sorts of interference. Disabling that function on Nix is an easy one liner, but takes a lot of effort to get there through heaps of documentation. So i decided to add the code snippet. Also fixed an error and mentioned that .lua files have been scrapped in wireplumber 5.0. :D
 
(15 intermediate revisions by 9 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 set it to false if you had it set previously, as sound.enable is only meant for ALSA-based configurations
  # On NixOS 24.05 or older, this option must be set:
  #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>


Use the <code>services.pipewire.extraConfig</code> option hierarchy in NixOS (available from 24.05 onwards) to create drop-in configuration files, if needed.
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.


==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, with 24.05 you can use <code>services.pipewire.wireplumber.configPackages</code> to create drop-in 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">
services.pipewire.wireplumber.configPackages = [
  services.pipewire.wireplumber.extraConfig."10-bluez" = {
(pkgs.writeTextDir "share/wireplumber/bluetooth.lua.d/51-bluez-config.lua" ''
    "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>


Once [https://github.com/NixOS/nixpkgs/pull/292115 #292115] is merged and has reached nixos-unstable, you'll be able to use <code>services.pipewire.wireplumber.extraLuaConfig</code> as well:
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>:


<syntaxHighlight lang=nix>
<syntaxhighlight lang="nix">
services.pipewire.wireplumber.extraLuaConfig.bluetooth."51-bluez-config" = ''
  services.pipewire.wireplumber.configPackages = [
bluez_monitor.properties = {
    (pkgs.writeTextDir "share/wireplumber/wireplumber.conf.d/10-bluez.conf" ''
["bluez5.enable-sbc-xq"] = true,
      monitor.bluez.properties = {
["bluez5.enable-msbc"] = true,
        bluez5.enable-sbc-xq = true
["bluez5.enable-hw-volume"] = true,
        bluez5.enable-msbc = true
["bluez5.headset-roles"] = "[ hsp_hs hsp_ag hfp_hf hfp_ag ]"
        bluez5.enable-hw-volume = true
}
        bluez5.roles = [hsp_hs hsp_ag hfp_hf hfp_ag]
'';
      }
</syntaxHighlight>
    '')
 
  ];
If you are still using 23.11 or earlier, you can specify these files in <code>environment.etc</code>:
</syntaxhighlight>
 
<syntaxHighlight lang=nix>
environment.etc = {
"wireplumber/bluetooth.lua.d/51-bluez-config.lua".text = ''
bluez_monitor.properties = {
["bluez5.enable-sbc-xq"] = true,
["bluez5.enable-msbc"] = true,
["bluez5.enable-hw-volume"] = true,
["bluez5.headset-roles"] = "[ hsp_hs hsp_ag hfp_hf hfp_ag ]"
}
'';
};
</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.


If you're still 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:
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].
 
<syntaxHighlight lang=nix>
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 109: 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").
Line 122: 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.


For NixOS 24.05 and newer:
<syntaxHighlight lang=nix>
<syntaxHighlight lang=nix>
services.pipewire.extraConfig.pipewire."91-null-sinks" = {
  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";
      };
    }
  ];
};
</syntaxHighlight>
 
If you're still using 23.11 or earlier, you can use <code>environment.etc</code> and <code>pkgs.formats.json</code>:
<syntaxHighlight lang=nix>
environment.etc = let
  json = pkgs.formats.json {};
in {
  "pipewire/pipewire.d/91-null-sinks.conf".source = json.generate "91-null-sinks.conf" {
     "context.objects" = [
     "context.objects" = [
       {
       {
Line 172: 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 180: 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 190: 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>


Line 220: 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:
For 24.05 and newer:
 
<syntaxHighlight lang=nix>
<syntaxHighlight lang=nix>
services.pipewire.extraConfig.pipewire."92-low-latency" = {
  services.pipewire.extraConfig.pipewire."92-low-latency" = {
  "context.properties" = {
    "context.properties" = {
    "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;
    };
   };
   };
};
</syntaxHighlight>
</syntaxHighlight>
If you're still using 23.11 or earlier, you can use <code>environment.etc</code> and <code>pkgs.formats.json</code> like in [[PipeWire#Advanced_Configuration|Advanced Configuration]].


<strong>NOTE</strong>: 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.
<strong>NOTE</strong>: 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.
Line 238: 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.
For 24.05 or newer:
 
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
services.pipewire.extraConfig.pipewire-pulse."92-low-latency" = {
  services.pipewire.extraConfig.pipewire-pulse."92-low-latency" = {
  "context.properties" = [
    "context.properties" = [
    {
      {
      name = "libpipewire-module-protocol-pulse";
        name = "libpipewire-module-protocol-pulse";
      args = { };
        args = { };
    }
      }
  ];
    ];
  "pulse.properties" = {
    "pulse.properties" = {
    "pulse.min.req" = "32/48000";
      "pulse.min.req" = "32/48000";
    "pulse.default.req" = "32/48000";
      "pulse.default.req" = "32/48000";
    "pulse.max.req" = "32/48000";
      "pulse.max.req" = "32/48000";
    "pulse.min.quantum" = "32/48000";
      "pulse.min.quantum" = "32/48000";
    "pulse.max.quantum" = "32/48000";
      "pulse.max.quantum" = "32/48000";
    };
    "stream.properties" = {
      "node.latency" = "32/48000";
      "resample.quality" = 1;
    };
   };
   };
  "stream.properties" = {
    "node.latency" = "32/48000";
    "resample.quality" = 1;
  };
};
</syntaxhighlight>
</syntaxhighlight>
If you're still using 23.11 or earlier, you can use <code>environment.etc</code> and <code>pkgs.formats.json</code> like in [[PipeWire#Advanced_Configuration|Advanced Configuration]].


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>.
Line 266: Line 187:
===Controlling the ALSA devices===
===Controlling the ALSA devices===
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:
For 24.05 and newer:
 
<syntaxHighlight lang=nix>
<syntaxHighlight lang=nix>
services.pipewire.wireplumber.configPackages = [
  services.pipewire.wireplumber.configPackages = [
  (pkgs.writeTextDir "share/wireplumber/main.lua.d/99-alsa-lowlatency.lua" ''
    (pkgs.writeTextDir "share/wireplumber/main.lua.d/99-alsa-lowlatency.lua" ''
    alsa_monitor.rules = {
      alsa_monitor.rules = {
      {
        {
        matches = {{{ "node.name", "matches", "alsa_output.*" }}};
          matches = {{{ "node.name", "matches", "alsa_output.*" }}};
        apply_properties = {
          apply_properties = {
          ["audio.format"] = "S32LE",
            ["audio.format"] = "S32LE",
          ["audio.rate"] = "96000", -- for USB soundcards it should be twice your desired rate
            ["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.period-size"] = 2, -- defaults to 1024, tweak by trial-and-error
          -- ["api.alsa.disable-batch"] = true, -- generally, USB soundcards use the batch mode
            -- ["api.alsa.disable-batch"] = true, -- generally, USB soundcards use the batch mode
          },
         },
         },
       },
       }
     }
     '')
  '')
  ];
];
</syntaxHighlight>
</syntaxHighlight>
Once [https://github.com/NixOS/nixpkgs/pull/292115 #292115] is merged and has reached nixos-unstable, you'll be able to use <code>services.pipewire.wireplumber.extraLuaConfig</code> as well:
<syntaxHighlight lang=nix>
services.pipewire.wireplumber.extraLuaConfig.main."99-alsa-lowlatency" = ''
  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>
If you're still using 23.11 or earlier, you can use <code>environment.etc</code>:
<syntaxHighlight lang=nix>
environment.etc.
"wireplumber/main.lua.d/99-alsa-lowlatency.lua".text = ''
  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
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
Line 323: Line 210:
$ pw-dump | grep node.name | grep alsa
$ pw-dump | grep node.name | grep alsa
</syntaxHighlight>
</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==
==Troubleshooting==