1 { config, lib, pkgs, ... }:
4 cfg = config.hardware.pulseaudio;
6 hasZeroconf = let z = cfg.zeroconf; in z.publish.enable || z.discovery.enable;
8 overriddenPackage = cfg.package.override
9 (lib.optionalAttrs hasZeroconf { zeroconfSupport = true; });
10 binary = "${lib.getBin overriddenPackage}/bin/pulseaudio";
11 binaryNoDaemon = "${binary} --daemonize=no";
13 # Forces 32bit pulseaudio and alsa-plugins to be built/supported for apps
14 # using 32bit alsa on 64bit linux.
15 enable32BitAlsaPlugins = cfg.support32Bit && pkgs.stdenv.hostPlatform.isx86_64 && (pkgs.pkgsi686Linux.alsa-lib != null && pkgs.pkgsi686Linux.libpulseaudio != null);
20 addModuleIf = cond: mod: lib.optionalString cond "load-module ${mod}";
21 allAnon = lib.optional cfg.tcp.anonymousClients.allowAll "auth-anonymous=1";
22 ipAnon = let a = cfg.tcp.anonymousClients.allowedIpRanges;
23 in lib.optional (a != []) ''auth-ip-acl=${lib.concatStringsSep ";" a}'';
24 in pkgs.writeTextFile {
27 .include ${cfg.configFile}
28 ${addModuleIf cfg.zeroconf.publish.enable "module-zeroconf-publish"}
29 ${addModuleIf cfg.zeroconf.discovery.enable "module-zeroconf-discover"}
30 ${addModuleIf cfg.tcp.enable (lib.concatStringsSep " "
31 ([ "module-native-protocol-tcp" ] ++ allAnon ++ ipAnon))}
32 ${addModuleIf config.services.jack.jackd.enable "module-jack-sink"}
33 ${addModuleIf config.services.jack.jackd.enable "module-jack-source"}
40 uid = ids.uids.pulseaudio;
41 gid = ids.gids.pulseaudio;
43 stateDir = "/run/pulse";
45 # Create pulse/client.conf even if PulseAudio is disabled so
46 # that we can disable the autospawn feature in programs that
47 # are built with PulseAudio support (like KDE).
48 clientConf = pkgs.writeText "client.conf" ''
50 ${cfg.extraClientConf}
53 # Write an /etc/asound.conf that causes all ALSA applications to
54 # be re-routed to the PulseAudio server through ALSA's Pulse
58 libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;
59 ${lib.optionalString enable32BitAlsaPlugins
60 "libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;"}
64 hint.description "Default Audio Device (via PulseAudio)"
67 libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;
68 ${lib.optionalString enable32BitAlsaPlugins
69 "libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;"}
80 hardware.pulseaudio = {
81 enable = lib.mkOption {
82 type = lib.types.bool;
85 Whether to enable the PulseAudio sound server.
89 systemWide = lib.mkOption {
90 type = lib.types.bool;
93 If false, a PulseAudio server is launched automatically for
94 each user that tries to use the sound system. The server runs
95 with user privileges. If true, one system-wide PulseAudio
96 server is launched on boot, running as the user "pulse", and
97 only users in the "pulse-access" group will have access to the server.
98 Please read the PulseAudio documentation for more details.
100 Don't enable this option unless you know what you are doing.
104 support32Bit = lib.mkOption {
105 type = lib.types.bool;
108 Whether to include the 32-bit pulseaudio libraries in the system or not.
109 This is only useful on 64-bit systems and currently limited to x86_64-linux.
113 configFile = lib.mkOption {
114 type = lib.types.nullOr lib.types.path;
116 The path to the default configuration options the PulseAudio server
117 should use. By default, the "default.pa" configuration
118 from the PulseAudio distribution is used.
122 extraConfig = lib.mkOption {
123 type = lib.types.lines;
126 Literal string to append to `configFile`
127 and the config file generated by the pulseaudio module.
131 extraClientConf = lib.mkOption {
132 type = lib.types.lines;
135 Extra configuration appended to pulse/client.conf file.
139 package = lib.mkOption {
140 type = lib.types.package;
141 default = if config.services.jack.jackd.enable
142 then pkgs.pulseaudioFull
143 else pkgs.pulseaudio;
144 defaultText = lib.literalExpression "pkgs.pulseaudio";
145 example = lib.literalExpression "pkgs.pulseaudioFull";
147 The PulseAudio derivation to use. This can be used to enable
148 features (such as JACK support, Bluetooth) via the
149 `pulseaudioFull` package.
153 extraModules = lib.mkOption {
154 type = lib.types.listOf lib.types.package;
156 example = lib.literalExpression "[ pkgs.pulseaudio-modules-bt ]";
158 Extra pulseaudio modules to use. This is intended for out-of-tree
159 pulseaudio modules like extra bluetooth codecs.
161 Extra modules take precedence over built-in pulseaudio modules.
166 logLevel = lib.mkOption {
167 type = lib.types.str;
170 The log level that the system-wide pulseaudio daemon should use,
175 config = lib.mkOption {
176 type = lib.types.attrsOf lib.types.unspecified;
178 description = "Config of the pulse daemon. See `man pulse-daemon.conf`.";
179 example = lib.literalExpression ''{ realtime-scheduling = "yes"; }'';
185 lib.mkEnableOption "discovery of pulseaudio sinks in the local network";
187 lib.mkEnableOption "publishing the pulseaudio sink in the local network";
190 # TODO: enable by default?
192 enable = lib.mkEnableOption "tcp streaming support";
195 allowAll = lib.mkEnableOption "all anonymous clients to stream to the server";
196 allowedIpRanges = lib.mkOption {
197 type = lib.types.listOf lib.types.str;
199 example = lib.literalExpression ''[ "127.0.0.1" "192.168.1.0/24" ]'';
201 A list of IP subnets that are allowed to stream to the server.
212 config = lib.mkIf cfg.enable (lib.mkMerge [
214 environment.etc."pulse/client.conf".source = clientConf;
216 environment.systemPackages = [ overriddenPackage ];
219 "alsa/conf.d/99-pulseaudio.conf".text = alsaConf;
221 "pulse/daemon.conf".source = pkgs.writeText "daemon.conf"
222 (lib.generators.toKeyValue {} cfg.daemon.config);
224 "openal/alsoft.conf".source = pkgs.writeText "alsoft.conf" "drivers=pulse";
226 "libao.conf".source = pkgs.writeText "libao.conf" "default_driver=pulse";
229 hardware.pulseaudio.configFile = lib.mkDefault "${lib.getBin overriddenPackage}/etc/pulse/default.pa";
231 # Disable flat volumes to enable relative ones
232 hardware.pulseaudio.daemon.config.flat-volumes = lib.mkDefault "no";
234 # Upstream defaults to speex-float-1 which results in audible artifacts
235 hardware.pulseaudio.daemon.config.resample-method = lib.mkDefault "speex-float-5";
237 # Allow PulseAudio to get realtime priority using rtkit.
238 security.rtkit.enable = true;
240 systemd.packages = [ overriddenPackage ];
242 # PulseAudio is packaged with udev rules to handle various audio device quirks
243 services.udev.packages = [ overriddenPackage ];
246 (lib.mkIf (cfg.extraModules != []) {
247 hardware.pulseaudio.daemon.config.dl-search-path = let
248 overriddenModules = builtins.map
249 (drv: drv.override { pulseaudio = overriddenPackage; })
251 modulePaths = builtins.map
252 (drv: "${drv}/lib/pulseaudio/modules")
253 # User-provided extra modules take precedence
254 (overriddenModules ++ [ overriddenPackage ]);
255 in lib.concatStringsSep ":" modulePaths;
258 (lib.mkIf hasZeroconf {
259 services.avahi.enable = true;
261 (lib.mkIf cfg.zeroconf.publish.enable {
262 services.avahi.publish.enable = true;
263 services.avahi.publish.userServices = true;
266 (lib.mkIf (!cfg.systemWide) {
268 "pulse/default.pa".source = myConfigFile;
271 services.pulseaudio = {
272 restartIfChanged = true;
274 RestartSec = "500ms";
275 PassEnvironment = "DISPLAY";
277 } // lib.optionalAttrs config.services.jack.jackd.enable {
278 environment.JACK_PROMISCUOUS_SERVER = "jackaudio";
280 sockets.pulseaudio = {
281 wantedBy = [ "sockets.target" ];
286 (lib.mkIf cfg.systemWide {
287 users.users.pulse = {
288 # For some reason, PulseAudio wants UID == GID.
289 uid = assert uid == gid; uid;
291 extraGroups = [ "audio" ];
292 description = "PulseAudio system service user";
299 users.groups.pulse.gid = gid;
300 users.groups.pulse-access = {};
302 systemd.services.pulseaudio = {
303 description = "PulseAudio System-Wide Server";
304 wantedBy = [ "sound.target" ];
305 before = [ "sound.target" ];
306 environment.PULSE_RUNTIME_PATH = stateDir;
309 ExecStart = "${binaryNoDaemon} --log-level=${cfg.daemon.logLevel} --system -n --file=${myConfigFile}";
310 Restart = "on-failure";
311 RestartSec = "500ms";
315 environment.variables.PULSE_COOKIE = "${stateDir}/.config/pulse/cookie";