9 cfg = config.services.pulseaudio;
15 z.publish.enable || z.discovery.enable;
17 overriddenPackage = cfg.package.override (
18 lib.optionalAttrs hasZeroconf { zeroconfSupport = true; }
20 binary = "${lib.getBin overriddenPackage}/bin/pulseaudio";
21 binaryNoDaemon = "${binary} --daemonize=no";
23 # Forces 32bit pulseaudio and alsa-plugins to be built/supported for apps
24 # using 32bit alsa on 64bit linux.
25 enable32BitAlsaPlugins =
27 && pkgs.stdenv.hostPlatform.isx86_64
28 && (pkgs.pkgsi686Linux.alsa-lib != null && pkgs.pkgsi686Linux.libpulseaudio != null);
32 addModuleIf = cond: mod: lib.optionalString cond "load-module ${mod}";
33 allAnon = lib.optional cfg.tcp.anonymousClients.allowAll "auth-anonymous=1";
36 a = cfg.tcp.anonymousClients.allowedIpRanges;
38 lib.optional (a != [ ]) ''auth-ip-acl=${lib.concatStringsSep ";" a}'';
43 .include ${cfg.configFile}
44 ${addModuleIf cfg.zeroconf.publish.enable "module-zeroconf-publish"}
45 ${addModuleIf cfg.zeroconf.discovery.enable "module-zeroconf-discover"}
46 ${addModuleIf cfg.tcp.enable (
47 lib.concatStringsSep " " ([ "module-native-protocol-tcp" ] ++ allAnon ++ ipAnon)
49 ${addModuleIf config.services.jack.jackd.enable "module-jack-sink"}
50 ${addModuleIf config.services.jack.jackd.enable "module-jack-source"}
57 uid = ids.uids.pulseaudio;
58 gid = ids.gids.pulseaudio;
60 stateDir = "/run/pulse";
62 # Create pulse/client.conf even if PulseAudio is disabled so
63 # that we can disable the autospawn feature in programs that
64 # are built with PulseAudio support (like KDE).
65 clientConf = pkgs.writeText "client.conf" ''
67 ${cfg.extraClientConf}
70 # Write an /etc/asound.conf that causes all ALSA applications to
71 # be re-routed to the PulseAudio server through ALSA's Pulse
75 libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;
76 ${lib.optionalString enable32BitAlsaPlugins "libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;"}
80 hint.description "Default Audio Device (via PulseAudio)"
83 libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;
84 ${lib.optionalString enable32BitAlsaPlugins "libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;"}
94 (lib.mkRenamedOptionModule [ "hardware" "pulseaudio" ] [ "services" "pulseaudio" ])
99 services.pulseaudio = {
100 enable = lib.mkOption {
101 type = lib.types.bool;
104 Whether to enable the PulseAudio sound server.
108 systemWide = lib.mkOption {
109 type = lib.types.bool;
112 If false, a PulseAudio server is launched automatically for
113 each user that tries to use the sound system. The server runs
114 with user privileges. If true, one system-wide PulseAudio
115 server is launched on boot, running as the user "pulse", and
116 only users in the "pulse-access" group will have access to the server.
117 Please read the PulseAudio documentation for more details.
119 Don't enable this option unless you know what you are doing.
123 support32Bit = lib.mkOption {
124 type = lib.types.bool;
127 Whether to include the 32-bit pulseaudio libraries in the system or not.
128 This is only useful on 64-bit systems and currently limited to x86_64-linux.
132 configFile = lib.mkOption {
133 type = lib.types.nullOr lib.types.path;
135 The path to the default configuration options the PulseAudio server
136 should use. By default, the "default.pa" configuration
137 from the PulseAudio distribution is used.
141 extraConfig = lib.mkOption {
142 type = lib.types.lines;
145 Literal string to append to `configFile`
146 and the config file generated by the pulseaudio module.
150 extraClientConf = lib.mkOption {
151 type = lib.types.lines;
154 Extra configuration appended to pulse/client.conf file.
158 package = lib.mkOption {
159 type = lib.types.package;
160 default = if config.services.jack.jackd.enable then pkgs.pulseaudioFull else pkgs.pulseaudio;
161 defaultText = lib.literalExpression "pkgs.pulseaudio";
162 example = lib.literalExpression "pkgs.pulseaudioFull";
164 The PulseAudio derivation to use. This can be used to enable
165 features (such as JACK support, Bluetooth) via the
166 `pulseaudioFull` package.
170 extraModules = lib.mkOption {
171 type = lib.types.listOf lib.types.package;
173 example = lib.literalExpression "[ pkgs.pulseaudio-modules-bt ]";
175 Extra pulseaudio modules to use. This is intended for out-of-tree
176 pulseaudio modules like extra bluetooth codecs.
178 Extra modules take precedence over built-in pulseaudio modules.
183 logLevel = lib.mkOption {
184 type = lib.types.str;
187 The log level that the system-wide pulseaudio daemon should use,
192 config = lib.mkOption {
193 type = lib.types.attrsOf lib.types.unspecified;
195 description = "Config of the pulse daemon. See `man pulse-daemon.conf`.";
196 example = lib.literalExpression ''{ realtime-scheduling = "yes"; }'';
201 discovery.enable = lib.mkEnableOption "discovery of pulseaudio sinks in the local network";
202 publish.enable = lib.mkEnableOption "publishing the pulseaudio sink in the local network";
205 # TODO: enable by default?
207 enable = lib.mkEnableOption "tcp streaming support";
210 allowAll = lib.mkEnableOption "all anonymous clients to stream to the server";
211 allowedIpRanges = lib.mkOption {
212 type = lib.types.listOf lib.types.str;
214 example = lib.literalExpression ''[ "127.0.0.1" "192.168.1.0/24" ]'';
216 A list of IP subnets that are allowed to stream to the server.
226 config = lib.mkIf cfg.enable (
229 environment.etc."pulse/client.conf".source = clientConf;
231 environment.systemPackages = [ overriddenPackage ];
234 "alsa/conf.d/99-pulseaudio.conf".text = alsaConf;
236 "pulse/daemon.conf".source = pkgs.writeText "daemon.conf" (
237 lib.generators.toKeyValue { } cfg.daemon.config
240 "openal/alsoft.conf".source = pkgs.writeText "alsoft.conf" "drivers=pulse";
242 "libao.conf".source = pkgs.writeText "libao.conf" "default_driver=pulse";
245 services.pulseaudio.configFile = lib.mkDefault "${lib.getBin overriddenPackage}/etc/pulse/default.pa";
247 # Disable flat volumes to enable relative ones
248 services.pulseaudio.daemon.config.flat-volumes = lib.mkDefault "no";
250 # Upstream defaults to speex-float-1 which results in audible artifacts
251 services.pulseaudio.daemon.config.resample-method = lib.mkDefault "speex-float-5";
253 # Allow PulseAudio to get realtime priority using rtkit.
254 security.rtkit.enable = true;
256 systemd.packages = [ overriddenPackage ];
258 # PulseAudio is packaged with udev rules to handle various audio device quirks
259 services.udev.packages = [ overriddenPackage ];
262 (lib.mkIf (cfg.extraModules != [ ]) {
263 services.pulseaudio.daemon.config.dl-search-path =
265 overriddenModules = builtins.map (
266 drv: drv.override { pulseaudio = overriddenPackage; }
269 builtins.map (drv: "${drv}/lib/pulseaudio/modules")
270 # User-provided extra modules take precedence
271 (overriddenModules ++ [ overriddenPackage ]);
273 lib.concatStringsSep ":" modulePaths;
276 (lib.mkIf hasZeroconf {
277 services.avahi.enable = true;
279 (lib.mkIf cfg.zeroconf.publish.enable {
280 services.avahi.publish.enable = true;
281 services.avahi.publish.userServices = true;
284 (lib.mkIf (!cfg.systemWide) {
286 "pulse/default.pa".source = myConfigFile;
289 services.pulseaudio =
291 restartIfChanged = true;
293 RestartSec = "500ms";
294 PassEnvironment = "DISPLAY";
297 // lib.optionalAttrs config.services.jack.jackd.enable {
298 environment.JACK_PROMISCUOUS_SERVER = "jackaudio";
300 sockets.pulseaudio = {
301 wantedBy = [ "sockets.target" ];
306 (lib.mkIf cfg.systemWide {
307 users.users.pulse = {
308 # For some reason, PulseAudio wants UID == GID.
313 extraGroups = [ "audio" ];
314 description = "PulseAudio system service user";
321 users.groups.pulse.gid = gid;
322 users.groups.pulse-access = { };
324 systemd.services.pulseaudio = {
325 description = "PulseAudio System-Wide Server";
326 wantedBy = [ "sound.target" ];
327 before = [ "sound.target" ];
328 environment.PULSE_RUNTIME_PATH = stateDir;
331 ExecStart = "${binaryNoDaemon} --log-level=${cfg.daemon.logLevel} --system -n --file=${myConfigFile}";
332 Restart = "on-failure";
333 RestartSec = "500ms";
337 environment.variables.PULSE_COOKIE = "${stateDir}/.config/pulse/cookie";