diff options
Diffstat (limited to '')
-rw-r--r-- | src/output/alsa_plugin.c | 52 | ||||
-rw-r--r-- | src/output/httpd_client.c | 5 | ||||
-rw-r--r-- | src/output/httpd_output_plugin.c | 5 | ||||
-rw-r--r-- | src/output/openal_plugin.c | 273 | ||||
-rw-r--r-- | src/output/recorder_output_plugin.c | 214 | ||||
-rw-r--r-- | src/output/shout_plugin.c | 4 | ||||
-rw-r--r-- | src/output_all.c | 14 | ||||
-rw-r--r-- | src/output_all.h | 7 | ||||
-rw-r--r-- | src/output_command.c | 6 | ||||
-rw-r--r-- | src/output_control.c | 18 | ||||
-rw-r--r-- | src/output_init.c | 116 | ||||
-rw-r--r-- | src/output_internal.h | 22 | ||||
-rw-r--r-- | src/output_list.c | 8 | ||||
-rw-r--r-- | src/output_state.c | 55 | ||||
-rw-r--r-- | src/output_state.h | 15 | ||||
-rw-r--r-- | src/output_thread.c | 204 |
16 files changed, 884 insertions, 134 deletions
diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c index 818c83ca2..f271668b1 100644 --- a/src/output/alsa_plugin.c +++ b/src/output/alsa_plugin.c @@ -183,6 +183,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 +221,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 +248,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 +292,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, 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_output_plugin.c b/src/output/httpd_output_plugin.c index 9fdf46456..d1296abc2 100644 --- a/src/output/httpd_output_plugin.c +++ b/src/output/httpd_output_plugin.c @@ -433,9 +433,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); 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/recorder_output_plugin.c b/src/output/recorder_output_plugin.c new file mode 100644 index 000000000..413e5d0d1 --- /dev/null +++ b/src/output/recorder_output_plugin.c @@ -0,0 +1,214 @@ +/* + * 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 <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 = creat(recorder->path, 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..f1b21bb36 100644 --- a/src/output/shout_plugin.c +++ b/src/output/shout_plugin.c @@ -471,10 +471,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_all.c b/src/output_all.c index 4b5ba3a6f..b7a42a6a3 100644 --- a/src/output_all.c +++ b/src/output_all.c @@ -52,6 +52,11 @@ static struct music_buffer *g_music_buffer; */ static struct music_pipe *g_mp; +/** + * The "elapsed_time" stamp of the most recently finished chunk. + */ +static float audio_output_all_elapsed_time; + unsigned int audio_output_count(void) { return num_audio_outputs; @@ -385,6 +390,8 @@ audio_output_all_check(void) this chunk */ return music_pipe_size(g_mp); + audio_output_all_elapsed_time = chunk->times; + is_tail = chunk->next == NULL; if (is_tail) /* this is the tail of the pipe - clear the @@ -468,9 +475,16 @@ audio_output_all_close(void) music_pipe_clear(g_mp, g_music_buffer); music_pipe_free(g_mp); g_mp = NULL; + audio_output_all_elapsed_time = 0.0; } g_music_buffer = NULL; audio_format_clear(&input_audio_format); } + +float +audio_output_all_get_elapsed_time(void) +{ + return audio_output_all_elapsed_time; +} diff --git a/src/output_all.h b/src/output_all.h index 2a09514b2..e124d3150 100644 --- a/src/output_all.h +++ b/src/output_all.h @@ -128,4 +128,11 @@ audio_output_all_pause(void); void audio_output_all_cancel(void); +/** + * Returns the "elapsed_time" stamp of the most recently finished + * chunk. + */ +float +audio_output_all_get_elapsed_time(void); + #endif diff --git a/src/output_command.c b/src/output_command.c index 5da176dde..9d948c8cc 100644 --- a/src/output_command.c +++ b/src/output_command.c @@ -31,6 +31,8 @@ #include "mixer_control.h" #include "idle.h" +extern unsigned audio_output_state_version; + bool audio_output_enable_index(unsigned idx) { @@ -44,6 +46,8 @@ audio_output_enable_index(unsigned idx) ao->enabled = true; idle_add(IDLE_OUTPUT); + ++audio_output_state_version; + return true; } @@ -67,5 +71,7 @@ audio_output_disable_index(unsigned idx) idle_add(IDLE_MIXER); } + ++audio_output_state_version; + return true; } diff --git a/src/output_control.c b/src/output_control.c index 16c0dbb75..ef77bf4fa 100644 --- a/src/output_control.c +++ b/src/output_control.c @@ -23,6 +23,7 @@ #include "output_thread.h" #include "mixer_control.h" #include "mixer_plugin.h" +#include "filter_plugin.h" #include <assert.h> #include <stdlib.h> @@ -93,26 +94,13 @@ audio_output_open(struct audio_output *ao, ao->in_audio_format = *audio_format; ao->chunk = NULL; - if (!ao->config_audio_format) { - if (ao->open) - audio_output_close(ao); - - /* no audio format is configured: copy in->out, let - the output's open() method determine the effective - out_audio_format */ - ao->out_audio_format = ao->in_audio_format; - } - ao->pipe = mp; if (ao->thread == NULL) audio_output_thread_start(ao); + ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN); open = ao->open; - if (!open) { - ao_command(ao, AO_COMMAND_OPEN); - open = ao->open; - } if (open && ao->mixer != NULL) mixer_open(ao->mixer); @@ -195,4 +183,6 @@ void audio_output_finish(struct audio_output *ao) notify_deinit(&ao->notify); g_mutex_free(ao->mutex); + + filter_free(ao->filter); } diff --git a/src/output_init.c b/src/output_init.c index 927424324..66444e9a5 100644 --- a/src/output_init.c +++ b/src/output_init.c @@ -23,9 +23,17 @@ #include "output_list.h" #include "audio_parser.h" #include "mixer_control.h" +#include "mixer_type.h" +#include "mixer_list.h" +#include "mixer/software_mixer_plugin.h" +#include "filter_plugin.h" +#include "filter_registry.h" +#include "filter/chain_filter_plugin.h" #include <glib.h> +#include <assert.h> + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "output" @@ -56,28 +64,81 @@ audio_output_detect(GError **error) return NULL; } +/** + * Determines the mixer type which should be used for the specified + * configuration block. + * + * This handles the deprecated options mixer_type (global) and + * mixer_enabled, if the mixer_type setting is not configured. + */ +static enum mixer_type +audio_output_mixer_type(const struct config_param *param) +{ + /* read the local "mixer_type" setting */ + const char *p = config_get_block_string(param, "mixer_type", NULL); + if (p != NULL) + return mixer_type_parse(p); + + /* try the local "mixer_enabled" setting next (deprecated) */ + if (!config_get_block_bool(param, "mixer_enabled", true)) + return MIXER_TYPE_NONE; + + /* fall back to the global "mixer_type" setting (also + deprecated) */ + return mixer_type_parse(config_get_string("mixer_type", "hardware")); +} + +static struct mixer * +audio_output_load_mixer(const struct config_param *param, + const struct mixer_plugin *plugin, + struct filter *filter_chain) +{ + struct mixer *mixer; + + switch (audio_output_mixer_type(param)) { + case MIXER_TYPE_NONE: + case MIXER_TYPE_UNKNOWN: + return NULL; + + case MIXER_TYPE_HARDWARE: + if (plugin == NULL) + return NULL; + + return mixer_new(plugin, param); + + case MIXER_TYPE_SOFTWARE: + mixer = mixer_new(&software_mixer_plugin, NULL); + assert(mixer != NULL); + + filter_chain_append(filter_chain, + software_mixer_get_filter(mixer)); + return mixer; + } + + assert(false); + return NULL; +} + bool audio_output_init(struct audio_output *ao, const struct config_param *param, GError **error) { - const char *format; const struct audio_output_plugin *plugin = NULL; if (param) { - const char *type = NULL; + const char *p; - type = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL); - if (type == NULL) { + p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL); + if (p == NULL) { g_set_error(error, audio_output_quark(), 0, "Missing \"type\" configuration"); return false; } - plugin = audio_output_plugin_get(type); + plugin = audio_output_plugin_get(p); if (plugin == NULL) { g_set_error(error, audio_output_quark(), 0, - "No such audio output plugin: %s", - type); + "No such audio output plugin: %s", p); return false; } @@ -89,8 +150,16 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, return false; } - format = config_get_block_string(param, AUDIO_OUTPUT_FORMAT, + p = config_get_block_string(param, AUDIO_OUTPUT_FORMAT, NULL); + ao->config_audio_format = p != NULL; + if (p != NULL) { + bool success = + audio_format_parse(&ao->out_audio_format, + p, error); + if (!success) + return false; + } } else { g_warning("No \"%s\" defined in config file\n", CONF_AUDIO_OUTPUT); @@ -103,7 +172,7 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, plugin->name); ao->name = "default detected output"; - format = NULL; + ao->config_audio_format = false; } ao->plugin = plugin; @@ -112,17 +181,10 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, ao->pause = false; ao->fail_timer = NULL; - pcm_convert_init(&ao->convert_state); + /* set up the filter chain */ - ao->config_audio_format = format != NULL; - if (ao->config_audio_format) { - bool ret; - - ret = audio_format_parse(&ao->out_audio_format, format, - error); - if (!ret) - return false; - } + ao->filter = filter_chain_new(); + assert(ao->filter != NULL); ao->thread = NULL; notify_init(&ao->notify); @@ -136,11 +198,17 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, if (ao->data == NULL) return false; - if (plugin->mixer_plugin != NULL && - config_get_block_bool(param, "mixer_enabled", true)) - ao->mixer = mixer_new(plugin->mixer_plugin, param); - else - ao->mixer = NULL; + ao->mixer = audio_output_load_mixer(param, plugin->mixer_plugin, + ao->filter); + + /* the "convert" filter must be the last one in the chain */ + + ao->convert_filter = filter_new(&convert_filter_plugin, NULL, NULL); + assert(ao->convert_filter != NULL); + + filter_chain_append(ao->filter, ao->convert_filter); + + /* done */ return true; } diff --git a/src/output_internal.h b/src/output_internal.h index 72596c1c3..4eb77cc49 100644 --- a/src/output_internal.h +++ b/src/output_internal.h @@ -21,7 +21,6 @@ #define MPD_OUTPUT_INTERNAL_H #include "audio_format.h" -#include "pcm_convert.h" #include "notify.h" #include <time.h> @@ -29,6 +28,13 @@ enum audio_output_command { AO_COMMAND_NONE = 0, AO_COMMAND_OPEN, + + /** + * This command is invoked when the input audio format + * changes. + */ + AO_COMMAND_REOPEN, + AO_COMMAND_CLOSE, AO_COMMAND_PAUSE, AO_COMMAND_CANCEL, @@ -107,7 +113,19 @@ struct audio_output { */ struct audio_format out_audio_format; - struct pcm_convert_state convert_state; + /** + * The filter object of this audio output. This is an + * instance of chain_filter_plugin. + */ + struct filter *filter; + + /** + * The convert_filter_plugin instance of this audio output. + * It is the last item in the filter chain, and is responsible + * for converting the input data into the appropriate format + * for this audio output. + */ + struct filter *convert_filter; /** * The thread handle, or NULL if the output thread isn't diff --git a/src/output_list.c b/src/output_list.c index 81de16649..476701a1a 100644 --- a/src/output_list.c +++ b/src/output_list.c @@ -28,12 +28,14 @@ extern const struct audio_output_plugin pipe_output_plugin; extern const struct audio_output_plugin alsaPlugin; extern const struct audio_output_plugin ao_output_plugin; extern const struct audio_output_plugin oss_output_plugin; +extern const struct audio_output_plugin openal_output_plugin; extern const struct audio_output_plugin osxPlugin; extern const struct audio_output_plugin solaris_output_plugin; extern const struct audio_output_plugin pulse_plugin; extern const struct audio_output_plugin mvp_output_plugin; extern const struct audio_output_plugin jackPlugin; extern const struct audio_output_plugin httpd_output_plugin; +extern const struct audio_output_plugin recorder_output_plugin; const struct audio_output_plugin *audio_output_plugins[] = { #ifdef HAVE_SHOUT @@ -55,6 +57,9 @@ const struct audio_output_plugin *audio_output_plugins[] = { #ifdef HAVE_OSS &oss_output_plugin, #endif +#ifdef HAVE_OPENAL + &openal_output_plugin, +#endif #ifdef HAVE_OSX &osxPlugin, #endif @@ -73,6 +78,9 @@ const struct audio_output_plugin *audio_output_plugins[] = { #ifdef ENABLE_HTTPD_OUTPUT &httpd_output_plugin, #endif +#ifdef ENABLE_RECORDER_OUTPUT + &recorder_output_plugin, +#endif NULL }; diff --git a/src/output_state.c b/src/output_state.c index c7e6c8579..be135f6d0 100644 --- a/src/output_state.c +++ b/src/output_state.c @@ -34,8 +34,10 @@ #define AUDIO_DEVICE_STATE "audio_device_state:" +unsigned audio_output_state_version; + void -saveAudioDevicesState(FILE *fp) +audio_output_state_save(FILE *fp) { unsigned n = audio_output_count(); @@ -49,35 +51,40 @@ saveAudioDevicesState(FILE *fp) } } -void -readAudioDevicesState(FILE *fp) +bool +audio_output_state_read(const char *line) { - char buffer[1024]; + long value; + char *endptr; + const char *name; + struct audio_output *ao; - while (fgets(buffer, sizeof(buffer), fp)) { - char *c, *name; - struct audio_output *ao; + if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE)) + return false; - g_strchomp(buffer); + line += sizeof(AUDIO_DEVICE_STATE) - 1; - if (!g_str_has_prefix(buffer, AUDIO_DEVICE_STATE)) - continue; + value = strtol(line, &endptr, 10); + if (*endptr != ':' || (value != 0 && value != 1)) + return false; - c = strchr(buffer, ':'); - if (!c || !(++c)) - goto errline; + if (value != 0) + /* state is "enabled": no-op */ + return true; - name = strchr(c, ':'); - if (!name || !(++name)) - goto errline; + name = endptr + 1; + ao = audio_output_find(name); + if (ao == NULL) { + g_debug("Ignoring device state for '%s'", name); + return true; + } - ao = audio_output_find(name); - if (ao != NULL && atoi(c) == 0) - ao->enabled = false; + ao->enabled = false; + return true; +} - continue; -errline: - /* nonfatal */ - g_warning("invalid line in state_file: %s\n", buffer); - } +unsigned +audio_output_state_get_version(void) +{ + return audio_output_state_version; } diff --git a/src/output_state.h b/src/output_state.h index 8592574ab..3b865f5fe 100644 --- a/src/output_state.h +++ b/src/output_state.h @@ -25,12 +25,21 @@ #ifndef OUTPUT_STATE_H #define OUTPUT_STATE_H +#include <stdbool.h> #include <stdio.h> -void -readAudioDevicesState(FILE *fp); +bool +audio_output_state_read(const char *line); void -saveAudioDevicesState(FILE *fp); +audio_output_state_save(FILE *fp); + +/** + * Generates a version number for the current state of the audio + * outputs. This is used by timer_save_state_file() to determine + * whether the state has changed and the state file should be saved. + */ +unsigned +audio_output_state_get_version(void); #endif diff --git a/src/output_thread.c b/src/output_thread.c index 785ac808f..e1f20e580 100644 --- a/src/output_thread.c +++ b/src/output_thread.c @@ -23,6 +23,8 @@ #include "chunk.h" #include "pipe.h" #include "player_control.h" +#include "filter_plugin.h" +#include "filter/convert_filter_plugin.h" #include <glib.h> @@ -41,6 +43,73 @@ static void ao_command_finished(struct audio_output *ao) } static void +ao_open(struct audio_output *ao) +{ + bool success; + GError *error = NULL; + const struct audio_format *filter_audio_format; + + assert(!ao->open); + assert(ao->fail_timer == NULL); + assert(ao->pipe != NULL); + assert(ao->chunk == NULL); + + /* open the filter */ + + filter_audio_format = filter_open(ao->filter, &ao->in_audio_format, + &error); + if (filter_audio_format == NULL) { + g_warning("Failed to open filter for \"%s\" [%s]: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + ao->fail_timer = g_timer_new(); + return; + } + + if (!ao->config_audio_format) + ao->out_audio_format = *filter_audio_format; + + success = ao_plugin_open(ao->plugin, ao->data, + &ao->out_audio_format, + &error); + + assert(!ao->open); + + if (!success) { + g_warning("Failed to open \"%s\" [%s]: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + filter_close(ao->filter); + ao->fail_timer = g_timer_new(); + return; + } + + convert_filter_set(ao->convert_filter, &ao->out_audio_format); + + g_mutex_lock(ao->mutex); + ao->open = true; + g_mutex_unlock(ao->mutex); + + g_debug("opened plugin=%s name=\"%s\" " + "audio_format=%u:%u:%u:%u", + ao->plugin->name, ao->name, + ao->out_audio_format.sample_rate, + ao->out_audio_format.bits, + ao->out_audio_format.channels, + ao->out_audio_format.reverse_endian); + + if (!audio_format_equals(&ao->in_audio_format, + &ao->out_audio_format)) + g_debug("converting from %u:%u:%u:%u", + ao->in_audio_format.sample_rate, + ao->in_audio_format.bits, + ao->in_audio_format.channels, + ao->in_audio_format.reverse_endian); +} + +static void ao_close(struct audio_output *ao) { assert(ao->open); @@ -53,11 +122,69 @@ ao_close(struct audio_output *ao) g_mutex_unlock(ao->mutex); ao_plugin_close(ao->plugin, ao->data); - pcm_convert_deinit(&ao->convert_state); + filter_close(ao->filter); g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name); } +static void +ao_reopen_filter(struct audio_output *ao) +{ + const struct audio_format *filter_audio_format; + GError *error = NULL; + + filter_close(ao->filter); + filter_audio_format = filter_open(ao->filter, &ao->in_audio_format, + &error); + if (filter_audio_format == NULL) { + g_warning("Failed to open filter for \"%s\" [%s]: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + /* this is a little code duplication fro ao_close(), + but we cannot call this function because we must + not call filter_close(ao->filter) again */ + + ao->pipe = NULL; + + g_mutex_lock(ao->mutex); + ao->chunk = NULL; + ao->open = false; + g_mutex_unlock(ao->mutex); + + ao_plugin_close(ao->plugin, ao->data); + + ao->fail_timer = g_timer_new(); + return; + } + + convert_filter_set(ao->convert_filter, &ao->out_audio_format); +} + +static void +ao_reopen(struct audio_output *ao) +{ + if (!ao->config_audio_format) { + if (ao->open) { + const struct music_pipe *mp = ao->pipe; + ao_close(ao); + ao->pipe = mp; + } + + /* no audio format is configured: copy in->out, let + the output's open() method determine the effective + out_audio_format */ + ao->out_audio_format = ao->in_audio_format; + } + + if (ao->open) + /* the audio format has changed, and all filters have + to be reconfigured */ + ao_reopen_filter(ao); + else + ao_open(ao); +} + static bool ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) { @@ -65,6 +192,8 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) size_t size = chunk->length; GError *error = NULL; + assert(ao != NULL); + assert(ao->filter != NULL); assert(!music_chunk_is_empty(chunk)); assert(music_chunk_check_format(chunk, &ao->in_audio_format)); assert(size % audio_format_frame_size(&ao->in_audio_format) == 0); @@ -75,18 +204,19 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) if (size == 0) return true; - if (!audio_format_equals(&ao->in_audio_format, - &ao->out_audio_format)) { - data = pcm_convert(&ao->convert_state, - &ao->in_audio_format, data, size, - &ao->out_audio_format, &size); - - /* under certain circumstances, pcm_convert() may - return an empty buffer - this condition should be - investigated further, but for now, do this check as - a workaround: */ - if (data == NULL) - return true; + data = filter_filter(ao->filter, data, size, &size, &error); + if (data == NULL) { + g_warning("\"%s\" [%s] failed to filter: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + ao_plugin_cancel(ao->plugin, ao->data); + ao_close(ao); + + /* don't automatically reopen this device for 10 + seconds */ + ao->fail_timer = g_timer_new(); + return false; } while (size > 0 && ao->command == AO_COMMAND_NONE) { @@ -182,8 +312,6 @@ static void ao_pause(struct audio_output *ao) static gpointer audio_output_task(gpointer arg) { struct audio_output *ao = arg; - bool ret; - GError *error; while (1) { switch (ao->command) { @@ -191,47 +319,12 @@ static gpointer audio_output_task(gpointer arg) break; case AO_COMMAND_OPEN: - assert(!ao->open); - assert(ao->fail_timer == NULL); - assert(ao->pipe != NULL); - assert(ao->chunk == NULL); - - error = NULL; - ret = ao_plugin_open(ao->plugin, ao->data, - &ao->out_audio_format, - &error); - - assert(!ao->open); - if (ret) { - pcm_convert_init(&ao->convert_state); - - g_mutex_lock(ao->mutex); - ao->open = true; - g_mutex_unlock(ao->mutex); - - g_debug("opened plugin=%s name=\"%s\" " - "audio_format=%u:%u:%u", - ao->plugin->name, - ao->name, - ao->out_audio_format.sample_rate, - ao->out_audio_format.bits, - ao->out_audio_format.channels); - - if (!audio_format_equals(&ao->in_audio_format, - &ao->out_audio_format)) - g_debug("converting from %u:%u:%u", - ao->in_audio_format.sample_rate, - ao->in_audio_format.bits, - ao->in_audio_format.channels); - } else { - g_warning("Failed to open \"%s\" [%s]: %s", - ao->name, ao->plugin->name, - error->message); - g_error_free(error); - - ao->fail_timer = g_timer_new(); - } + ao_open(ao); + ao_command_finished(ao); + break; + case AO_COMMAND_REOPEN: + ao_reopen(ao); ao_command_finished(ao); break; @@ -244,6 +337,7 @@ static gpointer audio_output_task(gpointer arg) ao_plugin_cancel(ao->plugin, ao->data); ao_close(ao); + filter_close(ao->filter); ao_command_finished(ao); break; |