summaryrefslogtreecommitdiff
path: root/audio
diff options
context:
space:
mode:
authornia <nia@pkgsrc.org>2022-07-03 16:09:15 +0000
committernia <nia@pkgsrc.org>2022-07-03 16:09:15 +0000
commit02abd1ef0037b69ccbca3af26d682e9be16a0052 (patch)
tree0aa6cd15eefaccc08af1062093de2ca3ed7f528d /audio
parent5377028327e31f884029f02b05656a0dba7e4465 (diff)
downloadpkgsrc-02abd1ef0037b69ccbca3af26d682e9be16a0052.tar.gz
add audio/snapcast
Snapcast is a multiroom client-server audio player, where all clients are time synchronized with the server to play perfectly synced audio. It's not a standalone player, but an extension that turns your existing audio player into a Sonos-like multiroom solution.
Diffstat (limited to 'audio')
-rw-r--r--audio/Makefile3
-rw-r--r--audio/snapcast/DESCR4
-rw-r--r--audio/snapcast/Makefile79
-rw-r--r--audio/snapcast/PLIST24
-rw-r--r--audio/snapcast/distinfo18
-rw-r--r--audio/snapcast/files/snapclient.sh23
-rw-r--r--audio/snapcast/files/snapserver.sh21
-rw-r--r--audio/snapcast/options.mk28
-rw-r--r--audio/snapcast/patches/patch-CMakeLists.txt32
-rw-r--r--audio/snapcast/patches/patch-client_CMakeLists.txt17
-rw-r--r--audio/snapcast/patches/patch-client_controller.cpp37
-rw-r--r--audio/snapcast/patches/patch-client_player_sun__player.cpp312
-rw-r--r--audio/snapcast/patches/patch-client_player_sun__player.hpp74
-rw-r--r--audio/snapcast/patches/patch-client_snapclient.cpp59
-rw-r--r--audio/snapcast/patches/patch-common_utils.hpp67
-rw-r--r--audio/snapcast/patches/patch-server_CMakeLists.txt15
-rw-r--r--audio/snapcast/patches/patch-server_etc_snapserver.conf24
-rw-r--r--audio/snapcast/patches/patch-server_server__settings.hpp15
-rw-r--r--audio/snapcast/patches/patch-server_snapserver.130
-rw-r--r--audio/snapcast/patches/patch-server_snapserver.cpp24
-rw-r--r--audio/snapcast/patches/patch-server_streamreader_pipe__stream.cpp15
21 files changed, 920 insertions, 1 deletions
diff --git a/audio/Makefile b/audio/Makefile
index e40853563f2..ac04df5e4d6 100644
--- a/audio/Makefile
+++ b/audio/Makefile
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.655 2022/07/01 18:36:27 nia Exp $
+# $NetBSD: Makefile,v 1.656 2022/07/03 16:09:15 nia Exp $
#
COMMENT= Audio tools, players, and libraries
@@ -442,6 +442,7 @@ SUBDIR+= shntool
SUBDIR+= shorten
SUBDIR+= sidplay
SUBDIR+= sidplay2
+SUBDIR+= snapcast
SUBDIR+= snd
SUBDIR+= sndfile-tools
SUBDIR+= solfege
diff --git a/audio/snapcast/DESCR b/audio/snapcast/DESCR
new file mode 100644
index 00000000000..5422fbb2f11
--- /dev/null
+++ b/audio/snapcast/DESCR
@@ -0,0 +1,4 @@
+Snapcast is a multiroom client-server audio player, where all clients are
+time synchronized with the server to play perfectly synced audio. It's not
+a standalone player, but an extension that turns your existing audio player
+into a Sonos-like multiroom solution.
diff --git a/audio/snapcast/Makefile b/audio/snapcast/Makefile
new file mode 100644
index 00000000000..00a20d02f44
--- /dev/null
+++ b/audio/snapcast/Makefile
@@ -0,0 +1,79 @@
+# $NetBSD: Makefile,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+DISTNAME= snapcast-0.26.0
+CATEGORIES= audio
+MASTER_SITES= ${MASTER_SITE_GITHUB:=badaix/}
+GITHUB_TAG= v${PKGVERSION_NOREV}
+
+MAINTAINER= nia@NetBSD.org
+HOMEPAGE= https://github.com/badaix/snapcast
+COMMENT= Multiroom client-server audio player,
+LICENSE= gnu-gpl-v3
+
+USE_CMAKE= yes
+USE_TOOLS+= pkg-config
+USE_LANGUAGES= c c++
+
+REPLACE_PYTHON+= server/etc/plug-ins/meta_mpd.py
+
+PYTHON_VERSIONS_INCOMPATIBLE= 27
+
+CONF_FILES+= ${PREFIX}/share/examples/snapcast/snapserver.conf \
+ ${PKG_SYSCONFDIR}/snapserver.conf
+
+SUBST_CLASSES+= etc
+SUBST_STAGE.etc= pre-configure
+SUBST_FILES.etc+= server/etc/snapserver.conf
+SUBST_FILES.etc+= server/server_settings.hpp
+SUBST_FILES.etc+= server/snapserver.cpp
+SUBST_FILES.etc+= server/snapserver.1
+SUBST_VARS.etc+= PKG_SYSCONFDIR
+SUBST_VARS.etc+= VARBASE
+
+RCD_SCRIPTS+= snapclient
+RCD_SCRIPTS+= snapserver
+
+SNAPCLIENT_USER?= snapclient
+SNAPCLIENT_GROUP?= snapclient
+
+SNAPSERVER_USER?= snapserver
+SNAPSERVER_GROUP?= snapserver
+
+BUILD_DEFS+= VARBASE
+
+.include "../../mk/bsd.prefs.mk"
+
+FILES_SUBST+= SNAPCLIENT_USER=${SNAPCLIENT_USER}
+FILES_SUBST+= SNAPCLIENT_GROUP=${SNAPCLIENT_GROUP}
+FILES_SUBST+= VARBASE=${VARBASE}
+
+PKG_GROUPS+= ${SNAPCLIENT_GROUP}
+PKG_GROUPS+= ${SNAPSERVER_GROUP}
+
+PKG_USERS+= ${SNAPCLIENT_USER}:${SNAPCLIENT_GROUP}
+PKG_USERS+= ${SNAPSERVER_USER}:${SNAPSERVER_GROUP}
+
+OWN_DIRS_PERMS+= ${VARBASE}/run/snapclient \
+ ${SNAPCLIENT_USER} ${SNAPCLIENT_GROUP} 0755
+
+OWN_DIRS_PERMS+= ${VARBASE}/run/snapserver \
+ ${SNAPCLIENT_USER} ${SNAPCLIENT_GROUP} 0755
+
+OWN_DIRS_PERMS+= ${VARBASE}/lib/snapclient \
+ ${SNAPSERVER_USER} ${SNAPSERVER_GROUP} 0755
+
+OWN_DIRS_PERMS+= ${VARBASE}/lib/snapserver \
+ ${SNAPSERVER_USER} ${SNAPSERVER_GROUP} 0755
+
+post-install:
+ cd ${WRKSRC} && ${CHMOD} +r ${DESTDIR}${PREFIX}/share/snapserver/plug-ins/meta_mpd.py
+
+.include "options.mk"
+.include "../../audio/flac/buildlink3.mk"
+.include "../../audio/libopus/buildlink3.mk"
+.include "../../audio/libsoxr/buildlink3.mk"
+.include "../../audio/libvorbis/buildlink3.mk"
+.include "../../devel/boost-headers/buildlink3.mk"
+.include "../../lang/python/application.mk"
+.include "../../textproc/expat/buildlink3.mk"
+.include "../../mk/bsd.pkg.mk"
diff --git a/audio/snapcast/PLIST b/audio/snapcast/PLIST
new file mode 100644
index 00000000000..d0559374a01
--- /dev/null
+++ b/audio/snapcast/PLIST
@@ -0,0 +1,24 @@
+@comment $NetBSD: PLIST,v 1.1 2022/07/03 16:09:15 nia Exp $
+bin/snapclient
+bin/snapserver
+man/man1/snapclient.1
+man/man1/snapserver.1
+share/examples/snapcast/snapserver.conf
+share/pixmaps/snapcast.svg
+share/snapserver/index.html
+share/snapserver/plug-ins/meta_mpd.py
+share/snapserver/snapweb/10-seconds-of-silence.mp3
+share/snapserver/snapweb/3rd-party/libflac.js
+share/snapserver/snapweb/config.js
+share/snapserver/snapweb/favicon.ico
+share/snapserver/snapweb/index.html
+share/snapserver/snapweb/launcher-icon.png
+share/snapserver/snapweb/manifest.json
+share/snapserver/snapweb/mute_icon.png
+share/snapserver/snapweb/play.png
+share/snapserver/snapweb/snapcast-512.png
+share/snapserver/snapweb/snapcontrol.js
+share/snapserver/snapweb/snapstream.js
+share/snapserver/snapweb/speaker_icon.png
+share/snapserver/snapweb/stop.png
+share/snapserver/snapweb/styles.css
diff --git a/audio/snapcast/distinfo b/audio/snapcast/distinfo
new file mode 100644
index 00000000000..7a4f9a0c71f
--- /dev/null
+++ b/audio/snapcast/distinfo
@@ -0,0 +1,18 @@
+$NetBSD: distinfo,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+BLAKE2s (snapcast-0.26.0.tar.gz) = e0ef56ee25d30c8536158949c5e5f85b32a8c410303da939ef844bb3dc300012
+SHA512 (snapcast-0.26.0.tar.gz) = fc7885e42a11794e33314544083251ffbb91a0cf160c6d4b854c56f57ffe9f38f75c7594478c9edabfe9076959938cd8de891dd456e66202692de664a75cde71
+Size (snapcast-0.26.0.tar.gz) = 1537036 bytes
+SHA1 (patch-CMakeLists.txt) = c3f02503c918e6843ab18d987b3e886e22e13865
+SHA1 (patch-client_CMakeLists.txt) = 48559046bd578e2d75f97b4ec422d4a56b567733
+SHA1 (patch-client_controller.cpp) = a87b5515a519ab579c36786b9727b58934128148
+SHA1 (patch-client_player_sun__player.cpp) = 6e98d22c9deaccc3bf2ac14b7e275dc1c8bc771a
+SHA1 (patch-client_player_sun__player.hpp) = d8eeba9f4c16e85833baba95c07f9a0600763752
+SHA1 (patch-client_snapclient.cpp) = d682d4c1de438251d1510d40387e9a0b2bcf926f
+SHA1 (patch-common_utils.hpp) = 8184a65459accd76b55e8e9e95d1911439fb4d8a
+SHA1 (patch-server_CMakeLists.txt) = 49144e902844bd3308871a625f5da56575904855
+SHA1 (patch-server_etc_snapserver.conf) = a740795aa764ffb5870a4d798518a0464ca3517b
+SHA1 (patch-server_server__settings.hpp) = 50950a4855ecc336dbec146b86935fa18942dc1d
+SHA1 (patch-server_snapserver.1) = 3459c6109635d1ad72c1aee92e302088a2317007
+SHA1 (patch-server_snapserver.cpp) = 62d5dcbfe369f9095e9b6c695680650f7eb458b6
+SHA1 (patch-server_streamreader_pipe__stream.cpp) = aec6fd900e0aca776510c3b413e457b9082b01b5
diff --git a/audio/snapcast/files/snapclient.sh b/audio/snapcast/files/snapclient.sh
new file mode 100644
index 00000000000..4de961f9962
--- /dev/null
+++ b/audio/snapcast/files/snapclient.sh
@@ -0,0 +1,23 @@
+#!@RCD_SCRIPTS_SHELL@
+#
+# $NetBSD: snapclient.sh,v 1.1 2022/07/03 16:09:15 nia Exp $
+#
+# PROVIDE: snapclient
+# KEYWORD: shutdown
+#
+
+if [ -f /etc/rc.subr ]
+then
+ . /etc/rc.subr
+fi
+
+name="snapclient"
+rcvar=${name}
+command="@PREFIX@/bin/snapclient"
+command_args="-d"
+pidfile="@VARBASE@/run/snapclient/pid"
+snapclient_user=@SNAPCLIENT_USER@
+snapclient_group=@SNAPCLIENT_GROUP@
+
+load_rc_config $name
+run_rc_command "$1"
diff --git a/audio/snapcast/files/snapserver.sh b/audio/snapcast/files/snapserver.sh
new file mode 100644
index 00000000000..e69b4ac11de
--- /dev/null
+++ b/audio/snapcast/files/snapserver.sh
@@ -0,0 +1,21 @@
+#!@RCD_SCRIPTS_SHELL@
+#
+# $NetBSD: snapserver.sh,v 1.1 2022/07/03 16:09:15 nia Exp $
+#
+# PROVIDE: snapserver
+# KEYWORD: shutdown
+#
+
+if [ -f /etc/rc.subr ]
+then
+ . /etc/rc.subr
+fi
+
+name="snapserver"
+rcvar=${name}
+command="@PREFIX@/bin/snapserver"
+command_args="-d"
+pidfile="@VARBASE@/run/snapserver/pid"
+
+load_rc_config $name
+run_rc_command "$1"
diff --git a/audio/snapcast/options.mk b/audio/snapcast/options.mk
new file mode 100644
index 00000000000..4dab7a7d865
--- /dev/null
+++ b/audio/snapcast/options.mk
@@ -0,0 +1,28 @@
+# $NetBSD: options.mk,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+PKG_OPTIONS_VAR= PKG_OPTIONS.snapcast
+PKG_SUPPORTED_OPTIONS= alsa avahi pulseaudio
+PKG_SUGGESTED_OPTIONS.Linux+= alsa
+
+.include "../../mk/bsd.options.mk"
+
+.if !empty(PKG_OPTIONS:Malsa)
+CMAKE_ARGS+= -DBUILD_WITH_ALSA=ON
+. include "../../audio/alsa-lib/buildlink3.mk"
+.else
+CMAKE_ARGS+= -DBUILD_WITH_ALSA=OFF
+.endif
+
+.if !empty(PKG_OPTIONS:Mavahi)
+CMAKE_ARGS+= -DBUILD_WITH_AVAHI=ON
+. include "../../net/avahi/buildlink3.mk"
+.else
+CMAKE_ARGS+= -DBUILD_WITH_AVAHI=OFF
+.endif
+
+.if !empty(PKG_OPTIONS:Mpulseaudio)
+CMAKE_ARGS+= -DBUILD_WITH_PULSE=ON
+. include "../../audio/pulseaudio/buildlink3.mk"
+.else
+CMAKE_ARGS+= -DBUILD_WITH_PULSE=OFF
+.endif
diff --git a/audio/snapcast/patches/patch-CMakeLists.txt b/audio/snapcast/patches/patch-CMakeLists.txt
new file mode 100644
index 00000000000..68e70b28d7f
--- /dev/null
+++ b/audio/snapcast/patches/patch-CMakeLists.txt
@@ -0,0 +1,32 @@
+$NetBSD: patch-CMakeLists.txt,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+Make ALSA optional. Add Sun Audio support for NetBSD.
+
+--- CMakeLists.txt.orig 2021-12-22 17:40:36.000000000 +0000
++++ CMakeLists.txt
+@@ -178,10 +178,12 @@ if(NOT WIN32 AND NOT ANDROID)
+ list(APPEND INCLUDE_DIRS "/usr/local/include")
+ else()
+
+- pkg_search_module(ALSA REQUIRED alsa)
++ if(BUILD_WITH_ALSA)
++ pkg_search_module(ALSA alsa)
+ if (ALSA_FOUND)
+ add_definitions(-DHAS_ALSA)
+ endif (ALSA_FOUND)
++ endif(BUILD_WITH_ALSA)
+
+ if(BUILD_WITH_PULSE)
+ pkg_search_module(PULSE libpulse)
+@@ -206,6 +208,11 @@ if(NOT WIN32 AND NOT ANDROID)
+ link_directories("/usr/local/lib")
+ list(APPEND INCLUDE_DIRS "/usr/local/include")
+ endif()
++
++ check_include_file("sys/audioio.h" SUN_FOUND)
++ if (SUN_FOUND)
++ add_definitions(-DHAS_SUN)
++ endif (SUN_FOUND)
+ endif()
+
+ pkg_search_module(SOXR soxr)
diff --git a/audio/snapcast/patches/patch-client_CMakeLists.txt b/audio/snapcast/patches/patch-client_CMakeLists.txt
new file mode 100644
index 00000000000..d732fd2dd1f
--- /dev/null
+++ b/audio/snapcast/patches/patch-client_CMakeLists.txt
@@ -0,0 +1,17 @@
+$NetBSD: patch-client_CMakeLists.txt,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+Add Sun Audio support for NetBSD.
+
+--- client/CMakeLists.txt.orig 2021-12-22 17:40:36.000000000 +0000
++++ client/CMakeLists.txt
+@@ -53,6 +53,10 @@ elseif(NOT ANDROID)
+ list(APPEND CLIENT_LIBRARIES ${PULSE_LIBRARIES})
+ list(APPEND CLIENT_INCLUDE ${PULSE_INCLUDE_DIRS})
+ endif (PULSE_FOUND)
++
++ if (SUN_FOUND)
++ list(APPEND CLIENT_SOURCES player/sun_player.cpp)
++ endif (SUN_FOUND)
+ endif (MACOSX)
+
+ if (ANDROID)
diff --git a/audio/snapcast/patches/patch-client_controller.cpp b/audio/snapcast/patches/patch-client_controller.cpp
new file mode 100644
index 00000000000..b8cc083617b
--- /dev/null
+++ b/audio/snapcast/patches/patch-client_controller.cpp
@@ -0,0 +1,37 @@
+$NetBSD: patch-client_controller.cpp,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+Add Sun Audio support for NetBSD.
+
+--- client/controller.cpp.orig 2021-12-22 17:40:36.000000000 +0000
++++ client/controller.cpp
+@@ -38,6 +38,9 @@
+ #ifdef HAS_PULSE
+ #include "player/pulse_player.hpp"
+ #endif
++#ifdef HAS_SUN
++#include "player/sun_player.hpp"
++#endif
+ #ifdef HAS_OPENSL
+ #include "player/opensl_player.hpp"
+ #endif
+@@ -92,6 +95,9 @@ std::unique_ptr<Player> Controller::crea
+ std::vector<std::string> Controller::getSupportedPlayerNames()
+ {
+ std::vector<std::string> result;
++#ifdef HAS_SUN
++ result.emplace_back(player::SUN);
++#endif
+ #ifdef HAS_ALSA
+ result.emplace_back(player::ALSA);
+ #endif
+@@ -188,6 +194,10 @@ void Controller::getNextMessage()
+ stream_ = make_shared<Stream>(sampleFormat_, settings_.player.sample_format);
+ stream_->setBufferLen(std::max(0, serverSettings_->getBufferMs() - serverSettings_->getLatency() - settings_.player.latency));
+
++#ifdef HAS_SUN
++ if (!player_)
++ player_ = createPlayer<SunPlayer>(settings_.player, player::SUN);
++#endif
+ #ifdef HAS_ALSA
+ if (!player_)
+ player_ = createPlayer<AlsaPlayer>(settings_.player, player::ALSA);
diff --git a/audio/snapcast/patches/patch-client_player_sun__player.cpp b/audio/snapcast/patches/patch-client_player_sun__player.cpp
new file mode 100644
index 00000000000..608ed25cca1
--- /dev/null
+++ b/audio/snapcast/patches/patch-client_player_sun__player.cpp
@@ -0,0 +1,312 @@
+$NetBSD: patch-client_player_sun__player.cpp,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+Add Sun Audio support for NetBSD.
+
+--- client/player/sun_player.cpp.orig 2022-07-03 14:25:19.031712372 +0000
++++ client/player/sun_player.cpp
+@@ -0,0 +1,305 @@
++/***
++ This file is part of snapcast
++ Copyright (C) 2014-2021 Johannes Pohl
++
++ This program 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.
++
++ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
++***/
++
++#include <sys/audioio.h>
++#include <sys/ioctl.h>
++#include <fcntl.h>
++#include <unistd.h>
++#include "sun_player.hpp"
++#include "common/aixlog.hpp"
++#include "common/snap_exception.hpp"
++#include "common/str_compat.hpp"
++#include "common/utils/logging.hpp"
++#include "common/utils/string_utils.hpp"
++
++#ifndef SUN_MAXDEVS
++#define SUN_MAXDEVS (16)
++#endif
++
++#ifndef AUDIO_GETBUFINFO
++#define AUDIO_GETBUFINFO AUDIO_GETINFO
++#endif
++
++#ifndef AUDIO_ENCODING_SLIENAR
++#define AUDIO_ENCODING_SLIENAR AUDIO_ENCODING_LINEAR8;
++#endif
++
++#ifndef AUDIO_ENCODING_SLIENAR_LE
++#define AUDIO_ENCODING_SLIENAR_LE AUDIO_ENCODING_LINEAR;
++#endif
++
++using namespace std::chrono_literals;
++using namespace std;
++
++namespace player
++{
++
++static constexpr std::chrono::milliseconds BUFFER_TIME = 10ms;
++static constexpr int PERIODS = 3;
++static constexpr int MIN_PERIODS = 1;
++
++static constexpr auto LOG_TAG = "Sun";
++
++SunPlayer::SunPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream)
++ : Player(io_context, settings, stream), handle_(-1)
++{
++}
++
++void SunPlayer::initSun()
++{
++ std::lock_guard<std::recursive_mutex> lock(mutex_);
++ const char *dev;
++ const SampleFormat& format = stream_->getFormat();
++ struct audio_info info;
++ uint32_t rate = format.rate();
++ int channels = format.channels();
++
++ // Open the PCM device in playback mode
++ if (settings_.pcm_device.name == "default")
++ dev = "/dev/audio";
++ else
++ dev = settings_.pcm_device.name.c_str();
++
++ if ((handle_ = open(dev, O_WRONLY)) < 0)
++ throw SnapException("Can't open " + settings_.pcm_device.name + ", error: " + strerror(errno));
++
++ AUDIO_INITINFO(&info);
++
++ switch (format.bits()) {
++ case 8:
++ info.play.encoding = AUDIO_ENCODING_SLINEAR;
++ info.play.precision = 8;
++ break;
++ case 16:
++ info.play.encoding = AUDIO_ENCODING_SLINEAR_LE;
++ info.play.precision = 16;
++ break;
++ case 32:
++ info.play.encoding = AUDIO_ENCODING_SLINEAR_LE;
++ info.play.precision = 32;
++ break;
++ default:
++ throw SnapException("Unsupported sample format: " + cpt::to_string(format.bits()));
++ break;
++ }
++
++ if (ioctl(handle_, AUDIO_SETINFO, &info) < 0)
++ {
++ throw SnapException("Unsupported sample format: " + cpt::to_string(format.bits()));
++ }
++
++ AUDIO_INITINFO(&info);
++
++ info.play.channels = channels;
++
++ if (ioctl(handle_, AUDIO_SETINFO, &info) < 0)
++ {
++ throw SnapException("Can't set channel count: " + string(strerror(errno)));
++ }
++
++ AUDIO_INITINFO(&info);
++
++ info.play.sample_rate = rate;
++
++ if (ioctl(handle_, AUDIO_SETINFO, &info) < 0)
++ {
++ throw SnapException("Can't set rate: " + string(strerror(errno)));
++ }
++
++ (void)ioctl(handle_, AUDIO_GETINFO, &info);
++
++ rate = info.play.sample_rate;
++
++ if (format.rate() != rate)
++ LOG(WARNING, LOG_TAG) << "Could not set sample rate to " << format.rate() << " Hz, using: " << rate << " Hz\n";
++
++ AUDIO_INITINFO(&info);
++ info.hiwat = PERIODS;
++ info.lowat = MIN_PERIODS;
++ (void)ioctl(handle_, AUDIO_SETINFO, &info);
++}
++
++
++void SunPlayer::uninitSun()
++{
++ std::lock_guard<std::recursive_mutex> lock(mutex_);
++ if (handle_ != -1)
++ {
++ close(handle_);
++ handle_ = -1;
++ }
++}
++
++
++void SunPlayer::start()
++{
++ try
++ {
++ initSun();
++ }
++ catch (const SnapException& e)
++ {
++ LOG(ERROR, LOG_TAG) << "Exception: " << e.what() << ", code: " << e.code() << "\n";
++ // Accept "Device or ressource busy", the worker loop will retry
++ if (e.code() != -EBUSY)
++ throw;
++ }
++
++ Player::start();
++}
++
++
++SunPlayer::~SunPlayer()
++{
++ stop();
++}
++
++
++void SunPlayer::stop()
++{
++ Player::stop();
++ uninitSun();
++}
++
++
++bool SunPlayer::needsThread() const
++{
++ return true;
++}
++
++void SunPlayer::worker()
++{
++ unsigned int framesDelay;
++ unsigned int framesAvail;
++ long lastChunkTick = chronos::getTickCount();
++ const SampleFormat& format = stream_->getFormat();
++ struct audio_info info;
++
++ while (active_)
++ {
++ if (handle_ == -1)
++ {
++ try
++ {
++ initSun();
++ }
++ catch (const std::exception& e)
++ {
++ LOG(ERROR, LOG_TAG) << "Exception in initSun: " << e.what() << endl;
++ chronos::sleep(100);
++ }
++ if (handle_ == -1)
++ continue;
++ }
++
++ if (ioctl(handle_, AUDIO_GETBUFINFO, &info) == -1) {
++ this_thread::sleep_for(10ms);
++ continue;
++ }
++
++ framesDelay = info.play.seek / format.frameSize();
++ framesAvail = (info.play.buffer_size - info.play.seek) / format.frameSize();
++
++ if (buffer_.size() < static_cast<size_t>(info.play.buffer_size))
++ {
++ LOG(DEBUG, LOG_TAG) << "Resizing buffer from " << buffer_.size() << " to " << info.play.buffer_size << "\n";
++ buffer_.resize(info.play.buffer_size);
++ }
++
++ if (framesAvail == 0)
++ {
++ auto frame_time = std::chrono::microseconds(static_cast<int>((info.blocksize / format.frameSize()) / format.usRate()));
++ std::chrono::microseconds wait = std::min(frame_time / 2, std::chrono::microseconds(10ms));
++ LOG(DEBUG, LOG_TAG) << "No frames available, waiting for " << wait.count() << " us\n";
++ this_thread::sleep_for(wait);
++ continue;
++ }
++
++
++ chronos::usec delay(static_cast<chronos::usec::rep>(1000 * static_cast<double>(framesDelay) / format.msRate()));
++
++ if (stream_->getPlayerChunk(buffer_.data(), delay, framesAvail))
++ {
++ lastChunkTick = chronos::getTickCount();
++ adjustVolume(buffer_.data(), framesAvail);
++ if (write(handle_, buffer_.data(), framesAvail * format.frameSize()) < 0)
++ {
++ LOG(ERROR, LOG_TAG) << "ERROR. Can't write to PCM device: " << strerror(errno) << "\n";
++ uninitSun();
++ }
++ }
++ else
++ {
++ LOG(INFO, LOG_TAG) << "Failed to get chunk\n";
++ while (active_ && !stream_->waitForChunk(100ms))
++ {
++ static utils::logging::TimeConditional cond(2s);
++ LOG(DEBUG, LOG_TAG) << cond << "Waiting for chunk\n";
++ if ((handle_ != -1) && (chronos::getTickCount() - lastChunkTick > 5000))
++ {
++ LOG(NOTICE, LOG_TAG) << "No chunk received for 5000ms. Closing audio device.\n";
++ uninitSun();
++ stream_->clearChunks();
++ }
++ }
++ }
++ }
++}
++
++
++
++vector<PcmDevice> SunPlayer::pcm_list()
++{
++ std::string name;
++ struct audio_device dev;
++ vector<PcmDevice> result;
++ PcmDevice pcmDevice;
++ int i;
++ int fd;
++ int props;
++
++ for (i = 0; i < SUN_MAXDEVS; ++i)
++ {
++ name = "/dev/audio" + cpt::to_string(i);
++ fd = open(name.c_str(), O_WRONLY);
++ if (fd == -1)
++ break;
++ if (ioctl(fd, AUDIO_GETPROPS, &props) == -1)
++ {
++ close(fd);
++ break;
++ }
++ if (ioctl(fd, AUDIO_GETDEV, &dev) == -1)
++ {
++ close(fd);
++ break;
++ }
++ close(fd);
++ if ((props & AUDIO_PROP_PLAYBACK) == 0)
++ {
++ continue;
++ }
++ pcmDevice.name = name;
++ pcmDevice.description = string(dev.name) + " " + string(dev.version);
++ pcmDevice.idx = i;
++ result.push_back(pcmDevice);
++ }
++ return result;
++}
++
++} // namespace player
diff --git a/audio/snapcast/patches/patch-client_player_sun__player.hpp b/audio/snapcast/patches/patch-client_player_sun__player.hpp
new file mode 100644
index 00000000000..ab29dd3ca7a
--- /dev/null
+++ b/audio/snapcast/patches/patch-client_player_sun__player.hpp
@@ -0,0 +1,74 @@
+$NetBSD: patch-client_player_sun__player.hpp,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+Add Sun Audio support for NetBSD.
+
+--- client/player/sun_player.hpp.orig 2022-07-03 13:22:32.864495808 +0000
++++ client/player/sun_player.hpp
+@@ -0,0 +1,67 @@
++/***
++ This file is part of snapcast
++ Copyright (C) 2014-2020 Johannes Pohl
++
++ This program 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.
++
++ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
++***/
++
++#ifndef SUN_PLAYER_HPP
++#define SUN_PLAYER_HPP
++
++#include "player.hpp"
++
++#include <chrono>
++#include <optional>
++
++
++namespace player
++{
++
++static constexpr auto SUN = "sun";
++
++/// Audio Player
++/**
++ * Audio player implementation using Sun/NetBSD audio
++ */
++class SunPlayer : public Player
++{
++public:
++ SunPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream);
++ ~SunPlayer() override;
++
++ void start() override;
++ void stop() override;
++
++ /// List the system's audio output devices
++ static std::vector<PcmDevice> pcm_list();
++
++protected:
++ void worker() override;
++ bool needsThread() const override;
++
++private:
++ /// initialize audio device
++ void initSun();
++ /// close audio device
++ void uninitSun();
++
++ int handle_;
++
++ std::recursive_mutex mutex_;
++ std::vector<char> buffer_;
++};
++
++} // namespace player
++
++#endif
diff --git a/audio/snapcast/patches/patch-client_snapclient.cpp b/audio/snapcast/patches/patch-client_snapclient.cpp
new file mode 100644
index 00000000000..d8a9f8977ba
--- /dev/null
+++ b/audio/snapcast/patches/patch-client_snapclient.cpp
@@ -0,0 +1,59 @@
+$NetBSD: patch-client_snapclient.cpp,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+Add Sun Audio support for NetBSD.
+
+--- client/snapclient.cpp.orig 2021-12-22 17:40:36.000000000 +0000
++++ client/snapclient.cpp
+@@ -32,6 +32,9 @@
+ #ifdef HAS_PULSE
+ #include "player/pulse_player.hpp"
+ #endif
++#ifdef HAS_SUN
++#include "player/sun_player.hpp"
++#endif
+ #ifdef HAS_WASAPI
+ #include "player/wasapi_player.hpp"
+ #endif
+@@ -62,8 +65,12 @@ PcmDevice getPcmDevice(const std::string
+ {
+ LOG(DEBUG, LOG_TAG) << "Trying to get PCM device for player: " << player << ", parameter: "
+ << ", card: " << soundcard << "\n";
+-#if defined(HAS_ALSA) || defined(HAS_PULSE) || defined(HAS_WASAPI)
++#if defined(HAS_ALSA) || defined(HAS_PULSE) || defined(HAS_WASAPI) || defined(HAS_SUN)
+ vector<PcmDevice> pcm_devices;
++#if defined(HAS_SUN)
++ if (player == player::SUN)
++ pcm_devices = SunPlayer::pcm_list();
++#endif
+ #if defined(HAS_ALSA)
+ if (player == player::ALSA)
+ pcm_devices = AlsaPlayer::pcm_list();
+@@ -142,7 +149,7 @@ int main(int argc, char** argv)
+ op.add<Value<string>>("", "hostID", "unique host id, default is MAC address", "", &settings.host_id);
+
+ // PCM device specific
+-#if defined(HAS_ALSA) || defined(HAS_PULSE) || defined(HAS_WASAPI)
++#if defined(HAS_ALSA) || defined(HAS_PULSE) || defined(HAS_WASAPI) || defined(HAS_SUN)
+ auto listSwitch = op.add<Switch>("l", "list", "list PCM devices");
+ /*auto soundcardValue =*/op.add<Value<string>>("s", "soundcard", "index or name of the pcm device", pcm_device, &pcm_device);
+ #endif
+@@ -210,7 +217,7 @@ int main(int argc, char** argv)
+
+ settings.player.player_name = utils::string::split_left(settings.player.player_name, ':', settings.player.parameter);
+
+-#if defined(HAS_ALSA) || defined(HAS_PULSE) || defined(HAS_WASAPI)
++#if defined(HAS_ALSA) || defined(HAS_PULSE) || defined(HAS_WASAPI) || defined(HAS_SUN)
+ if (listSwitch->is_set())
+ {
+ try
+@@ -224,6 +231,10 @@ int main(int argc, char** argv)
+ if (settings.player.player_name == player::PULSE)
+ pcm_devices = PulsePlayer::pcm_list(settings.player.parameter);
+ #endif
++#if defined(HAS_SUN)
++ if (settings.player.player_name == player::SUN)
++ pcm_devices = SunPlayer::pcm_list();
++#endif
+ #if defined(HAS_WASAPI)
+ if (settings.player.player_name == player::WASAPI)
+ pcm_devices = WASAPIPlayer::pcm_list();
diff --git a/audio/snapcast/patches/patch-common_utils.hpp b/audio/snapcast/patches/patch-common_utils.hpp
new file mode 100644
index 00000000000..0f5cebbb7cf
--- /dev/null
+++ b/audio/snapcast/patches/patch-common_utils.hpp
@@ -0,0 +1,67 @@
+$NetBSD: patch-common_utils.hpp,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+Add NetBSD support.
+
+--- common/utils.hpp.orig 2021-12-22 17:40:36.000000000 +0000
++++ common/utils.hpp
+@@ -1,5 +1,4 @@
+-/***
+- This file is part of snapcast
++/*** This file is part of snapcast
+ Copyright (C) 2014-2020 Johannes Pohl
+
+ This program is free software: you can redistribute it and/or modify
+@@ -44,7 +43,7 @@
+ #include <sys/stat.h>
+ #include <sys/types.h>
+ #include <vector>
+-#if !defined(WINDOWS) && !defined(FREEBSD)
++#if defined(MACOS) || defined(__linux__)
+ #include <sys/sysinfo.h>
+ #endif
+ #ifdef MACOS
+@@ -53,6 +52,10 @@
+ #include <ifaddrs.h>
+ #include <net/if_dl.h>
+ #endif
++#ifdef __NetBSD__
++#include <ifaddrs.h>
++#include <net/if_dl.h>
++#endif
+ #ifdef ANDROID
+ #include <sys/system_properties.h>
+ #endif
+@@ -306,7 +309,7 @@ static std::string getMacAddress(int soc
+ {
+ if (!(ifr.ifr_flags & IFF_LOOPBACK)) // don't count loopback
+ {
+-#ifdef MACOS
++#if defined(MACOS) || defined(__NetBSD__)
+ /// Dirty Mac version
+ struct ifaddrs *ifap, *ifaptr;
+ unsigned char* ptr;
+@@ -333,6 +336,7 @@ static std::string getMacAddress(int soc
+ }
+ #endif
+
++#ifndef __NetBSD__
+ #ifdef FREEBSD
+ if (ioctl(sock, SIOCGIFMAC, &ifr) == 0)
+ #else
+@@ -355,6 +359,7 @@ static std::string getMacAddress(int soc
+ return line;
+ }
+ }
++#endif
+ }
+ }
+ else
+@@ -369,7 +374,7 @@ static std::string getMacAddress(int soc
+ return "";
+
+ char mac[19];
+-#ifndef FREEBSD
++#if !defined(FREEBSD) && !defined(__NetBSD__)
+ sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", (unsigned char)ifr.ifr_hwaddr.sa_data[0], (unsigned char)ifr.ifr_hwaddr.sa_data[1],
+ (unsigned char)ifr.ifr_hwaddr.sa_data[2], (unsigned char)ifr.ifr_hwaddr.sa_data[3], (unsigned char)ifr.ifr_hwaddr.sa_data[4],
+ (unsigned char)ifr.ifr_hwaddr.sa_data[5]);
diff --git a/audio/snapcast/patches/patch-server_CMakeLists.txt b/audio/snapcast/patches/patch-server_CMakeLists.txt
new file mode 100644
index 00000000000..0ad3991dc0e
--- /dev/null
+++ b/audio/snapcast/patches/patch-server_CMakeLists.txt
@@ -0,0 +1,15 @@
+$NetBSD: patch-server_CMakeLists.txt,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+Install config files to examples, per pkgsrc conventions.
+
+--- server/CMakeLists.txt.orig 2021-12-22 17:40:36.000000000 +0000
++++ server/CMakeLists.txt
+@@ -114,7 +114,7 @@ else()
+
+ install(TARGETS snapserver COMPONENT server DESTINATION ${CMAKE_INSTALL_BINDIR})
+ install(FILES snapserver.1 COMPONENT server DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
+- install(FILES etc/snapserver.conf COMPONENT server DESTINATION ${CMAKE_INSTALL_SYSCONFDIR})
++ install(FILES etc/snapserver.conf COMPONENT server DESTINATION ${CMAKE_INSTALL_DATADIR}/examples/snapcast)
+ install(FILES etc/index.html COMPONENT server DESTINATION ${CMAKE_INSTALL_DATADIR}/snapserver)
+ install(DIRECTORY etc/snapweb/ DESTINATION ${CMAKE_INSTALL_DATADIR}/snapserver/snapweb)
+ install(FILES etc/plug-ins/meta_mpd.py PERMISSIONS OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE DESTINATION ${CMAKE_INSTALL_DATADIR}/snapserver/plug-ins/)
diff --git a/audio/snapcast/patches/patch-server_etc_snapserver.conf b/audio/snapcast/patches/patch-server_etc_snapserver.conf
new file mode 100644
index 00000000000..23018efc151
--- /dev/null
+++ b/audio/snapcast/patches/patch-server_etc_snapserver.conf
@@ -0,0 +1,24 @@
+$NetBSD: patch-server_etc_snapserver.conf,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+Don't hardcode various paths, allow pkgsrc to substitute them.
+
+--- server/etc/snapserver.conf.orig 2021-12-22 17:40:36.000000000 +0000
++++ server/etc/snapserver.conf
+@@ -30,7 +30,7 @@
+ #threads = -1
+
+ # the pid file when running as daemon
+-#pidfile = /var/run/snapserver/pid
++#pidfile = @VARBASE@/run/snapserver/pid
+
+ # the user to run as when daemonized
+ #user = snapserver
+@@ -39,7 +39,7 @@
+
+ # directory where persistent data is stored (server.json)
+ # if empty, data dir will be
+-# - "/var/lib/snapserver/" when running as daemon
++# - "@VARBASE@/lib/snapserver/" when running as daemon
+ # - "$HOME/.config/snapserver/" when not running as daemon
+ #datadir =
+
diff --git a/audio/snapcast/patches/patch-server_server__settings.hpp b/audio/snapcast/patches/patch-server_server__settings.hpp
new file mode 100644
index 00000000000..16bfe48671c
--- /dev/null
+++ b/audio/snapcast/patches/patch-server_server__settings.hpp
@@ -0,0 +1,15 @@
+$NetBSD: patch-server_server__settings.hpp,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+Don't hardcode various paths, allow pkgsrc to substitute them.
+
+--- server/server_settings.hpp.orig 2021-12-22 17:40:36.000000000 +0000
++++ server/server_settings.hpp
+@@ -30,7 +30,7 @@ struct ServerSettings
+ struct Server
+ {
+ int threads{-1};
+- std::string pid_file{"/var/run/snapserver/pid"};
++ std::string pid_file{"@VARBASE@/run/snapserver/pid"};
+ std::string user{"snapserver"};
+ std::string group{""};
+ std::string data_dir{""};
diff --git a/audio/snapcast/patches/patch-server_snapserver.1 b/audio/snapcast/patches/patch-server_snapserver.1
new file mode 100644
index 00000000000..b0d4c9885b6
--- /dev/null
+++ b/audio/snapcast/patches/patch-server_snapserver.1
@@ -0,0 +1,30 @@
+$NetBSD: patch-server_snapserver.1,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+Don't hardcode various paths, allow pkgsrc to substitute them.
+
+--- server/snapserver.1.orig 2021-12-22 17:40:36.000000000 +0000
++++ server/snapserver.1
+@@ -25,20 +25,17 @@ Show version number
+ Daemonize
+ optional process priority [-20..19]
+ .TP
+-\fB-c, --config arg (=/etc/snapserver.conf)\fR
++\fB-c, --config arg (=@PKG_SYSCONFDIR@/snapserver.conf)\fR
+ path to the configuration file
+ .SH FILES
+ .TP
+ \fI/tmp/snapfifo\fR
+ PCM input fifo file
+ .TP
+-\fI/etc/default/snapserver\fR
+-the daemon default configuration file
+-.TP
+-\fI/etc/snapserver.conf\fR
++\fI@PKG_SYSCONFDIR@/snapserver.conf\fR
+ the snapserver configuration file
+ .TP
+-\fI~/.config/snapcast/server.json\fR or (if $HOME is not set) \fI/var/lib/snapcast/server.json\fR
++\fI~/.config/snapcast/server.json\fR or (if $HOME is not set) \fI@VARBASE@/lib/snapcast/server.json\fR
+ persistent server data file
+ .SH "COPYRIGHT"
+ Copyright (C) 2014-2020 Johannes Pohl (snapcast@badaix.de).
diff --git a/audio/snapcast/patches/patch-server_snapserver.cpp b/audio/snapcast/patches/patch-server_snapserver.cpp
new file mode 100644
index 00000000000..4e19ee62556
--- /dev/null
+++ b/audio/snapcast/patches/patch-server_snapserver.cpp
@@ -0,0 +1,24 @@
+$NetBSD: patch-server_snapserver.cpp,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+Don't hardcode various paths, allow pkgsrc to substitute them.
+
+--- server/snapserver.cpp.orig 2021-12-22 17:40:36.000000000 +0000
++++ server/snapserver.cpp
+@@ -58,7 +58,7 @@ int main(int argc, char* argv[])
+ {
+ ServerSettings settings;
+ std::string pcmSource = "pipe:///tmp/snapfifo?name=default";
+- std::string config_file = "/etc/snapserver.conf";
++ std::string config_file = "@PKG_SYSCONFDIR@/snapserver.conf";
+
+ OptionParser op("Allowed options");
+ auto helpSwitch = op.add<Switch>("h", "help", "Produce help message, use -hh to show options from config file");
+@@ -252,7 +252,7 @@ int main(int argc, char* argv[])
+ throw std::invalid_argument("user must not be empty");
+
+ if (settings.server.data_dir.empty())
+- settings.server.data_dir = "/var/lib/snapserver";
++ settings.server.data_dir = "@VARBASE@/lib/snapserver";
+ Config::instance().init(settings.server.data_dir, settings.server.user, settings.server.group);
+
+ daemon = std::make_unique<Daemon>(settings.server.user, settings.server.group, settings.server.pid_file);
diff --git a/audio/snapcast/patches/patch-server_streamreader_pipe__stream.cpp b/audio/snapcast/patches/patch-server_streamreader_pipe__stream.cpp
new file mode 100644
index 00000000000..02a8a94163a
--- /dev/null
+++ b/audio/snapcast/patches/patch-server_streamreader_pipe__stream.cpp
@@ -0,0 +1,15 @@
+$NetBSD: patch-server_streamreader_pipe__stream.cpp,v 1.1 2022/07/03 16:09:15 nia Exp $
+
+Add NetBSD support.
+
+--- server/streamreader/pipe_stream.cpp.orig 2021-12-22 17:40:36.000000000 +0000
++++ server/streamreader/pipe_stream.cpp
+@@ -59,7 +59,7 @@ void PipeStream::do_connect()
+ {
+ int fd = open(uri_.path.c_str(), O_RDONLY | O_NONBLOCK);
+ int pipe_size = -1;
+-#if !defined(MACOS) && !defined(FREEBSD)
++#ifdef __linux__
+ pipe_size = fcntl(fd, F_GETPIPE_SZ);
+ #endif
+ LOG(TRACE, LOG_TAG) << "Stream: " << name_ << ", connect to pipe: " << uri_.path << ", fd: " << fd << ", pipe size: " << pipe_size << "\n";