Commit 75f4772b authored by Max Kellermann's avatar Max Kellermann

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
parent fe1b626f
......@@ -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
---------------------------
......
......@@ -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
......
......@@ -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])
......
......@@ -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>
......
/*
* 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,
};
......@@ -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
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment