aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteven O'Brien <steven_obrien1@yahoo.co.uk>2013-12-15 16:52:21 +0000
committerMax Kellermann <max@duempel.org>2013-12-19 23:25:50 +0100
commit35f85ddd860a819dbdebed1124f572719b28ef0b (patch)
tree7ec692ec60f78df495bbe9ce09dfd5aa22f0c8c6
parente30fa7d15e71b9109f4f44c0b388e88b001031af (diff)
downloadmpd-35f85ddd860a819dbdebed1124f572719b28ef0b.tar.gz
mpd-35f85ddd860a819dbdebed1124f572719b28ef0b.tar.xz
mpd-35f85ddd860a819dbdebed1124f572719b28ef0b.zip
add draft ALSA input plugin
I've created an elementary input plugin that plays sound from the soundcard, so you can use MPD to listen to anything connected to the line-in jack, or to Video4Linux FM radio cards that send audio through the soundcard. There has been a small number of posts here in the past requesting line-in input, so here is a first, simplistic stab at it. The patch adds a new sheme, alsa://, which causes mpd to play data read directly from a souncdard. It defaults to hw:0,0, but you can pass any ALSA device name in the URI. So, using mpc for example: mpc add alsa:// mpc play will play from device hw:0,0. To use a diffferent device: mpc add alsa://hw:1,0
-rw-r--r--Makefile.am8
-rw-r--r--NEWS2
-rw-r--r--src/InputRegistry.cxx7
-rw-r--r--src/input/AlsaInputPlugin.cxx206
-rw-r--r--src/input/AlsaInputPlugin.hxx28
-rw-r--r--src/ls.cxx3
6 files changed, 254 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am
index d5d9c0294..2b610a22f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -802,6 +802,14 @@ INPUT_LIBS = \
$(DESPOTIFY_LIBS) \
$(MMS_LIBS)
+if HAVE_ALSA
+libinput_a_SOURCES += \
+ src/input/AlsaInputPlugin.cxx \
+ src/input/AlsaInputPlugin.hxx
+INPUT_LIBS += $(ALSA_LIBS)
+endif
+
+
if ENABLE_CURL
libinput_a_SOURCES += \
src/input/CurlInputPlugin.cxx src/input/CurlInputPlugin.hxx \
diff --git a/NEWS b/NEWS
index 133e283fb..f83f72697 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,8 @@
ver 0.19 (not yet released)
* protocol
- new commands "addtagid", "cleartagid"
+* input
+ - alsa: new input plugin
* new resampler option using libsoxr
ver 0.18.6 (not yet released)
diff --git a/src/InputRegistry.cxx b/src/InputRegistry.cxx
index aa6c06ed1..ef4acb237 100644
--- a/src/InputRegistry.cxx
+++ b/src/InputRegistry.cxx
@@ -22,6 +22,10 @@
#include "util/Macros.hxx"
#include "input/FileInputPlugin.hxx"
+#ifdef HAVE_ALSA
+#include "input/AlsaInputPlugin.hxx"
+#endif
+
#ifdef ENABLE_ARCHIVE
#include "input/ArchiveInputPlugin.hxx"
#endif
@@ -48,6 +52,9 @@
const InputPlugin *const input_plugins[] = {
&input_plugin_file,
+#ifdef HAVE_ALSA
+ &input_plugin_alsa,
+#endif
#ifdef ENABLE_ARCHIVE
&input_plugin_archive,
#endif
diff --git a/src/input/AlsaInputPlugin.cxx b/src/input/AlsaInputPlugin.cxx
new file mode 100644
index 000000000..f014434c1
--- /dev/null
+++ b/src/input/AlsaInputPlugin.cxx
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2003-2013 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.
+ */
+
+/*
+ * ALSA code based on an example by Paul Davis released under GPL here:
+ * http://equalarea.com/paul/alsa-audio.html
+ * and one by Matthias Nagorni, also GPL, here:
+ * http://alsamodular.sourceforge.net/alsa_programming_howto.html
+ */
+
+#include "config.h"
+#include "AlsaInputPlugin.hxx"
+#include "InputPlugin.hxx"
+#include "InputStream.hxx"
+#include "util/Domain.hxx"
+#include "util/Error.hxx"
+#include "util/StringUtil.hxx"
+#include "Log.hxx"
+
+#include <alsa/asoundlib.h>
+
+static constexpr Domain alsa_input_domain("alsa");
+
+static constexpr const char *default_device = "hw:0,0";
+
+// this value chosen to balance between limiting latency and avoiding stutter
+static constexpr int max_frames_to_buffer = 64;
+
+// the following defaults are because the PcmDecoderPlugin forces CD format
+static constexpr snd_pcm_format_t default_format = SND_PCM_FORMAT_S16;
+static constexpr int default_channels = 2; // stereo
+static constexpr unsigned int default_rate = 44100; // cd quality
+
+struct AlsaInputStream {
+ InputStream base;
+ snd_pcm_t *capture_handle;
+ size_t frame_size;
+ size_t max_bytes_to_read;
+
+ AlsaInputStream(const char *uri, Mutex &mutex, Cond &cond,
+ snd_pcm_t *handle)
+ :base(input_plugin_alsa, uri, mutex, cond),
+ capture_handle(handle) {
+ frame_size = snd_pcm_format_width(default_format) / 8 * default_channels;
+ max_bytes_to_read = max_frames_to_buffer * frame_size;
+ base.mime = strdup("audio/x-mpd-cdda-pcm");
+ base.seekable = false;
+ base.size = -1;
+ base.ready = true;
+ }
+
+ ~AlsaInputStream() {
+ snd_pcm_close(capture_handle);
+ }
+};
+
+static InputStream *
+alsa_input_open(const char *uri, Mutex &mutex, Cond &cond, Error &error)
+{
+ int err;
+
+ // check uri is appropriate for alsa input
+ if (!StringStartsWith(uri, "alsa://"))
+ return nullptr;
+
+ const char *device = uri + 7;
+ if (device[0] == '\0')
+ device = default_device;
+
+ snd_pcm_t *capture_handle;
+ if ((err = snd_pcm_open(&capture_handle, device,
+ SND_PCM_STREAM_CAPTURE, 0)) < 0) {
+ error.Format(alsa_input_domain, "Failed to open device: %s (%s)", device, snd_strerror(err));
+ return nullptr;
+ }
+
+ snd_pcm_hw_params_t *hw_params;
+ if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
+ error.Format(alsa_input_domain, "Cannot allocate hardware parameter structure (%s)", snd_strerror(err));
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
+ error.Format(alsa_input_domain, "Cannot initialize hardware parameter structure (%s)", snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
+ error.Format(alsa_input_domain, "Cannot set access type (%s)", snd_strerror (err));
+ snd_pcm_hw_params_free (hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, default_format)) < 0) {
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ error.Format(alsa_input_domain, "Cannot set sample format (%s)", snd_strerror (err));
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, default_channels)) < 0) {
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ error.Format(alsa_input_domain, "Cannot set channels (%s)", snd_strerror (err));
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params_set_rate(capture_handle, hw_params, default_rate, 0)) < 0) {
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ error.Format(alsa_input_domain, "Cannot set sample rate (%s)", snd_strerror (err));
+ return nullptr;
+ }
+
+ if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
+ error.Format(alsa_input_domain, "Cannot set parameters (%s)", snd_strerror (err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ snd_pcm_hw_params_free (hw_params);
+
+ // clear any data already in the PCM buffer
+ if ((err = snd_pcm_drop(capture_handle)) < 0) {
+ error.Format(alsa_input_domain, "Cannot clear PCM buffer (%s)", snd_strerror (err));
+ snd_pcm_hw_params_free(hw_params);
+ snd_pcm_close(capture_handle);
+ return nullptr;
+ }
+
+ AlsaInputStream *ais = new AlsaInputStream(uri, mutex, cond, capture_handle);
+ return &ais->base;
+}
+
+static void
+alsa_input_close(InputStream *is)
+{
+ AlsaInputStream *ais = (AlsaInputStream*) is;
+ delete ais;
+}
+
+static size_t
+alsa_input_read(InputStream *is, void *ptr, size_t size,
+ gcc_unused Error &error)
+{
+ AlsaInputStream *ais = (AlsaInputStream*) is;
+ int num_frames = max_frames_to_buffer;
+ if (size < ais->max_bytes_to_read)
+ // calculate number of whole frames that will fit in size bytes
+ num_frames = size / ais->frame_size;
+
+ int ret;
+ while ((ret = snd_pcm_readi(ais->capture_handle, ptr,
+ num_frames)) < 0) {
+ snd_pcm_prepare(ais->capture_handle);
+ LogDebug(alsa_input_domain, "Buffer Overrun");
+ }
+
+ size_t nbytes = ret == max_frames_to_buffer
+ ? ais->max_bytes_to_read
+ : ret * ais->frame_size;
+ is->offset += nbytes;
+ return nbytes;
+}
+
+static bool
+alsa_input_eof(gcc_unused InputStream *is)
+{
+ return false;
+};
+
+const struct InputPlugin input_plugin_alsa = {
+ .name = "alsa",
+ .init = nullptr,
+ .finish = nullptr,
+ .open = alsa_input_open,
+ .close = alsa_input_close,
+ .check = nullptr,
+ .update = nullptr,
+ .tag = nullptr,
+ .available = nullptr,
+ .read = alsa_input_read,
+ .eof = alsa_input_eof,
+ .seek = nullptr
+};
diff --git a/src/input/AlsaInputPlugin.hxx b/src/input/AlsaInputPlugin.hxx
new file mode 100644
index 000000000..ac9519588
--- /dev/null
+++ b/src/input/AlsaInputPlugin.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2013 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_ALSA_INPUT_PLUGIN_HXX
+#define MPD_ALSA_INPUT_PLUGIN_HXX
+
+#include "InputPlugin.hxx"
+
+extern const struct InputPlugin input_plugin_alsa;
+
+
+#endif
diff --git a/src/ls.cxx b/src/ls.cxx
index 117c8875b..8039cf1d8 100644
--- a/src/ls.cxx
+++ b/src/ls.cxx
@@ -55,6 +55,9 @@ static const char *remoteUrlPrefixes[] = {
#ifdef ENABLE_DESPOTIFY
"spt://",
#endif
+#ifdef HAVE_ALSA
+ "alsa://",
+#endif
NULL
};