sound.scm 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2023 Ludovic Courtès <ludo@gnu.org>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; GNU Guix is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (gnu home services sound)
  19. #:use-module (gnu home services)
  20. #:use-module (gnu home services shepherd)
  21. #:use-module (guix records)
  22. #:use-module (guix gexp)
  23. #:use-module (srfi srfi-1)
  24. #:use-module (ice-9 match)
  25. #:export (home-pulseaudio-rtp-sink-service-type
  26. home-pulseaudio-rtp-source-service-type
  27. %pulseaudio-rtp-multicast-address))
  28. ;;;
  29. ;;; PulseAudio support.
  30. ;;;
  31. (define (with-pulseaudio-connection sock exp)
  32. ;; Wrap EXP in an expression where SOCK is bound to a socket connected to
  33. ;; the user's PulseAudio command-line interface socket.
  34. #~(let* ((#$sock (socket AF_UNIX SOCK_STREAM 0))
  35. (pulse-user-file
  36. (lambda (name)
  37. (string-append "/run/user/" (number->string (getuid))
  38. "/pulse/" name)))
  39. (file (pulse-user-file "cli")))
  40. (let loop ((tries 0))
  41. (catch #t
  42. (lambda ()
  43. (connect #$sock AF_UNIX file)
  44. (let ((result #$exp))
  45. (close-port #$sock)
  46. result))
  47. (lambda (key . args)
  48. (if (and (eq? key 'system-error)
  49. (= ENOENT (system-error-errno (cons key args)))
  50. (< tries 3))
  51. ;; The CLI socket doesn't exist yet, so send pulseaudio
  52. ;; SIGUSR2 so that it creates it and listens to it.
  53. (let ((pid (call-with-input-file (pulse-user-file "pid")
  54. read)))
  55. (when (and (integer? pid) (> pid 1))
  56. (kill pid SIGUSR2))
  57. ((@ (fibers) sleep) 1)
  58. (loop (+ tries 1)))
  59. (begin
  60. (close-port #$sock)
  61. (apply throw key args))))))))
  62. (define %pulseaudio-rtp-multicast-address
  63. ;; Default address used by 'module-rtp-sink' and 'module-rtp-recv'. This is
  64. ;; a multicast address, for the Session Announcement Protocol (SAP) and the
  65. ;; Session Description Protocol (SDP).
  66. "224.0.0.56")
  67. (define (pulseaudio-rtp-sink-shepherd-services destination-ip)
  68. (list (shepherd-service
  69. (provision '(pulseaudio-rtp-sink))
  70. (start
  71. #~(lambda* (#:optional (destination-ip #$destination-ip))
  72. #$(with-pulseaudio-connection
  73. #~sock
  74. #~(begin
  75. (display "\
  76. load-module module-null-sink \
  77. sink_name=rtp sink_properties=\"device.description='RTP network output'\"\n"
  78. sock)
  79. (display (string-append "\
  80. load-module module-rtp-send source=rtp.monitor"
  81. (if destination-ip
  82. (string-append
  83. " destination_ip="
  84. destination-ip)
  85. "")
  86. "\n")
  87. sock)
  88. #t))))
  89. (stop
  90. #~(lambda (_)
  91. #$(with-pulseaudio-connection
  92. #~sock
  93. #~(begin
  94. (display "unload-module module-rtp-send\n"
  95. sock)
  96. (display "unload-module module-null-sink\n"
  97. sock)
  98. #f))))
  99. (auto-start? #f))))
  100. (define home-pulseaudio-rtp-sink-service-type
  101. (service-type
  102. (name 'pulseaudio-rtp-sink)
  103. (extensions
  104. (list (service-extension home-shepherd-service-type
  105. pulseaudio-rtp-sink-shepherd-services)))
  106. (description
  107. "Define a PulseAudio sink to broadcast audio output over RTP, which can
  108. then by played by another PulseAudio instance.")
  109. ;; By default, send to the SAP multicast address, 224.0.0.56, which can be
  110. ;; network-intensive.
  111. (default-value %pulseaudio-rtp-multicast-address)))
  112. (define (pulseaudio-rtp-source-shepherd-services source-ip)
  113. (list (shepherd-service
  114. (provision '(pulseaudio-rtp-source))
  115. (start
  116. #~(lambda* (#:optional (source-ip #$source-ip))
  117. #$(with-pulseaudio-connection
  118. #~sock
  119. #~(begin
  120. (format sock "\
  121. load-module module-rtp-recv sap_address=~a\n" source-ip)
  122. #t))))
  123. (stop
  124. #~(lambda (_)
  125. #$(with-pulseaudio-connection
  126. #~sock
  127. #~(begin
  128. (display "unload-module module-rtp-recv\n"
  129. sock)
  130. #f))))
  131. (auto-start? #f))))
  132. (define home-pulseaudio-rtp-source-service-type
  133. (service-type
  134. (name 'pulseaudio-rtp-source)
  135. (extensions
  136. (list (service-extension home-shepherd-service-type
  137. pulseaudio-rtp-source-shepherd-services)))
  138. (description
  139. "Define a PulseAudio source to receive audio broadcasted over RTP by
  140. another PulseAudio instance.")
  141. (default-value %pulseaudio-rtp-multicast-address)))