diff options
Diffstat (limited to 'src/output')
-rw-r--r-- | src/output/alsa_plugin.c | 116 | ||||
-rw-r--r-- | src/output/fifo_output_plugin.c (renamed from src/output/fifo_plugin.c) | 11 | ||||
-rw-r--r-- | src/output/httpd_client.c | 5 | ||||
-rw-r--r-- | src/output/httpd_internal.h | 13 | ||||
-rw-r--r-- | src/output/httpd_output_plugin.c | 117 | ||||
-rw-r--r-- | src/output/jack_output_plugin.c | 697 | ||||
-rw-r--r-- | src/output/jack_plugin.c | 450 | ||||
-rw-r--r-- | src/output/mvp_plugin.c | 8 | ||||
-rw-r--r-- | src/output/openal_plugin.c | 273 | ||||
-rw-r--r-- | src/output/oss_plugin.c | 13 | ||||
-rw-r--r-- | src/output/pulse_output_plugin.c | 823 | ||||
-rw-r--r-- | src/output/pulse_output_plugin.h | 72 | ||||
-rw-r--r-- | src/output/pulse_plugin.c | 179 | ||||
-rw-r--r-- | src/output/recorder_output_plugin.c | 216 | ||||
-rw-r--r-- | src/output/shout_plugin.c | 13 | ||||
-rw-r--r-- | src/output/solaris_output_plugin.c | 3 |
16 files changed, 2322 insertions, 687 deletions
diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c index 818c83ca2..64a8127ba 100644 --- a/src/output/alsa_plugin.c +++ b/src/output/alsa_plugin.c @@ -69,6 +69,16 @@ struct alsa_data { /** the size of one audio frame */ size_t frame_size; + + /** + * The size of one period, in number of frames. + */ + snd_pcm_uframes_t period_frames; + + /** + * The number of frames written in the current period. + */ + snd_pcm_uframes_t period_position; }; /** @@ -183,6 +193,19 @@ get_bitformat(const struct audio_format *af) return SND_PCM_FORMAT_UNKNOWN; } +static snd_pcm_format_t +byteswap_bitformat(snd_pcm_format_t fmt) +{ + switch(fmt) { + case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE; + case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE; + case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE; + case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE; + case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE; + case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE; + default: return SND_PCM_FORMAT_UNKNOWN; + } +} /** * Set up the snd_pcm_t object which was opened by the caller. Set up * the configured settings and the audio format. @@ -208,7 +231,6 @@ alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, configure_hw: /* configure HW params */ snd_pcm_hw_params_alloca(&hwparams); - cmd = "snd_pcm_hw_params_any"; err = snd_pcm_hw_params_any(ad->pcm, hwparams); if (err < 0) @@ -236,13 +258,38 @@ configure_hw: } err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, bitformat); + if (err == -EINVAL && + byteswap_bitformat(bitformat) != SND_PCM_FORMAT_UNKNOWN) { + err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, + byteswap_bitformat(bitformat)); + if (err == 0) { + g_debug("ALSA device \"%s\": converting %u bit to reverse-endian\n", + alsa_device(ad), audio_format->bits); + audio_format->reverse_endian = 1; + } + } if (err == -EINVAL && (audio_format->bits == 24 || audio_format->bits == 16)) { /* fall back to 32 bit, let pcm_convert.c do the conversion */ err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, SND_PCM_FORMAT_S32); - if (err == 0) + if (err == 0) { + g_debug("ALSA device \"%s\": converting %u bit to 32 bit\n", + alsa_device(ad), audio_format->bits); + audio_format->bits = 32; + } + } + if (err == -EINVAL && (audio_format->bits == 24 || + audio_format->bits == 16)) { + /* fall back to 32 bit, let pcm_convert.c do the conversion */ + err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, + byteswap_bitformat(SND_PCM_FORMAT_S32)); + if (err == 0) { + g_debug("ALSA device \"%s\": converting %u bit to 32 bit backward-endian\n", + alsa_device(ad), audio_format->bits); audio_format->bits = 32; + audio_format->reverse_endian = 1; + } } if (err == -EINVAL && audio_format->bits != 16) { @@ -255,6 +302,17 @@ configure_hw: audio_format->bits = 16; } } + if (err == -EINVAL && audio_format->bits != 16) { + /* fall back to 16 bit, let pcm_convert.c do the conversion */ + err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, + byteswap_bitformat(SND_PCM_FORMAT_S16)); + if (err == 0) { + g_debug("ALSA device \"%s\": converting %u bit to 16 bit backward-endian\n", + alsa_device(ad), audio_format->bits); + audio_format->bits = 16; + audio_format->reverse_endian = 1; + } + } if (err < 0) { g_set_error(error, alsa_output_quark(), err, @@ -365,6 +423,9 @@ configure_hw: g_debug("buffer_size=%u period_size=%u", (unsigned)alsa_buffer_size, (unsigned)alsa_period_size); + ad->period_frames = alsa_period_size; + ad->period_position = 0; + return true; error: @@ -431,6 +492,7 @@ alsa_recover(struct alsa_data *ad, int err) /* fall-through to snd_pcm_prepare: */ case SND_PCM_STATE_SETUP: case SND_PCM_STATE_XRUN: + ad->period_position = 0; err = snd_pcm_prepare(ad->pcm); break; case SND_PCM_STATE_DISCONNECTED: @@ -448,11 +510,47 @@ alsa_recover(struct alsa_data *ad, int err) } static void +alsa_drain(void *data) +{ + struct alsa_data *ad = data; + + if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) + return; + + if (ad->period_position > 0) { + /* generate some silence to finish the partial + period */ + snd_pcm_uframes_t nframes = + ad->period_frames - ad->period_position; + size_t nbytes = nframes * ad->frame_size; + void *buffer = g_malloc(nbytes); + snd_pcm_hw_params_t *params; + snd_pcm_format_t format; + unsigned channels; + + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_current(ad->pcm, params); + snd_pcm_hw_params_get_format(params, &format); + snd_pcm_hw_params_get_channels(params, &channels); + + snd_pcm_format_set_silence(format, buffer, nframes * channels); + ad->writei(ad->pcm, buffer, nframes); + g_free(buffer); + } + + snd_pcm_drain(ad->pcm); + + ad->period_position = 0; +} + +static void alsa_cancel(void *data) { struct alsa_data *ad = data; - alsa_recover(ad, snd_pcm_drop(ad->pcm)); + ad->period_position = 0; + + snd_pcm_drop(ad->pcm); } static void @@ -460,9 +558,6 @@ alsa_close(void *data) { struct alsa_data *ad = data; - if (snd_pcm_state(ad->pcm) == SND_PCM_STATE_RUNNING) - snd_pcm_drain(ad->pcm); - snd_pcm_close(ad->pcm); } @@ -475,8 +570,11 @@ alsa_play(void *data, const void *chunk, size_t size, GError **error) while (true) { snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); - if (ret > 0) + if (ret > 0) { + ad->period_position = (ad->period_position + ret) + % ad->period_frames; return ret * ad->frame_size; + } if (ret < 0 && ret != -EAGAIN && ret != -EINTR && alsa_recover(ad, ret) < 0) { @@ -494,7 +592,9 @@ const struct audio_output_plugin alsaPlugin = { .finish = alsa_finish, .open = alsa_open, .play = alsa_play, + .drain = alsa_drain, .cancel = alsa_cancel, .close = alsa_close, - .mixer_plugin = &alsa_mixer, + + .mixer_plugin = &alsa_mixer_plugin, }; diff --git a/src/output/fifo_plugin.c b/src/output/fifo_output_plugin.c index 76bbe8cfa..b5e6f5314 100644 --- a/src/output/fifo_plugin.c +++ b/src/output/fifo_output_plugin.c @@ -17,9 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" -#include "../utils.h" -#include "../timer.h" +#include "output_api.h" +#include "utils.h" +#include "timer.h" +#include "fd_util.h" #include <glib.h> @@ -152,7 +153,7 @@ fifo_open(struct fifo_data *fd, GError **error) if (!fifo_check(fd, error)) return false; - fd->input = open(fd->path, O_RDONLY|O_NONBLOCK); + fd->input = open_cloexec(fd->path, O_RDONLY|O_NONBLOCK, 0); if (fd->input < 0) { g_set_error(error, fifo_output_quark(), errno, "Could not open FIFO \"%s\" for reading: %s", @@ -161,7 +162,7 @@ fifo_open(struct fifo_data *fd, GError **error) return false; } - fd->output = open(fd->path, O_WRONLY|O_NONBLOCK); + fd->output = open_cloexec(fd->path, O_WRONLY|O_NONBLOCK, 0); if (fd->output < 0) { g_set_error(error, fifo_output_quark(), errno, "Could not open FIFO \"%s\" for writing: %s", diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c index 52a398e3b..8157ebb44 100644 --- a/src/output/httpd_client.c +++ b/src/output/httpd_client.c @@ -482,11 +482,6 @@ httpd_client_queue_size(const struct httpd_client *client) return size; } -/* g_queue_clear() was introduced in GLib 2.14 */ -#if !GLIB_CHECK_VERSION(2,14,0) -#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0) -#endif - void httpd_client_cancel(struct httpd_client *client) { diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h index 2257e27a2..22155b7ba 100644 --- a/src/output/httpd_internal.h +++ b/src/output/httpd_internal.h @@ -30,11 +30,18 @@ #include <glib.h> #include <sys/socket.h> +#include <stdbool.h> struct httpd_client; struct httpd_output { /** + * True if the audio output is open and accepts client + * connections. + */ + bool open; + + /** * The configured encoder plugin. */ struct encoder *encoder; @@ -97,6 +104,12 @@ struct httpd_output { * function. */ char buffer[32768]; + + /** + * The maximum and current number of clients connected + * at the same time. + */ + guint clients_max, clients_cnt; }; /** diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c index 9fdf46456..53bcc2deb 100644 --- a/src/output/httpd_output_plugin.c +++ b/src/output/httpd_output_plugin.c @@ -25,6 +25,7 @@ #include "socket_util.h" #include "page.h" #include "icy_server.h" +#include "fd_util.h" #include <assert.h> @@ -46,6 +47,52 @@ httpd_output_quark(void) return g_quark_from_static_string("httpd_output"); } +static gboolean +httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source, + G_GNUC_UNUSED GIOCondition condition, + gpointer data); + +static bool +httpd_output_bind(struct httpd_output *httpd, GError **error_r) +{ + GIOChannel *channel; + + httpd->open = false; + + /* create and set up listener socket */ + + httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0, + (struct sockaddr *)&httpd->address, + httpd->address_size, + 16, error_r); + if (httpd->fd < 0) + return false; + + g_mutex_lock(httpd->mutex); + + channel = g_io_channel_unix_new(httpd->fd); + httpd->source_id = g_io_add_watch(channel, G_IO_IN, + httpd_listen_in_event, httpd); + g_io_channel_unref(channel); + + g_mutex_unlock(httpd->mutex); + + return true; +} + +static void +httpd_output_unbind(struct httpd_output *httpd) +{ + assert(!httpd->open); + + g_mutex_lock(httpd->mutex); + + g_source_remove(httpd->source_id); + close(httpd->fd); + + g_mutex_unlock(httpd->mutex); +} + static void * httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, const struct config_param *param, @@ -76,6 +123,8 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, else httpd->content_type = "application/octet-stream"; + httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0); + /* initialize listen address */ sin = (struct sockaddr_in *)&httpd->address; @@ -124,6 +173,7 @@ httpd_client_add(struct httpd_output *httpd, int fd) httpd->encoder->plugin->tag == NULL); httpd->clients = g_list_prepend(httpd->clients, client); + httpd->clients_cnt++; /* pass metadata to client */ if (httpd->metadata) @@ -138,18 +188,26 @@ httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source, struct httpd_output *httpd = data; int fd; struct sockaddr_storage sa; - socklen_t sa_length = sizeof(sa); + size_t sa_length = sizeof(sa); g_mutex_lock(httpd->mutex); /* the listener socket has become readable - a client has connected */ - fd = accept(httpd->fd, (struct sockaddr*)&sa, &sa_length); - if (fd >= 0) - httpd_client_add(httpd, fd); - else if (fd < 0 && errno != EINTR) + fd = accept_cloexec_nonblock(httpd->fd, (struct sockaddr*)&sa, + &sa_length); + if (fd >= 0) { + /* can we allow additional client */ + if (httpd->open && + (httpd->clients_max == 0 || + httpd->clients_cnt < httpd->clients_max)) + httpd_client_add(httpd, fd); + else + close(fd); + } else if (fd < 0 && errno != EINTR) { g_warning("accept() failed: %s", g_strerror(errno)); + } g_mutex_unlock(httpd->mutex); @@ -199,31 +257,30 @@ httpd_output_encoder_open(struct httpd_output *httpd, } static bool +httpd_output_enable(void *data, GError **error_r) +{ + struct httpd_output *httpd = data; + + return httpd_output_bind(httpd, error_r); +} + +static void +httpd_output_disable(void *data) +{ + struct httpd_output *httpd = data; + + httpd_output_unbind(httpd); +} + +static bool httpd_output_open(void *data, struct audio_format *audio_format, GError **error) { struct httpd_output *httpd = data; bool success; - GIOChannel *channel; g_mutex_lock(httpd->mutex); - /* create and set up listener socket */ - - httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0, - (struct sockaddr *)&httpd->address, - httpd->address_size, - 16, error); - if (httpd->fd < 0) { - g_mutex_unlock(httpd->mutex); - return false; - } - - channel = g_io_channel_unix_new(httpd->fd); - httpd->source_id = g_io_add_watch(channel, G_IO_IN, - httpd_listen_in_event, httpd); - g_io_channel_unref(channel); - /* open the encoder */ success = httpd_output_encoder_open(httpd, audio_format, error); @@ -237,8 +294,11 @@ httpd_output_open(void *data, struct audio_format *audio_format, /* initialize other attributes */ httpd->clients = NULL; + httpd->clients_cnt = 0; httpd->timer = timer_new(audio_format); + httpd->open = true; + g_mutex_unlock(httpd->mutex); return true; } @@ -257,6 +317,8 @@ static void httpd_output_close(void *data) g_mutex_lock(httpd->mutex); + httpd->open = false; + timer_free(httpd->timer); g_list_foreach(httpd->clients, httpd_client_delete, NULL); @@ -267,9 +329,6 @@ static void httpd_output_close(void *data) encoder_close(httpd->encoder); - g_source_remove(httpd->source_id); - close(httpd->fd); - g_mutex_unlock(httpd->mutex); } @@ -281,6 +340,7 @@ httpd_output_remove_client(struct httpd_output *httpd, assert(client != NULL); httpd->clients = g_list_remove(httpd->clients, client); + httpd->clients_cnt--; } void @@ -433,9 +493,8 @@ httpd_output_tag(void *data, const struct tag *tag) page_unref (httpd->metadata); httpd->metadata = - icy_server_metadata_page(tag, TAG_ITEM_ALBUM, - TAG_ITEM_ARTIST, - TAG_ITEM_TITLE, + icy_server_metadata_page(tag, TAG_ALBUM, + TAG_ARTIST, TAG_TITLE, TAG_NUM_OF_ITEM_TYPES); if (httpd->metadata != NULL) { g_mutex_lock(httpd->mutex); @@ -468,6 +527,8 @@ const struct audio_output_plugin httpd_output_plugin = { .name = "httpd", .init = httpd_output_init, .finish = httpd_output_finish, + .enable = httpd_output_enable, + .disable = httpd_output_disable, .open = httpd_output_open, .close = httpd_output_close, .send_tag = httpd_output_tag, diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c new file mode 100644 index 000000000..b099cdb9e --- /dev/null +++ b/src/output/jack_output_plugin.c @@ -0,0 +1,697 @@ +/* + * Copyright (C) 2003-2009 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 "output_api.h" +#include "config.h" + +#include <assert.h> + +#include <glib.h> +#include <jack/jack.h> +#include <jack/types.h> +#include <jack/ringbuffer.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "jack" + +enum { + MAX_PORTS = 16, +}; + +static const size_t sample_size = sizeof(jack_default_audio_sample_t); + +struct jack_data { + /** + * libjack options passed to jack_client_open(). + */ + jack_options_t options; + + const char *name; + + const char *server_name; + + /* configuration */ + + char *source_ports[MAX_PORTS]; + unsigned num_source_ports; + + char *destination_ports[MAX_PORTS]; + unsigned num_destination_ports; + + size_t ringbuffer_size; + + /* the current audio format */ + struct audio_format audio_format; + + /* jack library stuff */ + jack_port_t *ports[MAX_PORTS]; + jack_client_t *client; + jack_ringbuffer_t *ringbuffer[MAX_PORTS]; + + bool shutdown; + + /** + * While this flag is set, the "process" callback generates + * silence. + */ + bool pause; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +jack_output_quark(void) +{ + return g_quark_from_static_string("jack_output"); +} + +static int +mpd_jack_process(jack_nframes_t nframes, void *arg) +{ + struct jack_data *jd = (struct jack_data *) arg; + jack_default_audio_sample_t *out; + size_t available; + + if (nframes <= 0) + return 0; + + if (jd->pause) { + /* generate silence while MPD is paused */ + + for (unsigned i = 0; i < jd->audio_format.channels; ++i) { + out = jack_port_get_buffer(jd->ports[i], nframes); + + for (jack_nframes_t f = 0; f < nframes; ++f) + out[f] = 0.0; + } + + return 0; + } + + for (unsigned i = 0; i < jd->audio_format.channels; ++i) { + available = jack_ringbuffer_read_space(jd->ringbuffer[i]); + assert(available % sample_size == 0); + available /= sample_size; + if (available > nframes) + available = nframes; + + out = jack_port_get_buffer(jd->ports[i], nframes); + jack_ringbuffer_read(jd->ringbuffer[i], + (char *)out, available * sample_size); + + while (available < nframes) + /* ringbuffer underrun, fill with silence */ + out[available++] = 0.0; + } + + /* generate silence for the unused source ports */ + + for (unsigned i = jd->audio_format.channels; + i < jd->num_source_ports; ++i) { + out = jack_port_get_buffer(jd->ports[i], nframes); + + for (jack_nframes_t f = 0; f < nframes; ++f) + out[f] = 0.0; + } + + return 0; +} + +static void +mpd_jack_shutdown(void *arg) +{ + struct jack_data *jd = (struct jack_data *) arg; + jd->shutdown = true; +} + +static void +set_audioformat(struct jack_data *jd, struct audio_format *audio_format) +{ + audio_format->sample_rate = jack_get_sample_rate(jd->client); + + if (jd->num_source_ports == 1) + audio_format->channels = 1; + else if (audio_format->channels > jd->num_source_ports) + audio_format->channels = 2; + + if (audio_format->bits != 16 && audio_format->bits != 24) + audio_format->bits = 24; +} + +static void +mpd_jack_error(const char *msg) +{ + g_warning("%s", msg); +} + +#ifdef HAVE_JACK_SET_INFO_FUNCTION +static void +mpd_jack_info(const char *msg) +{ + g_message("%s", msg); +} +#endif + +/** + * Disconnect the JACK client. + */ +static void +mpd_jack_disconnect(struct jack_data *jd) +{ + assert(jd != NULL); + assert(jd->client != NULL); + + jack_deactivate(jd->client); + jack_client_close(jd->client); + jd->client = NULL; +} + +/** + * Connect the JACK client and performs some basic setup + * (e.g. register callbacks). + */ +static bool +mpd_jack_connect(struct jack_data *jd, GError **error_r) +{ + jack_status_t status; + + assert(jd != NULL); + + jd->shutdown = false; + + jd->client = jack_client_open(jd->name, jd->options, &status, + jd->server_name); + if (jd->client == NULL) { + g_set_error(error_r, jack_output_quark(), 0, + "Failed to connect to JACK server, status=%d", + status); + return false; + } + + jack_set_process_callback(jd->client, mpd_jack_process, jd); + jack_on_shutdown(jd->client, mpd_jack_shutdown, jd); + + for (unsigned i = 0; i < jd->num_source_ports; ++i) { + jd->ports[i] = jack_port_register(jd->client, + jd->source_ports[i], + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if (jd->ports[i] == NULL) { + g_set_error(error_r, jack_output_quark(), 0, + "Cannot register output port \"%s\"", + jd->source_ports[i]); + mpd_jack_disconnect(jd); + return false; + } + } + + return true; +} + +static bool +mpd_jack_test_default_device(void) +{ + return true; +} + +static unsigned +parse_port_list(int line, const char *source, char **dest, GError **error_r) +{ + char **list = g_strsplit(source, ",", 0); + unsigned n = 0; + + for (n = 0; list[n] != NULL; ++n) { + if (n >= MAX_PORTS) { + g_set_error(error_r, jack_output_quark(), 0, + "too many port names in line %d", + line); + return 0; + } + + dest[n] = list[n]; + } + + g_free(list); + + if (n == 0) { + g_set_error(error_r, jack_output_quark(), 0, + "at least one port name expected in line %d", + line); + return 0; + } + + return n; +} + +static void * +mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, GError **error_r) +{ + struct jack_data *jd; + const char *value; + + jd = g_new(struct jack_data, 1); + jd->options = JackNullOption; + + jd->name = config_get_block_string(param, "client_name", NULL); + if (jd->name != NULL) + jd->options |= JackUseExactName; + else + /* if there's a no configured client name, we don't + care about the JackUseExactName option */ + jd->name = "Music Player Daemon"; + + jd->server_name = config_get_block_string(param, "server_name", NULL); + if (jd->server_name != NULL) + jd->options |= JackServerName; + + if (!config_get_block_bool(param, "autostart", false)) + jd->options |= JackNoStartServer; + + /* configure the source ports */ + + value = config_get_block_string(param, "source_ports", "left,right"); + jd->num_source_ports = parse_port_list(param->line, value, + jd->source_ports, error_r); + if (jd->num_source_ports == 0) + return NULL; + + /* configure the destination ports */ + + value = config_get_block_string(param, "destination_ports", NULL); + if (value == NULL) { + /* compatibility with MPD < 0.16 */ + value = config_get_block_string(param, "ports", NULL); + if (value != NULL) + g_warning("deprecated option 'ports' in line %d", + param->line); + } + + if (value != NULL) { + jd->num_destination_ports = + parse_port_list(param->line, value, + jd->destination_ports, error_r); + if (jd->num_destination_ports == 0) + return NULL; + } else { + jd->num_destination_ports = 0; + } + + if (jd->num_destination_ports > 0 && + jd->num_destination_ports != jd->num_source_ports) + g_warning("number of source ports (%u) mismatches the " + "number of destination ports (%u) in line %d", + jd->num_source_ports, jd->num_destination_ports, + param->line); + + jd->ringbuffer_size = + config_get_block_unsigned(param, "ringbuffer_size", 32768); + + jack_set_error_function(mpd_jack_error); + +#ifdef HAVE_JACK_SET_INFO_FUNCTION + jack_set_info_function(mpd_jack_info); +#endif + + return jd; +} + +static void +mpd_jack_finish(void *data) +{ + struct jack_data *jd = data; + + for (unsigned i = 0; i < jd->num_source_ports; ++i) + g_free(jd->source_ports[i]); + + for (unsigned i = 0; i < jd->num_destination_ports; ++i) + g_free(jd->destination_ports[i]); + + g_free(jd); +} + +static bool +mpd_jack_enable(void *data, GError **error_r) +{ + struct jack_data *jd = (struct jack_data *)data; + + for (unsigned i = 0; i < jd->num_source_ports; ++i) + jd->ringbuffer[i] = NULL; + + return mpd_jack_connect(jd, error_r); +} + +static void +mpd_jack_disable(void *data) +{ + struct jack_data *jd = (struct jack_data *)data; + + if (jd->client != NULL) + mpd_jack_disconnect(jd); + + for (unsigned i = 0; i < jd->num_source_ports; ++i) { + if (jd->ringbuffer[i] != NULL) { + jack_ringbuffer_free(jd->ringbuffer[i]); + jd->ringbuffer[i] = NULL; + } + } +} + +/** + * Stops the playback on the JACK connection. + */ +static void +mpd_jack_stop(struct jack_data *jd) +{ + assert(jd != NULL); + + if (jd->client == NULL) + return; + + if (jd->shutdown) + /* the connection has failed; close it */ + mpd_jack_disconnect(jd); + else + /* the connection is alive: just stop playback */ + jack_deactivate(jd->client); +} + +static bool +mpd_jack_start(struct jack_data *jd, GError **error_r) +{ + const char *destination_ports[MAX_PORTS], **jports; + const char *duplicate_port = NULL; + unsigned num_destination_ports; + + assert(jd->client != NULL); + assert(jd->audio_format.channels <= jd->num_source_ports); + + /* allocate the ring buffers on the first open(); these + persist until MPD exits. It's too unsafe to delete them + because we can never know when mpd_jack_process() gets + called */ + for (unsigned i = 0; i < jd->num_source_ports; ++i) { + if (jd->ringbuffer[i] == NULL) + jd->ringbuffer[i] = + jack_ringbuffer_create(jd->ringbuffer_size); + + /* clear the ring buffer to be sure that data from + previous playbacks are gone */ + jack_ringbuffer_reset(jd->ringbuffer[i]); + } + + if ( jack_activate(jd->client) ) { + g_set_error(error_r, jack_output_quark(), 0, + "cannot activate client"); + mpd_jack_stop(jd); + return false; + } + + if (jd->num_destination_ports == 0) { + /* no output ports were configured - ask libjack for + defaults */ + jports = jack_get_ports(jd->client, NULL, NULL, + JackPortIsPhysical | JackPortIsInput); + if (jports == NULL) { + g_set_error(error_r, jack_output_quark(), 0, + "no ports found"); + mpd_jack_stop(jd); + return false; + } + + assert(*jports != NULL); + + for (num_destination_ports = 0; + num_destination_ports < MAX_PORTS && + jports[num_destination_ports] != NULL; + ++num_destination_ports) { + g_debug("destination_port[%u] = '%s'\n", + num_destination_ports, + jports[num_destination_ports]); + destination_ports[num_destination_ports] = + jports[num_destination_ports]; + } + } else { + /* use the configured output ports */ + + num_destination_ports = jd->num_destination_ports; + memcpy(destination_ports, jd->destination_ports, + num_destination_ports * sizeof(*destination_ports)); + + jports = NULL; + } + + assert(num_destination_ports > 0); + + if (jd->audio_format.channels >= 2 && num_destination_ports == 1) { + /* mix stereo signal on one speaker */ + + while (num_destination_ports < jd->audio_format.channels) + destination_ports[num_destination_ports++] = + destination_ports[0]; + } else if (num_destination_ports > jd->audio_format.channels) { + if (jd->audio_format.channels == 1 && num_destination_ports > 2) { + /* mono input file: connect the one source + channel to the both destination channels */ + duplicate_port = destination_ports[1]; + num_destination_ports = 1; + } else + /* connect only as many ports as we need */ + num_destination_ports = jd->audio_format.channels; + } + + assert(num_destination_ports <= jd->num_source_ports); + + for (unsigned i = 0; i < num_destination_ports; ++i) { + int ret; + + ret = jack_connect(jd->client, jack_port_name(jd->ports[i]), + destination_ports[i]); + if (ret != 0) { + g_set_error(error_r, jack_output_quark(), 0, + "Not a valid JACK port: %s", + destination_ports[i]); + + if (jports != NULL) + free(jports); + + mpd_jack_stop(jd); + return false; + } + } + + if (duplicate_port != NULL) { + /* mono input file: connect the one source channel to + the both destination channels */ + int ret; + + ret = jack_connect(jd->client, jack_port_name(jd->ports[0]), + duplicate_port); + if (ret != 0) { + g_set_error(error_r, jack_output_quark(), 0, + "Not a valid JACK port: %s", + duplicate_port); + + if (jports != NULL) + free(jports); + + mpd_jack_stop(jd); + return false; + } + } + + if (jports != NULL) + free(jports); + + return true; +} + +static bool +mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r) +{ + struct jack_data *jd = data; + + assert(jd != NULL); + + jd->pause = false; + + if (jd->client == NULL && !mpd_jack_connect(jd, error_r)) + return false; + + set_audioformat(jd, audio_format); + jd->audio_format = *audio_format; + + if (!mpd_jack_start(jd, error_r)) + return false; + + return true; +} + +static void +mpd_jack_close(G_GNUC_UNUSED void *data) +{ + struct jack_data *jd = data; + + mpd_jack_stop(jd); +} + +static inline jack_default_audio_sample_t +sample_16_to_jack(int16_t sample) +{ + return sample / (jack_default_audio_sample_t)(1 << (16 - 1)); +} + +static void +mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src, + unsigned num_samples) +{ + jack_default_audio_sample_t sample; + unsigned i; + + while (num_samples-- > 0) { + for (i = 0; i < jd->audio_format.channels; ++i) { + sample = sample_16_to_jack(*src++); + jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample, + sizeof(sample)); + } + } +} + +static inline jack_default_audio_sample_t +sample_24_to_jack(int32_t sample) +{ + return sample / (jack_default_audio_sample_t)(1 << (24 - 1)); +} + +static void +mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src, + unsigned num_samples) +{ + jack_default_audio_sample_t sample; + unsigned i; + + while (num_samples-- > 0) { + for (i = 0; i < jd->audio_format.channels; ++i) { + sample = sample_24_to_jack(*src++); + jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample, + sizeof(sample)); + } + } +} + +static void +mpd_jack_write_samples(struct jack_data *jd, const void *src, + unsigned num_samples) +{ + switch (jd->audio_format.bits) { + case 16: + mpd_jack_write_samples_16(jd, (const int16_t*)src, + num_samples); + break; + + case 24: + mpd_jack_write_samples_24(jd, (const int32_t*)src, + num_samples); + break; + + default: + assert(false); + } +} + +static size_t +mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r) +{ + struct jack_data *jd = data; + const size_t frame_size = audio_format_frame_size(&jd->audio_format); + size_t space = 0, space1; + + jd->pause = false; + + assert(size % frame_size == 0); + size /= frame_size; + + while (true) { + if (jd->shutdown) { + g_set_error(error_r, jack_output_quark(), 0, + "Refusing to play, because " + "there is no client thread"); + return 0; + } + + space = jack_ringbuffer_write_space(jd->ringbuffer[0]); + for (unsigned i = 1; i < jd->audio_format.channels; ++i) { + space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]); + if (space > space1) + /* send data symmetrically */ + space = space1; + } + + if (space >= frame_size) + break; + + /* XXX do something more intelligent to + synchronize */ + g_usleep(1000); + } + + space /= sample_size; + if (space < size) + size = space; + + mpd_jack_write_samples(jd, chunk, size); + return size * frame_size; +} + +static bool +mpd_jack_pause(void *data) +{ + struct jack_data *jd = data; + + if (jd->shutdown) + return false; + + jd->pause = true; + + /* due to a MPD API limitation, we have to sleep a little bit + here, to avoid hogging the CPU */ + g_usleep(50000); + + return true; +} + +const struct audio_output_plugin jack_output_plugin = { + .name = "jack", + .test_default_device = mpd_jack_test_default_device, + .init = mpd_jack_init, + .finish = mpd_jack_finish, + .enable = mpd_jack_enable, + .disable = mpd_jack_disable, + .open = mpd_jack_open, + .play = mpd_jack_play, + .pause = mpd_jack_pause, + .close = mpd_jack_close, +}; diff --git a/src/output/jack_plugin.c b/src/output/jack_plugin.c deleted file mode 100644 index 5dc1eca24..000000000 --- a/src/output/jack_plugin.c +++ /dev/null @@ -1,450 +0,0 @@ -/* - * Copyright (C) 2003-2009 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 "../output_api.h" -#include "config.h" - -#include <assert.h> - -#include <glib.h> -#include <jack/jack.h> -#include <jack/types.h> -#include <jack/ringbuffer.h> - -#include <stdlib.h> -#include <stdio.h> -#include <sys/types.h> -#include <unistd.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "jack" - -static const size_t sample_size = sizeof(jack_default_audio_sample_t); - -static const char *const port_names[2] = { - "left", "right", -}; - -struct jack_data { - const char *name; - - /* configuration */ - char *output_ports[2]; - int ringbuffer_size; - - /* the current audio format */ - struct audio_format audio_format; - - /* jack library stuff */ - jack_port_t *ports[2]; - jack_client_t *client; - jack_ringbuffer_t *ringbuffer[2]; - - bool shutdown; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -jack_output_quark(void) -{ - return g_quark_from_static_string("jack_output"); -} - -static void -mpd_jack_client_free(struct jack_data *jd) -{ - assert(jd != NULL); - - if (jd->client != NULL) { - jack_deactivate(jd->client); - jack_client_close(jd->client); - jd->client = NULL; - } - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) { - if (jd->ringbuffer[i] != NULL) { - jack_ringbuffer_free(jd->ringbuffer[i]); - jd->ringbuffer[i] = NULL; - } - } -} - -static void -mpd_jack_free(struct jack_data *jd) -{ - assert(jd != NULL); - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->output_ports); ++i) - g_free(jd->output_ports[i]); - - g_free(jd); -} - -static void -mpd_jack_finish(void *data) -{ - struct jack_data *jd = data; - mpd_jack_free(jd); -} - -static int -mpd_jack_process(jack_nframes_t nframes, void *arg) -{ - struct jack_data *jd = (struct jack_data *) arg; - jack_default_audio_sample_t *out; - size_t available; - - if (nframes <= 0) - return 0; - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) { - available = jack_ringbuffer_read_space(jd->ringbuffer[i]); - assert(available % sample_size == 0); - available /= sample_size; - if (available > nframes) - available = nframes; - - out = jack_port_get_buffer(jd->ports[i], nframes); - jack_ringbuffer_read(jd->ringbuffer[i], - (char *)out, available * sample_size); - - while (available < nframes) - /* ringbuffer underrun, fill with silence */ - out[available++] = 0.0; - } - - return 0; -} - -static void -mpd_jack_shutdown(void *arg) -{ - struct jack_data *jd = (struct jack_data *) arg; - jd->shutdown = true; -} - -static void -set_audioformat(struct jack_data *jd, struct audio_format *audio_format) -{ - audio_format->sample_rate = jack_get_sample_rate(jd->client); - audio_format->channels = 2; - - if (audio_format->bits != 16 && audio_format->bits != 24) - audio_format->bits = 24; -} - -static void -mpd_jack_error(const char *msg) -{ - g_warning("%s", msg); -} - -#ifdef HAVE_JACK_SET_INFO_FUNCTION -static void -mpd_jack_info(const char *msg) -{ - g_message("%s", msg); -} -#endif - -static void * -mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, GError **error) -{ - struct jack_data *jd; - const char *value; - - jd = g_new(struct jack_data, 1); - jd->name = config_get_block_string(param, "name", "mpd_jack"); - - g_debug("mpd_jack_init (pid=%d)", getpid()); - - value = config_get_block_string(param, "ports", NULL); - if (value != NULL) { - char **ports = g_strsplit(value, ",", 0); - - if (ports[0] == NULL || ports[1] == NULL || ports[2] != NULL) { - g_set_error(error, jack_output_quark(), 0, - "two port names expected in line %d", - param->line); - return NULL; - } - - jd->output_ports[0] = ports[0]; - jd->output_ports[1] = ports[1]; - - g_free(ports); - } else { - jd->output_ports[0] = NULL; - jd->output_ports[1] = NULL; - } - - jd->ringbuffer_size = - config_get_block_unsigned(param, "ringbuffer_size", 32768); - - jack_set_error_function(mpd_jack_error); - -#ifdef HAVE_JACK_SET_INFO_FUNCTION - jack_set_info_function(mpd_jack_info); -#endif - - return jd; -} - -static bool -mpd_jack_test_default_device(void) -{ - return true; -} - -static bool -mpd_jack_connect(struct jack_data *jd, GError **error) -{ - const char *output_ports[2], **jports; - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) - jd->ringbuffer[i] = - jack_ringbuffer_create(jd->ringbuffer_size); - - jd->shutdown = false; - - if ((jd->client = jack_client_new(jd->name)) == NULL) { - g_set_error(error, jack_output_quark(), 0, - "Failed to connect to JACK server"); - return false; - } - - jack_set_process_callback(jd->client, mpd_jack_process, jd); - jack_on_shutdown(jd->client, mpd_jack_shutdown, jd); - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) { - jd->ports[i] = jack_port_register(jd->client, port_names[i], - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - if (jd->ports[i] == NULL) { - g_set_error(error, jack_output_quark(), 0, - "Cannot register output port \"%s\"", - port_names[i]); - return false; - } - } - - if ( jack_activate(jd->client) ) { - g_set_error(error, jack_output_quark(), 0, - "cannot activate client"); - return false; - } - - if (jd->output_ports[1] == NULL) { - /* no output ports were configured - ask libjack for - defaults */ - jports = jack_get_ports(jd->client, NULL, NULL, - JackPortIsPhysical | JackPortIsInput); - if (jports == NULL) { - g_set_error(error, jack_output_quark(), 0, - "no ports found"); - return false; - } - - output_ports[0] = jports[0]; - output_ports[1] = jports[1] != NULL ? jports[1] : jports[0]; - - g_debug("output_ports: %s %s", jports[0], jports[1]); - } else { - /* use the configured output ports */ - - output_ports[0] = jd->output_ports[0]; - output_ports[1] = jd->output_ports[1]; - - jports = NULL; - } - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) { - int ret; - - ret = jack_connect(jd->client, jack_port_name(jd->ports[i]), - output_ports[i]); - if (ret != 0) { - g_set_error(error, jack_output_quark(), 0, - "Not a valid JACK port: %s", - output_ports[i]); - - if (jports != NULL) - free(jports); - - return false; - } - } - - if (jports != NULL) - free(jports); - - return true; -} - -static bool -mpd_jack_open(void *data, struct audio_format *audio_format, GError **error) -{ - struct jack_data *jd = data; - - assert(jd != NULL); - - if (!mpd_jack_connect(jd, error)) { - mpd_jack_client_free(jd); - return false; - } - - set_audioformat(jd, audio_format); - jd->audio_format = *audio_format; - - return true; -} - -static void -mpd_jack_close(G_GNUC_UNUSED void *data) -{ - struct jack_data *jd = data; - - mpd_jack_client_free(jd); -} - -static void -mpd_jack_cancel (G_GNUC_UNUSED void *data) -{ -} - -static inline jack_default_audio_sample_t -sample_16_to_jack(int16_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (16 - 1)); -} - -static void -mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src, - unsigned num_samples) -{ - jack_default_audio_sample_t sample; - - while (num_samples-- > 0) { - sample = sample_16_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample, - sizeof(sample)); - - sample = sample_16_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample, - sizeof(sample)); - } -} - -static inline jack_default_audio_sample_t -sample_24_to_jack(int32_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (24 - 1)); -} - -static void -mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src, - unsigned num_samples) -{ - jack_default_audio_sample_t sample; - - while (num_samples-- > 0) { - sample = sample_24_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample, - sizeof(sample)); - - sample = sample_24_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample, - sizeof(sample)); - } -} - -static void -mpd_jack_write_samples(struct jack_data *jd, const void *src, - unsigned num_samples) -{ - switch (jd->audio_format.bits) { - case 16: - mpd_jack_write_samples_16(jd, (const int16_t*)src, - num_samples); - break; - - case 24: - mpd_jack_write_samples_24(jd, (const int32_t*)src, - num_samples); - break; - - default: - assert(false); - } -} - -static size_t -mpd_jack_play(void *data, const void *chunk, size_t size, GError **error) -{ - struct jack_data *jd = data; - const size_t frame_size = audio_format_frame_size(&jd->audio_format); - size_t space = 0, space1; - - assert(size % frame_size == 0); - size /= frame_size; - - while (true) { - if (jd->shutdown) { - g_set_error(error, jack_output_quark(), 0, - "Refusing to play, because " - "there is no client thread"); - return 0; - } - - space = jack_ringbuffer_write_space(jd->ringbuffer[0]); - space1 = jack_ringbuffer_write_space(jd->ringbuffer[1]); - if (space > space1) - /* send data symmetrically */ - space = space1; - - if (space >= frame_size) - break; - - /* XXX do something more intelligent to - synchronize */ - g_usleep(1000); - } - - space /= sample_size; - if (space < size) - size = space; - - mpd_jack_write_samples(jd, chunk, size); - return size * frame_size; -} - -const struct audio_output_plugin jackPlugin = { - .name = "jack", - .test_default_device = mpd_jack_test_default_device, - .init = mpd_jack_init, - .finish = mpd_jack_finish, - .open = mpd_jack_open, - .play = mpd_jack_play, - .cancel = mpd_jack_cancel, - .close = mpd_jack_close, -}; diff --git a/src/output/mvp_plugin.c b/src/output/mvp_plugin.c index 96f9435a8..7e6dd6d31 100644 --- a/src/output/mvp_plugin.c +++ b/src/output/mvp_plugin.c @@ -22,7 +22,8 @@ * http://mvpmc.sourceforge.net/ */ -#include "../output_api.h" +#include "output_api.h" +#include "fd_util.h" #include <glib.h> @@ -115,7 +116,7 @@ mvp_output_test_default_device(void) { int fd; - fd = open("/dev/adec_pcm", O_WRONLY); + fd = open_cloexec("/dev/adec_pcm", O_WRONLY, 0); if (fd >= 0) { close(fd); @@ -230,7 +231,8 @@ mvp_output_open(void *data, struct audio_format *audio_format, GError **error) int mix[5] = { 0, 2, 7, 1, 0 }; bool success; - if ((md->fd = open("/dev/adec_pcm", O_RDWR | O_NONBLOCK)) < 0) { + md->fd = open_cloexec("/dev/adec_pcm", O_RDWR | O_NONBLOCK, 0); + if (md->fd < 0) { g_set_error(error, mvp_output_quark(), errno, "Error opening /dev/adec_pcm: %s", strerror(errno)); diff --git a/src/output/openal_plugin.c b/src/output/openal_plugin.c new file mode 100644 index 000000000..92ee82ef3 --- /dev/null +++ b/src/output/openal_plugin.c @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2003-2009 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 "../output_api.h" +#include "../timer.h" +#include "config.h" + +#include <glib.h> + +#ifndef HAVE_OSX +#include <AL/al.h> +#include <AL/alc.h> +#else +#include <OpenAL/al.h> +#include <OpenAL/alc.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "openal" + +/* should be enough for buffer size = 2048 */ +#define NUM_BUFFERS 16 + +struct openal_data { + const char *device_name; + ALCdevice *device; + ALCcontext *context; + Timer *timer; + ALuint buffers[NUM_BUFFERS]; + int filled; + ALuint source; + ALenum format; + ALuint frequency; +}; + +static inline GQuark +openal_output_quark(void) +{ + return g_quark_from_static_string("openal_output"); +} + +static ALenum +openal_audio_format(struct audio_format *audio_format) +{ + /* Only 8 and 16 bit samples are supported */ + if (audio_format->bits != 16 && audio_format->bits != 8) + audio_format->bits = 16; + + switch (audio_format->bits) + { + case 16: + if (audio_format->channels == 2) + return AL_FORMAT_STEREO16; + if (audio_format->channels == 1) + return AL_FORMAT_MONO16; + break; + + case 8: + if (audio_format->channels == 2) + return AL_FORMAT_STEREO8; + if (audio_format->channels == 1) + return AL_FORMAT_MONO8; + break; + } + + return 0; +} + +static bool +openal_setup_context(struct openal_data *od, + GError **error) +{ + od->device = alcOpenDevice(od->device_name); + + if (od->device == NULL) { + g_set_error(error, openal_output_quark(), 0, + "Error opening OpenAL device \"%s\"\n", + od->device_name); + return false; + } + + od->context = alcCreateContext(od->device, NULL); + + if (od->context == NULL) { + g_set_error(error, openal_output_quark(), 0, + "Error creating context for \"%s\"\n", + od->device_name); + alcCloseDevice(od->device); + return false; + } + + 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) +{ + const char *device_name = config_get_block_string(param, "device", NULL); + struct openal_data *od; + + if (device_name == NULL) { + device_name = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); + } + + od = g_new(struct openal_data, 1); + od->device_name = device_name; + + return od; +} + +static void +openal_finish(void *data) +{ + struct openal_data *od = data; + + g_free(od); +} + +static bool +openal_open(void *data, struct audio_format *audio_format, + GError **error) +{ + struct openal_data *od = data; + + od->format = openal_audio_format(audio_format); + + if (!od->format) { + g_set_error(error, openal_output_quark(), 0, + "Unsupported audio format (%i channels, %i bps)", + audio_format->channels, + audio_format->bits); + return false; + } + + if (!openal_setup_context(od, error)) { + return false; + } + + alcMakeContextCurrent(od->context); + alGenBuffers(NUM_BUFFERS, od->buffers); + + if (alGetError() != AL_NO_ERROR) { + g_set_error(error, openal_output_quark(), 0, + "Failed to generate buffers"); + return false; + } + + alGenSources(1, &od->source); + + if (alGetError() != AL_NO_ERROR) { + g_set_error(error, openal_output_quark(), 0, + "Failed to generate source"); + alDeleteBuffers(NUM_BUFFERS, od->buffers); + return false; + } + + od->filled = 0; + od->timer = timer_new(audio_format); + od->frequency = audio_format->sample_rate; + + return true; +} + +static void +openal_close(void *data) +{ + struct openal_data *od = data; + + timer_free(od->timer); + alcMakeContextCurrent(od->context); + alDeleteSources(1, &od->source); + alDeleteBuffers(NUM_BUFFERS, od->buffers); + alcDestroyContext(od->context); + alcCloseDevice(od->device); +} + +static size_t +openal_play(void *data, const void *chunk, size_t size, + G_GNUC_UNUSED GError **error) +{ + struct openal_data *od = data; + 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); + } + + 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) { + alSourcePlay(od->source); + } + + return size; +} + +static void +openal_cancel(void *data) +{ + struct openal_data *od = data; + + od->filled = 0; + alcMakeContextCurrent(od->context); + alSourceStop(od->source); + openal_unqueue_buffers(od); +} + +const struct audio_output_plugin openal_output_plugin = { + .name = "openal", + .init = openal_init, + .finish = openal_finish, + .open = openal_open, + .close = openal_close, + .play = openal_play, + .cancel = openal_cancel, +}; diff --git a/src/output/oss_plugin.c b/src/output/oss_plugin.c index a66bc0598..6518c3f49 100644 --- a/src/output/oss_plugin.c +++ b/src/output/oss_plugin.c @@ -17,8 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" +#include "output_api.h" #include "mixer_list.h" +#include "fd_util.h" #include <glib.h> @@ -343,7 +344,9 @@ oss_output_test_default_device(void) int fd, i; for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { - if ((fd = open(default_devices[i], O_WRONLY)) >= 0) { + fd = open_cloexec(default_devices[i], O_WRONLY, 0); + + if (fd >= 0) { close(fd); return true; } @@ -516,7 +519,8 @@ oss_open(struct oss_data *od, GError **error) { bool success; - if ((od->fd = open(od->device, O_WRONLY)) < 0) { + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { g_set_error(error, oss_output_quark(), errno, "Error opening OSS device \"%s\": %s", od->device, strerror(errno)); @@ -601,5 +605,6 @@ const struct audio_output_plugin oss_output_plugin = { .close = oss_output_close, .play = oss_output_play, .cancel = oss_output_cancel, - .mixer_plugin = &oss_mixer, + + .mixer_plugin = &oss_mixer_plugin, }; diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c new file mode 100644 index 000000000..13e1b6624 --- /dev/null +++ b/src/output/pulse_output_plugin.c @@ -0,0 +1,823 @@ +/* + * Copyright (C) 2003-2009 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 "pulse_output_plugin.h" +#include "output_api.h" +#include "mixer_list.h" +#include "mixer/pulse_mixer_plugin.h" + +#include <glib.h> + +#include <pulse/thread-mainloop.h> +#include <pulse/context.h> +#include <pulse/stream.h> +#include <pulse/introspect.h> +#include <pulse/subscribe.h> +#include <pulse/error.h> + +#include <assert.h> + +#define MPD_PULSE_NAME "Music Player Daemon" + +/** + * The quark used for GError.domain. + */ +static inline GQuark +pulse_output_quark(void) +{ + return g_quark_from_static_string("pulse_output"); +} + +void +pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm) +{ + assert(po != NULL); + assert(po->mixer == NULL); + assert(pm != NULL); + + po->mixer = pm; + + if (po->mainloop == NULL) + return; + + pa_threaded_mainloop_lock(po->mainloop); + + if (po->context != NULL && + pa_context_get_state(po->context) == PA_CONTEXT_READY) { + pulse_mixer_on_connect(pm, po->context); + + if (po->stream != NULL && + pa_stream_get_state(po->stream) == PA_STREAM_READY) + pulse_mixer_on_change(pm, po->context, po->stream); + } + + pa_threaded_mainloop_unlock(po->mainloop); +} + +void +pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm) +{ + assert(po != NULL); + assert(pm != NULL); + assert(po->mixer == pm); + + po->mixer = NULL; +} + +bool +pulse_output_set_volume(struct pulse_output *po, + const struct pa_cvolume *volume, GError **error_r) +{ + pa_operation *o; + + if (po->context == NULL || po->stream == NULL || + pa_stream_get_state(po->stream) != PA_STREAM_READY) { + g_set_error(error_r, pulse_output_quark(), 0, "disconnected"); + return false; + } + + o = pa_context_set_sink_input_volume(po->context, + pa_stream_get_index(po->stream), + volume, NULL, NULL); + if (o == NULL) { + g_set_error(error_r, pulse_output_quark(), 0, + "failed to set PulseAudio volume: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + + pa_operation_unref(o); + return true; +} + +/** + * \brief waits for a pulseaudio operation to finish, frees it and + * unlocks the mainloop + * \param operation the operation to wait for + * \return true if operation has finished normally (DONE state), + * false otherwise + */ +static bool +pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop, + struct pa_operation *operation) +{ + pa_operation_state_t state; + + assert(mainloop != NULL); + assert(operation != NULL); + + state = pa_operation_get_state(operation); + while (state == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(mainloop); + state = pa_operation_get_state(operation); + } + + pa_operation_unref(operation); + + return state == PA_OPERATION_DONE; +} + +/** + * Callback function for stream operation. It just sends a signal to + * the caller thread, to wake pulse_wait_for_operation() up. + */ +static void +pulse_output_stream_success_cb(G_GNUC_UNUSED pa_stream *s, + G_GNUC_UNUSED int success, void *userdata) +{ + struct pulse_output *po = userdata; + + pa_threaded_mainloop_signal(po->mainloop, 0); +} + +static void +pulse_output_context_state_cb(struct pa_context *context, void *userdata) +{ + struct pulse_output *po = userdata; + + switch (pa_context_get_state(context)) { + case PA_CONTEXT_READY: + if (po->mixer != NULL) + pulse_mixer_on_connect(po->mixer, context); + + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + if (po->mixer != NULL) + pulse_mixer_on_disconnect(po->mixer); + + /* the caller thread might be waiting for these + states */ + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } +} + +static void +pulse_output_subscribe_cb(pa_context *context, + pa_subscription_event_type_t t, + uint32_t idx, void *userdata) +{ + struct pulse_output *po = userdata; + pa_subscription_event_type_t facility + = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; + pa_subscription_event_type_t type + = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK; + + if (po->mixer != NULL && + facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT && + po->stream != NULL && + pa_stream_get_state(po->stream) == PA_STREAM_READY && + idx == pa_stream_get_index(po->stream) && + (type == PA_SUBSCRIPTION_EVENT_NEW || + type == PA_SUBSCRIPTION_EVENT_CHANGE)) + pulse_mixer_on_change(po->mixer, context, po->stream); +} + +/** + * Attempt to connect asynchronously to the PulseAudio server. + * + * @return true on success, false on error + */ +static bool +pulse_output_connect(struct pulse_output *po, GError **error_r) +{ + int error; + + error = pa_context_connect(po->context, po->server, + (pa_context_flags_t)0, NULL); + if (error < 0) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_context_connect() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + + return true; +} + +/** + * Create, set up and connect a context. + * + * @return true on success, false on error + */ +static bool +pulse_output_setup_context(struct pulse_output *po, GError **error_r) +{ + po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop), + MPD_PULSE_NAME); + if (po->context == NULL) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_context_new() has failed"); + return false; + } + + pa_context_set_state_callback(po->context, + pulse_output_context_state_cb, po); + pa_context_set_subscribe_callback(po->context, + pulse_output_subscribe_cb, po); + + if (!pulse_output_connect(po, error_r)) { + pa_context_unref(po->context); + return false; + } + + return true; +} + +/** + * Frees and clears the context. + */ +static void +pulse_output_delete_context(struct pulse_output *po) +{ + pa_context_disconnect(po->context); + pa_context_unref(po->context); + po->context = NULL; +} + +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) +{ + struct pulse_output *po; + + g_setenv("PULSE_PROP_media.role", "music", true); + + po = g_new(struct pulse_output, 1); + 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); + + po->mixer = NULL; + po->mainloop = NULL; + po->context = NULL; + po->stream = NULL; + + return po; +} + +static void +pulse_output_finish(void *data) +{ + struct pulse_output *po = data; + + g_free(po); +} + +static bool +pulse_output_enable(void *data, GError **error_r) +{ + struct pulse_output *po = data; + + assert(po->mainloop == NULL); + assert(po->context == NULL); + + /* create the libpulse mainloop and start the thread */ + + po->mainloop = pa_threaded_mainloop_new(); + if (po->mainloop == NULL) { + g_free(po); + + g_set_error(error_r, pulse_output_quark(), 0, + "pa_threaded_mainloop_new() has failed"); + return false; + } + + pa_threaded_mainloop_lock(po->mainloop); + + if (pa_threaded_mainloop_start(po->mainloop) < 0) { + pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_free(po->mainloop); + g_free(po); + + g_set_error(error_r, pulse_output_quark(), 0, + "pa_threaded_mainloop_start() has failed"); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + /* create the libpulse context and connect it */ + + pa_threaded_mainloop_lock(po->mainloop); + + if (!pulse_output_setup_context(po, error_r)) { + pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_stop(po->mainloop); + pa_threaded_mainloop_free(po->mainloop); + g_free(po); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + return true; +} + +static void +pulse_output_disable(void *data) +{ + struct pulse_output *po = data; + + pa_threaded_mainloop_stop(po->mainloop); + if (po->context != NULL) + pulse_output_delete_context(po); + pa_threaded_mainloop_free(po->mainloop); + po->mainloop = NULL; +} + +/** + * Check if the context is (already) connected, and waits if not. If + * the context has been disconnected, retry to connect. + * + * @return true on success, false on error + */ +static bool +pulse_output_wait_connection(struct pulse_output *po, GError **error_r) +{ + pa_context_state_t state; + + pa_threaded_mainloop_lock(po->mainloop); + + if (po->context == NULL && !pulse_output_setup_context(po, error_r)) + return false; + + while (true) { + state = pa_context_get_state(po->context); + switch (state) { + case PA_CONTEXT_READY: + /* nothing to do */ + pa_threaded_mainloop_unlock(po->mainloop); + return true; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + /* failure */ + g_set_error(error_r, pulse_output_quark(), 0, + "failed to connect: %s", + pa_strerror(pa_context_errno(po->context))); + pulse_output_delete_context(po); + pa_threaded_mainloop_unlock(po->mainloop); + return false; + + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + /* wait some more */ + pa_threaded_mainloop_wait(po->mainloop); + break; + } + } +} + +static void +pulse_output_stream_state_cb(pa_stream *stream, void *userdata) +{ + struct pulse_output *po = userdata; + + switch (pa_stream_get_state(stream)) { + case PA_STREAM_READY: + if (po->mixer != NULL) + pulse_mixer_on_change(po->mixer, po->context, stream); + + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + if (po->mixer != NULL) + pulse_mixer_on_disconnect(po->mixer); + + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; + } +} + +static void +pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes, + void *userdata) +{ + struct pulse_output *po = userdata; + + po->writable = nbytes; + pa_threaded_mainloop_signal(po->mainloop, 0); +} + +static bool +pulse_output_open(void *data, struct audio_format *audio_format, + GError **error_r) +{ + struct pulse_output *po = data; + pa_sample_spec ss; + int error; + + if (po->context != NULL) { + switch (pa_context_get_state(po->context)) { + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + /* the connection was closed meanwhile; delete + it, and pulse_output_wait_connection() will + reopen it */ + pulse_output_delete_context(po); + break; + + case PA_CONTEXT_READY: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } + } + + if (!pulse_output_wait_connection(po, error_r)) + return false; + + /* MPD doesn't support the other pulseaudio sample formats, so + we just force MPD to send us everything as 16 bit */ + audio_format->bits = 16; + + ss.format = PA_SAMPLE_S16NE; + ss.rate = audio_format->sample_rate; + ss.channels = audio_format->channels; + + pa_threaded_mainloop_lock(po->mainloop); + + /* 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))); + pa_threaded_mainloop_unlock(po->mainloop); + return false; + } + + 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, + NULL, 0, NULL, NULL); + if (error < 0) { + pa_stream_unref(po->stream); + po->stream = NULL; + + g_set_error(error_r, pulse_output_quark(), 0, + "pa_stream_connect_playback() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + pa_threaded_mainloop_unlock(po->mainloop); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + +#if !PA_CHECK_VERSION(0,9,11) + po->pause = false; +#endif + + return true; +} + +static void +pulse_output_close(void *data) +{ + struct pulse_output *po = data; + pa_operation *o; + + pa_threaded_mainloop_lock(po->mainloop); + + if (pa_stream_get_state(po->stream) == PA_STREAM_READY) { + o = pa_stream_drain(po->stream, + pulse_output_stream_success_cb, po); + if (o == NULL) { + g_warning("pa_stream_drain() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + } else + pulse_wait_for_operation(po->mainloop, o); + } + + pa_stream_disconnect(po->stream); + pa_stream_unref(po->stream); + po->stream = NULL; + + if (po->context != NULL && + pa_context_get_state(po->context) != PA_CONTEXT_READY) + pulse_output_delete_context(po); + + pa_threaded_mainloop_unlock(po->mainloop); +} + +/** + * Check if the stream is (already) connected, and waits for a signal + * if not. The mainloop must be locked before calling this function. + * + * @return the current stream state + */ +static pa_stream_state_t +pulse_output_check_stream(struct pulse_output *po) +{ + pa_stream_state_t state = pa_stream_get_state(po->stream); + + switch (state) { + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + case PA_STREAM_UNCONNECTED: + break; + + case PA_STREAM_CREATING: + pa_threaded_mainloop_wait(po->mainloop); + state = pa_stream_get_state(po->stream); + break; + } + + return state; +} + +/** + * Check if the stream is (already) connected, and waits if not. The + * mainloop must be locked before calling this function. + * + * @return true on success, false on error + */ +static bool +pulse_output_wait_stream(struct pulse_output *po, GError **error_r) +{ + pa_stream_state_t state = pa_stream_get_state(po->stream); + + switch (state) { + case PA_STREAM_READY: + return true; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + case PA_STREAM_UNCONNECTED: + g_set_error(error_r, pulse_output_quark(), 0, + "disconnected"); + return false; + + case PA_STREAM_CREATING: + break; + } + + do { + state = pulse_output_check_stream(po); + } while (state == PA_STREAM_CREATING); + + if (state != PA_STREAM_READY) { + g_set_error(error_r, pulse_output_quark(), 0, + "failed to connect the stream: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + + return true; +} + +/** + * Determines whether the stream is paused. On libpulse older than + * 0.9.11, it uses a custom pause flag. + */ +static bool +pulse_output_stream_is_paused(struct pulse_output *po) +{ + assert(po->stream != NULL); + +#if !defined(PA_CHECK_VERSION) || !PA_CHECK_VERSION(0,9,11) + return po->pause; +#else + return pa_stream_is_corked(po->stream); +#endif +} + +/** + * Sets cork mode on the stream. + */ +static bool +pulse_output_stream_pause(struct pulse_output *po, bool pause, + GError **error_r) +{ + pa_operation *o; + + assert(po->stream != NULL); + + o = pa_stream_cork(po->stream, pause, + pulse_output_stream_success_cb, po); + if (o == NULL) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_stream_cork() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + + if (!pulse_wait_for_operation(po->mainloop, o)) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_stream_cork() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + +#if !PA_CHECK_VERSION(0,9,11) + po->pause = pause; +#endif + return true; +} + +static size_t +pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r) +{ + struct pulse_output *po = data; + int error; + + assert(po->stream != NULL); + + pa_threaded_mainloop_lock(po->mainloop); + + /* check if the stream is (already) connected */ + + if (!pulse_output_wait_stream(po, error_r)) { + pa_threaded_mainloop_unlock(po->mainloop); + return 0; + } + + assert(po->context != NULL); + + /* unpause if previously paused */ + + if (pulse_output_stream_is_paused(po) && + !pulse_output_stream_pause(po, false, error_r)) + return 0; + + /* wait until the server allows us to write */ + + while (po->writable == 0) { + pa_threaded_mainloop_wait(po->mainloop); + + if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { + pa_threaded_mainloop_unlock(po->mainloop); + g_set_error(error_r, pulse_output_quark(), 0, + "disconnected"); + return false; + } + } + + /* now write */ + + if (size > po->writable) + /* don't send more than possible */ + size = po->writable; + + po->writable -= size; + + error = pa_stream_write(po->stream, chunk, size, NULL, + 0, PA_SEEK_RELATIVE); + pa_threaded_mainloop_unlock(po->mainloop); + if (error < 0) { + g_set_error(error_r, pulse_output_quark(), error, + "%s", pa_strerror(error)); + return 0; + } + + return size; +} + +static void +pulse_output_cancel(void *data) +{ + struct pulse_output *po = data; + pa_operation *o; + + assert(po->stream != NULL); + + pa_threaded_mainloop_lock(po->mainloop); + + if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { + /* no need to flush when the stream isn't connected + yet */ + pa_threaded_mainloop_unlock(po->mainloop); + return; + } + + assert(po->context != NULL); + + o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po); + if (o == NULL) { + g_warning("pa_stream_flush() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + pa_threaded_mainloop_unlock(po->mainloop); + return; + } + + pulse_wait_for_operation(po->mainloop, o); + pa_threaded_mainloop_unlock(po->mainloop); +} + +static bool +pulse_output_pause(void *data) +{ + struct pulse_output *po = data; + GError *error = NULL; + + assert(po->stream != NULL); + + pa_threaded_mainloop_lock(po->mainloop); + + /* check if the stream is (already/still) connected */ + + if (!pulse_output_wait_stream(po, &error)) { + pa_threaded_mainloop_unlock(po->mainloop); + g_warning("%s", error->message); + g_error_free(error); + return false; + } + + assert(po->context != NULL); + + /* cork the stream */ + + if (pulse_output_stream_is_paused(po)) { + /* already paused; due to a MPD API limitation, we + have to sleep a little bit here, to avoid hogging + the CPU */ + + g_usleep(50000); + } else if (!pulse_output_stream_pause(po, true, &error)) { + pa_threaded_mainloop_unlock(po->mainloop); + g_warning("%s", error->message); + g_error_free(error); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + return true; +} + +static bool +pulse_output_test_default_device(void) +{ + struct pulse_output *po; + bool success; + + po = pulse_output_init(NULL, NULL, NULL); + if (po == NULL) + return false; + + success = pulse_output_wait_connection(po, NULL); + pulse_output_finish(po); + + return success; +} + +const struct audio_output_plugin pulse_output_plugin = { + .name = "pulse", + + .test_default_device = pulse_output_test_default_device, + .init = pulse_output_init, + .finish = pulse_output_finish, + .enable = pulse_output_enable, + .disable = pulse_output_disable, + .open = pulse_output_open, + .play = pulse_output_play, + .cancel = pulse_output_cancel, + .pause = pulse_output_pause, + .close = pulse_output_close, + + .mixer_plugin = &pulse_mixer_plugin, +}; diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h new file mode 100644 index 000000000..e6b37443f --- /dev/null +++ b/src/output/pulse_output_plugin.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2009 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_PULSE_OUTPUT_PLUGIN_H +#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 pa_cvolume; + +struct pulse_output { + 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 +}; + +void +pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm); + +void +pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm); + +bool +pulse_output_set_volume(struct pulse_output *po, + const struct pa_cvolume *volume, GError **error_r); + +#endif diff --git a/src/output/pulse_plugin.c b/src/output/pulse_plugin.c deleted file mode 100644 index ffc7abc8b..000000000 --- a/src/output/pulse_plugin.c +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2003-2009 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 "../output_api.h" -#include "mixer_list.h" - -#include <glib.h> -#include <pulse/simple.h> -#include <pulse/error.h> - -#define MPD_PULSE_NAME "mpd" - -struct pulse_data { - const char *name; - const char *server; - const char *sink; - - pa_simple *s; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -pulse_output_quark(void) -{ - return g_quark_from_static_string("pulse_output"); -} - -static struct pulse_data *pulse_new_data(void) -{ - struct pulse_data *ret; - - ret = g_new(struct pulse_data, 1); - - ret->server = NULL; - ret->sink = NULL; - - return ret; -} - -static void pulse_free_data(struct pulse_data *pd) -{ - g_free(pd); -} - -static void * -pulse_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, G_GNUC_UNUSED GError **error) -{ - struct pulse_data *pd; - - pd = pulse_new_data(); - pd->name = config_get_block_string(param, "name", "mpd_pulse"); - pd->server = config_get_block_string(param, "server", NULL); - pd->sink = config_get_block_string(param, "sink", NULL); - - return pd; -} - -static void pulse_finish(void *data) -{ - struct pulse_data *pd = data; - - pulse_free_data(pd); -} - -static bool pulse_test_default_device(void) -{ - pa_simple *s; - pa_sample_spec ss; - int error; - - ss.format = PA_SAMPLE_S16NE; - ss.rate = 44100; - ss.channels = 2; - - s = pa_simple_new(NULL, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, NULL, - MPD_PULSE_NAME, &ss, NULL, NULL, &error); - if (!s) { - g_message("Cannot connect to default PulseAudio server: %s\n", - pa_strerror(error)); - return false; - } - - pa_simple_free(s); - - return true; -} - -static bool -pulse_open(void *data, struct audio_format *audio_format, GError **error_r) -{ - struct pulse_data *pd = data; - pa_sample_spec ss; - int error; - - /* MPD doesn't support the other pulseaudio sample formats, so - we just force MPD to send us everything as 16 bit */ - audio_format->bits = 16; - - ss.format = PA_SAMPLE_S16NE; - ss.rate = audio_format->sample_rate; - ss.channels = audio_format->channels; - - pd->s = pa_simple_new(pd->server, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, - pd->sink, pd->name, - &ss, NULL, NULL, - &error); - if (!pd->s) { - g_set_error(error_r, pulse_output_quark(), error, - "Cannot connect to PulseAudio server: %s", - pa_strerror(error)); - return false; - } - - return true; -} - -static void pulse_cancel(void *data) -{ - struct pulse_data *pd = data; - int error; - - if (pa_simple_flush(pd->s, &error) < 0) - g_warning("Flush failed in PulseAudio output \"%s\": %s\n", - pd->name, pa_strerror(error)); -} - -static void pulse_close(void *data) -{ - struct pulse_data *pd = data; - - pa_simple_drain(pd->s, NULL); - pa_simple_free(pd->s); -} - -static size_t -pulse_play(void *data, const void *chunk, size_t size, GError **error_r) -{ - struct pulse_data *pd = data; - int error; - - if (pa_simple_write(pd->s, chunk, size, &error) < 0) { - g_set_error(error_r, pulse_output_quark(), error, - "%s", pa_strerror(error)); - return 0; - } - - return size; -} - -const struct audio_output_plugin pulse_plugin = { - .name = "pulse", - .test_default_device = pulse_test_default_device, - .init = pulse_init, - .finish = pulse_finish, - .open = pulse_open, - .play = pulse_play, - .cancel = pulse_cancel, - .close = pulse_close, - .mixer_plugin = &pulse_mixer, -}; diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c new file mode 100644 index 000000000..11dd6c041 --- /dev/null +++ b/src/output/recorder_output_plugin.c @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2003-2009 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 "output_api.h" +#include "encoder_plugin.h" +#include "encoder_list.h" +#include "fd_util.h" + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "recorder" + +struct recorder_output { + /** + * The configured encoder plugin. + */ + struct encoder *encoder; + + /** + * The destination file name. + */ + const char *path; + + /** + * The destination file descriptor. + */ + int fd; + + /** + * The buffer for encoder_read(). + */ + char buffer[32768]; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +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) +{ + struct recorder_output *recorder = g_new(struct recorder_output, 1); + const char *encoder_name; + const struct encoder_plugin *encoder_plugin; + + /* read configuration */ + + encoder_name = config_get_block_string(param, "encoder", "vorbis"); + encoder_plugin = encoder_plugin_get(encoder_name); + if (encoder_plugin == NULL) { + g_set_error(error_r, recorder_output_quark(), 0, + "No such encoder: %s", encoder_name); + return NULL; + } + + recorder->path = config_get_block_string(param, "path", NULL); + if (recorder->path == NULL) { + g_set_error(error_r, recorder_output_quark(), 0, + "'path' not configured"); + return NULL; + } + + /* initialize encoder */ + + recorder->encoder = encoder_init(encoder_plugin, param, error_r); + if (recorder->encoder == NULL) + return NULL; + + return recorder; +} + +static void +recorder_output_finish(void *data) +{ + struct recorder_output *recorder = data; + + encoder_finish(recorder->encoder); + g_free(recorder); +} + +/** + * Writes pending data from the encoder to the output file. + */ +static bool +recorder_output_encoder_to_file(struct recorder_output *recorder, + GError **error_r) +{ + size_t size = 0, position, nbytes; + + assert(recorder->fd >= 0); + + /* read from the encoder */ + + size = encoder_read(recorder->encoder, recorder->buffer, + sizeof(recorder->buffer)); + if (size == 0) + return true; + + /* write everything into the file */ + + position = 0; + while (true) { + nbytes = write(recorder->fd, recorder->buffer + position, + size - position); + if (nbytes > 0) { + position += (size_t)nbytes; + if (position >= size) + return true; + } else if (nbytes == 0) { + /* shouldn't happen for files */ + g_set_error(error_r, recorder_output_quark(), 0, + "write() returned 0"); + return false; + } else if (errno != EINTR) { + g_set_error(error_r, recorder_output_quark(), 0, + "Failed to write to '%s': %s", + recorder->path, g_strerror(errno)); + return false; + } + } +} + +static bool +recorder_output_open(void *data, struct audio_format *audio_format, + GError **error_r) +{ + struct recorder_output *recorder = data; + bool success; + + /* create the output file */ + + recorder->fd = open_cloexec(recorder->path, O_CREAT|O_WRONLY|O_TRUNC, + 0666); + if (recorder->fd < 0) { + g_set_error(error_r, recorder_output_quark(), 0, + "Failed to create '%s': %s", + recorder->path, g_strerror(errno)); + return false; + } + + /* open the encoder */ + + success = encoder_open(recorder->encoder, audio_format, error_r); + if (!success) { + close(recorder->fd); + unlink(recorder->path); + return false; + } + + return true; +} + +static void +recorder_output_close(void *data) +{ + struct recorder_output *recorder = data; + + /* flush the encoder and write the rest to the file */ + + if (encoder_flush(recorder->encoder, NULL)) + recorder_output_encoder_to_file(recorder, NULL); + + /* now really close everything */ + + encoder_close(recorder->encoder); + + close(recorder->fd); +} + +static size_t +recorder_output_play(void *data, const void *chunk, size_t size, + GError **error_r) +{ + struct recorder_output *recorder = data; + + return encoder_write(recorder->encoder, chunk, size, error_r) && + recorder_output_encoder_to_file(recorder, error_r) + ? size : 0; +} + +const struct audio_output_plugin recorder_output_plugin = { + .name = "recorder", + .init = recorder_output_init, + .finish = recorder_output_finish, + .open = recorder_output_open, + .close = recorder_output_close, + .play = recorder_output_play, +}; diff --git a/src/output/shout_plugin.c b/src/output/shout_plugin.c index 4412d26ff..da90efd2d 100644 --- a/src/output/shout_plugin.c +++ b/src/output/shout_plugin.c @@ -126,6 +126,13 @@ my_shout_init_driver(const struct audio_format *audio_format, struct block_param *block_param; int public; + if (audio_format == NULL || + !audio_format_fully_defined(audio_format)) { + g_set_error(error, shout_output_quark(), 0, + "Need full audio format specification"); + return NULL; + } + sd = new_shout_data(); if (shout_init_count == 0) @@ -191,8 +198,6 @@ my_shout_init_driver(const struct audio_format *audio_format, } } - check_block_param("format"); - encoding = config_get_block_string(param, "encoding", "ogg"); encoder_plugin = shout_encoder_plugin_get(encoding); if (encoder_plugin == NULL) { @@ -471,10 +476,10 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size) for (unsigned i = 0; i < tag->num_items; i++) { switch (tag->items[i]->type) { - case TAG_ITEM_ARTIST: + case TAG_ARTIST: strncpy(artist, tag->items[i]->value, size); break; - case TAG_ITEM_TITLE: + case TAG_TITLE: strncpy(title, tag->items[i]->value, size); break; diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c index 5febf0afc..4f3d86835 100644 --- a/src/output/solaris_output_plugin.c +++ b/src/output/solaris_output_plugin.c @@ -18,6 +18,7 @@ */ #include "output_api.h" +#include "fd_util.h" #include <glib.h> @@ -91,7 +92,7 @@ solaris_output_open(void *data, struct audio_format *audio_format, /* open the device in non-blocking mode */ - so->fd = open(so->device, O_WRONLY|O_NONBLOCK); + so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK); if (so->fd < 0) { g_set_error(error, solaris_output_quark(), errno, "Failed to open %s: %s", |