diff options
Diffstat (limited to 'src/output')
41 files changed, 2819 insertions, 491 deletions
diff --git a/src/output/alsa_plugin.c b/src/output/alsa_output_plugin.c index ae06847c2..f939ba5b4 100644 --- a/src/output/alsa_plugin.c +++ b/src/output/alsa_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "alsa_output_plugin.h" #include "output_api.h" #include "mixer_list.h" @@ -42,6 +43,8 @@ typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, snd_pcm_uframes_t size); struct alsa_data { + struct audio_output base; + /** the configured name of the ALSA device; NULL for the default device */ char *device; @@ -142,23 +145,27 @@ alsa_configure(struct alsa_data *ad, const struct config_param *param) #endif } -static void * -alsa_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, - G_GNUC_UNUSED GError **error) +static struct audio_output * +alsa_init(const struct config_param *param, GError **error_r) { struct alsa_data *ad = alsa_data_new(); + if (!ao_base_init(&ad->base, &alsa_output_plugin, param, error_r)) { + g_free(ad); + return NULL; + } + alsa_configure(ad, param); - return ad; + return &ad->base; } static void -alsa_finish(void *data) +alsa_finish(struct audio_output *ao) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; + ao_base_finish(&ad->base); alsa_data_free(ad); /* free libasound's config cache */ @@ -205,6 +212,9 @@ get_bitformat(enum sample_format sample_format) case SAMPLE_FORMAT_S32: return SND_PCM_FORMAT_S32; + + case SAMPLE_FORMAT_FLOAT: + return SND_PCM_FORMAT_FLOAT; } assert(false); @@ -532,9 +542,9 @@ error: } static bool -alsa_open(void *data, struct audio_format *audio_format, GError **error) +alsa_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; int err; bool success; @@ -596,9 +606,9 @@ alsa_recover(struct alsa_data *ad, int err) } static void -alsa_drain(void *data) +alsa_drain(struct audio_output *ao) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) return; @@ -630,9 +640,9 @@ alsa_drain(void *data) } static void -alsa_cancel(void *data) +alsa_cancel(struct audio_output *ao) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; ad->period_position = 0; @@ -640,17 +650,18 @@ alsa_cancel(void *data) } static void -alsa_close(void *data) +alsa_close(struct audio_output *ao) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; snd_pcm_close(ad->pcm); } static size_t -alsa_play(void *data, const void *chunk, size_t size, GError **error) +alsa_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) { - struct alsa_data *ad = data; + struct alsa_data *ad = (struct alsa_data *)ao; size /= ad->frame_size; @@ -671,7 +682,7 @@ alsa_play(void *data, const void *chunk, size_t size, GError **error) } } -const struct audio_output_plugin alsaPlugin = { +const struct audio_output_plugin alsa_output_plugin = { .name = "alsa", .test_default_device = alsa_test_default_device, .init = alsa_init, diff --git a/src/output/alsa_output_plugin.h b/src/output/alsa_output_plugin.h new file mode 100644 index 000000000..daa1f3615 --- /dev/null +++ b/src/output/alsa_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_OUTPUT_PLUGIN_H +#define MPD_ALSA_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin alsa_output_plugin; + +#endif diff --git a/src/output/ao_plugin.c b/src/output/ao_output_plugin.c index 42ece5a3a..c0790681e 100644 --- a/src/output/ao_plugin.c +++ b/src/output/ao_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "ao_output_plugin.h" #include "output_api.h" #include <ao/ao.h> @@ -32,6 +33,8 @@ static const ao_sample_format OUR_AO_FORMAT_INITIALIZER; static unsigned ao_output_ref; struct ao_data { + struct audio_output base; + size_t write_size; int driver; ao_option *options; @@ -78,12 +81,17 @@ ao_output_error(GError **error_r) "%s", error); } -static void * -ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, +static struct audio_output * +ao_output_init(const struct config_param *param, GError **error) { struct ao_data *ad = g_new(struct ao_data, 1); + + if (!ao_base_init(&ad->base, &ao_output_plugin, param, error)) { + g_free(ad); + return NULL; + } + ao_info *ai; const char *value; @@ -106,6 +114,7 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, g_set_error(error, ao_output_quark(), 0, "\"%s\" is not a valid ao driver", value); + ao_base_finish(&ad->base); g_free(ad); return NULL; } @@ -113,6 +122,7 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, if ((ai = ao_driver_info(ad->driver)) == NULL) { g_set_error(error, ao_output_quark(), 0, "problems getting driver info"); + ao_base_finish(&ad->base); g_free(ad); return NULL; } @@ -131,6 +141,7 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, g_set_error(error, ao_output_quark(), 0, "problems parsing options \"%s\"", options[i]); + ao_base_finish(&ad->base); g_free(ad); return NULL; } @@ -144,15 +155,16 @@ ao_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, g_strfreev(options); } - return ad; + return &ad->base; } static void -ao_output_finish(void *data) +ao_output_finish(struct audio_output *ao) { - struct ao_data *ad = (struct ao_data *)data; + struct ao_data *ad = (struct ao_data *)ao; ao_free_options(ad->options); + ao_base_finish(&ad->base); g_free(ad); ao_output_ref--; @@ -162,19 +174,19 @@ ao_output_finish(void *data) } static void -ao_output_close(void *data) +ao_output_close(struct audio_output *ao) { - struct ao_data *ad = (struct ao_data *)data; + struct ao_data *ad = (struct ao_data *)ao; ao_close(ad->device); } static bool -ao_output_open(void *data, struct audio_format *audio_format, +ao_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { ao_sample_format format = OUR_AO_FORMAT_INITIALIZER; - struct ao_data *ad = (struct ao_data *)data; + struct ao_data *ad = (struct ao_data *)ao; switch (audio_format->format) { case SAMPLE_FORMAT_S8: @@ -226,10 +238,10 @@ static int ao_play_deconst(ao_device *device, const void *output_samples, } static size_t -ao_output_play(void *data, const void *chunk, size_t size, +ao_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) { - struct ao_data *ad = (struct ao_data *)data; + struct ao_data *ad = (struct ao_data *)ao; if (size > ad->write_size) size = ad->write_size; diff --git a/src/output/ao_output_plugin.h b/src/output/ao_output_plugin.h new file mode 100644 index 000000000..9a3a47c05 --- /dev/null +++ b/src/output/ao_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_AO_OUTPUT_PLUGIN_H +#define MPD_AO_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin ao_output_plugin; + +#endif diff --git a/src/output/ffado_output_plugin.c b/src/output/ffado_output_plugin.c index 723698ed0..ba239a4ad 100644 --- a/src/output/ffado_output_plugin.c +++ b/src/output/ffado_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -30,6 +30,7 @@ */ #include "config.h" +#include "ffado_output_plugin.h" #include "output_api.h" #include "timer.h" @@ -53,6 +54,8 @@ struct mpd_ffado_stream { }; struct mpd_ffado_device { + struct audio_output base; + char *device_name; int verbose; unsigned period_size, nb_buffers; @@ -82,21 +85,26 @@ 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, +static struct audio_output * +ffado_init(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); + if (!ao_base_init(&fd->base, &ffado_output_plugin, param, error_r)) { + g_free(fd); + return NULL; + } + 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) { + ao_base_finish(&fd->base); g_set_error(error_r, ffado_output_quark(), 0, "invalid period_size setting"); return false; @@ -104,20 +112,22 @@ ffado_init(G_GNUC_UNUSED const struct audio_format *audio_format, fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3); if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) { + ao_base_finish(&fd->base); g_set_error(error_r, ffado_output_quark(), 0, "invalid nb_buffers setting"); return false; } - return fd; + return &fd->base; } static void -ffado_finish(void *data) +ffado_finish(struct audio_output *ao) { - struct mpd_ffado_device *fd = data; + struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; g_free(fd->device_name); + ao_base_finish(&fd->base); g_free(fd); } @@ -227,9 +237,10 @@ ffado_configure(struct mpd_ffado_device *fd, struct audio_format *audio_format, } static bool -ffado_open(void *data, struct audio_format *audio_format, GError **error_r) +ffado_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error_r) { - struct mpd_ffado_device *fd = data; + struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; /* will be converted to floating point, choose best input format */ @@ -273,9 +284,9 @@ ffado_open(void *data, struct audio_format *audio_format, GError **error_r) } static void -ffado_close(void *data) +ffado_close(struct audio_output *ao) { - struct mpd_ffado_device *fd = data; + struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; ffado_streaming_stop(fd->dev); ffado_streaming_finish(fd->dev); @@ -287,9 +298,10 @@ ffado_close(void *data) } static size_t -ffado_play(void *data, const void *chunk, size_t size, GError **error_r) +ffado_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error_r) { - struct mpd_ffado_device *fd = data; + struct mpd_ffado_device *fd = (struct mpd_ffado_device *)ao; /* wait for prefious buffer to finish (if it was full) */ diff --git a/src/output/ffado_output_plugin.h b/src/output/ffado_output_plugin.h new file mode 100644 index 000000000..4dde01859 --- /dev/null +++ b/src/output/ffado_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_FFADO_OUTPUT_PLUGIN_H +#define MPD_FFADO_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin ffado_output_plugin; + +#endif diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c index f4217ec4d..b8f405624 100644 --- a/src/output/fifo_output_plugin.c +++ b/src/output/fifo_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "fifo_output_plugin.h" #include "output_api.h" #include "utils.h" #include "timer.h" @@ -38,11 +39,13 @@ #define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */ struct fifo_data { + struct audio_output base; + char *path; int input; int output; bool created; - Timer *timer; + struct timer *timer; }; /** @@ -175,54 +178,55 @@ fifo_open(struct fifo_data *fd, GError **error) return true; } -static void * -fifo_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, - GError **error) +static struct audio_output * +fifo_output_init(const struct config_param *param, + GError **error_r) { struct fifo_data *fd; - char *value, *path; - - value = config_dup_block_string(param, "path", NULL); - if (value == NULL) { - g_set_error(error, fifo_output_quark(), errno, - "No \"path\" parameter specified"); - return NULL; - } - path = parsePath(value); - g_free(value); + GError *error = NULL; + char *path = config_dup_block_path(param, "path", &error); if (!path) { - g_set_error(error, fifo_output_quark(), errno, - "Could not parse \"path\" parameter"); + if (error != NULL) + g_propagate_error(error_r, error); + else + g_set_error(error_r, fifo_output_quark(), 0, + "No \"path\" parameter specified"); return NULL; } fd = fifo_data_new(); fd->path = path; - if (!fifo_open(fd, error)) { + if (!ao_base_init(&fd->base, &fifo_output_plugin, param, error_r)) { + fifo_data_free(fd); + return NULL; + } + + if (!fifo_open(fd, error_r)) { + ao_base_finish(&fd->base); fifo_data_free(fd); return NULL; } - return fd; + return &fd->base; } static void -fifo_output_finish(void *data) +fifo_output_finish(struct audio_output *ao) { - struct fifo_data *fd = (struct fifo_data *)data; + struct fifo_data *fd = (struct fifo_data *)ao; fifo_close(fd); + ao_base_finish(&fd->base); fifo_data_free(fd); } static bool -fifo_output_open(void *data, struct audio_format *audio_format, +fifo_output_open(struct audio_output *ao, struct audio_format *audio_format, G_GNUC_UNUSED GError **error) { - struct fifo_data *fd = (struct fifo_data *)data; + struct fifo_data *fd = (struct fifo_data *)ao; fd->timer = timer_new(audio_format); @@ -230,17 +234,17 @@ fifo_output_open(void *data, struct audio_format *audio_format, } static void -fifo_output_close(void *data) +fifo_output_close(struct audio_output *ao) { - struct fifo_data *fd = (struct fifo_data *)data; + struct fifo_data *fd = (struct fifo_data *)ao; timer_free(fd->timer); } static void -fifo_output_cancel(void *data) +fifo_output_cancel(struct audio_output *ao) { - struct fifo_data *fd = (struct fifo_data *)data; + struct fifo_data *fd = (struct fifo_data *)ao; char buf[FIFO_BUFFER_SIZE]; int bytes = 1; @@ -255,18 +259,25 @@ fifo_output_cancel(void *data) } } +static unsigned +fifo_output_delay(struct audio_output *ao) +{ + struct fifo_data *fd = (struct fifo_data *)ao; + + return fd->timer->started + ? timer_delay(fd->timer) + : 0; +} + static size_t -fifo_output_play(void *data, const void *chunk, size_t size, +fifo_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) { - struct fifo_data *fd = (struct fifo_data *)data; + struct fifo_data *fd = (struct fifo_data *)ao; ssize_t bytes; if (!fd->timer->started) timer_start(fd->timer); - else - timer_sync(fd->timer); - timer_add(fd->timer, size); while (true) { @@ -278,7 +289,7 @@ fifo_output_play(void *data, const void *chunk, size_t size, switch (errno) { case EAGAIN: /* The pipe is full, so empty it */ - fifo_output_cancel(fd); + fifo_output_cancel(&fd->base); continue; case EINTR: continue; @@ -298,6 +309,7 @@ const struct audio_output_plugin fifo_output_plugin = { .finish = fifo_output_finish, .open = fifo_output_open, .close = fifo_output_close, + .delay = fifo_output_delay, .play = fifo_output_play, .cancel = fifo_output_cancel, }; diff --git a/src/output/fifo_output_plugin.h b/src/output/fifo_output_plugin.h new file mode 100644 index 000000000..85f7985e1 --- /dev/null +++ b/src/output/fifo_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_FIFO_OUTPUT_PLUGIN_H +#define MPD_FIFO_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin fifo_output_plugin; + +#endif diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c index 995c1f659..8efedc2b3 100644 --- a/src/output/httpd_client.c +++ b/src/output/httpd_client.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -93,6 +93,11 @@ struct httpd_client { */ size_t current_position; + /** + * If DLNA streaming was an option. + */ + bool dlna_streaming_requested; + /* ICY */ /** @@ -234,6 +239,15 @@ httpd_client_handle_line(struct httpd_client *client, const char *line) return true; } + if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) { + /* Send as dlna */ + client->dlna_streaming_requested = true; + /* metadata is not supported by dlna streaming, so disable it */ + client->metadata_supported = false; + client->metadata_requested = false; + return true; + } + /* expect more request headers */ return true; } @@ -285,16 +299,21 @@ httpd_client_send_response(struct httpd_client *client) assert(client != NULL); assert(client->state == RESPONSE); - if (!client->metadata_requested) { + if (client->dlna_streaming_requested) { g_snprintf(buffer, sizeof(buffer), - "HTTP/1.1 200 OK\r\n" + "HTTP/1.1 206 OK\r\n" "Content-Type: %s\r\n" + "Content-Length: 10000\r\n" + "Content-RangeX: 0-1000000/1000000\r\n" + "transferMode.dlna.org: Streaming\r\n" + "Accept-Ranges: bytes\r\n" "Connection: close\r\n" - "Pragma: no-cache\r\n" - "Cache-Control: no-cache, no-store\r\n" + "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" + "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n" "\r\n", client->httpd->content_type); - } else { + + } else if (client->metadata_requested) { gchar *metadata_header; metadata_header = icy_server_metadata_header( @@ -307,6 +326,16 @@ httpd_client_send_response(struct httpd_client *client) g_strlcpy(buffer, metadata_header, sizeof(buffer)); g_free(metadata_header); + + } else { /* revert to a normal HTTP request */ + g_snprintf(buffer, sizeof(buffer), + "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Connection: close\r\n" + "Pragma: no-cache\r\n" + "Cache-Control: no-cache, no-store\r\n" + "\r\n", + client->httpd->content_type); } status = g_io_channel_write_chars(client->channel, @@ -476,6 +505,7 @@ httpd_client_new(struct httpd_output *httpd, int fd, bool metadata_supported) client->input = fifo_buffer_new(4096); client->state = REQUEST; + client->dlna_streaming_requested = false; client->metadata_supported = metadata_supported; client->metadata_requested = false; client->metadata_sent = true; diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h index 7ebd0bbc0..739163f42 100644 --- a/src/output/httpd_client.h +++ b/src/output/httpd_client.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h index 277e70f11..5dcb8ab9b 100644 --- a/src/output/httpd_internal.h +++ b/src/output/httpd_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,7 @@ #ifndef MPD_OUTPUT_HTTPD_INTERNAL_H #define MPD_OUTPUT_HTTPD_INTERNAL_H +#include "output_internal.h" #include "timer.h" #include <glib.h> @@ -34,6 +35,8 @@ struct httpd_client; struct httpd_output { + struct audio_output base; + /** * True if the audio output is open and accepts client * connections. @@ -65,10 +68,10 @@ struct httpd_output { GMutex *mutex; /** - * A #Timer object to synchronize this output with the + * A #timer object to synchronize this output with the * wallclock. */ - Timer *timer; + struct timer *timer; /** * The listener socket. diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c index 2c140a300..e7344320c 100644 --- a/src/output/httpd_output_plugin.c +++ b/src/output/httpd_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,12 +18,13 @@ */ #include "config.h" +#include "httpd_output_plugin.h" #include "httpd_internal.h" #include "httpd_client.h" #include "output_api.h" #include "encoder_plugin.h" #include "encoder_list.h" -#include "socket_util.h" +#include "resolver.h" #include "page.h" #include "icy_server.h" #include "fd_util.h" @@ -78,12 +79,16 @@ httpd_output_unbind(struct httpd_output *httpd) g_mutex_unlock(httpd->mutex); } -static void * -httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, +static struct audio_output * +httpd_output_init(const struct config_param *param, GError **error) { struct httpd_output *httpd = g_new(struct httpd_output, 1); + if (!ao_base_init(&httpd->base, &httpd_output_plugin, param, error)) { + g_free(httpd); + return NULL; + } + const char *encoder_name, *bind_to_address; const struct encoder_plugin *encoder_plugin; guint port; @@ -103,6 +108,7 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, if (encoder_plugin == NULL) { g_set_error(error, httpd_output_quark(), 0, "No such encoder: %s", encoder_name); + ao_base_finish(&httpd->base); g_free(httpd); return NULL; } @@ -120,8 +126,11 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, ? server_socket_add_host(httpd->server_socket, bind_to_address, port, error) : server_socket_add_port(httpd->server_socket, port, error); - if (!success) + if (!success) { + ao_base_finish(&httpd->base); + g_free(httpd); return NULL; + } /* initialize metadata */ httpd->metadata = NULL; @@ -130,8 +139,11 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, /* initialize encoder */ httpd->encoder = encoder_init(encoder_plugin, param, error); - if (httpd->encoder == NULL) + if (httpd->encoder == NULL) { + ao_base_finish(&httpd->base); + g_free(httpd); return NULL; + } /* determine content type */ httpd->content_type = encoder_get_mime_type(httpd->encoder); @@ -141,13 +153,13 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, httpd->mutex = g_mutex_new(); - return httpd; + return &httpd->base; } static void -httpd_output_finish(void *data) +httpd_output_finish(struct audio_output *ao) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; if (httpd->metadata) page_unref(httpd->metadata); @@ -155,6 +167,7 @@ httpd_output_finish(void *data) encoder_finish(httpd->encoder); server_socket_free(httpd->server_socket); g_mutex_free(httpd->mutex); + ao_base_finish(&httpd->base); g_free(httpd); } @@ -286,26 +299,26 @@ httpd_output_encoder_open(struct httpd_output *httpd, } static bool -httpd_output_enable(void *data, GError **error_r) +httpd_output_enable(struct audio_output *ao, GError **error_r) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; return httpd_output_bind(httpd, error_r); } static void -httpd_output_disable(void *data) +httpd_output_disable(struct audio_output *ao) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; httpd_output_unbind(httpd); } static bool -httpd_output_open(void *data, struct audio_format *audio_format, +httpd_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; bool success; g_mutex_lock(httpd->mutex); @@ -338,9 +351,10 @@ httpd_client_delete(gpointer data, G_GNUC_UNUSED gpointer user_data) httpd_client_free(client); } -static void httpd_output_close(void *data) +static void +httpd_output_close(struct audio_output *ao) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; g_mutex_lock(httpd->mutex); @@ -379,9 +393,9 @@ httpd_output_send_header(struct httpd_output *httpd, } static unsigned -httpd_output_delay(void *data) +httpd_output_delay(struct audio_output *ao) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; return httpd->timer->started ? timer_delay(httpd->timer) @@ -457,9 +471,10 @@ httpd_output_encode_and_play(struct httpd_output *httpd, } static size_t -httpd_output_play(void *data, const void *chunk, size_t size, GError **error) +httpd_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; bool has_clients; g_mutex_lock(httpd->mutex); @@ -483,9 +498,9 @@ httpd_output_play(void *data, const void *chunk, size_t size, GError **error) } static bool -httpd_output_pause(void *data) +httpd_output_pause(struct audio_output *ao) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; g_mutex_lock(httpd->mutex); bool has_clients = httpd->clients != NULL; @@ -493,7 +508,7 @@ httpd_output_pause(void *data) if (has_clients) { static const char silence[1020]; - return httpd_output_play(data, silence, sizeof(silence), + return httpd_output_play(ao, silence, sizeof(silence), NULL) > 0; } else { g_usleep(100000); @@ -511,9 +526,9 @@ httpd_send_metadata(gpointer data, gpointer user_data) } static void -httpd_output_tag(void *data, const struct tag *tag) +httpd_output_tag(struct audio_output *ao, const struct tag *tag) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; assert(tag != NULL); @@ -570,9 +585,9 @@ httpd_client_cancel_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) } static void -httpd_output_cancel(void *data) +httpd_output_cancel(struct audio_output *ao) { - struct httpd_output *httpd = data; + struct httpd_output *httpd = (struct httpd_output *)ao; g_mutex_lock(httpd->mutex); g_list_foreach(httpd->clients, httpd_client_cancel_callback, NULL); diff --git a/src/output/httpd_output_plugin.h b/src/output/httpd_output_plugin.h new file mode 100644 index 000000000..d0eb1533f --- /dev/null +++ b/src/output/httpd_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_HTTPD_OUTPUT_PLUGIN_H +#define MPD_HTTPD_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin httpd_output_plugin; + +#endif diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c index 2767d4eb8..cd769088b 100644 --- a/src/output/jack_output_plugin.c +++ b/src/output/jack_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "jack_output_plugin.h" #include "output_api.h" #include <assert.h> @@ -43,6 +44,8 @@ enum { static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t); struct jack_data { + struct audio_output base; + /** * libjack options passed to jack_client_open(). */ @@ -291,14 +294,18 @@ parse_port_list(int line, const char *source, char **dest, GError **error_r) return n; } -static void * -mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, GError **error_r) +static struct audio_output * +mpd_jack_init(const struct config_param *param, GError **error_r) { - struct jack_data *jd; + struct jack_data *jd = g_new(struct jack_data, 1); + + if (!ao_base_init(&jd->base, &jack_output_plugin, param, error_r)) { + g_free(jd); + return NULL; + } + const char *value; - jd = g_new(struct jack_data, 1); jd->options = JackNullOption; jd->name = config_get_block_string(param, "client_name", NULL); @@ -361,13 +368,13 @@ mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format, jack_set_info_function(mpd_jack_info); #endif - return jd; + return &jd->base; } static void -mpd_jack_finish(void *data) +mpd_jack_finish(struct audio_output *ao) { - struct jack_data *jd = data; + struct jack_data *jd = (struct jack_data *)ao; for (unsigned i = 0; i < jd->num_source_ports; ++i) g_free(jd->source_ports[i]); @@ -375,13 +382,14 @@ mpd_jack_finish(void *data) for (unsigned i = 0; i < jd->num_destination_ports; ++i) g_free(jd->destination_ports[i]); + ao_base_finish(&jd->base); g_free(jd); } static bool -mpd_jack_enable(void *data, GError **error_r) +mpd_jack_enable(struct audio_output *ao, GError **error_r) { - struct jack_data *jd = (struct jack_data *)data; + struct jack_data *jd = (struct jack_data *)ao; for (unsigned i = 0; i < jd->num_source_ports; ++i) jd->ringbuffer[i] = NULL; @@ -390,9 +398,9 @@ mpd_jack_enable(void *data, GError **error_r) } static void -mpd_jack_disable(void *data) +mpd_jack_disable(struct audio_output *ao) { - struct jack_data *jd = (struct jack_data *)data; + struct jack_data *jd = (struct jack_data *)ao; if (jd->client != NULL) mpd_jack_disconnect(jd); @@ -555,9 +563,10 @@ mpd_jack_start(struct jack_data *jd, GError **error_r) } static bool -mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r) +mpd_jack_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error_r) { - struct jack_data *jd = data; + struct jack_data *jd = (struct jack_data *)ao; assert(jd != NULL); @@ -576,9 +585,9 @@ mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r) } static void -mpd_jack_close(G_GNUC_UNUSED void *data) +mpd_jack_close(G_GNUC_UNUSED struct audio_output *ao) { - struct jack_data *jd = data; + struct jack_data *jd = (struct jack_data *)ao; mpd_jack_stop(jd); } @@ -648,9 +657,10 @@ mpd_jack_write_samples(struct jack_data *jd, const void *src, } static size_t -mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r) +mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error_r) { - struct jack_data *jd = data; + struct jack_data *jd = (struct jack_data *)ao; const size_t frame_size = audio_format_frame_size(&jd->audio_format); size_t space = 0, space1; @@ -692,9 +702,9 @@ mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r) } static bool -mpd_jack_pause(void *data) +mpd_jack_pause(struct audio_output *ao) { - struct jack_data *jd = data; + struct jack_data *jd = (struct jack_data *)ao; if (jd->shutdown) return false; diff --git a/src/output/jack_output_plugin.h b/src/output/jack_output_plugin.h new file mode 100644 index 000000000..2f94ae7dc --- /dev/null +++ b/src/output/jack_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_JACK_OUTPUT_PLUGIN_H +#define MPD_JACK_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin jack_output_plugin; + +#endif diff --git a/src/output/mvp_plugin.c b/src/output/mvp_output_plugin.c index 6cc8fa34e..aec09248f 100644 --- a/src/output/mvp_plugin.c +++ b/src/output/mvp_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,7 @@ */ #include "config.h" +#include "mvp_output_plugin.h" #include "output_api.h" #include "fd_util.h" @@ -69,6 +70,8 @@ typedef struct { #define MVP_GET_AUD_REGS _IOW('a',28,aud_ctl_regs_t*) struct mvp_data { + struct audio_output base; + struct audio_format audio_format; int fd; }; @@ -130,21 +133,26 @@ mvp_output_test_default_device(void) return false; } -static void * -mvp_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error) +static struct audio_output * +mvp_output_init(G_GNUC_UNUSED const struct config_param *param, GError **error) { struct mvp_data *md = g_new(struct mvp_data, 1); + + if (!ao_base_init(&md->base, &mvp_output_plugin, param, error)) { + g_free(md); + return NULL; + } + md->fd = -1; - return md; + return &md->base; } static void -mvp_output_finish(void *data) +mvp_output_finish(struct audio_output *ao) { - struct mvp_data *md = data; + struct mvp_data *md = (struct mvp_data *)ao; + ao_base_finish(&md->base); g_free(md); } @@ -225,9 +233,10 @@ mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format, } static bool -mvp_output_open(void *data, struct audio_format *audio_format, GError **error) +mvp_output_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error) { - struct mvp_data *md = data; + struct mvp_data *md = (struct mvp_data *)ao; long long int stc = 0; int mix[5] = { 0, 2, 7, 1, 0 }; bool success; @@ -273,17 +282,17 @@ mvp_output_open(void *data, struct audio_format *audio_format, GError **error) return true; } -static void mvp_output_close(void *data) +static void mvp_output_close(struct audio_output *ao) { - struct mvp_data *md = data; + struct mvp_data *md = (struct mvp_data *)ao; if (md->fd >= 0) close(md->fd); md->fd = -1; } -static void mvp_output_cancel(void *data) +static void mvp_output_cancel(struct audio_output *ao) { - struct mvp_data *md = data; + struct mvp_data *md = (struct mvp_data *)ao; if (md->fd >= 0) { ioctl(md->fd, MVP_SET_AUD_RESET, 0x11); close(md->fd); @@ -292,16 +301,17 @@ static void mvp_output_cancel(void *data) } static size_t -mvp_output_play(void *data, const void *chunk, size_t size, GError **error) +mvp_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) { - struct mvp_data *md = data; + struct mvp_data *md = (struct mvp_data *)ao; ssize_t ret; /* reopen the device since it was closed by dropBufferedAudio */ if (md->fd < 0) { bool success; - success = mvp_output_open(md, &md->audio_format, error); + success = mvp_output_open(ao, &md->audio_format, error); if (!success) return 0; } diff --git a/src/output/mvp_output_plugin.h b/src/output/mvp_output_plugin.h new file mode 100644 index 000000000..e403de2b7 --- /dev/null +++ b/src/output/mvp_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_MVP_OUTPUT_PLUGIN_H +#define MPD_MVP_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin mvp_output_plugin; + +#endif diff --git a/src/output/null_plugin.c b/src/output/null_output_plugin.c index 89abbd91f..9d7588fff 100644 --- a/src/output/null_plugin.c +++ b/src/output/null_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "null_output_plugin.h" #include "output_api.h" #include "timer.h" @@ -26,39 +27,42 @@ #include <assert.h> struct null_data { + struct audio_output base; + bool sync; - Timer *timer; + struct timer *timer; }; -static void * -null_init(G_GNUC_UNUSED const struct audio_format *audio_format, - G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error) +static struct audio_output * +null_init(const struct config_param *param, GError **error_r) { struct null_data *nd = g_new(struct null_data, 1); + if (!ao_base_init(&nd->base, &null_output_plugin, param, error_r)) { + g_free(nd); + return NULL; + } + nd->sync = config_get_block_bool(param, "sync", true); - nd->timer = NULL; - return nd; + return &nd->base; } static void -null_finish(void *data) +null_finish(struct audio_output *ao) { - struct null_data *nd = data; - - assert(nd->timer == NULL); + struct null_data *nd = (struct null_data *)ao; + ao_base_finish(&nd->base); g_free(nd); } static bool -null_open(void *data, struct audio_format *audio_format, +null_open(struct audio_output *ao, struct audio_format *audio_format, G_GNUC_UNUSED GError **error) { - struct null_data *nd = data; + struct null_data *nd = (struct null_data *)ao; if (nd->sync) nd->timer = timer_new(audio_format); @@ -67,40 +71,45 @@ null_open(void *data, struct audio_format *audio_format, } static void -null_close(void *data) +null_close(struct audio_output *ao) { - struct null_data *nd = data; + struct null_data *nd = (struct null_data *)ao; - if (nd->timer != NULL) { + if (nd->sync) timer_free(nd->timer); - nd->timer = NULL; - } +} + +static unsigned +null_delay(struct audio_output *ao) +{ + struct null_data *nd = (struct null_data *)ao; + + return nd->sync && nd->timer->started + ? timer_delay(nd->timer) + : 0; } static size_t -null_play(void *data, G_GNUC_UNUSED const void *chunk, size_t size, +null_play(struct audio_output *ao, G_GNUC_UNUSED const void *chunk, size_t size, G_GNUC_UNUSED GError **error) { - struct null_data *nd = data; - Timer *timer = nd->timer; + struct null_data *nd = (struct null_data *)ao; + struct timer *timer = nd->timer; if (!nd->sync) return size; if (!timer->started) timer_start(timer); - else - timer_sync(timer); - timer_add(timer, size); return size; } static void -null_cancel(void *data) +null_cancel(struct audio_output *ao) { - struct null_data *nd = data; + struct null_data *nd = (struct null_data *)ao; if (!nd->sync) return; @@ -114,6 +123,7 @@ const struct audio_output_plugin null_output_plugin = { .finish = null_finish, .open = null_open, .close = null_close, + .delay = null_delay, .play = null_play, .cancel = null_cancel, }; diff --git a/src/output/null_output_plugin.h b/src/output/null_output_plugin.h new file mode 100644 index 000000000..392bf0aa3 --- /dev/null +++ b/src/output/null_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_NULL_OUTPUT_PLUGIN_H +#define MPD_NULL_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin null_output_plugin; + +#endif diff --git a/src/output/openal_plugin.c b/src/output/openal_output_plugin.c index e5db8ac34..ebd35ef12 100644 --- a/src/output/openal_plugin.c +++ b/src/output/openal_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,8 +18,8 @@ */ #include "config.h" +#include "openal_output_plugin.h" #include "output_api.h" -#include "timer.h" #include <glib.h> @@ -38,12 +38,13 @@ #define NUM_BUFFERS 16 struct openal_data { + struct audio_output base; + const char *device_name; ALCdevice *device; ALCcontext *context; - Timer *timer; ALuint buffers[NUM_BUFFERS]; - int filled; + unsigned filled; ALuint source; ALenum format; ALuint frequency; @@ -80,6 +81,29 @@ openal_audio_format(struct audio_format *audio_format) } } +G_GNUC_PURE +static inline ALint +openal_get_source_i(const struct openal_data *od, ALenum param) +{ + ALint value; + alGetSourcei(od->source, param, &value); + return value; +} + +G_GNUC_PURE +static inline bool +openal_has_processed(const struct openal_data *od) +{ + return openal_get_source_i(od, AL_BUFFERS_PROCESSED) > 0; +} + +G_GNUC_PURE +static inline ALint +openal_is_playing(const struct openal_data *od) +{ + return openal_get_source_i(od, AL_SOURCE_STATE) == AL_PLAYING; +} + static bool openal_setup_context(struct openal_data *od, GError **error) @@ -106,23 +130,8 @@ openal_setup_context(struct openal_data *od, return true; } -static void -openal_unqueue_buffers(struct openal_data *od) -{ - ALint num; - ALuint buffer; - - alGetSourcei(od->source, AL_BUFFERS_QUEUED, &num); - - while (num--) { - alSourceUnqueueBuffers(od->source, 1, &buffer); - } -} - -static void * -openal_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, - G_GNUC_UNUSED GError **error) +static struct audio_output * +openal_init(const struct config_param *param, GError **error_r) { const char *device_name = config_get_block_string(param, "device", NULL); struct openal_data *od; @@ -132,35 +141,33 @@ openal_init(G_GNUC_UNUSED const struct audio_format *audio_format, } od = g_new(struct openal_data, 1); + if (!ao_base_init(&od->base, &openal_output_plugin, param, error_r)) { + g_free(od); + return NULL; + } + od->device_name = device_name; - return od; + return &od->base; } static void -openal_finish(void *data) +openal_finish(struct audio_output *ao) { - struct openal_data *od = data; + struct openal_data *od = (struct openal_data *)ao; + ao_base_finish(&od->base); g_free(od); } static bool -openal_open(void *data, struct audio_format *audio_format, +openal_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct openal_data *od = data; + struct openal_data *od = (struct openal_data *)ao; od->format = openal_audio_format(audio_format); - if (!od->format) { - struct audio_format_string s; - g_set_error(error, openal_output_quark(), 0, - "Unsupported audio format: %s", - audio_format_to_string(audio_format, &s)); - return false; - } - if (!openal_setup_context(od, error)) { return false; } @@ -184,18 +191,16 @@ openal_open(void *data, struct audio_format *audio_format, } od->filled = 0; - od->timer = timer_new(audio_format); od->frequency = audio_format->sample_rate; return true; } static void -openal_close(void *data) +openal_close(struct audio_output *ao) { - struct openal_data *od = data; + struct openal_data *od = (struct openal_data *)ao; - timer_free(od->timer); alcMakeContextCurrent(od->context); alDeleteSources(1, &od->source); alDeleteBuffers(NUM_BUFFERS, od->buffers); @@ -203,61 +208,63 @@ openal_close(void *data) alcCloseDevice(od->device); } +static unsigned +openal_delay(struct audio_output *ao) +{ + struct openal_data *od = (struct openal_data *)ao; + + return od->filled < NUM_BUFFERS || openal_has_processed(od) + ? 0 + /* we don't know exactly how long we must wait for the + next buffer to finish, so this is a random + guess: */ + : 50; +} + static size_t -openal_play(void *data, const void *chunk, size_t size, +openal_play(struct audio_output *ao, const void *chunk, size_t size, G_GNUC_UNUSED GError **error) { - struct openal_data *od = data; + struct openal_data *od = (struct openal_data *)ao; ALuint buffer; - ALint num, state; if (alcGetCurrentContext() != od->context) { alcMakeContextCurrent(od->context); } - alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num); - if (od->filled < NUM_BUFFERS) { /* fill all buffers */ buffer = od->buffers[od->filled]; od->filled++; } else { /* wait for processed buffer */ - while (num < 1) { - if (!od->timer->started) { - timer_start(od->timer); - } else { - timer_sync(od->timer); - } - - timer_add(od->timer, size); - - alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num); - } + while (!openal_has_processed(od)) + g_usleep(10); alSourceUnqueueBuffers(od->source, 1, &buffer); } alBufferData(buffer, od->format, chunk, size, od->frequency); alSourceQueueBuffers(od->source, 1, &buffer); - alGetSourcei(od->source, AL_SOURCE_STATE, &state); - if (state != AL_PLAYING) { + if (!openal_is_playing(od)) alSourcePlay(od->source); - } return size; } static void -openal_cancel(void *data) +openal_cancel(struct audio_output *ao) { - struct openal_data *od = data; + struct openal_data *od = (struct openal_data *)ao; od->filled = 0; alcMakeContextCurrent(od->context); alSourceStop(od->source); - openal_unqueue_buffers(od); + + /* force-unqueue all buffers */ + alSourcei(od->source, AL_BUFFER, 0); + od->filled = 0; } const struct audio_output_plugin openal_output_plugin = { @@ -266,6 +273,7 @@ const struct audio_output_plugin openal_output_plugin = { .finish = openal_finish, .open = openal_open, .close = openal_close, + .delay = openal_delay, .play = openal_play, .cancel = openal_cancel, }; diff --git a/src/output/openal_output_plugin.h b/src/output/openal_output_plugin.h new file mode 100644 index 000000000..25f6ccf46 --- /dev/null +++ b/src/output/openal_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_OPENAL_OUTPUT_PLUGIN_H +#define MPD_OPENAL_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin openal_output_plugin; + +#endif diff --git a/src/output/oss_plugin.c b/src/output/oss_output_plugin.c index 9261b423c..46505873b 100644 --- a/src/output/oss_plugin.c +++ b/src/output/oss_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "oss_output_plugin.h" #include "output_api.h" #include "mixer_list.h" #include "fd_util.h" @@ -51,6 +52,8 @@ #endif struct oss_data { + struct audio_output base; + int fd; const char *device; @@ -142,7 +145,7 @@ oss_output_test_default_device(void) return false; } -static void * +static struct audio_output * oss_open_default(GError **error) { int i; @@ -153,8 +156,14 @@ oss_open_default(GError **error) ret[i] = oss_stat_device(default_devices[i], &err[i]); if (ret[i] == OSS_STAT_NO_ERROR) { struct oss_data *od = oss_data_new(); + if (!ao_base_init(&od->base, &oss_output_plugin, NULL, + error)) { + g_free(od); + return NULL; + } + od->device = default_devices[i]; - return od; + return &od->base; } } @@ -184,26 +193,31 @@ oss_open_default(GError **error) return NULL; } -static void * -oss_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, - GError **error) +static struct audio_output * +oss_output_init(const struct config_param *param, GError **error) { const char *device = config_get_block_string(param, "device", NULL); if (device != NULL) { struct oss_data *od = oss_data_new(); + if (!ao_base_init(&od->base, &oss_output_plugin, param, + error)) { + g_free(od); + return NULL; + } + od->device = device; - return od; + return &od->base; } return oss_open_default(error); } static void -oss_output_finish(void *data) +oss_output_finish(struct audio_output *ao) { - struct oss_data *od = data; + struct oss_data *od = (struct oss_data *)ao; + ao_base_finish(&od->base); oss_data_free(od); } @@ -381,6 +395,7 @@ sample_format_to_oss(enum sample_format format) { switch (format) { case SAMPLE_FORMAT_UNDEFINED: + case SAMPLE_FORMAT_FLOAT: return AFMT_QUERY; case SAMPLE_FORMAT_S8: @@ -606,9 +621,10 @@ oss_reopen(struct oss_data *od, GError **error_r) } static bool -oss_output_open(void *data, struct audio_format *audio_format, GError **error) +oss_output_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error) { - struct oss_data *od = data; + struct oss_data *od = (struct oss_data *)ao; od->fd = open_cloexec(od->device, O_WRONLY, 0); if (od->fd < 0) { @@ -628,17 +644,17 @@ oss_output_open(void *data, struct audio_format *audio_format, GError **error) } static void -oss_output_close(void *data) +oss_output_close(struct audio_output *ao) { - struct oss_data *od = data; + struct oss_data *od = (struct oss_data *)ao; oss_close(od); } static void -oss_output_cancel(void *data) +oss_output_cancel(struct audio_output *ao) { - struct oss_data *od = data; + struct oss_data *od = (struct oss_data *)ao; if (od->fd >= 0) { ioctl(od->fd, SNDCTL_DSP_RESET, 0); @@ -647,9 +663,10 @@ oss_output_cancel(void *data) } static size_t -oss_output_play(void *data, const void *chunk, size_t size, GError **error) +oss_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) { - struct oss_data *od = data; + struct oss_data *od = (struct oss_data *)ao; ssize_t ret; /* reopen the device since it was closed by dropBufferedAudio */ diff --git a/src/output/oss_output_plugin.h b/src/output/oss_output_plugin.h new file mode 100644 index 000000000..2aecc2b3a --- /dev/null +++ b/src/output/oss_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_OSS_OUTPUT_PLUGIN_H +#define MPD_OSS_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin oss_output_plugin; + +#endif diff --git a/src/output/osx_plugin.c b/src/output/osx_output_plugin.c index 5284afc29..5ee2f18aa 100644 --- a/src/output/osx_plugin.c +++ b/src/output/osx_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "osx_output_plugin.h" #include "output_api.h" #include <glib.h> @@ -28,6 +29,13 @@ #define G_LOG_DOMAIN "osx" struct osx_output { + struct audio_output base; + + /* configuration settings */ + OSType component_subtype; + /* only applicable with kAudioUnitSubType_HALOutput */ + const char *device_name; + AudioUnit au; GMutex *mutex; GCond *condition; @@ -54,13 +62,36 @@ osx_output_test_default_device(void) return true; } -static void * -osx_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error) +static void +osx_output_configure(struct osx_output *oo, const struct config_param *param) +{ + const char *device = config_get_block_string(param, "device", NULL); + + if (device == NULL || 0 == strcmp(device, "default")) { + oo->component_subtype = kAudioUnitSubType_DefaultOutput; + oo->device_name = NULL; + } + else if (0 == strcmp(device, "system")) { + oo->component_subtype = kAudioUnitSubType_SystemOutput; + oo->device_name = NULL; + } + else { + oo->component_subtype = kAudioUnitSubType_HALOutput; + /* XXX am I supposed to g_strdup() this? */ + oo->device_name = device; + } +} + +static struct audio_output * +osx_output_init(const struct config_param *param, GError **error_r) { struct osx_output *oo = g_new(struct osx_output, 1); + if (!ao_base_init(&oo->base, &osx_output_plugin, param, error_r)) { + g_free(oo); + return NULL; + } + osx_output_configure(oo, param); oo->mutex = g_mutex_new(); oo->condition = g_cond_new(); @@ -69,12 +100,13 @@ osx_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, oo->buffer = NULL; oo->buffer_size = 0; - return oo; + return &oo->base; } -static void osx_output_finish(void *data) +static void +osx_output_finish(struct audio_output *ao) { - struct osx_output *od = data; + struct osx_output *od = (struct osx_output *)ao; g_free(od->buffer); g_mutex_free(od->mutex); @@ -82,22 +114,93 @@ static void osx_output_finish(void *data) g_free(od); } -static void osx_output_cancel(void *data) +static bool +osx_output_set_device(struct osx_output *oo, GError **error) { - struct osx_output *od = data; + bool ret = true; + OSStatus status; + UInt32 size, numdevices; + AudioDeviceID *deviceids = NULL; + char name[256]; + unsigned int i; + + if (oo->component_subtype != kAudioUnitSubType_HALOutput) + goto done; + + /* how many audio devices are there? */ + status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, + &size, + NULL); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine number of OS X audio devices: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } - g_mutex_lock(od->mutex); - od->len = 0; - g_mutex_unlock(od->mutex); -} + /* what are the available audio device IDs? */ + numdevices = size / sizeof(AudioDeviceID); + deviceids = g_malloc(size); + status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, + &size, + deviceids); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine OS X audio device IDs: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } -static void osx_output_close(void *data) -{ - struct osx_output *od = data; + /* which audio device matches oo->device_name? */ + for (i = 0; i < numdevices; i++) { + size = sizeof(name); + status = AudioDeviceGetProperty(deviceids[i], 0, false, + kAudioDevicePropertyDeviceName, + &size, name); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine OS X device name " + "(device %u): %s", + (unsigned int) deviceids[i], + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + if (strcmp(oo->device_name, name) == 0) { + g_debug("found matching device: ID=%u, name=%s", + (unsigned int) deviceids[i], name); + break; + } + } + if (i == numdevices) { + g_warning("Found no audio device with name '%s' " + "(will use default audio device)", + oo->device_name); + goto done; + } - AudioOutputUnitStop(od->au); - AudioUnitUninitialize(od->au); - CloseComponent(od->au); + status = AudioUnitSetProperty(oo->au, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &(deviceids[i]), + sizeof(AudioDeviceID)); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to set OS X audio output device: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + g_debug("set OS X audio output device ID=%u, name=%s", + (unsigned int) deviceids[i], name); + +done: + if (deviceids != NULL) + g_free(deviceids); + return ret; } static OSStatus @@ -147,61 +250,89 @@ osx_render(void *vdata, } static bool -osx_output_open(void *data, struct audio_format *audio_format, GError **error) +osx_output_enable(struct audio_output *ao, GError **error_r) { - struct osx_output *od = data; - ComponentDescription desc; - Component comp; - AURenderCallbackStruct callback; - AudioStreamBasicDescription stream_description; - OSStatus status; - ComponentResult result; + struct osx_output *oo = (struct osx_output *)ao; + ComponentDescription desc; desc.componentType = kAudioUnitType_Output; - desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentSubType = oo->component_subtype; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; - comp = FindNextComponent(NULL, &desc); + Component comp = FindNextComponent(NULL, &desc); if (comp == 0) { - g_set_error(error, osx_output_quark(), 0, + g_set_error(error_r, osx_output_quark(), 0, "Error finding OS X component"); return false; } - status = OpenAComponent(comp, &od->au); + OSStatus status = OpenAComponent(comp, &oo->au); if (status != noErr) { - g_set_error(error, osx_output_quark(), 0, + g_set_error(error_r, osx_output_quark(), status, "Unable to open OS X component: %s", GetMacOSStatusCommentString(status)); return false; } - status = AudioUnitInitialize(od->au); - if (status != noErr) { - CloseComponent(od->au); - g_set_error(error, osx_output_quark(), 0, - "Unable to initialize OS X audio unit: %s", - GetMacOSStatusCommentString(status)); + if (!osx_output_set_device(oo, error_r)) { + CloseComponent(oo->au); return false; } + AURenderCallbackStruct callback; callback.inputProc = osx_render; - callback.inputProcRefCon = od; + callback.inputProcRefCon = oo; - result = AudioUnitSetProperty(od->au, - kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, 0, - &callback, sizeof(callback)); + ComponentResult result = + AudioUnitSetProperty(oo->au, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, 0, + &callback, sizeof(callback)); if (result != noErr) { - AudioUnitUninitialize(od->au); - CloseComponent(od->au); - g_set_error(error, osx_output_quark(), 0, + CloseComponent(oo->au); + g_set_error(error_r, osx_output_quark(), result, "unable to set callback for OS X audio unit"); return false; } + return true; +} + +static void +osx_output_disable(struct audio_output *ao) +{ + struct osx_output *oo = (struct osx_output *)ao; + + CloseComponent(oo->au); +} + +static void +osx_output_cancel(struct audio_output *ao) +{ + struct osx_output *od = (struct osx_output *)ao; + + g_mutex_lock(od->mutex); + od->len = 0; + g_mutex_unlock(od->mutex); +} + +static void +osx_output_close(struct audio_output *ao) +{ + struct osx_output *od = (struct osx_output *)ao; + + AudioOutputUnitStop(od->au); + AudioUnitUninitialize(od->au); +} + +static bool +osx_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) +{ + struct osx_output *od = (struct osx_output *)ao; + + AudioStreamBasicDescription stream_description; stream_description.mSampleRate = audio_format->sample_rate; stream_description.mFormatID = kAudioFormatLinearPCM; stream_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; @@ -215,9 +346,13 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) stream_description.mBitsPerChannel = 16; break; + case SAMPLE_FORMAT_S32: + stream_description.mBitsPerChannel = 32; + break; + default: - audio_format->format = SAMPLE_FORMAT_S16; - stream_description.mBitsPerChannel = 16; + audio_format->format = SAMPLE_FORMAT_S32; + stream_description.mBitsPerChannel = 32; break; } @@ -231,18 +366,25 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) stream_description.mBytesPerFrame = stream_description.mBytesPerPacket; stream_description.mChannelsPerFrame = audio_format->channels; - result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, 0, - &stream_description, - sizeof(stream_description)); + ComponentResult result = + AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, + &stream_description, + sizeof(stream_description)); if (result != noErr) { - AudioUnitUninitialize(od->au); - CloseComponent(od->au); - g_set_error(error, osx_output_quark(), 0, + g_set_error(error, osx_output_quark(), result, "Unable to set format on OS X device"); return false; } + OSStatus status = AudioUnitInitialize(od->au); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to initialize OS X audio unit: %s", + GetMacOSStatusCommentString(status)); + return false; + } + /* create a buffer of 1s */ od->buffer_size = (audio_format->sample_rate) * audio_format_frame_size(audio_format); @@ -253,7 +395,8 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) status = AudioOutputUnitStart(od->au); if (status != 0) { - g_set_error(error, osx_output_quark(), 0, + AudioUnitUninitialize(od->au); + g_set_error(error, osx_output_quark(), status, "unable to start audio output: %s", GetMacOSStatusCommentString(status)); return false; @@ -263,10 +406,10 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) } static size_t -osx_output_play(void *data, const void *chunk, size_t size, +osx_output_play(struct audio_output *ao, const void *chunk, size_t size, G_GNUC_UNUSED GError **error) { - struct osx_output *od = data; + struct osx_output *od = (struct osx_output *)ao; size_t start, nbytes; g_mutex_lock(od->mutex); @@ -296,11 +439,13 @@ osx_output_play(void *data, const void *chunk, size_t size, return nbytes; } -const struct audio_output_plugin osxPlugin = { +const struct audio_output_plugin osx_output_plugin = { .name = "osx", .test_default_device = osx_output_test_default_device, .init = osx_output_init, .finish = osx_output_finish, + .enable = osx_output_enable, + .disable = osx_output_disable, .open = osx_output_open, .close = osx_output_close, .play = osx_output_play, diff --git a/src/output/osx_output_plugin.h b/src/output/osx_output_plugin.h new file mode 100644 index 000000000..814702d4f --- /dev/null +++ b/src/output/osx_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_OSX_OUTPUT_PLUGIN_H +#define MPD_OSX_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin osx_output_plugin; + +#endif diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c index 1d1aec7b1..90c5a5331 100644 --- a/src/output/pipe_output_plugin.c +++ b/src/output/pipe_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,12 +18,15 @@ */ #include "config.h" +#include "pipe_output_plugin.h" #include "output_api.h" #include <stdio.h> #include <errno.h> struct pipe_output { + struct audio_output base; + char *cmd; FILE *fh; }; @@ -37,13 +40,17 @@ pipe_output_quark(void) return g_quark_from_static_string("pipe_output"); } -static void * -pipe_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, +static struct audio_output * +pipe_output_init(const struct config_param *param, GError **error) { struct pipe_output *pd = g_new(struct pipe_output, 1); + if (!ao_base_init(&pd->base, &pipe_output_plugin, param, error)) { + g_free(pd); + return NULL; + } + pd->cmd = config_dup_block_string(param, "command", NULL); if (pd->cmd == NULL) { g_set_error(error, pipe_output_quark(), 0, @@ -51,23 +58,25 @@ pipe_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, return NULL; } - return pd; + return &pd->base; } static void -pipe_output_finish(void *data) +pipe_output_finish(struct audio_output *ao) { - struct pipe_output *pd = data; + struct pipe_output *pd = (struct pipe_output *)ao; g_free(pd->cmd); + ao_base_finish(&pd->base); g_free(pd); } static bool -pipe_output_open(void *data, G_GNUC_UNUSED struct audio_format *audio_format, +pipe_output_open(struct audio_output *ao, + G_GNUC_UNUSED struct audio_format *audio_format, G_GNUC_UNUSED GError **error) { - struct pipe_output *pd = data; + struct pipe_output *pd = (struct pipe_output *)ao; pd->fh = popen(pd->cmd, "w"); if (pd->fh == NULL) { @@ -81,17 +90,17 @@ pipe_output_open(void *data, G_GNUC_UNUSED struct audio_format *audio_format, } static void -pipe_output_close(void *data) +pipe_output_close(struct audio_output *ao) { - struct pipe_output *pd = data; + struct pipe_output *pd = (struct pipe_output *)ao; pclose(pd->fh); } static size_t -pipe_output_play(void *data, const void *chunk, size_t size, GError **error) +pipe_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) { - struct pipe_output *pd = data; + struct pipe_output *pd = (struct pipe_output *)ao; size_t ret; ret = fwrite(chunk, 1, size, pd->fh); diff --git a/src/output/pipe_output_plugin.h b/src/output/pipe_output_plugin.h new file mode 100644 index 000000000..9f014f829 --- /dev/null +++ b/src/output/pipe_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_PIPE_OUTPUT_PLUGIN_H +#define MPD_PIPE_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin pipe_output_plugin; + +#endif diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c index 5fe2f572e..0dc9be0e4 100644 --- a/src/output/pulse_output_plugin.c +++ b/src/output/pulse_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -31,11 +31,44 @@ #include <pulse/introspect.h> #include <pulse/subscribe.h> #include <pulse/error.h> +#include <pulse/version.h> #include <assert.h> +#include <stddef.h> #define MPD_PULSE_NAME "Music Player Daemon" +#if !defined(PA_CHECK_VERSION) +/** + * This macro was implemented in libpulse 0.9.16. + */ +#define PA_CHECK_VERSION(a,b,c) false +#endif + +struct pulse_output { + struct audio_output base; + + const char *name; + const char *server; + const char *sink; + + struct pulse_mixer *mixer; + + struct pa_threaded_mainloop *mainloop; + struct pa_context *context; + struct pa_stream *stream; + + size_t writable; + +#if !PA_CHECK_VERSION(0,9,11) + /** + * We need this variable because pa_stream_is_corked() wasn't + * added before 0.9.11. + */ + bool pause; +#endif +}; + /** * The quark used for GError.domain. */ @@ -46,6 +79,18 @@ pulse_output_quark(void) } void +pulse_output_lock(struct pulse_output *po) +{ + pa_threaded_mainloop_lock(po->mainloop); +} + +void +pulse_output_unlock(struct pulse_output *po) +{ + pa_threaded_mainloop_unlock(po->mainloop); +} + +void pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm) { assert(po != NULL); @@ -299,16 +344,19 @@ pulse_output_setup_context(struct pulse_output *po, GError **error_r) return true; } -static void * -pulse_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, - G_GNUC_UNUSED GError **error_r) +static struct audio_output * +pulse_output_init(const struct config_param *param, GError **error_r) { struct pulse_output *po; g_setenv("PULSE_PROP_media.role", "music", true); po = g_new(struct pulse_output, 1); + if (!ao_base_init(&po->base, &pulse_output_plugin, param, error_r)) { + g_free(po); + return NULL; + } + po->name = config_get_block_string(param, "name", "mpd_pulse"); po->server = config_get_block_string(param, "server", NULL); po->sink = config_get_block_string(param, "sink", NULL); @@ -318,21 +366,22 @@ pulse_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, po->context = NULL; po->stream = NULL; - return po; + return &po->base; } static void -pulse_output_finish(void *data) +pulse_output_finish(struct audio_output *ao) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; + ao_base_finish(&po->base); g_free(po); } static bool -pulse_output_enable(void *data, GError **error_r) +pulse_output_enable(struct audio_output *ao, GError **error_r) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; assert(po->mainloop == NULL); assert(po->context == NULL); @@ -376,9 +425,9 @@ pulse_output_enable(void *data, GError **error_r) } static void -pulse_output_disable(void *data) +pulse_output_disable(struct audio_output *ao) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; assert(po->mainloop != NULL); @@ -494,11 +543,46 @@ pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes, pa_threaded_mainloop_signal(po->mainloop, 0); } +/** + * Create, set up and connect a context. + * + * Caller must lock the main loop. + * + * @return true on success, false on error + */ +static bool +pulse_output_setup_stream(struct pulse_output *po, const pa_sample_spec *ss, + GError **error_r) +{ + assert(po != NULL); + assert(po->context != NULL); + + po->stream = pa_stream_new(po->context, po->name, ss, NULL); + if (po->stream == NULL) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_stream_new() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + +#if PA_CHECK_VERSION(0,9,8) + pa_stream_set_suspended_callback(po->stream, + pulse_output_stream_suspended_cb, po); +#endif + + pa_stream_set_state_callback(po->stream, + pulse_output_stream_state_cb, po); + pa_stream_set_write_callback(po->stream, + pulse_output_stream_write_cb, po); + + return true; +} + static bool -pulse_output_open(void *data, struct audio_format *audio_format, +pulse_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error_r) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; pa_sample_spec ss; int error; @@ -540,25 +624,11 @@ pulse_output_open(void *data, struct audio_format *audio_format, /* create a stream .. */ - po->stream = pa_stream_new(po->context, po->name, &ss, NULL); - if (po->stream == NULL) { - g_set_error(error_r, pulse_output_quark(), 0, - "pa_stream_new() has failed: %s", - pa_strerror(pa_context_errno(po->context))); + if (!pulse_output_setup_stream(po, &ss, error_r)) { pa_threaded_mainloop_unlock(po->mainloop); return false; } -#if PA_CHECK_VERSION(0,9,8) - pa_stream_set_suspended_callback(po->stream, - pulse_output_stream_suspended_cb, po); -#endif - - pa_stream_set_state_callback(po->stream, - pulse_output_stream_state_cb, po); - pa_stream_set_write_callback(po->stream, - pulse_output_stream_write_cb, po); - /* .. and connect it (asynchronously) */ error = pa_stream_connect_playback(po->stream, po->sink, @@ -583,9 +653,9 @@ pulse_output_open(void *data, struct audio_format *audio_format, } static void -pulse_output_close(void *data) +pulse_output_close(struct audio_output *ao) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; pa_operation *o; assert(po->mainloop != NULL); @@ -732,9 +802,10 @@ pulse_output_stream_pause(struct pulse_output *po, bool pause, } static size_t -pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r) +pulse_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error_r) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; int error; assert(po->mainloop != NULL); @@ -802,9 +873,9 @@ pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r) } static void -pulse_output_cancel(void *data) +pulse_output_cancel(struct audio_output *ao) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; pa_operation *o; assert(po->mainloop != NULL); @@ -834,9 +905,9 @@ pulse_output_cancel(void *data) } static bool -pulse_output_pause(void *data) +pulse_output_pause(struct audio_output *ao) { - struct pulse_output *po = data; + struct pulse_output *po = (struct pulse_output *)ao; GError *error = NULL; assert(po->mainloop != NULL); @@ -881,12 +952,12 @@ pulse_output_test_default_device(void) struct pulse_output *po; bool success; - po = pulse_output_init(NULL, NULL, NULL); + po = (struct pulse_output *)pulse_output_init(NULL, NULL); if (po == NULL) return false; success = pulse_output_wait_connection(po, NULL); - pulse_output_finish(po); + pulse_output_finish(&po->base); return success; } diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h index 06e3aec43..02a51f27b 100644 --- a/src/output/pulse_output_plugin.h +++ b/src/output/pulse_output_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,43 +21,20 @@ #define MPD_PULSE_OUTPUT_PLUGIN_H #include <stdbool.h> -#include <stddef.h> #include <glib.h> -#include <pulse/version.h> - -#if !defined(PA_CHECK_VERSION) -/** - * This macro was implemented in libpulse 0.9.16. - */ -#define PA_CHECK_VERSION(a,b,c) false -#endif - -struct pa_operation; +struct pulse_output; +struct pulse_mixer; struct pa_cvolume; -struct pulse_output { - const char *name; - const char *server; - const char *sink; - - struct pulse_mixer *mixer; +extern const struct audio_output_plugin pulse_output_plugin; - struct pa_threaded_mainloop *mainloop; - struct pa_context *context; - struct pa_stream *stream; - - size_t writable; +void +pulse_output_lock(struct pulse_output *po); -#if !PA_CHECK_VERSION(0,9,11) - /** - * We need this variable because pa_stream_is_corked() wasn't - * added before 0.9.11. - */ - bool pause; -#endif -}; +void +pulse_output_unlock(struct pulse_output *po); void pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm); diff --git a/src/output/raop_output_plugin.c b/src/output/raop_output_plugin.c new file mode 100644 index 000000000..68d6897cb --- /dev/null +++ b/src/output/raop_output_plugin.c @@ -0,0 +1,1050 @@ +/* + * Copyright (C) 2003-2011 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 "config.h" +#include "raop_output_plugin.h" +#include "output_api.h" +#include "mixer_list.h" +#include "fd_util.h" +#include "ntp_server.h" +#include "rtsp_client.h" +#include "glib_compat.h" + +#include <glib.h> +#include <unistd.h> +#include <sys/time.h> +#include <openssl/aes.h> +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/rsa.h> +#include <openssl/engine.h> + +#ifndef WIN32 +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netdb.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "raop" + +struct play_state { + bool playing; + unsigned short seq_num; + unsigned int rtptime; + unsigned int sync_src; + unsigned int start_rtptime; + struct timeval start_time; + struct timeval last_send; +}; + +/*********************************************************************/ + +enum pause_state { + NO_PAUSE = 0, + OP_PAUSE, + NODATA_PAUSE, +}; + +#define MINIMUM_SAMPLE_SIZE 32 + +#define RAOP_FD_READ (1<<0) +#define RAOP_FD_WRITE (1<<1) + +/*********************************************************************/ + +struct encrypt_data { + AES_KEY ctx; + unsigned char iv[16]; // initialization vector for aes-cbc + unsigned char nv[16]; // next vector for aes-cbc + unsigned char key[16]; // key for aes-cbc +}; + +/*********************************************************************/ + +struct raop_data { + struct audio_output base; + + struct rtspcl_data *rtspcl; + const char *addr; // target host address + short rtsp_port; + struct sockaddr_in ctrl_addr; + struct sockaddr_in data_addr; + + bool is_master; + struct raop_data *next; + + unsigned volume; + + GMutex *control_mutex; + + bool started; + bool paused; +}; + +/*********************************************************************/ + +struct control_data { + unsigned short port; + int fd; +}; + +/*********************************************************************/ + +#define NUMSAMPLES 352 +#define RAOP_BUFFER_SIZE NUMSAMPLES * 4 +#define RAOP_HEADER_SIZE 12 +#define ALAC_MAX_HEADER_SIZE 8 +#define RAOP_MAX_PACKET_SIZE RAOP_BUFFER_SIZE + RAOP_HEADER_SIZE + ALAC_MAX_HEADER_SIZE + +// session +struct raop_session_data { + struct raop_data *raop_list; + struct ntp_server ntp; + struct control_data ctrl; + struct encrypt_data encrypt; + struct play_state play_state; + + int data_fd; + + unsigned char buffer[RAOP_BUFFER_SIZE]; + size_t bufferSize; + + unsigned char data[RAOP_MAX_PACKET_SIZE]; + int wblk_wsize; + int wblk_remsize; + + GMutex *data_mutex; + GMutex *list_mutex; +}; + +/*********************************************************************/ + +static struct raop_session_data *raop_session = NULL; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +raop_output_quark(void) +{ + return g_quark_from_static_string("raop_output"); +} + +static void +raop_session_free(struct raop_session_data *session) +{ + assert(session != NULL); + assert(session->raop_list == NULL); + + ntp_server_close(&session->ntp); + + if (session->data_mutex != NULL) + g_mutex_free(session->data_mutex); + + if (session->list_mutex != NULL) + g_mutex_free(session->list_mutex); + + if (raop_session->data_fd >= 0) + close_socket(raop_session->data_fd); + + if (raop_session->ctrl.fd >= 0) + close_socket(raop_session->ctrl.fd); + + g_free(session); +} + +static struct raop_session_data * +raop_session_new(GError **error_r) +{ + struct raop_session_data *session = g_new(struct raop_session_data, 1); + session->raop_list = NULL; + + session->data_mutex = g_mutex_new(); + session->list_mutex = g_mutex_new(); + + ntp_server_init(&session->ntp); + session->ctrl.port = 6001; + session->ctrl.fd = -1; + session->play_state.playing = false; + session->play_state.seq_num = (short) g_random_int(); + session->play_state.rtptime = g_random_int(); + session->play_state.sync_src = g_random_int(); + session->play_state.last_send.tv_sec = 0; + session->play_state.last_send.tv_usec = 0; + session->data_fd = -1; + + if (!RAND_bytes(session->encrypt.iv, sizeof(session->encrypt.iv)) || + !RAND_bytes(session->encrypt.key, sizeof(session->encrypt.key))) { + raop_session_free(session); + g_set_error(error_r, raop_output_quark(), 0, + "RAND_bytes error code=%ld", ERR_get_error()); + return NULL; + } + memcpy(session->encrypt.nv, session->encrypt.iv, sizeof(session->encrypt.nv)); + for (unsigned i = 0; i < 16; i++) { + printf("0x%x ", session->encrypt.key[i]); + } + printf("\n"); + AES_set_encrypt_key(session->encrypt.key, 128, &session->encrypt.ctx); + + memset(session->buffer, 0, RAOP_BUFFER_SIZE); + session->bufferSize = 0; + + return session; +} + +static struct raop_data * +new_raop_data(const struct config_param *param, GError **error_r) +{ + struct raop_data *ret = g_new(struct raop_data, 1); + if (!ao_base_init(&ret->base, &raop_output_plugin, param, error_r)) { + g_free(ret); + return NULL; + } + + ret->control_mutex = g_mutex_new(); + + ret->next = NULL; + ret->is_master = 0; + ret->started = 0; + ret->paused = 0; + + if (raop_session == NULL && + (raop_session = raop_session_new(error_r)) == NULL) { + g_mutex_free(ret->control_mutex); + ao_base_finish(&ret->base); + g_free(ret); + return NULL; + } + + return ret; +} + +/* + * remove one character from a string + * return the number of deleted characters + */ +static int +remove_char_from_string(char *str, char c) +{ + char *src, *dst; + + /* skip all characters that don't need to be copied */ + src = strchr(str, c); + if (!src) + return 0; + + for (dst = src; *src; src++) + if (*src != c) + *(dst++) = *src; + + *dst = '\0'; + + return src - dst; +} + +/* bind an opened socket to specified hostname and port. + * if hostname=NULL, use INADDR_ANY. + * if *port=0, use dynamically assigned port + */ +static int bind_host(int sd, char *hostname, unsigned long ulAddr, + unsigned short *port, GError **error_r) +{ + struct sockaddr_in my_addr; + socklen_t nlen = sizeof(struct sockaddr); + struct hostent *h; + + memset(&my_addr, 0, sizeof(my_addr)); + /* use specified hostname */ + if (hostname) { + /* get server IP address (no check if input is IP address or DNS name) */ + h = gethostbyname(hostname); + if (h == NULL) { + if (strstr(hostname, "255.255.255.255") == hostname) { + my_addr.sin_addr.s_addr=-1; + } else { + if ((my_addr.sin_addr.s_addr = inet_addr(hostname)) == 0xFFFFFFFF) { + g_set_error(error_r, raop_output_quark(), 0, + "failed to resolve host '%s'", + hostname); + return -1; + } + } + my_addr.sin_family = AF_INET; + } else { + my_addr.sin_family = h->h_addrtype; + memcpy((char *) &my_addr.sin_addr.s_addr, + h->h_addr_list[0], h->h_length); + } + } else { + // if hostname=NULL, use INADDR_ANY + if (ulAddr) + my_addr.sin_addr.s_addr = ulAddr; + else + my_addr.sin_addr.s_addr = htonl(INADDR_ANY); + my_addr.sin_family = AF_INET; + } + + /* bind a specified port */ + my_addr.sin_port = htons(*port); + + if (bind(sd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "failed to bind socket: %s", + g_strerror(errno)); + return -1; + } + + if (*port == 0) { + getsockname(sd, (struct sockaddr *) &my_addr, &nlen); + *port = ntohs(my_addr.sin_port); + } + + return 0; +} + +/* + * open udp port + */ +static int +open_udp_socket(char *hostname, unsigned short *port, + GError **error_r) +{ + int sd; + const int size = 30000; + + /* socket creation */ + sd = socket(PF_INET, SOCK_DGRAM, 0); + if (sd < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "failed to create UDP socket: %s", + g_strerror(errno)); + return -1; + } + if (setsockopt(sd, SOL_SOCKET, SO_SNDBUF, (const char *) &size, sizeof(size)) < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "failed to set UDP buffer size: %s", + g_strerror(errno)); + return -1; + } + if (bind_host(sd, hostname, 0, port, error_r)) { + close_socket(sd); + return -1; + } + + return sd; +} + +static bool +get_sockaddr_by_host(const char *host, short destport, + struct sockaddr_in *addr, + GError **error_r) +{ + struct hostent *h; + + h = gethostbyname(host); + if (h) { + addr->sin_family = h->h_addrtype; + memcpy((char *) &addr->sin_addr.s_addr, h->h_addr_list[0], h->h_length); + } else { + addr->sin_family = AF_INET; + if ((addr->sin_addr.s_addr=inet_addr(host))==0xFFFFFFFF) { + g_set_error(error_r, rtsp_client_quark(), 0, + "failed to resolve host '%s'", host); + return false; + } + } + addr->sin_port = htons(destport); + return true; +} + +/* + * Calculate the current NTP time, store it in the buffer. + */ +static void +fill_int(unsigned char *buffer, uint32_t value) +{ + uint32_t be = GINT32_TO_BE(value); + memcpy(buffer, &be, sizeof(be)); +} + +/* + * Store time in the NTP format in the buffer + */ +static void +fill_time_buffer_with_time(unsigned char *buffer, struct timeval *tout) +{ + unsigned long secs_to_baseline = 964697997; + double fraction; + unsigned long long_fraction; + unsigned long secs; + + fraction = ((double) tout->tv_usec) / 1000000.0; + long_fraction = (unsigned long) (fraction * 256.0 * 256.0 * 256.0 * 256.0); + secs = secs_to_baseline + tout->tv_sec; + fill_int(buffer, secs); + fill_int(buffer + 4, long_fraction); +} + +static void +get_time_for_rtp(struct play_state *state, struct timeval *tout) +{ + unsigned long rtp_diff = state->rtptime - state->start_rtptime; + unsigned long add_secs = rtp_diff / 44100; + unsigned long add_usecs = (((rtp_diff % 44100) * 10000) / 441) % 1000000; + tout->tv_sec = state->start_time.tv_sec + add_secs; + tout->tv_usec = state->start_time.tv_usec + add_usecs; + if (tout->tv_usec >= 1000000) { + tout->tv_sec++; + tout->tv_usec = tout->tv_usec % 1000000; + } +} + +/* + * Send a control command + */ +static bool +send_control_command(struct control_data *ctrl, struct raop_data *rd, + struct play_state *state, + GError **error_r) +{ + unsigned char buf[20]; + int diff; + int num_bytes; + struct timeval ctrl_time; + + diff = 88200; + if (rd->started) { + buf[0] = 0x80; + diff += NUMSAMPLES; + } else { + buf[0] = 0x90; + state->playing = true; + state->start_rtptime = state->rtptime; + } + buf[1] = 0xd4; + buf[2] = 0x00; + buf[3] = 0x07; + fill_int(buf + 4, state->rtptime - diff); + get_time_for_rtp(state, &ctrl_time); + fill_time_buffer_with_time(buf + 8, &ctrl_time); + fill_int(buf + 16, state->rtptime); + + num_bytes = sendto(ctrl->fd, (const void *)buf, sizeof(buf), 0, + (struct sockaddr *)&rd->ctrl_addr, + sizeof(rd->ctrl_addr)); + if (num_bytes < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "Unable to send control command: %s", + g_strerror(errno)); + return false; + } + + return true; +} + +static int rsa_encrypt(const unsigned char *text, int len, unsigned char *res) +{ + RSA *rsa; + gsize usize; + unsigned char *modulus; + unsigned char *exponent; + int size; + + char n[] = + "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" + "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" + "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" + "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" + "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" + "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; + char e[] = "AQAB"; + + rsa = RSA_new(); + + modulus = g_base64_decode(n, &usize); + rsa->n = BN_bin2bn(modulus, usize, NULL); + exponent = g_base64_decode(e, &usize); + rsa->e = BN_bin2bn(exponent, usize, NULL); + g_free(modulus); + g_free(exponent); + size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING); + + RSA_free(rsa); + return size; +} + +static int +raop_encrypt(struct encrypt_data *encryp, unsigned char *data, int size) +{ + // any bytes that fall beyond the last 16 byte page should be sent + // in the clear + int alt_size = size - (size % 16); + + memcpy(encryp->nv, encryp->iv, 16); + + AES_cbc_encrypt(data, data, alt_size, &encryp->ctx, encryp->nv, 1); + + return size; +} + +/* write bits filed data, *bpos=0 for msb, *bpos=7 for lsb + d=data, blen=length of bits field +*/ +static inline void +bits_write(unsigned char **p, unsigned char d, int blen, int *bpos) +{ + int lb, rb, bd; + lb =7 - *bpos; + rb = lb - blen + 1; + if (rb >= 0) { + bd = d << rb; + if (*bpos) + **p |= bd; + else + **p = bd; + *bpos += blen; + } else { + bd = d >> -rb; + **p |= bd; + *p += 1; + **p = d << (8 + rb); + *bpos = -rb; + } +} + +static bool +wrap_pcm(unsigned char *buffer, int bsize, int *size, unsigned char *inData, int inSize) +{ + unsigned char one[4]; + int count = 0; + int bpos = 0; + unsigned char *bp = buffer; + int i, nodata = 0; + bits_write(&bp, 1, 3, &bpos); // channel=1, stereo + bits_write(&bp, 0, 4, &bpos); // unknown + bits_write(&bp, 0, 8, &bpos); // unknown + bits_write(&bp, 0, 4, &bpos); // unknown + if (bsize != 4096 && false) + bits_write(&bp, 1, 1, &bpos); // hassize + else + bits_write(&bp, 0, 1, &bpos); // hassize + bits_write(&bp, 0, 2, &bpos); // unused + bits_write(&bp, 1, 1, &bpos); // is-not-compressed + if (bsize != 4096 && false) { + // size of data, integer, big endian + bits_write(&bp, (bsize >> 24) & 0xff, 8, &bpos); + bits_write(&bp, (bsize >> 16) & 0xff, 8, &bpos); + bits_write(&bp, (bsize >> 8) & 0xff, 8, &bpos); + bits_write(&bp, bsize&0xff, 8, &bpos); + } + while (1) { + if (inSize <= count * 4) nodata = 1; + if (nodata) break; + one[0] = inData[count * 4]; + one[1] = inData[count * 4 + 1]; + one[2] = inData[count * 4 + 2]; + one[3] = inData[count * 4 + 3]; + +#if BYTE_ORDER == BIG_ENDIAN + bits_write(&bp, one[0], 8, &bpos); + bits_write(&bp, one[1], 8, &bpos); + bits_write(&bp, one[2], 8, &bpos); + bits_write(&bp, one[3], 8, &bpos); +#else + bits_write(&bp, one[1], 8, &bpos); + bits_write(&bp, one[0], 8, &bpos); + bits_write(&bp, one[3], 8, &bpos); + bits_write(&bp, one[2], 8, &bpos); +#endif + + if (++count == bsize) break; + } + if (!count) return false; // when no data at all, it should stop playing + /* when readable size is less than bsize, fill 0 at the bottom */ + for(i = 0; i < (bsize - count) * 4; i++) { + bits_write(&bp, 0, 8, &bpos); + } + *size = (int)(bp - buffer); + if (bpos) *size += 1; + return true; +} + +static bool +raopcl_connect(struct raop_data *rd, GError **error_r) +{ + unsigned char buf[4 + 8 + 16]; + char sid[16]; + char sci[24]; + char act_r[17]; + char *sac=NULL, *key = NULL, *iv = NULL; + char sdp[1024]; + int rval = false; + unsigned char rsakey[512]; + struct timeval current_time; + unsigned int sessionNum; + int i; + + + gettimeofday(¤t_time,NULL); + sessionNum = current_time.tv_sec + 2082844804; + + RAND_bytes(buf, sizeof(buf)); + sprintf(act_r, "%u", (unsigned int) g_random_int()); + sprintf(sid, "%u", sessionNum); + sprintf(sci, "%08x%08x", *((int *)(buf + 4)), *((int *)(buf + 8))); + sac = g_base64_encode(buf + 12, 16); + rd->rtspcl = rtspcl_open(); + rtspcl_set_useragent(rd->rtspcl, "iTunes/8.1.1 (Macintosh; U; PPC Mac OS X 10.4)"); + rtspcl_add_exthds(rd->rtspcl, "Client-Instance", sci); + rtspcl_add_exthds(rd->rtspcl, "DACP-ID", sci); + rtspcl_add_exthds(rd->rtspcl, "Active-Remote", act_r); + if (!rtspcl_connect(rd->rtspcl, rd->addr, rd->rtsp_port, sid, error_r)) + goto erexit; + + i = rsa_encrypt(raop_session->encrypt.key, 16, rsakey); + key = g_base64_encode(rsakey, i); + remove_char_from_string(key, '='); + iv = g_base64_encode(raop_session->encrypt.iv, 16); + remove_char_from_string(iv, '='); + sprintf(sdp, + "v=0\r\n" + "o=iTunes %s 0 IN IP4 %s\r\n" + "s=iTunes\r\n" + "c=IN IP4 %s\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/AVP 96\r\n" + "a=rtpmap:96 AppleLossless\r\n" + "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n" + "a=rsaaeskey:%s\r\n" + "a=aesiv:%s\r\n", + sid, rtspcl_local_ip(rd->rtspcl), rd->addr, NUMSAMPLES, key, iv); + remove_char_from_string(sac, '='); + // rtspcl_add_exthds(rd->rtspcl, "Apple-Challenge", sac); + if (!rtspcl_announce_sdp(rd->rtspcl, sdp, error_r)) + goto erexit; + // if (!rtspcl_mark_del_exthds(rd->rtspcl, "Apple-Challenge")) goto erexit; + if (!rtspcl_setup(rd->rtspcl, NULL, + raop_session->ctrl.port, raop_session->ntp.port, + error_r)) + goto erexit; + + if (!get_sockaddr_by_host(rd->addr, rd->rtspcl->control_port, + &rd->ctrl_addr, error_r)) + goto erexit; + + if (!get_sockaddr_by_host(rd->addr, rd->rtspcl->server_port, + &rd->data_addr, error_r)) + goto erexit; + + if (!rtspcl_record(rd->rtspcl, + raop_session->play_state.seq_num, + raop_session->play_state.rtptime, + error_r)) + goto erexit; + + rval = true; + + erexit: + g_free(sac); + g_free(key); + g_free(iv); + return rval; +} + +static int +difference (struct timeval *t1, struct timeval *t2) +{ + int ret = 150000000; + if (t1->tv_sec - t2->tv_sec < 150) { + ret = (t1->tv_sec - t2->tv_sec) * 1000000; + ret += t1->tv_usec - t2->tv_usec; + } + return ret; +} + +/* + * With airtunes version 2, we don't get responses back when we send audio + * data. The only requests we get from the airtunes device are timing + * requests. + */ +static bool +send_audio_data(int fd, GError **error_r) +{ + int i = 0; + struct timeval current_time, rtp_time; + struct raop_data *rd = raop_session->raop_list; + + get_time_for_rtp(&raop_session->play_state, &rtp_time); + gettimeofday(¤t_time, NULL); + int diff = difference(&rtp_time, ¤t_time); + if (diff > 0) + g_usleep(diff); + + gettimeofday(&raop_session->play_state.last_send, NULL); + while (rd) { + if (rd->started) { + raop_session->data[1] = 0x60; + } else { + rd->started = true; + raop_session->data[1] = 0xe0; + } + i = sendto(fd, (const void *)(raop_session->data + raop_session->wblk_wsize), + raop_session->wblk_remsize, 0, (struct sockaddr *) &rd->data_addr, + sizeof(rd->data_addr)); + if (i < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "write error: %s", + g_strerror(errno)); + return false; + } + if (i == 0) { + g_set_error_literal(error_r, raop_output_quark(), 0, + "disconnected on the other end"); + return false; + } + rd = rd->next; + } + raop_session->wblk_wsize += i; + raop_session->wblk_remsize -= i; + + return true; +} + +static struct audio_output * +raop_output_init(const struct config_param *param, GError **error_r) +{ + const char *host = config_get_block_string(param, "host", NULL); + if (host == NULL) { + g_set_error_literal(error_r, raop_output_quark(), 0, + "missing option 'host'"); + return NULL; + } + + struct raop_data *rd; + + rd = new_raop_data(param, error_r); + if (rd == NULL) + return NULL; + + rd->addr = host; + rd->rtsp_port = config_get_block_unsigned(param, "port", 5000); + rd->volume = config_get_block_unsigned(param, "volume", 75); + return &rd->base; +} + +static bool +raop_set_volume_local(struct raop_data *rd, int volume, GError **error_r) +{ + char vol_str[128]; + sprintf(vol_str, "volume: %d.000000\r\n", volume); + return rtspcl_set_parameter(rd->rtspcl, vol_str, error_r); +} + + +static void +raop_output_finish(struct audio_output *ao) +{ + struct raop_data *rd = (struct raop_data *)ao; + + if (rd->rtspcl) + rtspcl_close(rd->rtspcl); + + g_mutex_free(rd->control_mutex); + ao_base_finish(&rd->base); + g_free(rd); +} + +#define RAOP_VOLUME_MIN -30 +#define RAOP_VOLUME_MAX 0 + +int +raop_get_volume(struct raop_data *rd) +{ + return rd->volume; +} + +bool +raop_set_volume(struct raop_data *rd, unsigned volume, GError **error_r) +{ + int raop_volume; + bool rval; + + //set parameter volume + if (volume == 0) { + raop_volume = -144; + } else { + raop_volume = RAOP_VOLUME_MIN + + (RAOP_VOLUME_MAX - RAOP_VOLUME_MIN) * volume / 100; + } + g_mutex_lock(rd->control_mutex); + rval = raop_set_volume_local(rd, raop_volume, error_r); + if (rval) rd->volume = volume; + g_mutex_unlock(rd->control_mutex); + + return rval; +} + +static void +raop_output_cancel(struct audio_output *ao) +{ + //flush + struct key_data kd; + struct raop_data *rd = (struct raop_data *)ao; + int flush_diff = 1; + + rd->started = 0; + if (rd->is_master) { + raop_session->play_state.playing = false; + } + if (rd->paused) { + return; + } + + g_mutex_lock(rd->control_mutex); + static char rtp_key[] = "RTP-Info"; + kd.key = rtp_key; + char buf[128]; + sprintf(buf, "seq=%d; rtptime=%d", raop_session->play_state.seq_num + flush_diff, raop_session->play_state.rtptime + NUMSAMPLES * flush_diff); + kd.data = buf; + kd.next = NULL; + exec_request(rd->rtspcl, "FLUSH", NULL, NULL, 1, + &kd, NULL, NULL); + g_mutex_unlock(rd->control_mutex); +} + +static bool +raop_output_pause(struct audio_output *ao) +{ + struct raop_data *rd = (struct raop_data *)ao; + + rd->paused = true; + return true; +} + +/** + * Remove the output from the session's list. Caller must not lock + * the list_mutex. + */ +static void +raop_output_remove(struct raop_data *rd) +{ + struct raop_data *iter = raop_session->raop_list; + struct raop_data *prev = NULL; + + g_mutex_lock(raop_session->list_mutex); + while (iter) { + if (iter == rd) { + if (prev != NULL) { + prev->next = rd->next; + } else { + raop_session->raop_list = rd->next; + } + if (rd->is_master && raop_session->raop_list != NULL) { + raop_session->raop_list->is_master = true; + } + rd->next = NULL; + rd->is_master = false; + break; + } + prev = iter; + iter = iter->next; + } + g_mutex_unlock(raop_session->list_mutex); + + if (raop_session->raop_list == NULL) { + raop_session_free(raop_session); + raop_session = NULL; + } +} + +static void +raop_output_close(struct audio_output *ao) +{ + //teardown + struct raop_data *rd = (struct raop_data *)ao; + + raop_output_remove(rd); + + g_mutex_lock(rd->control_mutex); + exec_request(rd->rtspcl, "TEARDOWN", NULL, NULL, 0, + NULL, NULL, NULL); + g_mutex_unlock(rd->control_mutex); + + rd->started = 0; +} + + +static bool +raop_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error_r) +{ + //setup, etc. + struct raop_data *rd = (struct raop_data *)ao; + + g_mutex_lock(raop_session->list_mutex); + if (raop_session->raop_list == NULL) { + // first raop, need to initialize session data + unsigned short myport = 0; + raop_session->raop_list = rd; + rd->is_master = true; + + raop_session->data_fd = open_udp_socket(NULL, &myport, + error_r); + if (raop_session->data_fd < 0) + return false; + + if (!ntp_server_open(&raop_session->ntp, error_r)) + return false; + + raop_session->ctrl.fd = + open_udp_socket(NULL, &raop_session->ctrl.port, + error_r); + if (raop_session->ctrl.fd < 0) { + ntp_server_close(&raop_session->ntp); + raop_session->ctrl.fd = -1; + g_mutex_unlock(raop_session->list_mutex); + return false; + } + } + g_mutex_unlock(raop_session->list_mutex); + + audio_format->format = SAMPLE_FORMAT_S16; + if (!raopcl_connect(rd, error_r)) { + raop_output_remove(rd); + return false; + } + + if (!raop_set_volume(rd, rd->volume, error_r)) { + raop_output_remove(rd); + return false; + } + + g_mutex_lock(raop_session->list_mutex); + if (!rd->is_master) { + rd->next = raop_session->raop_list; + raop_session->raop_list = rd; + } + g_mutex_unlock(raop_session->list_mutex); + return true; +} + +static size_t +raop_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error_r) +{ + //raopcl_send_sample + struct raop_data *rd = (struct raop_data *)ao; + size_t rval = 0, orig_size = size; + + rd->paused = false; + if (!rd->is_master) { + // only process data for the master raop + return size; + } + + g_mutex_lock(raop_session->data_mutex); + + if (raop_session->play_state.rtptime <= NUMSAMPLES) { + // looped over, need new reference point to calculate correct times + raop_session->play_state.playing = false; + } + + while (raop_session->bufferSize + size >= RAOP_BUFFER_SIZE) { + // ntp header + unsigned char header[] = { + 0x80, 0x60, 0x00, 0x00, + // rtptime + 0x00, 0x00, 0x00, 0x00, + // device + 0x7e, 0xad, 0xd2, 0xd3, + }; + + + int count = 0; + int copyBytes = RAOP_BUFFER_SIZE - raop_session->bufferSize; + + if (!raop_session->play_state.playing || + raop_session->play_state.seq_num % (44100 / NUMSAMPLES + 1) == 0) { + struct raop_data *iter; + g_mutex_lock(raop_session->list_mutex); + if (!raop_session->play_state.playing) { + gettimeofday(&raop_session->play_state.start_time,NULL); + } + iter = raop_session->raop_list; + while (iter) { + if (!send_control_command(&raop_session->ctrl, iter, + &raop_session->play_state, + error_r)) + goto erexit; + + iter = iter->next; + } + g_mutex_unlock(raop_session->list_mutex); + } + + fill_int(header + 8, raop_session->play_state.sync_src); + + memcpy(raop_session->buffer + raop_session->bufferSize, chunk, copyBytes); + raop_session->bufferSize += copyBytes; + chunk = ((const char *)chunk) + copyBytes; + size -= copyBytes; + + if (!wrap_pcm(raop_session->data + RAOP_HEADER_SIZE, NUMSAMPLES, &count, raop_session->buffer, RAOP_BUFFER_SIZE)) { + g_warning("unable to encode %d bytes properly\n", RAOP_BUFFER_SIZE); + } + + memcpy(raop_session->data, header, RAOP_HEADER_SIZE); + raop_session->data[2] = raop_session->play_state.seq_num >> 8; + raop_session->data[3] = raop_session->play_state.seq_num & 0xff; + raop_session->play_state.seq_num ++; + + fill_int(raop_session->data + 4, raop_session->play_state.rtptime); + raop_session->play_state.rtptime += NUMSAMPLES; + + raop_encrypt(&raop_session->encrypt, raop_session->data + RAOP_HEADER_SIZE, count); + raop_session->wblk_remsize = count + RAOP_HEADER_SIZE; + raop_session->wblk_wsize = 0; + + if (!send_audio_data(raop_session->data_fd, error_r)) + goto erexit; + + raop_session->bufferSize = 0; + } + if (size > 0) { + memcpy(raop_session->buffer + raop_session->bufferSize, chunk, size); + raop_session->bufferSize += size; + } + rval = orig_size; + erexit: + g_mutex_unlock(raop_session->data_mutex); + return rval; +} + +const struct audio_output_plugin raop_output_plugin = { + .name = "raop", + .init = raop_output_init, + .finish = raop_output_finish, + .open = raop_output_open, + .play = raop_output_play, + .cancel = raop_output_cancel, + .pause = raop_output_pause, + .close = raop_output_close, + .mixer_plugin = &raop_mixer_plugin, +}; diff --git a/src/output/raop_output_plugin.h b/src/output/raop_output_plugin.h new file mode 100644 index 000000000..6aca97836 --- /dev/null +++ b/src/output/raop_output_plugin.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2003-2011 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_RAOP_OUTPUT_PLUGIN_H +#define MPD_RAOP_OUTPUT_PLUGIN_H + +#include <glib.h> + +#include <stdbool.h> + +struct raop_data; + +extern const struct audio_output_plugin raop_output_plugin; + +bool +raop_set_volume(struct raop_data *rd, unsigned volume, GError **error_r); + +int +raop_get_volume(struct raop_data *rd); + +#endif diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c index 10d64106c..00adc5d19 100644 --- a/src/output/recorder_output_plugin.c +++ b/src/output/recorder_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "recorder_output_plugin.h" #include "output_api.h" #include "encoder_plugin.h" #include "encoder_list.h" @@ -34,6 +35,8 @@ #define G_LOG_DOMAIN "recorder" struct recorder_output { + struct audio_output base; + /** * The configured encoder plugin. */ @@ -64,11 +67,16 @@ recorder_output_quark(void) return g_quark_from_static_string("recorder_output"); } -static void * -recorder_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, GError **error_r) +static struct audio_output * +recorder_output_init(const struct config_param *param, GError **error_r) { struct recorder_output *recorder = g_new(struct recorder_output, 1); + if (!ao_base_init(&recorder->base, &recorder_output_plugin, param, + error_r)) { + g_free(recorder); + return NULL; + } + const char *encoder_name; const struct encoder_plugin *encoder_plugin; @@ -95,19 +103,21 @@ recorder_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, if (recorder->encoder == NULL) goto failure; - return recorder; + return &recorder->base; failure: + ao_base_finish(&recorder->base); g_free(recorder); return NULL; } static void -recorder_output_finish(void *data) +recorder_output_finish(struct audio_output *ao) { - struct recorder_output *recorder = data; + struct recorder_output *recorder = (struct recorder_output *)ao; encoder_finish(recorder->encoder); + ao_base_finish(&recorder->base); g_free(recorder); } @@ -154,10 +164,11 @@ recorder_output_encoder_to_file(struct recorder_output *recorder, } static bool -recorder_output_open(void *data, struct audio_format *audio_format, +recorder_output_open(struct audio_output *ao, + struct audio_format *audio_format, GError **error_r) { - struct recorder_output *recorder = data; + struct recorder_output *recorder = (struct recorder_output *)ao; bool success; /* create the output file */ @@ -185,9 +196,9 @@ recorder_output_open(void *data, struct audio_format *audio_format, } static void -recorder_output_close(void *data) +recorder_output_close(struct audio_output *ao) { - struct recorder_output *recorder = data; + struct recorder_output *recorder = (struct recorder_output *)ao; /* flush the encoder and write the rest to the file */ @@ -202,10 +213,10 @@ recorder_output_close(void *data) } static size_t -recorder_output_play(void *data, const void *chunk, size_t size, +recorder_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error_r) { - struct recorder_output *recorder = data; + struct recorder_output *recorder = (struct recorder_output *)ao; return encoder_write(recorder->encoder, chunk, size, error_r) && recorder_output_encoder_to_file(recorder, error_r) diff --git a/src/output/recorder_output_plugin.h b/src/output/recorder_output_plugin.h new file mode 100644 index 000000000..a9bf755bd --- /dev/null +++ b/src/output/recorder_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_RECORDER_OUTPUT_PLUGIN_H +#define MPD_RECORDER_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin recorder_output_plugin; + +#endif diff --git a/src/output/roar_output_plugin.c b/src/output/roar_output_plugin.c new file mode 100644 index 000000000..7566834f7 --- /dev/null +++ b/src/output/roar_output_plugin.c @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * 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 "config.h" +#include "roar_output_plugin.h" +#include "output_api.h" +#include "mixer_list.h" +#include "roar_output_plugin.h" + +#include <glib.h> +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#include <roaraudio.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "roaraudio" + +typedef struct roar +{ + struct audio_output base; + + roar_vs_t * vss; + int err; + char *host; + char *name; + int role; + struct roar_connection con; + struct roar_audio_info info; + GMutex *lock; + volatile bool alive; +} roar_t; + +static inline GQuark +roar_output_quark(void) +{ + return g_quark_from_static_string("roar_output"); +} + +static int +roar_output_get_volume_locked(struct roar *roar) +{ + if (roar->vss == NULL || !roar->alive) + return -1; + + float l, r; + int error; + if (roar_vs_volume_get(roar->vss, &l, &r, &error) < 0) + return -1; + + return (l + r) * 50; +} + +int +roar_output_get_volume(struct roar *roar) +{ + g_mutex_lock(roar->lock); + int volume = roar_output_get_volume_locked(roar); + g_mutex_unlock(roar->lock); + return volume; +} + +static bool +roar_output_set_volume_locked(struct roar *roar, unsigned volume) +{ + assert(volume <= 100); + + if (roar->vss == NULL || !roar->alive) + return false; + + int error; + float level = volume / 100.0; + + roar_vs_volume_mono(roar->vss, level, &error); + return true; +} + +bool +roar_output_set_volume(struct roar *roar, unsigned volume) +{ + g_mutex_lock(roar->lock); + bool success = roar_output_set_volume_locked(roar, volume); + g_mutex_unlock(roar->lock); + return success; +} + +static void +roar_configure(struct roar * self, const struct config_param *param) +{ + self->host = config_dup_block_string(param, "server", NULL); + self->name = config_dup_block_string(param, "name", "MPD"); + + const char *role = config_get_block_string(param, "role", "music"); + self->role = role != NULL + ? roar_str2role(role) + : ROAR_ROLE_MUSIC; +} + +static struct audio_output * +roar_init(const struct config_param *param, GError **error_r) +{ + struct roar *self = g_new0(struct roar, 1); + + if (!ao_base_init(&self->base, &roar_output_plugin, param, error_r)) { + g_free(self); + return NULL; + } + + self->lock = g_mutex_new(); + self->err = ROAR_ERROR_NONE; + roar_configure(self, param); + return &self->base; +} + +static void +roar_finish(struct audio_output *ao) +{ + struct roar *self = (struct roar *)ao; + + g_free(self->host); + g_free(self->name); + g_mutex_free(self->lock); + + ao_base_finish(&self->base); + g_free(self); +} + +static void +roar_use_audio_format(struct roar_audio_info *info, + struct audio_format *audio_format) +{ + info->rate = audio_format->sample_rate; + info->channels = audio_format->channels; + info->codec = ROAR_CODEC_PCM_S; + + switch (audio_format->format) { + case SAMPLE_FORMAT_UNDEFINED: + info->bits = 16; + audio_format->format = SAMPLE_FORMAT_S16; + break; + + case SAMPLE_FORMAT_S8: + info->bits = 8; + break; + + case SAMPLE_FORMAT_S16: + info->bits = 16; + break; + + case SAMPLE_FORMAT_S24: + info->bits = 24; + break; + + case SAMPLE_FORMAT_S24_P32: + info->bits = 32; + audio_format->format = SAMPLE_FORMAT_S32; + break; + + case SAMPLE_FORMAT_S32: + info->bits = 32; + break; + } +} + +static bool +roar_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) +{ + struct roar *self = (struct roar *)ao; + g_mutex_lock(self->lock); + + if (roar_simple_connect(&(self->con), self->host, self->name) < 0) + { + g_set_error(error, roar_output_quark(), 0, + "Failed to connect to Roar server"); + g_mutex_unlock(self->lock); + return false; + } + + self->vss = roar_vs_new_from_con(&(self->con), &(self->err)); + + if (self->vss == NULL || self->err != ROAR_ERROR_NONE) + { + g_set_error(error, roar_output_quark(), 0, + "Failed to connect to server"); + g_mutex_unlock(self->lock); + return false; + } + + roar_use_audio_format(&self->info, audio_format); + + if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY, + &(self->err)) < 0) + { + g_set_error(error, roar_output_quark(), 0, "Failed to start stream"); + g_mutex_unlock(self->lock); + return false; + } + roar_vs_role(self->vss, self->role, &(self->err)); + self->alive = true; + + g_mutex_unlock(self->lock); + return true; +} + +static void +roar_close(struct audio_output *ao) +{ + struct roar *self = (struct roar *)ao; + g_mutex_lock(self->lock); + self->alive = false; + + if (self->vss != NULL) + roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err)); + self->vss = NULL; + roar_disconnect(&(self->con)); + g_mutex_unlock(self->lock); +} + +static void +roar_cancel_locked(struct roar *self) +{ + if (self->vss == NULL) + return; + + roar_vs_t *vss = self->vss; + self->vss = NULL; + roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); + self->alive = false; + + vss = roar_vs_new_from_con(&(self->con), &(self->err)); + if (vss == NULL) + return; + + if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY, + &(self->err)) < 0) { + roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); + g_warning("Failed to start stream"); + return; + } + + roar_vs_role(vss, self->role, &(self->err)); + self->vss = vss; + self->alive = true; +} + +static void +roar_cancel(struct audio_output *ao) +{ + struct roar *self = (struct roar *)ao; + + g_mutex_lock(self->lock); + roar_cancel_locked(self); + g_mutex_unlock(self->lock); +} + +static size_t +roar_play(struct audio_output *ao, const void *chunk, size_t size, GError **error) +{ + struct roar *self = (struct roar *)ao; + ssize_t rc; + + if (self->vss == NULL) + { + g_set_error(error, roar_output_quark(), 0, "Connection is invalid"); + return 0; + } + + rc = roar_vs_write(self->vss, chunk, size, &(self->err)); + if ( rc <= 0 ) + { + g_set_error(error, roar_output_quark(), 0, "Failed to play data"); + return 0; + } + + return rc; +} + +static const char* +roar_tag_convert(enum tag_type type, bool *is_uuid) +{ + *is_uuid = false; + switch (type) + { + case TAG_ARTIST: + case TAG_ALBUM_ARTIST: + return "AUTHOR"; + case TAG_ALBUM: + return "ALBUM"; + case TAG_TITLE: + return "TITLE"; + case TAG_TRACK: + return "TRACK"; + case TAG_NAME: + return "NAME"; + case TAG_GENRE: + return "GENRE"; + case TAG_DATE: + return "DATE"; + case TAG_PERFORMER: + return "PERFORMER"; + case TAG_COMMENT: + return "COMMENT"; + case TAG_DISC: + return "DISCID"; + case TAG_COMPOSER: +#ifdef ROAR_META_TYPE_COMPOSER + return "COMPOSER"; +#else + return "AUTHOR"; +#endif + case TAG_MUSICBRAINZ_ARTISTID: + case TAG_MUSICBRAINZ_ALBUMID: + case TAG_MUSICBRAINZ_ALBUMARTISTID: + case TAG_MUSICBRAINZ_TRACKID: + *is_uuid = true; + return "HASH"; + + default: + return NULL; + } +} + +static void +roar_send_tag(struct audio_output *ao, const struct tag *meta) +{ + struct roar *self = (struct roar *)ao; + + if (self->vss == NULL) + return; + + g_mutex_lock(self->lock); + size_t cnt = 1; + struct roar_keyval vals[32]; + memset(vals, 0, sizeof(vals)); + char uuid_buf[32][64]; + + char timebuf[16]; + snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", + meta->time / 3600, (meta->time % 3600) / 60, meta->time % 60); + + vals[0].key = g_strdup("LENGTH"); + vals[0].value = timebuf; + + for (unsigned i = 0; i < meta->num_items && cnt < 32; i++) + { + bool is_uuid = false; + const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid); + if (key != NULL) + { + if (is_uuid) + { + snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s", + meta->items[i]->value); + vals[cnt].key = g_strdup(key); + vals[cnt].value = uuid_buf[cnt]; + } + else + { + vals[cnt].key = g_strdup(key); + vals[cnt].value = meta->items[i]->value; + } + cnt++; + } + } + + roar_vs_meta(self->vss, vals, cnt, &(self->err)); + + for (unsigned i = 0; i < 32; i++) + g_free(vals[i].key); + + g_mutex_unlock(self->lock); +} + +const struct audio_output_plugin roar_output_plugin = { + .name = "roar", + .init = roar_init, + .finish = roar_finish, + .open = roar_open, + .play = roar_play, + .cancel = roar_cancel, + .close = roar_close, + .send_tag = roar_send_tag, + + .mixer_plugin = &roar_mixer_plugin +}; diff --git a/src/output/roar_output_plugin.h b/src/output/roar_output_plugin.h new file mode 100644 index 000000000..78b628cc4 --- /dev/null +++ b/src/output/roar_output_plugin.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2011 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_ROAR_OUTPUT_PLUGIN_H +#define MPD_ROAR_OUTPUT_PLUGIN_H + +#include <stdbool.h> + +struct roar; + +extern const struct audio_output_plugin roar_output_plugin; + +int +roar_output_get_volume(struct roar *roar); + +bool +roar_output_set_volume(struct roar *roar, unsigned volume); + +#endif diff --git a/src/output/shout_plugin.c b/src/output/shout_output_plugin.c index 35efd9fc7..35356d659 100644 --- a/src/output/shout_plugin.c +++ b/src/output/shout_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "shout_output_plugin.h" #include "output_api.h" #include "encoder_plugin.h" #include "encoder_list.h" @@ -41,6 +42,8 @@ struct shout_buffer { }; struct shout_data { + struct audio_output base; + shout_t *shout_conn; shout_metadata_t *shout_meta; @@ -107,9 +110,8 @@ static void free_shout_data(struct shout_data *sd) } \ } -static void * -my_shout_init_driver(const struct audio_format *audio_format, - const struct config_param *param, +static struct audio_output * +my_shout_init_driver(const struct config_param *param, GError **error) { struct shout_data *sd; @@ -125,18 +127,26 @@ my_shout_init_driver(const struct audio_format *audio_format, const char *user; char *name; const char *value; - struct block_param *block_param; + const struct block_param *block_param; int public; - if (audio_format == NULL || - !audio_format_fully_defined(audio_format)) { + sd = new_shout_data(); + + if (!ao_base_init(&sd->base, &shout_output_plugin, param, error)) { + free_shout_data(sd); + return NULL; + } + + const struct audio_format *audio_format = + &sd->base.config_audio_format; + if (!audio_format_fully_defined(audio_format)) { g_set_error(error, shout_output_quark(), 0, "Need full audio format specification"); + ao_base_finish(&sd->base); + free_shout_data(sd); return NULL; } - sd = new_shout_data(); - if (shout_init_count == 0) shout_init(); @@ -277,6 +287,13 @@ my_shout_init_driver(const struct audio_format *audio_format, goto failure; } + value = config_get_block_string(param, "url", NULL); + if (value != NULL && shout_set_url(sd->shout_conn, value)) { + g_set_error(error, shout_output_quark(), 0, + "%s", shout_get_error(sd->shout_conn)); + goto failure; + } + { char temp[11]; memset(temp, 0, sizeof(temp)); @@ -299,9 +316,10 @@ my_shout_init_driver(const struct audio_format *audio_format, } } - return sd; + return &sd->base; failure: + ao_base_finish(&sd->base); free_shout_data(sd); return NULL; } @@ -371,12 +389,14 @@ static void close_shout_conn(struct shout_data * sd) } } -static void my_shout_finish_driver(void *data) +static void +my_shout_finish_driver(struct audio_output *ao) { - struct shout_data *sd = (struct shout_data *)data; + struct shout_data *sd = (struct shout_data *)ao; encoder_finish(sd->encoder); + ao_base_finish(&sd->base); free_shout_data(sd); shout_init_count--; @@ -385,17 +405,19 @@ static void my_shout_finish_driver(void *data) shout_shutdown(); } -static void my_shout_drop_buffered_audio(void *data) +static void +my_shout_drop_buffered_audio(struct audio_output *ao) { G_GNUC_UNUSED - struct shout_data *sd = (struct shout_data *)data; + struct shout_data *sd = (struct shout_data *)ao; /* needs to be implemented for shout */ } -static void my_shout_close_device(void *data) +static void +my_shout_close_device(struct audio_output *ao) { - struct shout_data *sd = (struct shout_data *)data; + struct shout_data *sd = (struct shout_data *)ao; close_shout_conn(sd); } @@ -422,10 +444,10 @@ shout_connect(struct shout_data *sd, GError **error) } static bool -my_shout_open_device(void *data, struct audio_format *audio_format, +my_shout_open_device(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct shout_data *sd = (struct shout_data *)data; + struct shout_data *sd = (struct shout_data *)ao; bool ret; ret = shout_connect(sd, error); @@ -445,9 +467,9 @@ my_shout_open_device(void *data, struct audio_format *audio_format, } static unsigned -my_shout_delay(void *data) +my_shout_delay(struct audio_output *ao) { - struct shout_data *sd = (struct shout_data *)data; + struct shout_data *sd = (struct shout_data *)ao; int delay = shout_delay(sd->shout_conn); if (delay < 0) @@ -457,9 +479,10 @@ my_shout_delay(void *data) } static size_t -my_shout_play(void *data, const void *chunk, size_t size, GError **error) +my_shout_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) { - struct shout_data *sd = (struct shout_data *)data; + struct shout_data *sd = (struct shout_data *)ao; return encoder_write(sd->encoder, chunk, size, error) && write_page(sd, error) @@ -468,11 +491,11 @@ my_shout_play(void *data, const void *chunk, size_t size, GError **error) } static bool -my_shout_pause(void *data) +my_shout_pause(struct audio_output *ao) { static const char silence[1020]; - return my_shout_play(data, silence, sizeof(silence), NULL); + return my_shout_play(ao, silence, sizeof(silence), NULL); } static void @@ -501,10 +524,10 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size) snprintf(dest, size, "%s - %s", artist, title); } -static void my_shout_set_tag(void *data, +static void my_shout_set_tag(struct audio_output *ao, const struct tag *tag) { - struct shout_data *sd = (struct shout_data *)data; + struct shout_data *sd = (struct shout_data *)ao; bool ret; GError *error = NULL; @@ -543,7 +566,7 @@ static void my_shout_set_tag(void *data, write_page(sd, NULL); } -const struct audio_output_plugin shoutPlugin = { +const struct audio_output_plugin shout_output_plugin = { .name = "shout", .init = my_shout_init_driver, .finish = my_shout_finish_driver, diff --git a/src/output/shout_output_plugin.h b/src/output/shout_output_plugin.h new file mode 100644 index 000000000..9a7378803 --- /dev/null +++ b/src/output/shout_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_SHOUT_OUTPUT_PLUGIN_H +#define MPD_SHOUT_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin shout_output_plugin; + +#endif diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c index 6f6f507b3..ce726009a 100644 --- a/src/output/solaris_output_plugin.c +++ b/src/output/solaris_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "solaris_output_plugin.h" #include "output_api.h" #include "fd_util.h" @@ -53,6 +54,8 @@ struct audio_info { #define G_LOG_DOMAIN "solaris_output" struct solaris_output { + struct audio_output base; + /* configuration */ const char *device; @@ -77,31 +80,35 @@ solaris_output_test_default_device(void) access("/dev/audio", W_OK) == 0; } -static void * -solaris_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, - G_GNUC_UNUSED GError **error) +static struct audio_output * +solaris_output_init(const struct config_param *param, GError **error_r) { struct solaris_output *so = g_new(struct solaris_output, 1); + if (!ao_base_init(&so->base, &solaris_output_plugin, param, error_r)) { + g_free(so); + return NULL; + } + so->device = config_get_block_string(param, "device", "/dev/audio"); - return so; + return &so->base; } static void -solaris_output_finish(void *data) +solaris_output_finish(struct audio_output *ao) { - struct solaris_output *so = data; + struct solaris_output *so = (struct solaris_output *)ao; + ao_base_finish(&so->base); g_free(so); } static bool -solaris_output_open(void *data, struct audio_format *audio_format, +solaris_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error) { - struct solaris_output *so = data; + struct solaris_output *so = (struct solaris_output *)ao; struct audio_info info; int ret, flags; @@ -152,17 +159,18 @@ solaris_output_open(void *data, struct audio_format *audio_format, } static void -solaris_output_close(void *data) +solaris_output_close(struct audio_output *ao) { - struct solaris_output *so = data; + struct solaris_output *so = (struct solaris_output *)ao; close(so->fd); } static size_t -solaris_output_play(void *data, const void *chunk, size_t size, GError **error) +solaris_output_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) { - struct solaris_output *so = data; + struct solaris_output *so = (struct solaris_output *)ao; ssize_t nbytes; nbytes = write(so->fd, chunk, size); @@ -176,9 +184,9 @@ solaris_output_play(void *data, const void *chunk, size_t size, GError **error) } static void -solaris_output_cancel(void *data) +solaris_output_cancel(struct audio_output *ao) { - struct solaris_output *so = data; + struct solaris_output *so = (struct solaris_output *)ao; ioctl(so->fd, I_FLUSH); } diff --git a/src/output/solaris_output_plugin.h b/src/output/solaris_output_plugin.h new file mode 100644 index 000000000..600aea8c2 --- /dev/null +++ b/src/output/solaris_output_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2011 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_SOLARIS_OUTPUT_PLUGIN_H +#define MPD_SOLARIS_OUTPUT_PLUGIN_H + +extern const struct audio_output_plugin solaris_output_plugin; + +#endif diff --git a/src/output/winmm_output_plugin.c b/src/output/winmm_output_plugin.c index b9687874d..47d98498f 100644 --- a/src/output/winmm_output_plugin.c +++ b/src/output/winmm_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -18,6 +18,7 @@ */ #include "config.h" +#include "winmm_output_plugin.h" #include "output_api.h" #include "pcm_buffer.h" #include "mixer_list.h" @@ -37,6 +38,8 @@ struct winmm_buffer { }; struct winmm_output { + struct audio_output base; + UINT device_id; HWAVEOUT handle; @@ -71,59 +74,80 @@ winmm_output_test_default_device(void) return waveOutGetNumDevs() > 0; } -static UINT -get_device_id(const char *device_name) +static bool +get_device_id(const char *device_name, UINT *device_id, GError **error_r) { /* if device is not specified use wave mapper */ - if (device_name == NULL) - return WAVE_MAPPER; + if (device_name == NULL) { + *device_id = WAVE_MAPPER; + return true; + } + + UINT numdevs = waveOutGetNumDevs(); /* check for device id */ char *endptr; UINT id = strtoul(device_name, &endptr, 0); - if (endptr > device_name && *endptr == 0) - return id; + if (endptr > device_name && *endptr == 0) { + if (id >= numdevs) + goto fail; + *device_id = id; + return true; + } /* check for device name */ - for (UINT i = 0; i < waveOutGetNumDevs(); i++) { + for (UINT i = 0; i < numdevs; i++) { WAVEOUTCAPS caps; MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps)); if (result != MMSYSERR_NOERROR) continue; /* szPname is only 32 chars long, so it is often truncated. Use partial match to work around this. */ - if (strstr(device_name, caps.szPname) == device_name) - return i; + if (strstr(device_name, caps.szPname) == device_name) { + *device_id = i; + return true; + } } - /* fallback to wave mapper */ - return WAVE_MAPPER; +fail: + g_set_error(error_r, winmm_output_quark(), 0, + "device \"%s\" is not found", device_name); + return false; } -static void * -winmm_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, - G_GNUC_UNUSED const struct config_param *param, - G_GNUC_UNUSED GError **error) +static struct audio_output * +winmm_output_init(const struct config_param *param, GError **error_r) { struct winmm_output *wo = g_new(struct winmm_output, 1); + if (!ao_base_init(&wo->base, &winmm_output_plugin, param, error_r)) { + g_free(wo); + return NULL; + } + const char *device = config_get_block_string(param, "device", NULL); - wo->device_id = get_device_id(device); - return wo; + if (!get_device_id(device, &wo->device_id, error_r)) { + ao_base_finish(&wo->base); + g_free(wo); + return NULL; + } + + return &wo->base; } static void -winmm_output_finish(void *data) +winmm_output_finish(struct audio_output *ao) { - struct winmm_output *wo = data; + struct winmm_output *wo = (struct winmm_output *)ao; + ao_base_finish(&wo->base); g_free(wo); } static bool -winmm_output_open(void *data, struct audio_format *audio_format, +winmm_output_open(struct audio_output *ao, struct audio_format *audio_format, GError **error_r) { - struct winmm_output *wo = data; + struct winmm_output *wo = (struct winmm_output *)ao; wo->event = CreateEvent(NULL, false, false, NULL); if (wo->event == NULL) { @@ -179,9 +203,9 @@ winmm_output_open(void *data, struct audio_format *audio_format, } static void -winmm_output_close(void *data) +winmm_output_close(struct audio_output *ao) { - struct winmm_output *wo = data; + struct winmm_output *wo = (struct winmm_output *)ao; for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) pcm_buffer_deinit(&wo->buffers[i].buffer); @@ -252,9 +276,9 @@ winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, } static size_t -winmm_output_play(void *data, const void *chunk, size_t size, GError **error_r) +winmm_output_play(struct audio_output *ao, const void *chunk, size_t size, GError **error_r) { - struct winmm_output *wo = data; + struct winmm_output *wo = (struct winmm_output *)ao; /* get the next buffer from the ring and prepare it */ struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer]; @@ -307,18 +331,18 @@ winmm_stop(struct winmm_output *wo) } static void -winmm_output_drain(void *data) +winmm_output_drain(struct audio_output *ao) { - struct winmm_output *wo = data; + struct winmm_output *wo = (struct winmm_output *)ao; if (!winmm_drain_all_buffers(wo, NULL)) winmm_stop(wo); } static void -winmm_output_cancel(void *data) +winmm_output_cancel(struct audio_output *ao) { - struct winmm_output *wo = data; + struct winmm_output *wo = (struct winmm_output *)ao; winmm_stop(wo); } diff --git a/src/output/winmm_output_plugin.h b/src/output/winmm_output_plugin.h index 39507275a..0605530e1 100644 --- a/src/output/winmm_output_plugin.h +++ b/src/output/winmm_output_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,10 +20,18 @@ #ifndef MPD_WINMM_OUTPUT_PLUGIN_H #define MPD_WINMM_OUTPUT_PLUGIN_H +#include "check.h" + +#ifdef ENABLE_WINMM_OUTPUT + #include <windows.h> struct winmm_output; +extern const struct audio_output_plugin winmm_output_plugin; + HWAVEOUT winmm_output_get_handle(struct winmm_output*); #endif + +#endif |