home: services: Add 'pulseaudio-rtp-sink' and 'pulseaudio-rtp-source'.

* gnu/home/services/sound.scm: New file.
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
* doc/guix.texi (Sound Home Services): New section.
This commit is contained in:
Ludovic Courtès 2023-03-01 23:08:28 +01:00
parent 833febb522
commit 674d893316
No known key found for this signature in database
GPG key ID: 090B11993D9AEBB5
3 changed files with 258 additions and 10 deletions

View file

@ -41688,6 +41688,7 @@ services)}.
* Desktop: Desktop Home Services. Services for graphical environments. * Desktop: Desktop Home Services. Services for graphical environments.
* Guix: Guix Home Services. Services for Guix. * Guix: Guix Home Services. Services for Guix.
* Fonts: Fonts Home Services. Services for managing User's fonts. * Fonts: Fonts Home Services. Services for managing User's fonts.
* Sound: Sound Home Services. Dealing with audio.
@end menu @end menu
@c In addition to that Home Services can provide @c In addition to that Home Services can provide
@ -42601,6 +42602,101 @@ like this:
@end lisp @end lisp
@end defvar @end defvar
@node Sound Home Services
@subsection Sound Home Services
The @code{(gnu home services sound)} module provides services related to
sound support.
@cindex PulseAudio, home service
@cindex RTP, for PulseAudio
The following services dynamically reconfigure the
@uref{https://pulseaudio.org,PulseAudio sound server}: they let you
toggle broadcast of audio output over the network using the
@acronym{RTP, real-time transport protocol} and, correspondingly,
playback of sound received over RTP. Once
@code{home-pulseaudio-rtp-sink-service-type} is among your home
services, you can start broadcasting audio output by running this
command:
@example
herd start pulseaudio-rtp-sink
@end example
You can then run a PulseAudio-capable mixer, such as @code{pavucontrol}
or @code{pulsemixer} (both from the same-named package) to control which
audio stream(s) should be sent to the RTP ``sink''.
By default, audio is broadcasted to a multicast address: any device on
the @acronym{LAN, local area network} receives it and may play it.
Using multicast in this way puts a lot of pressure on the network and
degrades its performance, so you may instead prefer sending to
specifically one device. The first way to do that is by specifying the
IP address of the target device when starting the service:
@example
herd start pulseaudio-rtp-sink 192.168.1.42
@end example
The other option is to specify this IP address as the one to use by
default in your home environment configuration:
@lisp
(service home-pulseaudio-rtp-sink-service-type
"192.168.1.42")
@end lisp
On the device where you intend to receive and play the RTP stream, you
can use @code{home-pulseaudio-rtp-source-service-type}, like so:
@lisp
(service home-pulseaudio-rtp-source-service-type)
@end lisp
This will then let you start the receiving module for PulseAudio:
@example
herd start pulseaudio-rtp-source
@end example
Again, by default it will listen on the multicast address. If, instead,
you'd like it to listen for direct incoming connections, you can do that
by running:
@lisp
(service home-pulseaudio-rtp-source-service-type
"0.0.0.0")
@end lisp
The reference of these services is given below.
@defvar home-pulseaudio-rtp-sink-service-type
@defvarx home-pulseaudio-rtp-source-service-type
This is the type of the service to send, respectively receive, audio
streams over @acronym{RTP, real-time transport protocol}.
The value associated with this service is the IP address (a string)
where to send, respectively receive, the audio stream. By default,
audio is sent/received on multicast address
@code{%pulseaudio-rtp-multicast-address}.
This service defines one Shepherd service: @code{pulseaudio-rtp-sink},
respectively @code{pulseaudio-rtp-source}. The service is not started
by default, so you have to explicitly start it when you want to turn it
on, as in this example:
@example
herd start pulseaudio-rtp-sink
@end example
Stopping the Shepherd service turns off broadcasting.
@end defvar
@defvar %pulseaudio-rtp-multicast-address
This is the multicast address used by default by the two services above.
@end defvar
@node Invoking guix home @node Invoking guix home
@section Invoking @command{guix home} @section Invoking @command{guix home}

151
gnu/home/services/sound.scm Normal file
View file

@ -0,0 +1,151 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2023 Ludovic Courtès <ludo@gnu.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 (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))
;;;
;;; 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)))

View file

@ -1,5 +1,5 @@
# GNU Guix --- Functional package management for GNU # GNU Guix --- Functional package management for GNU
# Copyright © 2012-2021, 2021-2022 Ludovic Courtès <ludo@gnu.org> # Copyright © 2012-2023 Ludovic Courtès <ludo@gnu.org>
# Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2022 Andreas Enge <andreas@enge.fr> # Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2022 Andreas Enge <andreas@enge.fr>
# Copyright © 2016 Mathieu Lirzin <mthl@gnu.org> # Copyright © 2016 Mathieu Lirzin <mthl@gnu.org>
# Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Mark H Weaver <mhw@netris.org> # Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Mark H Weaver <mhw@netris.org>
@ -94,6 +94,7 @@ GNU_SYSTEM_MODULES = \
%D%/home/services/pm.scm \ %D%/home/services/pm.scm \
%D%/home/services/shells.scm \ %D%/home/services/shells.scm \
%D%/home/services/shepherd.scm \ %D%/home/services/shepherd.scm \
%D%/home/services/sound.scm \
%D%/home/services/ssh.scm \ %D%/home/services/ssh.scm \
%D%/home/services/mcron.scm \ %D%/home/services/mcron.scm \
%D%/home/services/utils.scm \ %D%/home/services/utils.scm \