guix-play/gnu/home/services/sound.scm
Brian Cully afdbf7f271
gnu: home: Add home-pipewire service.
This adds a set of home Shepherd services which will start the required
services for a functional PipeWire setup.

* gnu/home/services/sound.scm (home-pipewire-shepherd-service,
home-wireplumber-shepherd-service, home-pipewire-shepherd-services,
home-pipewire-asoundrc, home-pipewire-xdg-configuration,
home-pipewire-pulseaudio-shepherd-service): New procedures.
(home-pipewire-service-type): New service type.
(home-pipewire-configuration): New struct.
(home-pipewire-disable-pulseaudio-auto-start): New variable.
* doc/guix.texi (Sound Home Services): Document it.

Change-Id: I99e0ae860de91d459c3c554ec5503bf35f785a2a
Signed-off-by: Oleg Pykhalov <go.wigust@gmail.com>
2023-12-26 15:41:59 +03:00

252 lines
9.3 KiB
Scheme
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2023 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2023 Brian Cully <bjc@spork.org>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (gnu home services sound)
#:use-module (gnu home services)
#:use-module (gnu home services shepherd)
#:use-module (gnu home services xdg)
#:use-module (gnu packages linux)
#:use-module (gnu services configuration)
#:use-module (guix records)
#:use-module (guix gexp)
#:use-module (srfi srfi-1)
#:use-module (ice-9 match)
#:export (home-pulseaudio-rtp-sink-service-type
home-pulseaudio-rtp-source-service-type
%pulseaudio-rtp-multicast-address
home-pipewire-configuration
home-pipewire-service-type))
;;;
;;; PipeWire support.
;;;
(define-configuration/no-serialization home-pipewire-configuration
(pipewire
(file-like pipewire)
"The PipeWire package to use.")
(wireplumber
(file-like wireplumber)
"The WirePlumber package to use.")
(enable-pulseaudio?
(boolean #t)
"When true, enable PipeWire's PulseAudio emulation support, allowing
PulseAudio clients to use PipeWire transparently."))
(define (home-pipewire-shepherd-service config)
(shepherd-service
(documentation "PipeWire media processing.")
(provision '(pipewire))
(requirement '(dbus))
(start #~(make-forkexec-constructor
(list #$(file-append
(home-pipewire-configuration-pipewire config)
"/bin/pipewire"))))
(stop #~(make-kill-destructor))))
(define (home-pipewire-pulseaudio-shepherd-service config)
(shepherd-service
(documentation "Drop-in PulseAudio replacement service for PipeWire.")
(provision '(pipewire-pulseaudio))
(requirement '(pipewire))
(start #~(make-forkexec-constructor
(list #$(file-append
(home-pipewire-configuration-pipewire config)
"/bin/pipewire-pulse"))))
(stop #~(make-kill-destructor))))
(define (home-wireplumber-shepherd-service config)
(shepherd-service
(documentation "WirePlumber session management for PipeWire.")
(provision '(wireplumber))
(requirement '(pipewire))
(start #~(make-forkexec-constructor
(list #$(file-append
(home-pipewire-configuration-wireplumber config)
"/bin/wireplumber"))))
(stop #~(make-kill-destructor))))
(define (home-pipewire-shepherd-services config)
(cons* (home-pipewire-shepherd-service config)
(home-wireplumber-shepherd-service config)
(if (home-pipewire-configuration-enable-pulseaudio? config)
(list (home-pipewire-pulseaudio-shepherd-service config))
'())))
(define (home-pipewire-asoundrc config)
(match-record config <home-pipewire-configuration>
(pipewire)
(mixed-text-file
"asoundrc"
"<" pipewire "/share/alsa/alsa.conf.d/50-pipewire.conf>\n"
"<" pipewire "/share/alsa/alsa.conf.d/99-pipewire-default.conf>\n"
"pcm_type.pipewire {\n"
" lib \"" pipewire "/lib/alsa-lib/libasound_module_pcm_pipewire.so\"\n"
"}\n"
"ctl_type.pipewire {\n"
" lib \"" pipewire "/lib/alsa-lib/libasound_module_ctl_pipewire.so\"\n"
"}\n")))
(define home-pipewire-disable-pulseaudio-auto-start
(plain-file "client.conf" "autospawn = no"))
(define (home-pipewire-xdg-configuration config)
(cons* `("alsa/asoundrc" ,(home-pipewire-asoundrc config))
(if (home-pipewire-configuration-enable-pulseaudio? config)
`(("pulse/client.conf"
,home-pipewire-disable-pulseaudio-auto-start))
'())))
(define home-pipewire-service-type
(service-type
(name 'pipewire)
(extensions
(list (service-extension home-shepherd-service-type
home-pipewire-shepherd-services)
(service-extension home-xdg-configuration-files-service-type
home-pipewire-xdg-configuration)))
(description
"Start essential PipeWire services.")
(default-value (home-pipewire-configuration))))
;;;
;;; PulseAudio support.
;;;
(define (with-pulseaudio-connection sock exp)
;; Wrap EXP in an expression where SOCK is bound to a socket connected to
;; the user's PulseAudio command-line interface socket.
#~(let* ((#$sock (socket AF_UNIX SOCK_STREAM 0))
(pulse-user-file
(lambda (name)
(string-append "/run/user/" (number->string (getuid))
"/pulse/" name)))
(file (pulse-user-file "cli")))
(let loop ((tries 0))
(catch #t
(lambda ()
(connect #$sock AF_UNIX file)
(let ((result #$exp))
(close-port #$sock)
result))
(lambda (key . args)
(if (and (eq? key 'system-error)
(= ENOENT (system-error-errno (cons key args)))
(< tries 3))
;; The CLI socket doesn't exist yet, so send pulseaudio
;; SIGUSR2 so that it creates it and listens to it.
(let ((pid (call-with-input-file (pulse-user-file "pid")
read)))
(when (and (integer? pid) (> pid 1))
(kill pid SIGUSR2))
((@ (fibers) sleep) 1)
(loop (+ tries 1)))
(begin
(close-port #$sock)
(apply throw key args))))))))
(define %pulseaudio-rtp-multicast-address
;; Default address used by 'module-rtp-sink' and 'module-rtp-recv'. This is
;; a multicast address, for the Session Announcement Protocol (SAP) and the
;; Session Description Protocol (SDP).
"224.0.0.56")
(define (pulseaudio-rtp-sink-shepherd-services destination-ip)
(list (shepherd-service
(provision '(pulseaudio-rtp-sink))
(start
#~(lambda* (#:optional (destination-ip #$destination-ip))
#$(with-pulseaudio-connection
#~sock
#~(begin
(display "\
load-module module-null-sink \
sink_name=rtp sink_properties=\"device.description='RTP network output'\"\n"
sock)
(display (string-append "\
load-module module-rtp-send source=rtp.monitor"
(if destination-ip
(string-append
" destination_ip="
destination-ip)
"")
"\n")
sock)
#t))))
(stop
#~(lambda (_)
#$(with-pulseaudio-connection
#~sock
#~(begin
(display "unload-module module-rtp-send\n"
sock)
(display "unload-module module-null-sink\n"
sock)
#f))))
(auto-start? #f))))
(define home-pulseaudio-rtp-sink-service-type
(service-type
(name 'pulseaudio-rtp-sink)
(extensions
(list (service-extension home-shepherd-service-type
pulseaudio-rtp-sink-shepherd-services)))
(description
"Define a PulseAudio sink to broadcast audio output over RTP, which can
then by played by another PulseAudio instance.")
;; By default, send to the SAP multicast address, 224.0.0.56, which can be
;; network-intensive.
(default-value %pulseaudio-rtp-multicast-address)))
(define (pulseaudio-rtp-source-shepherd-services source-ip)
(list (shepherd-service
(provision '(pulseaudio-rtp-source))
(start
#~(lambda* (#:optional (source-ip #$source-ip))
#$(with-pulseaudio-connection
#~sock
#~(begin
(format sock "\
load-module module-rtp-recv sap_address=~a\n" source-ip)
#t))))
(stop
#~(lambda (_)
#$(with-pulseaudio-connection
#~sock
#~(begin
(display "unload-module module-rtp-recv\n"
sock)
#f))))
(auto-start? #f))))
(define home-pulseaudio-rtp-source-service-type
(service-type
(name 'pulseaudio-rtp-source)
(extensions
(list (service-extension home-shepherd-service-type
pulseaudio-rtp-source-shepherd-services)))
(description
"Define a PulseAudio source to receive audio broadcasted over RTP by
another PulseAudio instance.")
(default-value %pulseaudio-rtp-multicast-address)))