aboutsummaryrefslogtreecommitdiffstats
path: root/src/output/ffado_output_plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/output/ffado_output_plugin.c')
-rw-r--r--src/output/ffado_output_plugin.c344
1 files changed, 344 insertions, 0 deletions
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,
+};