aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMax Kellermann <max@duempel.org>2010-10-03 21:07:55 +0200
committerMax Kellermann <max@duempel.org>2010-10-27 21:25:41 +0200
commit75f4772ba2584a3d533ee11fb210201d45f64bb6 (patch)
treefd9c9f6e3c534419ce03d13d7854b5979b6c0c84
parentfe1b626f760d5c5e0726befb1c243f4a901613a6 (diff)
downloadmpd-75f4772ba2584a3d533ee11fb210201d45f64bb6.tar.gz
mpd-75f4772ba2584a3d533ee11fb210201d45f64bb6.tar.xz
mpd-75f4772ba2584a3d533ee11fb210201d45f64bb6.zip
output: new output plugin "ffado"
Using libffado, to play on firewire audio devices. Warning: this plugin was not tested successfully. I just couldn't keep libffado2 from crashing. Use at your own risk. For details, see my Debian bug reports: http://bugs.debian.org/601657 http://bugs.debian.org/601659
-rw-r--r--INSTALL3
-rw-r--r--Makefile.am6
-rw-r--r--configure.ac17
-rw-r--r--doc/user.xml37
-rw-r--r--src/output/ffado_output_plugin.c344
-rw-r--r--src/output_list.c4
6 files changed, 411 insertions, 0 deletions
diff --git a/INSTALL b/INSTALL
index cf087ef83..54ade434d 100644
--- a/INSTALL
+++ b/INSTALL
@@ -59,6 +59,9 @@ You also need an encoder: either libvorbisenc (ogg), or liblame (mp3).
OpenAL - http://kcat.strangesoft.net/openal.html
Open Audio Library
+libffado - http://www.ffado.org/
+For FireWire audio devices.
+
Optional Input Dependencies
---------------------------
diff --git a/Makefile.am b/Makefile.am
index 8f126676b..92a2a9f55 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -640,6 +640,7 @@ endif
OUTPUT_CFLAGS = \
$(AO_CFLAGS) \
$(ALSA_CFLAGS) \
+ $(FFADO_CFLAGS) \
$(JACK_CFLAGS) \
$(OPENAL_CFLAGS) \
$(PULSE_CFLAGS) \
@@ -649,6 +650,7 @@ OUTPUT_LIBS = \
$(LIBWRAP_LDFLAGS) \
$(AO_LIBS) \
$(ALSA_LIBS) \
+ $(FFADO_LIBS) \
$(JACK_LIBS) \
$(OPENAL_LIBS) \
$(PULSE_LIBS) \
@@ -681,6 +683,10 @@ OUTPUT_SRC += src/output/alsa_plugin.c
MIXER_SRC += src/mixer/alsa_mixer_plugin.c
endif
+if ENABLE_FFADO_OUTPUT
+OUTPUT_SRC += src/output/ffado_output_plugin.c
+endif
+
if HAVE_AO
OUTPUT_SRC += src/output/ao_plugin.c
endif
diff --git a/configure.ac b/configure.ac
index c59845018..94221506d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -155,6 +155,10 @@ AC_ARG_ENABLE(documentation,
[build documentation (default: disable)]),,
[enable_documentation=no])
+AC_ARG_ENABLE(ffado,
+ AS_HELP_STRING([--enable-ffado], [enable libffado (FireWire) support]),,
+ [enable_ffado=auto])
+
AC_ARG_ENABLE(ffmpeg,
AS_HELP_STRING([--enable-ffmpeg],
[enable FFMPEG support]),,
@@ -1205,6 +1209,17 @@ fi
AM_CONDITIONAL(HAVE_ALSA, test x$enable_alsa = xyes)
+dnl ----------------------------------- FFADO ---------------------------------
+
+MPD_AUTO_PKG(ffado, FFADO, [libffado],
+ [libffado output plugin], [libffado not found])
+
+if test x$enable_ffado = xyes; then
+ AC_DEFINE(ENABLE_FFADO_OUTPUT, 1, [Define to enable the libffado output plugin])
+fi
+
+AM_CONDITIONAL(ENABLE_FFADO_OUTPUT, test x$enable_ffado = xyes)
+
dnl ----------------------------------- FIFO ----------------------------------
if test x$enable_fifo = xyes; then
AC_CHECK_FUNC([mkfifo],
@@ -1395,6 +1410,7 @@ dnl --------------------- Post Audio Output Plugins Tests ---------------------
if
test x$enable_alsa = xno &&
test x$enable_ao = xno &&
+ test x$enable_ffado = xno &&
test x$enable_fifo = xno &&
test x$enable_httpd_output = xno &&
test x$enable_jack = xno &&
@@ -1529,6 +1545,7 @@ results(id3,[ID3])
echo -en '\nPlayback support:\n\t'
results(alsa,ALSA)
+results(ffado,FFADO)
results(fifo,FIFO)
results(recorder_output,[File Recorder])
results(httpd_output,[HTTP Daemon])
diff --git a/doc/user.xml b/doc/user.xml
index c783a981b..17bbdf91f 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -789,6 +789,43 @@ cd mpd-version</programlisting>
</section>
<section>
+ <title><varname>ffado</varname></title>
+
+ <para>
+ The <varname>ffado</varname> plugin connects to FireWire
+ audio devices via <filename>libffado</filename>.
+ </para>
+
+ <para>
+ Warning: this plugin was not tested successfully. I just
+ couldn't keep libffado2 from crashing. Use at your own
+ risk.
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Setting</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>device</varname>
+ <parameter>NAME</parameter>
+ </entry>
+ <entry>
+ Sets the device which should be used, e.g. "hw:0".
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
<title><varname>jack</varname></title>
<para>
diff --git a/src/output/ffado_output_plugin.c b/src/output/ffado_output_plugin.c
new file mode 100644
index 000000000..588957d26
--- /dev/null
+++ b/src/output/ffado_output_plugin.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2003-2010 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.
+ */
+
+/*
+ * Warning: this plugin was not tested successfully. I just couldn't
+ * keep libffado2 from crashing. Use at your own risk.
+ *
+ * For details, see my Debian bug reports:
+ *
+ * http://bugs.debian.org/601657
+ * http://bugs.debian.org/601659
+ * http://bugs.debian.org/601663
+ *
+ */
+
+#include "config.h"
+#include "output_api.h"
+#include "timer.h"
+
+#include <glib.h>
+#include <assert.h>
+
+#include <libffado/ffado.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "ffado"
+
+enum {
+ MAX_STREAMS = 8,
+};
+
+struct mpd_ffado_stream {
+ /** libffado's stream number */
+ int number;
+
+ float *buffer;
+};
+
+struct mpd_ffado_device {
+ char *device_name;
+ int verbose;
+ unsigned period_size, nb_buffers;
+
+ ffado_device_t *dev;
+
+ /**
+ * The current sample position inside the stream buffers. New
+ * samples get appended at this position on all streams at the
+ * same time. When the buffers are full
+ * (buffer_position==period_size),
+ * ffado_streaming_transfer_playback_buffers() gets called to
+ * hand them over to libffado.
+ */
+ unsigned buffer_position;
+
+ /**
+ * The number of streams which are really used by MPD.
+ */
+ int num_streams;
+ struct mpd_ffado_stream streams[MAX_STREAMS];
+};
+
+static inline GQuark
+ffado_output_quark(void)
+{
+ return g_quark_from_static_string("ffado_output");
+}
+
+static void *
+ffado_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param,
+ GError **error_r)
+{
+ g_debug("using libffado version %s, API=%d",
+ ffado_get_version(), ffado_get_api_version());
+
+ struct mpd_ffado_device *fd = g_new(struct mpd_ffado_device, 1);
+ fd->device_name = config_dup_block_string(param, "device", NULL);
+ fd->verbose = config_get_block_unsigned(param, "verbose", 0);
+
+ fd->period_size = config_get_block_unsigned(param, "period_size",
+ 1024);
+ if (fd->period_size == 0 || fd->period_size > 1024 * 1024) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "invalid period_size setting");
+ return false;
+ }
+
+ fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3);
+ if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "invalid nb_buffers setting");
+ return false;
+ }
+
+ return fd;
+}
+
+static void
+ffado_finish(void *data)
+{
+ struct mpd_ffado_device *fd = data;
+
+ g_free(fd->device_name);
+ g_free(fd);
+}
+
+static bool
+ffado_configure_stream(ffado_device_t *dev, struct mpd_ffado_stream *stream,
+ GError **error_r)
+{
+ char *buffer = (char *)stream->buffer;
+ if (ffado_streaming_set_playback_stream_buffer(dev, stream->number,
+ buffer) != 0) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "failed to configure stream buffer");
+ return false;
+ }
+
+ if (ffado_streaming_playback_stream_onoff(dev, stream->number,
+ 1) != 0) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "failed to disable stream");
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+ffado_configure(struct mpd_ffado_device *fd, struct audio_format *audio_format,
+ GError **error_r)
+{
+ assert(fd != NULL);
+ assert(fd->dev != NULL);
+ assert(audio_format->channels <= MAX_STREAMS);
+
+ if (ffado_streaming_set_audio_datatype(fd->dev,
+ ffado_audio_datatype_float) != 0) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "ffado_streaming_set_audio_datatype() failed");
+ return false;
+ }
+
+ int num_streams = ffado_streaming_get_nb_playback_streams(fd->dev);
+ if (num_streams < 0) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "ffado_streaming_get_nb_playback_streams() failed");
+ return false;
+ }
+
+ g_debug("there are %d playback streams", num_streams);
+
+ fd->num_streams = 0;
+ for (int i = 0; i < num_streams; ++i) {
+ char name[256];
+ ffado_streaming_get_playback_stream_name(fd->dev, i, name,
+ sizeof(name) - 1);
+
+ ffado_streaming_stream_type type =
+ ffado_streaming_get_playback_stream_type(fd->dev, i);
+ if (type != ffado_stream_type_audio) {
+ g_debug("stream %d name='%s': not an audio stream",
+ i, name);
+ continue;
+ }
+
+ if (fd->num_streams >= audio_format->channels) {
+ g_debug("stream %d name='%s': ignoring",
+ i, name);
+ continue;
+ }
+
+ g_debug("stream %d name='%s'", i, name);
+
+ struct mpd_ffado_stream *stream =
+ &fd->streams[fd->num_streams++];
+
+ stream->number = i;
+
+ /* allocated buffer is zeroed = silence */
+ stream->buffer = g_new0(float, fd->period_size);
+
+ if (!ffado_configure_stream(fd->dev, stream, error_r))
+ return false;
+ }
+
+ if (!audio_valid_channel_count(fd->num_streams)) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "invalid channel count from libffado: %u",
+ audio_format->channels);
+ return false;
+ }
+
+ g_debug("configured %d audio streams", fd->num_streams);
+
+ if (ffado_streaming_prepare(fd->dev) != 0) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "ffado_streaming_prepare() failed");
+ return false;
+ }
+
+ if (ffado_streaming_start(fd->dev) != 0) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "ffado_streaming_start() failed");
+ return false;
+ }
+
+ audio_format->channels = fd->num_streams;
+ return true;
+}
+
+static bool
+ffado_open(void *data, struct audio_format *audio_format, GError **error_r)
+{
+ struct mpd_ffado_device *fd = data;
+
+ /* will be converted to floating point, choose best input
+ format */
+ audio_format->format = SAMPLE_FORMAT_S24_P32;
+
+ ffado_device_info_t device_info;
+ memset(&device_info, 0, sizeof(device_info));
+ if (fd->device_name != NULL) {
+ device_info.nb_device_spec_strings = 1;
+ device_info.device_spec_strings = &fd->device_name;
+ }
+
+ ffado_options_t options;
+ memset(&options, 0, sizeof(options));
+ options.sample_rate = audio_format->sample_rate;
+ options.period_size = fd->period_size;
+ options.nb_buffers = fd->nb_buffers;
+ options.verbose = fd->verbose;
+
+ fd->dev = ffado_streaming_init(device_info, options);
+ if (fd->dev == NULL) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "ffado_streaming_init() failed");
+ return false;
+ }
+
+ if (!ffado_configure(fd, audio_format, error_r)) {
+ ffado_streaming_finish(fd->dev);
+
+ for (int i = 0; i < fd->num_streams; ++i) {
+ struct mpd_ffado_stream *stream = &fd->streams[i];
+ g_free(stream->buffer);
+ }
+
+ return false;
+ }
+
+ fd->buffer_position = 0;
+
+ return true;
+}
+
+static void
+ffado_close(void *data)
+{
+ struct mpd_ffado_device *fd = data;
+
+ ffado_streaming_stop(fd->dev);
+ ffado_streaming_finish(fd->dev);
+
+ for (int i = 0; i < fd->num_streams; ++i) {
+ struct mpd_ffado_stream *stream = &fd->streams[i];
+ g_free(stream->buffer);
+ }
+}
+
+static size_t
+ffado_play(void *data, const void *chunk, size_t size, GError **error_r)
+{
+ struct mpd_ffado_device *fd = data;
+
+ /* wait for prefious buffer to finish (if it was full) */
+
+ if (fd->buffer_position >= fd->period_size) {
+ switch (ffado_streaming_wait(fd->dev)) {
+ case ffado_wait_ok:
+ case ffado_wait_xrun:
+ break;
+
+ default:
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "ffado_streaming_wait() failed");
+ return 0;
+ }
+
+ fd->buffer_position = 0;
+ }
+
+ /* copy samples to stream buffers, non-interleaved */
+
+ const int32_t *p = chunk;
+ unsigned num_frames = size / sizeof(*p) / fd->num_streams;
+ if (num_frames > fd->period_size - fd->buffer_position)
+ num_frames = fd->period_size - fd->buffer_position;
+
+ for (unsigned i = num_frames; i > 0; --i) {
+ for (int stream = 0; stream < fd->num_streams; ++stream)
+ fd->streams[stream].buffer[fd->buffer_position] =
+ *p++ / (float)(1 << 23);
+ ++fd->buffer_position;
+ }
+
+ /* if buffer full, transfer to device */
+
+ if (fd->buffer_position >= fd->period_size &&
+ ffado_streaming_transfer_playback_buffers(fd->dev) != 0) {
+ g_set_error(error_r, ffado_output_quark(), 0,
+ "ffado_streaming_transfer_playback_buffers() failed");
+ return 0;
+ }
+
+ return num_frames * sizeof(*p) * fd->num_streams;
+}
+
+const struct audio_output_plugin ffado_output_plugin = {
+ .name = "ffado",
+ .init = ffado_init,
+ .finish = ffado_finish,
+ .open = ffado_open,
+ .close = ffado_close,
+ .play = ffado_play,
+};
diff --git a/src/output_list.c b/src/output_list.c
index 0d1e70968..8238f581b 100644
--- a/src/output_list.c
+++ b/src/output_list.c
@@ -37,6 +37,7 @@ extern const struct audio_output_plugin jack_output_plugin;
extern const struct audio_output_plugin httpd_output_plugin;
extern const struct audio_output_plugin recorder_output_plugin;
extern const struct audio_output_plugin winmm_output_plugin;
+extern const struct audio_output_plugin ffado_output_plugin;
const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef HAVE_SHOUT
@@ -85,6 +86,9 @@ const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef ENABLE_WINMM_OUTPUT
&winmm_output_plugin,
#endif
+#ifdef ENABLE_FFADO_OUTPUT
+ &ffado_output_plugin,
+#endif
NULL
};