Hi, I've implemented an sndio output plugin for use on OpenBSD. I have a few enhancements to make such as tweaking the application buffer size via a config option or allowing the user to select a specific audio device but that can also be done incrementally in separate patches.
The current implementation uses sndiod(8) by default. This allows the audio hardware to be shared by multiple applications. If you have any comments let me know. From dd720e0104cfeed8b1687ff0a9bf3af0b72a8e12 Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos <[email protected]> Date: Sun, 19 Jun 2016 11:21:08 +0100 Subject: [PATCH] Add sndio output plugin --- Makefile.am | 6 + configure.ac | 16 +++ src/output/Registry.cxx | 4 + src/output/plugins/SndioOutputPlugin.cxx | 210 +++++++++++++++++++++++++++++++ src/output/plugins/SndioOutputPlugin.hxx | 25 ++++ 5 files changed, 261 insertions(+) create mode 100644 src/output/plugins/SndioOutputPlugin.cxx create mode 100644 src/output/plugins/SndioOutputPlugin.hxx diff --git a/Makefile.am b/Makefile.am index 87c8dd0..dd47df7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1396,6 +1396,12 @@ liboutput_plugins_a_SOURCES += \ src/output/plugins/FifoOutputPlugin.hxx endif +if HAVE_SNDIO +liboutput_plugins_a_SOURCES += \ + src/output/plugins/SndioOutputPlugin.cxx \ + src/output/plugins/SndioOutputPlugin.hxx +endif + if HAVE_HAIKU liboutput_plugins_a_SOURCES += \ src/output/plugins/HaikuOutputPlugin.cxx \ diff --git a/configure.ac b/configure.ac index 24ce7f4..a3d7dd3 100644 --- a/configure.ac +++ b/configure.ac @@ -334,6 +334,11 @@ AC_ARG_ENABLE(fifo, [disable support for writing audio to a FIFO (default: enable)]),, enable_fifo=yes) +AC_ARG_ENABLE(sndio, + AS_HELP_STRING([--disable-sndio], + [disable support for sndio output (default: enable)]),, + enable_sndio=yes) + AC_ARG_ENABLE(haiku, AS_HELP_STRING([--enable-haiku], [enable the Haiku output plugin (default: auto)]),, @@ -1109,6 +1114,16 @@ fi MPD_DEFINE_CONDITIONAL(enable_fifo, HAVE_FIFO, [support for writing audio to a FIFO]) +dnl ----------------------------------- SNDIO ---------------------------------- +if test x$enable_sndio = xyes; then + AC_CHECK_HEADER(sndio.h, + [enable_sndio=yes], + [enable_sndio=no;AC_MSG_WARN([sndio.h not found -- disabling support for sndio output])]) +fi + +MPD_DEFINE_CONDITIONAL(enable_sndio, HAVE_SNDIO, + [support for sndio output]) + dnl ----------------------------------- Haiku --------------------------------- if test x$enable_haiku = xauto; then AC_CHECK_HEADER(media/MediaDefs.h, @@ -1429,6 +1444,7 @@ results(id3,[ID3]) printf '\nPlayback support:\n\t' results(alsa,ALSA) results(fifo,FIFO) +results(sndio,[SNDIO]) results(recorder_output,[File Recorder]) results(haiku,[Haiku]) results(httpd_output,[HTTP Daemon]) diff --git a/src/output/Registry.cxx b/src/output/Registry.cxx index e8b17d0..9d8d826 100644 --- a/src/output/Registry.cxx +++ b/src/output/Registry.cxx @@ -23,6 +23,7 @@ #include "plugins/AlsaOutputPlugin.hxx" #include "plugins/AoOutputPlugin.hxx" #include "plugins/FifoOutputPlugin.hxx" +#include "plugins/SndioOutputPlugin.hxx" #include "plugins/httpd/HttpdOutputPlugin.hxx" #include "plugins/HaikuOutputPlugin.hxx" #include "plugins/JackOutputPlugin.hxx" @@ -52,6 +53,9 @@ const AudioOutputPlugin *const audio_output_plugins[] = { #ifdef HAVE_FIFO &fifo_output_plugin, #endif +#ifdef HAVE_SNDIO + &sndio_output_plugin, +#endif #ifdef HAVE_HAIKU &haiku_output_plugin, #endif diff --git a/src/output/plugins/SndioOutputPlugin.cxx b/src/output/plugins/SndioOutputPlugin.cxx new file mode 100644 index 0000000..2234697 --- /dev/null +++ b/src/output/plugins/SndioOutputPlugin.cxx @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2016 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <errno.h> +#include <sndio.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "SndioOutputPlugin.hxx" +#include "config/ConfigError.hxx" +#include "../OutputAPI.hxx" +#include "../Wrapper.hxx" +#include "../Timer.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" +#include "Log.hxx" + +class SndioOutput { + friend struct AudioOutputWrapper<SndioOutput>; + AudioOutput base; + struct sio_hdl *sio_hdl; + Timer *timer; + +public: + SndioOutput() + :base(sndio_output_plugin), + sio_hdl(nullptr) {} + ~SndioOutput() {} + + bool Configure(const ConfigBlock &block, Error &error); + + static SndioOutput *Create(const ConfigBlock &block, Error &error); + + bool Open(AudioFormat &audio_format, Error &error); + void Close(); + unsigned Delay() const; + size_t Play(const void *chunk, size_t size, Error &error); + void Cancel(); +}; + +static constexpr Domain sndio_output_domain("sndio_output"); + +bool +SndioOutput::Configure(const ConfigBlock &block, Error &error) +{ + if (!base.Configure(block, error)) + return false; + return true; +} + +SndioOutput * +SndioOutput::Create(const ConfigBlock &block, Error &error) +{ + SndioOutput *ao = new SndioOutput(); + + if (!ao->Configure(block, error)) { + delete ao; + return nullptr; + } + + return ao; +} + +bool +SndioOutput::Open(AudioFormat &audio_format, gcc_unused Error &error) +{ + struct sio_par par; + unsigned bits, rate, chans; + + sio_hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0); + if (!sio_hdl) { + error.Format(sndio_output_domain, -1, + "Failed to open default sndio device"); + return false; + } + + switch (audio_format.format) { + case SampleFormat::S16: + bits = 16; + break; + case SampleFormat::S32: + bits = 32; + break; + default: + audio_format.format = SampleFormat::S16; + bits = 16; + break; + } + + rate = audio_format.sample_rate; + chans = audio_format.channels; + + sio_initpar(&par); + par.bits = bits; + par.rate = rate; + par.pchan = chans; + par.sig = 1; + par.le = SIO_LE_NATIVE; + + if (!sio_setpar(sio_hdl, &par) || + !sio_getpar(sio_hdl, &par)) { + error.Format(sndio_output_domain, -1, + "Failed to set/get audio params"); + goto err; + } + + if (par.bits != bits || + par.rate < rate * 995 / 1000 || + par.rate > rate * 1005 / 1000 || + par.pchan != chans || + par.sig != 1 || + par.le != SIO_LE_NATIVE) { + error.Format(sndio_output_domain, -1, + "Requested audio params cannot be satisfied"); + goto err; + } + + if (!sio_start(sio_hdl)) { + error.Format(sndio_output_domain, -1, + "Failed to start audio device"); + goto err; + } + + timer = new Timer(audio_format); + return true; +err: + sio_close(sio_hdl); + sio_hdl = nullptr; + return false; +} + +void +SndioOutput::Close() +{ + if (sio_hdl) + sio_close(sio_hdl); + delete timer; +} + +unsigned +SndioOutput::Delay() const +{ + return timer->IsStarted() + ? timer->GetDelay() + : 0; +} + +size_t +SndioOutput::Play(const void *chunk, size_t size, Error &error) +{ + ssize_t n; + + if (!timer->IsStarted()) + timer->Start(); + timer->Add(size); + +again: + n = sio_write(sio_hdl, chunk, size); + if (n < 0) { + if (errno == EINTR) + goto again; + error.FormatErrno("Failed to write %zu bytes to sndio output", size); + return 0; + } + + return n; +} + +void +SndioOutput::Cancel() +{ + timer->Reset(); +} + +typedef AudioOutputWrapper<SndioOutput> Wrapper; + +const struct AudioOutputPlugin sndio_output_plugin = { + "sndio", + nullptr, + &Wrapper::Init, + &Wrapper::Finish, + nullptr, + nullptr, + &Wrapper::Open, + &Wrapper::Close, + &Wrapper::Delay, + nullptr, + &Wrapper::Play, + nullptr, + &Wrapper::Cancel, + nullptr, + nullptr, +}; diff --git a/src/output/plugins/SndioOutputPlugin.hxx b/src/output/plugins/SndioOutputPlugin.hxx new file mode 100644 index 0000000..d8db1f6 --- /dev/null +++ b/src/output/plugins/SndioOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2016 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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 2 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SNDIO_OUTPUT_PLUGIN_HXX +#define MPD_SNDIO_OUTPUT_PLUGIN_HXX + +extern const struct AudioOutputPlugin sndio_output_plugin; + +#endif -- 2.8.4 _______________________________________________ mpd-devel mailing list [email protected] http://mailman.blarg.de/listinfo/mpd-devel
