1 { config, lib, pkgs, ... }:
8 cfg = config.hardware.pulseaudio;
9 alsaCfg = config.sound;
11 systemWide = cfg.enable && cfg.systemWide;
12 nonSystemWide = cfg.enable && !cfg.systemWide;
13 hasZeroconf = let z = cfg.zeroconf; in z.publish.enable || z.discovery.enable;
15 overriddenPackage = cfg.package.override
16 (optionalAttrs hasZeroconf { zeroconfSupport = true; });
17 binary = "${getBin overriddenPackage}/bin/pulseaudio";
18 binaryNoDaemon = "${binary} --daemonize=no";
20 # Forces 32bit pulseaudio and alsa-plugins to be built/supported for apps
21 # using 32bit alsa on 64bit linux.
22 enable32BitAlsaPlugins = cfg.support32Bit && stdenv.isx86_64 && (pkgs.pkgsi686Linux.alsa-lib != null && pkgs.pkgsi686Linux.libpulseaudio != null);
27 addModuleIf = cond: mod: optionalString cond "load-module ${mod}";
28 allAnon = optional cfg.tcp.anonymousClients.allowAll "auth-anonymous=1";
29 ipAnon = let a = cfg.tcp.anonymousClients.allowedIpRanges;
30 in optional (a != []) ''auth-ip-acl=${concatStringsSep ";" a}'';
34 .include ${cfg.configFile}
35 ${addModuleIf cfg.zeroconf.publish.enable "module-zeroconf-publish"}
36 ${addModuleIf cfg.zeroconf.discovery.enable "module-zeroconf-discover"}
37 ${addModuleIf cfg.tcp.enable (concatStringsSep " "
38 ([ "module-native-protocol-tcp" ] ++ allAnon ++ ipAnon))}
39 ${addModuleIf config.services.jack.jackd.enable "module-jack-sink"}
40 ${addModuleIf config.services.jack.jackd.enable "module-jack-source"}
47 uid = ids.uids.pulseaudio;
48 gid = ids.gids.pulseaudio;
50 stateDir = "/run/pulse";
52 # Create pulse/client.conf even if PulseAudio is disabled so
53 # that we can disable the autospawn feature in programs that
54 # are built with PulseAudio support (like KDE).
55 clientConf = writeText "client.conf" ''
57 ${cfg.extraClientConf}
60 # Write an /etc/asound.conf that causes all ALSA applications to
61 # be re-routed to the PulseAudio server through ALSA's Pulse
63 alsaConf = writeText "asound.conf" (''
65 libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;
66 ${lib.optionalString enable32BitAlsaPlugins
67 "libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;"}
71 hint.description "Default Audio Device (via PulseAudio)"
74 libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;
75 ${lib.optionalString enable32BitAlsaPlugins
76 "libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;"}
81 ${alsaCfg.extraConfig}
88 hardware.pulseaudio = {
92 description = lib.mdDoc ''
93 Whether to enable the PulseAudio sound server.
97 systemWide = mkOption {
100 description = lib.mdDoc ''
101 If false, a PulseAudio server is launched automatically for
102 each user that tries to use the sound system. The server runs
103 with user privileges. If true, one system-wide PulseAudio
104 server is launched on boot, running as the user "pulse", and
105 only users in the "pulse-access" group will have access to the server.
106 Please read the PulseAudio documentation for more details.
108 Don't enable this option unless you know what you are doing.
112 support32Bit = mkOption {
115 description = lib.mdDoc ''
116 Whether to include the 32-bit pulseaudio libraries in the system or not.
117 This is only useful on 64-bit systems and currently limited to x86_64-linux.
121 configFile = mkOption {
122 type = types.nullOr types.path;
123 description = lib.mdDoc ''
124 The path to the default configuration options the PulseAudio server
125 should use. By default, the "default.pa" configuration
126 from the PulseAudio distribution is used.
130 extraConfig = mkOption {
133 description = lib.mdDoc ''
134 Literal string to append to `configFile`
135 and the config file generated by the pulseaudio module.
139 extraClientConf = mkOption {
142 description = lib.mdDoc ''
143 Extra configuration appended to pulse/client.conf file.
148 type = types.package;
149 default = if config.services.jack.jackd.enable
150 then pkgs.pulseaudioFull
151 else pkgs.pulseaudio;
152 defaultText = literalExpression "pkgs.pulseaudio";
153 example = literalExpression "pkgs.pulseaudioFull";
154 description = lib.mdDoc ''
155 The PulseAudio derivation to use. This can be used to enable
156 features (such as JACK support, Bluetooth) via the
157 `pulseaudioFull` package.
161 extraModules = mkOption {
162 type = types.listOf types.package;
164 example = literalExpression "[ pkgs.pulseaudio-modules-bt ]";
165 description = lib.mdDoc ''
166 Extra pulseaudio modules to use. This is intended for out-of-tree
167 pulseaudio modules like extra bluetooth codecs.
169 Extra modules take precedence over built-in pulseaudio modules.
174 logLevel = mkOption {
177 description = lib.mdDoc ''
178 The log level that the system-wide pulseaudio daemon should use,
184 type = types.attrsOf types.unspecified;
186 description = lib.mdDoc "Config of the pulse daemon. See `man pulse-daemon.conf`.";
187 example = literalExpression ''{ realtime-scheduling = "yes"; }'';
193 mkEnableOption (lib.mdDoc "discovery of pulseaudio sinks in the local network");
195 mkEnableOption (lib.mdDoc "publishing the pulseaudio sink in the local network");
198 # TODO: enable by default?
200 enable = mkEnableOption (lib.mdDoc "tcp streaming support");
203 allowAll = mkEnableOption (lib.mdDoc "all anonymous clients to stream to the server");
204 allowedIpRanges = mkOption {
205 type = types.listOf types.str;
207 example = literalExpression ''[ "127.0.0.1" "192.168.1.0/24" ]'';
208 description = lib.mdDoc ''
209 A list of IP subnets that are allowed to stream to the server.
223 "pulse/client.conf".source = clientConf;
226 hardware.pulseaudio.configFile = mkDefault "${getBin overriddenPackage}/etc/pulse/default.pa";
230 environment.systemPackages = [ overriddenPackage ];
235 "asound.conf".source = alsaConf;
237 "pulse/daemon.conf".source = writeText "daemon.conf"
238 (lib.generators.toKeyValue {} cfg.daemon.config);
240 "openal/alsoft.conf".source = writeText "alsoft.conf" "drivers=pulse";
242 "libao.conf".source = writeText "libao.conf" "default_driver=pulse";
245 # Disable flat volumes to enable relative ones
246 hardware.pulseaudio.daemon.config.flat-volumes = mkDefault "no";
248 # Upstream defaults to speex-float-1 which results in audible artifacts
249 hardware.pulseaudio.daemon.config.resample-method = mkDefault "speex-float-5";
251 # Allow PulseAudio to get realtime priority using rtkit.
252 security.rtkit.enable = true;
254 systemd.packages = [ overriddenPackage ];
256 # PulseAudio is packaged with udev rules to handle various audio device quirks
257 services.udev.packages = [ overriddenPackage ];
260 (mkIf (cfg.extraModules != []) {
261 hardware.pulseaudio.daemon.config.dl-search-path = let
262 overriddenModules = builtins.map
263 (drv: drv.override { pulseaudio = overriddenPackage; })
265 modulePaths = builtins.map
266 (drv: "${drv}/lib/pulseaudio/modules")
267 # User-provided extra modules take precedence
268 (overriddenModules ++ [ overriddenPackage ]);
269 in lib.concatStringsSep ":" modulePaths;
273 services.avahi.enable = true;
275 (mkIf cfg.zeroconf.publish.enable {
276 services.avahi.publish.enable = true;
277 services.avahi.publish.userServices = true;
280 (mkIf nonSystemWide {
282 "pulse/default.pa".source = myConfigFile;
285 services.pulseaudio = {
286 restartIfChanged = true;
288 RestartSec = "500ms";
289 PassEnvironment = "DISPLAY";
291 } // optionalAttrs config.services.jack.jackd.enable {
292 environment.JACK_PROMISCUOUS_SERVER = "jackaudio";
294 sockets.pulseaudio = {
295 wantedBy = [ "sockets.target" ];
301 users.users.pulse = {
302 # For some reason, PulseAudio wants UID == GID.
303 uid = assert uid == gid; uid;
305 extraGroups = [ "audio" ];
306 description = "PulseAudio system service user";
312 users.groups.pulse.gid = gid;
313 users.groups.pulse-access = {};
315 systemd.services.pulseaudio = {
316 description = "PulseAudio System-Wide Server";
317 wantedBy = [ "sound.target" ];
318 before = [ "sound.target" ];
319 environment.PULSE_RUNTIME_PATH = stateDir;
322 ExecStart = "${binaryNoDaemon} --log-level=${cfg.daemon.logLevel} --system -n --file=${myConfigFile}";
323 Restart = "on-failure";
324 RestartSec = "500ms";
328 environment.variables.PULSE_COOKIE = "${stateDir}/.config/pulse/cookie";