diff options
Diffstat (limited to '')
-rw-r--r-- | src/output_thread.c | 513 |
1 files changed, 423 insertions, 90 deletions
diff --git a/src/output_thread.c b/src/output_thread.c index 770b377e8..df9d7801b 100644 --- a/src/output_thread.c +++ b/src/output_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,12 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_thread.h" #include "output_api.h" #include "output_internal.h" #include "chunk.h" #include "pipe.h" #include "player_control.h" +#include "pcm_mix.h" +#include "filter_plugin.h" +#include "filter/convert_filter_plugin.h" +#include "filter/replay_gain_filter_plugin.h" #include <glib.h> @@ -37,71 +42,388 @@ static void ao_command_finished(struct audio_output *ao) { assert(ao->command != AO_COMMAND_NONE); ao->command = AO_COMMAND_NONE; + + g_mutex_unlock(ao->mutex); notify_signal(&audio_output_client_notify); + g_mutex_lock(ao->mutex); +} + +static bool +ao_enable(struct audio_output *ao) +{ + GError *error = NULL; + bool success; + + if (ao->really_enabled) + return true; + + g_mutex_unlock(ao->mutex); + success = ao_plugin_enable(ao->plugin, ao->data, &error); + g_mutex_lock(ao->mutex); + if (!success) { + g_warning("Failed to enable \"%s\" [%s]: %s\n", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + return false; + } + + ao->really_enabled = true; + return true; +} + +static void +ao_close(struct audio_output *ao, bool drain); + +static void +ao_disable(struct audio_output *ao) +{ + if (ao->open) + ao_close(ao, false); + + if (ao->really_enabled) { + ao->really_enabled = false; + + g_mutex_unlock(ao->mutex); + ao_plugin_disable(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + } +} + +static const struct audio_format * +ao_filter_open(struct audio_output *ao, + struct audio_format *audio_format, + GError **error_r) +{ + /* the replay_gain filter cannot fail here */ + if (ao->replay_gain_filter != NULL) + filter_open(ao->replay_gain_filter, audio_format, error_r); + if (ao->other_replay_gain_filter != NULL) + filter_open(ao->other_replay_gain_filter, audio_format, + error_r); + + const struct audio_format *af + = filter_open(ao->filter, audio_format, error_r); + if (af == NULL) { + if (ao->replay_gain_filter != NULL) + filter_close(ao->replay_gain_filter); + if (ao->other_replay_gain_filter != NULL) + filter_close(ao->other_replay_gain_filter); + } + + return af; +} + +static void +ao_filter_close(struct audio_output *ao) +{ + if (ao->replay_gain_filter != NULL) + filter_close(ao->replay_gain_filter); + if (ao->other_replay_gain_filter != NULL) + filter_close(ao->other_replay_gain_filter); + + filter_close(ao->filter); } static void -ao_close(struct audio_output *ao) +ao_open(struct audio_output *ao) +{ + bool success; + GError *error = NULL; + const struct audio_format *filter_audio_format; + struct audio_format_string af_string; + + assert(!ao->open); + assert(ao->fail_timer == NULL); + assert(ao->pipe != NULL); + assert(ao->chunk == NULL); + + /* enable the device (just in case the last enable has failed) */ + + if (!ao_enable(ao)) + /* still no luck */ + return; + + /* open the filter */ + + filter_audio_format = ao_filter_open(ao, &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; + } + + ao->out_audio_format = *filter_audio_format; + audio_format_mask_apply(&ao->out_audio_format, + &ao->config_audio_format); + + g_mutex_unlock(ao->mutex); + success = ao_plugin_open(ao->plugin, ao->data, + &ao->out_audio_format, + &error); + g_mutex_lock(ao->mutex); + + assert(!ao->open); + + if (!success) { + g_warning("Failed to open \"%s\" [%s]: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + ao_filter_close(ao); + ao->fail_timer = g_timer_new(); + return; + } + + convert_filter_set(ao->convert_filter, &ao->out_audio_format); + + ao->open = true; + + g_debug("opened plugin=%s name=\"%s\" " + "audio_format=%s", + ao->plugin->name, ao->name, + audio_format_to_string(&ao->out_audio_format, &af_string)); + + if (!audio_format_equals(&ao->in_audio_format, + &ao->out_audio_format)) + g_debug("converting from %s", + audio_format_to_string(&ao->in_audio_format, + &af_string)); +} + +static void +ao_close(struct audio_output *ao, bool drain) { assert(ao->open); ao->pipe = NULL; - g_mutex_lock(ao->mutex); ao->chunk = NULL; ao->open = false; + g_mutex_unlock(ao->mutex); + if (drain) + ao_plugin_drain(ao->plugin, ao->data); + else + ao_plugin_cancel(ao->plugin, ao->data); + ao_plugin_close(ao->plugin, ao->data); - pcm_convert_deinit(&ao->convert_state); + ao_filter_close(ao); + + g_mutex_lock(ao->mutex); g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name); } -static bool -ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) +static void +ao_reopen_filter(struct audio_output *ao) { - const char *data = chunk->data; - size_t size = chunk->length; + const struct audio_format *filter_audio_format; GError *error = NULL; + ao_filter_close(ao); + filter_audio_format = ao_filter_open(ao, &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; + + ao->chunk = NULL; + ao->open = false; + ao->fail_timer = g_timer_new(); + + g_mutex_unlock(ao->mutex); + ao_plugin_close(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + + return; + } + + convert_filter_set(ao->convert_filter, &ao->out_audio_format); +} + +static void +ao_reopen(struct audio_output *ao) +{ + if (!audio_format_fully_defined(&ao->config_audio_format)) { + if (ao->open) { + const struct music_pipe *mp = ao->pipe; + ao_close(ao, true); + 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; + audio_format_mask_apply(&ao->out_audio_format, + &ao->config_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 const char * +ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, + struct filter *replay_gain_filter, + unsigned *replay_gain_serial_p, + size_t *length_r) +{ + assert(chunk != 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); - if (chunk->tag != NULL) + const char *data = chunk->data; + size_t length = chunk->length; + + (void)ao; + + assert(length % audio_format_frame_size(&ao->in_audio_format) == 0); + + if (length > 0 && replay_gain_filter != NULL) { + if (chunk->replay_gain_serial != *replay_gain_serial_p) { + replay_gain_filter_set_info(replay_gain_filter, + chunk->replay_gain_serial != 0 + ? &chunk->replay_gain_info + : NULL); + *replay_gain_serial_p = chunk->replay_gain_serial; + } + + GError *error = NULL; + data = filter_filter(replay_gain_filter, data, length, + &length, &error); + if (data == NULL) { + g_warning("\"%s\" [%s] failed to filter: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + return NULL; + } + } + + *length_r = length; + return data; +} + +static const char * +ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, + size_t *length_r) +{ + GError *error = NULL; + + size_t length; + const char *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter, + &ao->replay_gain_serial, &length); + if (data == NULL) + return NULL; + + if (length == 0) { + /* empty chunk, nothing to do */ + *length_r = 0; + return data; + } + + /* cross-fade */ + + if (chunk->other != NULL) { + size_t other_length; + const char *other_data = + ao_chunk_data(ao, chunk->other, + ao->other_replay_gain_filter, + &ao->other_replay_gain_serial, + &other_length); + if (other_data == NULL) + return NULL; + + if (other_length == 0) { + *length_r = 0; + return data; + } + + /* if the "other" chunk is longer, then that trailer + is used as-is, without mixing; it is part of the + "next" song being faded in, and if there's a rest, + it means cross-fading ends here */ + + if (length > other_length) + length = other_length; + + char *dest = pcm_buffer_get(&ao->cross_fade_buffer, + other_length); + memcpy(dest, other_data, other_length); + pcm_mix(dest, data, length, &ao->in_audio_format, + 1.0 - chunk->mix_ratio); + + data = dest; + length = other_length; + } + + /* apply filter chain */ + + data = filter_filter(ao->filter, data, length, &length, &error); + if (data == NULL) { + g_warning("\"%s\" [%s] failed to filter: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + return NULL; + } + + *length_r = length; + return data; +} + +static bool +ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) +{ + GError *error = NULL; + + assert(ao != NULL); + assert(ao->filter != NULL); + + if (chunk->tag != NULL) { + g_mutex_unlock(ao->mutex); ao_plugin_send_tag(ao->plugin, ao->data, chunk->tag); + g_mutex_lock(ao->mutex); + } - if (size == 0) - return true; + size_t size; + const char *data = ao_filter_chunk(ao, chunk, &size); + if (data == NULL) { + ao_close(ao, false); - 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; + /* 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) { size_t nbytes; + g_mutex_unlock(ao->mutex); nbytes = ao_plugin_play(ao->plugin, ao->data, data, size, &error); + g_mutex_lock(ao->mutex); if (nbytes == 0) { /* play()==0 means failure */ g_warning("\"%s\" [%s] failed to play: %s", ao->name, ao->plugin->name, error->message); g_error_free(error); - ao_plugin_cancel(ao->plugin, ao->data); - ao_close(ao); + ao_close(ao, false); /* don't automatically reopen this device for 10 seconds */ @@ -119,32 +441,45 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) return true; } -static void ao_play(struct audio_output *ao) +static const struct music_chunk * +ao_next_chunk(struct audio_output *ao) +{ + return ao->chunk != NULL + /* continue the previous play() call */ + ? ao->chunk->next + /* get the first chunk from the pipe */ + : music_pipe_peek(ao->pipe); +} + +/** + * Plays all remaining chunks, until the tail of the pipe has been + * reached (and no more chunks are queued), or until a command is + * received. + * + * @return true if at least one chunk has been available, false if the + * tail of the pipe was already reached + */ +static bool +ao_play(struct audio_output *ao) { bool success; const struct music_chunk *chunk; assert(ao->pipe != NULL); - g_mutex_lock(ao->mutex); - chunk = ao->chunk; - if (chunk != NULL) - /* continue the previous play() call */ - chunk = chunk->next; - else - chunk = music_pipe_peek(ao->pipe); + chunk = ao_next_chunk(ao); + if (chunk == NULL) + /* no chunk available */ + return false; + ao->chunk_finished = false; while (chunk != NULL && ao->command == AO_COMMAND_NONE) { assert(!ao->chunk_finished); ao->chunk = chunk; - g_mutex_unlock(ao->mutex); success = ao_play_chunk(ao, chunk); - - g_mutex_lock(ao->mutex); - if (!success) { assert(ao->chunk == NULL); break; @@ -155,23 +490,32 @@ static void ao_play(struct audio_output *ao) } ao->chunk_finished = true; + g_mutex_unlock(ao->mutex); + player_lock_signal(); + g_mutex_lock(ao->mutex); - notify_signal(&pc.notify); + return true; } static void ao_pause(struct audio_output *ao) { bool ret; + g_mutex_unlock(ao->mutex); ao_plugin_cancel(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + ao->pause = true; ao_command_finished(ao); do { + g_mutex_unlock(ao->mutex); ret = ao_plugin_pause(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + if (!ret) { - ao_close(ao); + ao_close(ao, false); break; } } while (ao->command == AO_COMMAND_NONE); @@ -182,56 +526,31 @@ static void ao_pause(struct audio_output *ao) static gpointer audio_output_task(gpointer arg) { struct audio_output *ao = arg; - bool ret; - GError *error; + + g_mutex_lock(ao->mutex); while (1) { switch (ao->command) { case AO_COMMAND_NONE: 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); + case AO_COMMAND_ENABLE: + ao_enable(ao); + ao_command_finished(ao); + break; - g_mutex_lock(ao->mutex); - ao->open = true; - g_mutex_unlock(ao->mutex); + case AO_COMMAND_DISABLE: + ao_disable(ao); + ao_command_finished(ao); + break; - 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(); - } + case AO_COMMAND_OPEN: + ao_open(ao); + ao_command_finished(ao); + break; + case AO_COMMAND_REOPEN: + ao_reopen(ao); ao_command_finished(ao); break; @@ -239,11 +558,7 @@ static gpointer audio_output_task(gpointer arg) assert(ao->open); assert(ao->pipe != NULL); - ao->pipe = NULL; - ao->chunk = NULL; - - ao_plugin_cancel(ao->plugin, ao->data); - ao_close(ao); + ao_close(ao, false); ao_command_finished(ao); break; @@ -264,6 +579,19 @@ static gpointer audio_output_task(gpointer arg) the new command first */ continue; + case AO_COMMAND_DRAIN: + if (ao->open) { + assert(ao->chunk == NULL); + assert(music_pipe_peek(ao->pipe) == NULL); + + g_mutex_unlock(ao->mutex); + ao_plugin_drain(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + } + + ao_command_finished(ao); + continue; + case AO_COMMAND_CANCEL: ao->chunk = NULL; if (ao->open) @@ -273,19 +601,24 @@ static gpointer audio_output_task(gpointer arg) /* the player thread will now clear our music pipe - wait for a notify, to give it some time */ - notify_wait(&ao->notify); + if (ao->command == AO_COMMAND_NONE) + g_cond_wait(ao->cond, ao->mutex); continue; case AO_COMMAND_KILL: ao->chunk = NULL; ao_command_finished(ao); + g_mutex_unlock(ao->mutex); return NULL; } - if (ao->open) - ao_play(ao); + if (ao->open && ao_play(ao)) + /* don't wait for an event if there are more + chunks in the pipe */ + continue; - notify_wait(&ao->notify); + if (ao->command == AO_COMMAND_NONE) + g_cond_wait(ao->cond, ao->mutex); } } |