diff --git a/Makefile.am b/Makefile.am index 87c8dd0ff..dd47df7e8 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/NEWS b/NEWS index e75a9fc10..e3dad2832 100644 --- a/NEWS +++ b/NEWS @@ -37,6 +37,7 @@ ver 0.20 (not yet released) - pulse: set channel map to WAVE-EX - recorder: record tags - recorder: allow dynamic file names + - sndio: new output plugin * mixer - null: new plugin * resampler diff --git a/configure.ac b/configure.ac index 24ce7f421..dbbde3a79 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([--enable-sndio], + [enable support for sndio output plugin (default: auto)]),, + enable_sndio=auto) + AC_ARG_ENABLE(haiku, AS_HELP_STRING([--enable-haiku], [enable the Haiku output plugin (default: auto)]),, @@ -1109,6 +1114,20 @@ fi MPD_DEFINE_CONDITIONAL(enable_fifo, HAVE_FIFO, [support for writing audio to a FIFO]) +dnl ----------------------------------- SNDIO ---------------------------------- +if test x$enable_sndio = xauto; 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 + +if test x$enable_sndio = xyes; then + AC_DEFINE(HAVE_SNDIO,1,[Define for compiling sndio support]) + LIBS="$LIBS -lsndio" +fi + +AM_CONDITIONAL(HAVE_SNDIO, test x$enable_sndio = xyes) + dnl ----------------------------------- Haiku --------------------------------- if test x$enable_haiku = xauto; then AC_CHECK_HEADER(media/MediaDefs.h, @@ -1429,6 +1448,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 e8b17d088..9d8d82652 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 000000000..b3b990ad4 --- /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 +#include +#include +#include + +#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; + 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); + + while (1) { + n = sio_write(sio_hdl, chunk, size); + if (n < 0) { + if (errno == EINTR) + continue; + error.FormatErrno("Failed to write %zu bytes to sndio output", size); + return 0; + } + return n; + } +} + +void +SndioOutput::Cancel() +{ + timer->Reset(); +} + +typedef AudioOutputWrapper 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 000000000..d8db1f636 --- /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