From de0ab43bc12be345d85779e444266153ff5be007 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 4 Jan 2013 09:46:41 +0100 Subject: output_*: convert to C++ --- src/CommandLine.cxx | 2 +- src/OutputAll.cxx | 608 +++++++++++++++++++++++++++++++++++++++++++ src/OutputCommand.cxx | 90 +++++++ src/OutputCommand.hxx | 44 ++++ src/OutputCommands.cxx | 5 +- src/OutputControl.cxx | 340 ++++++++++++++++++++++++ src/OutputControl.hxx | 93 +++++++ src/OutputError.hxx | 35 +++ src/OutputFinish.cxx | 63 +++++ src/OutputInit.cxx | 336 ++++++++++++++++++++++++ src/OutputList.cxx | 106 ++++++++ src/OutputList.hxx | 33 +++ src/OutputPlugin.cxx | 113 ++++++++ src/OutputThread.cxx | 690 +++++++++++++++++++++++++++++++++++++++++++++++++ src/OutputThread.hxx | 27 ++ src/output_all.c | 601 ------------------------------------------ src/output_command.c | 87 ------- src/output_command.h | 46 ---- src/output_control.c | 336 ------------------------ src/output_control.h | 94 ------- src/output_error.h | 35 --- src/output_finish.c | 60 ----- src/output_init.c | 333 ------------------------ src/output_list.c | 106 -------- src/output_list.h | 33 --- src/output_plugin.c | 109 -------- src/output_thread.c | 685 ------------------------------------------------ src/output_thread.h | 27 -- 28 files changed, 2580 insertions(+), 2557 deletions(-) create mode 100644 src/OutputAll.cxx create mode 100644 src/OutputCommand.cxx create mode 100644 src/OutputCommand.hxx create mode 100644 src/OutputControl.cxx create mode 100644 src/OutputControl.hxx create mode 100644 src/OutputError.hxx create mode 100644 src/OutputFinish.cxx create mode 100644 src/OutputInit.cxx create mode 100644 src/OutputList.cxx create mode 100644 src/OutputList.hxx create mode 100644 src/OutputPlugin.cxx create mode 100644 src/OutputThread.cxx create mode 100644 src/OutputThread.hxx delete mode 100644 src/output_all.c delete mode 100644 src/output_command.c delete mode 100644 src/output_command.h delete mode 100644 src/output_control.c delete mode 100644 src/output_control.h delete mode 100644 src/output_error.h delete mode 100644 src/output_finish.c delete mode 100644 src/output_init.c delete mode 100644 src/output_list.c delete mode 100644 src/output_list.h delete mode 100644 src/output_plugin.c delete mode 100644 src/output_thread.c delete mode 100644 src/output_thread.h (limited to 'src') diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx index a588ad5b2..4d9d2ca9d 100644 --- a/src/CommandLine.cxx +++ b/src/CommandLine.cxx @@ -29,7 +29,7 @@ extern "C" { #include "decoder_list.h" #include "decoder_plugin.h" -#include "output_list.h" +#include "OutputList.hxx" #include "output_plugin.h" #include "input_registry.h" #include "input_plugin.h" diff --git a/src/OutputAll.cxx b/src/OutputAll.cxx new file mode 100644 index 000000000..ec716a411 --- /dev/null +++ b/src/OutputAll.cxx @@ -0,0 +1,608 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +extern "C" { +#include "output_all.h" +#include "output_internal.h" +} + +#include "OutputControl.hxx" +#include "OutputError.hxx" +#include "mpd_error.h" + +extern "C" { +#include "player_control.h" +#include "conf.h" +#include "chunk.h" +#include "pipe.h" +#include "buffer.h" +#include "notify.h" +} + +#ifndef NDEBUG +#include "chunk.h" +#endif + +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "output" + +static struct audio_format input_audio_format; + +static struct audio_output **audio_outputs; +static unsigned int num_audio_outputs; + +/** + * The #music_buffer object where consumed chunks are returned. + */ +static struct music_buffer *g_music_buffer; + +/** + * The #music_pipe object which feeds all audio outputs. It is filled + * by audio_output_all_play(). + */ +static struct music_pipe *g_mp; + +/** + * The "elapsed_time" stamp of the most recently finished chunk. + */ +static float audio_output_all_elapsed_time = -1.0; + +unsigned int audio_output_count(void) +{ + return num_audio_outputs; +} + +struct audio_output * +audio_output_get(unsigned i) +{ + assert(i < num_audio_outputs); + + assert(audio_outputs[i] != NULL); + + return audio_outputs[i]; +} + +struct audio_output * +audio_output_find(const char *name) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = audio_output_get(i); + + if (strcmp(ao->name, name) == 0) + return ao; + } + + /* name not found */ + return NULL; +} + +static unsigned +audio_output_config_count(void) +{ + unsigned int nr = 0; + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param))) + nr++; + if (!nr) + nr = 1; /* we'll always have at least one device */ + return nr; +} + +void +audio_output_all_init(struct player_control *pc) +{ + const struct config_param *param = NULL; + unsigned int i; + GError *error = NULL; + + notify_init(&audio_output_client_notify); + + num_audio_outputs = audio_output_config_count(); + audio_outputs = g_new(struct audio_output *, num_audio_outputs); + + for (i = 0; i < num_audio_outputs; i++) + { + unsigned int j; + + param = config_get_next_param(CONF_AUDIO_OUTPUT, param); + + /* only allow param to be NULL if there just one audioOutput */ + assert(param || (num_audio_outputs == 1)); + + struct audio_output *output = audio_output_new(param, pc, &error); + if (output == NULL) { + if (param != NULL) + MPD_ERROR("line %i: %s", + param->line, error->message); + else + MPD_ERROR("%s", error->message); + } + + audio_outputs[i] = output; + + /* require output names to be unique: */ + for (j = 0; j < i; j++) { + if (!strcmp(output->name, audio_outputs[j]->name)) { + MPD_ERROR("output devices with identical " + "names: %s\n", output->name); + } + } + } +} + +void +audio_output_all_finish(void) +{ + unsigned int i; + + for (i = 0; i < num_audio_outputs; i++) { + audio_output_disable(audio_outputs[i]); + audio_output_finish(audio_outputs[i]); + } + + g_free(audio_outputs); + audio_outputs = NULL; + num_audio_outputs = 0; + + notify_deinit(&audio_output_client_notify); +} + +void +audio_output_all_enable_disable(void) +{ + for (unsigned i = 0; i < num_audio_outputs; i++) { + struct audio_output *ao = audio_outputs[i]; + bool enabled; + + g_mutex_lock(ao->mutex); + enabled = ao->really_enabled; + g_mutex_unlock(ao->mutex); + + if (ao->enabled != enabled) { + if (ao->enabled) + audio_output_enable(ao); + else + audio_output_disable(ao); + } + } +} + +/** + * Determine if all (active) outputs have finished the current + * command. + */ +static bool +audio_output_all_finished(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = audio_outputs[i]; + bool not_finished; + + g_mutex_lock(ao->mutex); + not_finished = audio_output_is_open(ao) && + !audio_output_command_is_finished(ao); + g_mutex_unlock(ao->mutex); + + if (not_finished) + return false; + } + + return true; +} + +static void audio_output_wait_all(void) +{ + while (!audio_output_all_finished()) + notify_wait(&audio_output_client_notify); +} + +/** + * Signals all audio outputs which are open. + */ +static void +audio_output_allow_play_all(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_allow_play(audio_outputs[i]); +} + +static void +audio_output_reset_reopen(struct audio_output *ao) +{ + g_mutex_lock(ao->mutex); + + if (!ao->open && ao->fail_timer != NULL) { + g_timer_destroy(ao->fail_timer); + ao->fail_timer = NULL; + } + + g_mutex_unlock(ao->mutex); +} + +/** + * Resets the "reopen" flag on all audio devices. MPD should + * immediately retry to open the device instead of waiting for the + * timeout when the user wants to start playback. + */ +static void +audio_output_all_reset_reopen(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = audio_outputs[i]; + + audio_output_reset_reopen(ao); + } +} + +/** + * Opens all output devices which are enabled, but closed. + * + * @return true if there is at least open output device which is open + */ +static bool +audio_output_all_update(void) +{ + unsigned int i; + bool ret = false; + + if (!audio_format_defined(&input_audio_format)) + return false; + + for (i = 0; i < num_audio_outputs; ++i) + ret = audio_output_update(audio_outputs[i], + &input_audio_format, g_mp) || ret; + + return ret; +} + +bool +audio_output_all_play(struct music_chunk *chunk, GError **error_r) +{ + bool ret; + unsigned int i; + + assert(g_music_buffer != NULL); + assert(g_mp != NULL); + assert(chunk != NULL); + assert(music_chunk_check_format(chunk, &input_audio_format)); + + ret = audio_output_all_update(); + if (!ret) { + /* TODO: obtain real error */ + g_set_error(error_r, output_quark(), 0, + "Failed to open audio output"); + return false; + } + + music_pipe_push(g_mp, chunk); + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_play(audio_outputs[i]); + + return true; +} + +bool +audio_output_all_open(const struct audio_format *audio_format, + struct music_buffer *buffer, + GError **error_r) +{ + bool ret = false, enabled = false; + unsigned int i; + + assert(audio_format != NULL); + assert(buffer != NULL); + assert(g_music_buffer == NULL || g_music_buffer == buffer); + assert((g_mp == NULL) == (g_music_buffer == NULL)); + + g_music_buffer = buffer; + + /* the audio format must be the same as existing chunks in the + pipe */ + assert(g_mp == NULL || music_pipe_check_format(g_mp, audio_format)); + + if (g_mp == NULL) + g_mp = music_pipe_new(); + else + /* if the pipe hasn't been cleared, the the audio + format must not have changed */ + assert(music_pipe_empty(g_mp) || + audio_format_equals(audio_format, + &input_audio_format)); + + input_audio_format = *audio_format; + + audio_output_all_reset_reopen(); + audio_output_all_enable_disable(); + audio_output_all_update(); + + for (i = 0; i < num_audio_outputs; ++i) { + if (audio_outputs[i]->enabled) + enabled = true; + + if (audio_outputs[i]->open) + ret = true; + } + + if (!enabled) + g_set_error(error_r, output_quark(), 0, + "All audio outputs are disabled"); + else if (!ret) + /* TODO: obtain real error */ + g_set_error(error_r, output_quark(), 0, + "Failed to open audio output"); + + if (!ret) + /* close all devices if there was an error */ + audio_output_all_close(); + + return ret; +} + +/** + * Has the specified audio output already consumed this chunk? + */ +static bool +chunk_is_consumed_in(const struct audio_output *ao, + const struct music_chunk *chunk) +{ + if (!ao->open) + return true; + + if (ao->chunk == NULL) + return false; + + assert(chunk == ao->chunk || music_pipe_contains(g_mp, ao->chunk)); + + if (chunk != ao->chunk) { + assert(chunk->next != NULL); + return true; + } + + return ao->chunk_finished && chunk->next == NULL; +} + +/** + * Has this chunk been consumed by all audio outputs? + */ +static bool +chunk_is_consumed(const struct music_chunk *chunk) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) { + const struct audio_output *ao = audio_outputs[i]; + bool consumed; + + g_mutex_lock(ao->mutex); + consumed = chunk_is_consumed_in(ao, chunk); + g_mutex_unlock(ao->mutex); + + if (!consumed) + return false; + } + + return true; +} + +/** + * There's only one chunk left in the pipe (#g_mp), and all audio + * outputs have consumed it already. Clear the reference. + */ +static void +clear_tail_chunk(G_GNUC_UNUSED const struct music_chunk *chunk, bool *locked) +{ + assert(chunk->next == NULL); + assert(music_pipe_contains(g_mp, chunk)); + + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = audio_outputs[i]; + + /* this mutex will be unlocked by the caller when it's + ready */ + g_mutex_lock(ao->mutex); + locked[i] = ao->open; + + if (!locked[i]) { + g_mutex_unlock(ao->mutex); + continue; + } + + assert(ao->chunk == chunk); + assert(ao->chunk_finished); + ao->chunk = NULL; + } +} + +unsigned +audio_output_all_check(void) +{ + const struct music_chunk *chunk; + bool is_tail; + struct music_chunk *shifted; + bool locked[num_audio_outputs]; + + assert(g_music_buffer != NULL); + assert(g_mp != NULL); + + while ((chunk = music_pipe_peek(g_mp)) != NULL) { + assert(!music_pipe_empty(g_mp)); + + if (!chunk_is_consumed(chunk)) + /* at least one output is not finished playing + this chunk */ + return music_pipe_size(g_mp); + + if (chunk->length > 0 && chunk->times >= 0.0) + /* only update elapsed_time if the chunk + provides a defined value */ + audio_output_all_elapsed_time = chunk->times; + + is_tail = chunk->next == NULL; + if (is_tail) + /* this is the tail of the pipe - clear the + chunk reference in all outputs */ + clear_tail_chunk(chunk, locked); + + /* remove the chunk from the pipe */ + shifted = music_pipe_shift(g_mp); + assert(shifted == chunk); + + if (is_tail) + /* unlock all audio outputs which were locked + by clear_tail_chunk() */ + for (unsigned i = 0; i < num_audio_outputs; ++i) + if (locked[i]) + g_mutex_unlock(audio_outputs[i]->mutex); + + /* return the chunk to the buffer */ + music_buffer_return(g_music_buffer, shifted); + } + + return 0; +} + +bool +audio_output_all_wait(struct player_control *pc, unsigned threshold) +{ + player_lock(pc); + + if (audio_output_all_check() < threshold) { + player_unlock(pc); + return true; + } + + player_wait(pc); + player_unlock(pc); + + return audio_output_all_check() < threshold; +} + +void +audio_output_all_pause(void) +{ + unsigned int i; + + audio_output_all_update(); + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_pause(audio_outputs[i]); + + audio_output_wait_all(); +} + +void +audio_output_all_drain(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_drain_async(audio_outputs[i]); + + audio_output_wait_all(); +} + +void +audio_output_all_cancel(void) +{ + unsigned int i; + + /* send the cancel() command to all audio outputs */ + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_cancel(audio_outputs[i]); + + audio_output_wait_all(); + + /* clear the music pipe and return all chunks to the buffer */ + + if (g_mp != NULL) + music_pipe_clear(g_mp, g_music_buffer); + + /* the audio outputs are now waiting for a signal, to + synchronize the cleared music pipe */ + + audio_output_allow_play_all(); + + /* invalidate elapsed_time */ + + audio_output_all_elapsed_time = -1.0; +} + +void +audio_output_all_close(void) +{ + unsigned int i; + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_close(audio_outputs[i]); + + if (g_mp != NULL) { + assert(g_music_buffer != NULL); + + music_pipe_clear(g_mp, g_music_buffer); + music_pipe_free(g_mp); + g_mp = NULL; + } + + g_music_buffer = NULL; + + audio_format_clear(&input_audio_format); + + audio_output_all_elapsed_time = -1.0; +} + +void +audio_output_all_release(void) +{ + unsigned int i; + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_release(audio_outputs[i]); + + if (g_mp != NULL) { + assert(g_music_buffer != NULL); + + music_pipe_clear(g_mp, g_music_buffer); + music_pipe_free(g_mp); + g_mp = NULL; + } + + g_music_buffer = NULL; + + audio_format_clear(&input_audio_format); + + audio_output_all_elapsed_time = -1.0; +} + +void +audio_output_all_song_border(void) +{ + /* clear the elapsed_time pointer at the beginning of a new + song */ + audio_output_all_elapsed_time = 0.0; +} + +float +audio_output_all_get_elapsed_time(void) +{ + return audio_output_all_elapsed_time; +} diff --git a/src/OutputCommand.cxx b/src/OutputCommand.cxx new file mode 100644 index 000000000..ed4cd8e88 --- /dev/null +++ b/src/OutputCommand.cxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Glue functions for controlling the audio outputs over the MPD + * protocol. These functions perform extra validation on all + * parameters, because they might be from an untrusted source. + * + */ + +#include "config.h" +#include "OutputCommand.hxx" + +extern "C" { +#include "output_all.h" +#include "output_internal.h" +#include "output_plugin.h" +#include "mixer_control.h" +#include "player_control.h" +#include "idle.h" +} + +extern unsigned audio_output_state_version; + +bool +audio_output_enable_index(unsigned idx) +{ + struct audio_output *ao; + + if (idx >= audio_output_count()) + return false; + + ao = audio_output_get(idx); + if (ao->enabled) + return true; + + ao->enabled = true; + idle_add(IDLE_OUTPUT); + + pc_update_audio(ao->player_control); + + ++audio_output_state_version; + + return true; +} + +bool +audio_output_disable_index(unsigned idx) +{ + struct audio_output *ao; + struct mixer *mixer; + + if (idx >= audio_output_count()) + return false; + + ao = audio_output_get(idx); + if (!ao->enabled) + return true; + + ao->enabled = false; + idle_add(IDLE_OUTPUT); + + mixer = ao->mixer; + if (mixer != NULL) { + mixer_close(mixer); + idle_add(IDLE_MIXER); + } + + pc_update_audio(ao->player_control); + + ++audio_output_state_version; + + return true; +} diff --git a/src/OutputCommand.hxx b/src/OutputCommand.hxx new file mode 100644 index 000000000..74eaf8f1c --- /dev/null +++ b/src/OutputCommand.hxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2013 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. + */ + +/* + * Glue functions for controlling the audio outputs over the MPD + * protocol. These functions perform extra validation on all + * parameters, because they might be from an untrusted source. + * + */ + +#ifndef MPD_OUTPUT_COMMAND_HXX +#define MPD_OUTPUT_COMMAND_HXX + +/** + * Enables an audio output. Returns false if the specified output + * does not exist. + */ +bool +audio_output_enable_index(unsigned idx); + +/** + * Disables an audio output. Returns false if the specified output + * does not exist. + */ +bool +audio_output_disable_index(unsigned idx); + +#endif diff --git a/src/OutputCommands.cxx b/src/OutputCommands.cxx index 4dd689c4e..7d626477a 100644 --- a/src/OutputCommands.cxx +++ b/src/OutputCommands.cxx @@ -20,13 +20,10 @@ #include "config.h" #include "OutputCommands.hxx" #include "OutputPrint.hxx" +#include "OutputCommand.hxx" #include "protocol/Result.hxx" #include "protocol/ArgParser.hxx" -extern "C" { -#include "output_command.h" -} - #include enum command_return diff --git a/src/OutputControl.cxx b/src/OutputControl.cxx new file mode 100644 index 000000000..36e80a014 --- /dev/null +++ b/src/OutputControl.cxx @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputControl.hxx" +#include "OutputThread.hxx" + +extern "C" { +#include "output_api.h" +#include "output_internal.h" +#include "mixer_control.h" +#include "mixer_plugin.h" +#include "notify.h" +} + +#include "filter_plugin.h" + +#include +#include + +enum { + /** after a failure, wait this number of seconds before + automatically reopening the device */ + REOPEN_AFTER = 10, +}; + +struct notify audio_output_client_notify; + +/** + * Waits for command completion. + * + * @param ao the #audio_output instance; must be locked + */ +static void ao_command_wait(struct audio_output *ao) +{ + while (ao->command != AO_COMMAND_NONE) { + g_mutex_unlock(ao->mutex); + notify_wait(&audio_output_client_notify); + g_mutex_lock(ao->mutex); + } +} + +/** + * Sends a command to the #audio_output object, but does not wait for + * completion. + * + * @param ao the #audio_output instance; must be locked + */ +static void ao_command_async(struct audio_output *ao, + enum audio_output_command cmd) +{ + assert(ao->command == AO_COMMAND_NONE); + ao->command = cmd; + g_cond_signal(ao->cond); +} + +/** + * Sends a command to the #audio_output object and waits for + * completion. + * + * @param ao the #audio_output instance; must be locked + */ +static void +ao_command(struct audio_output *ao, enum audio_output_command cmd) +{ + ao_command_async(ao, cmd); + ao_command_wait(ao); +} + +/** + * Lock the #audio_output object and execute the command + * synchronously. + */ +static void +ao_lock_command(struct audio_output *ao, enum audio_output_command cmd) +{ + g_mutex_lock(ao->mutex); + ao_command(ao, cmd); + g_mutex_unlock(ao->mutex); +} + +void +audio_output_enable(struct audio_output *ao) +{ + if (ao->thread == NULL) { + if (ao->plugin->enable == NULL) { + /* don't bother to start the thread now if the + device doesn't even have a enable() method; + just assign the variable and we're done */ + ao->really_enabled = true; + return; + } + + audio_output_thread_start(ao); + } + + ao_lock_command(ao, AO_COMMAND_ENABLE); +} + +void +audio_output_disable(struct audio_output *ao) +{ + if (ao->thread == NULL) { + if (ao->plugin->disable == NULL) + ao->really_enabled = false; + else + /* if there's no thread yet, the device cannot + be enabled */ + assert(!ao->really_enabled); + + return; + } + + ao_lock_command(ao, AO_COMMAND_DISABLE); +} + +/** + * Object must be locked (and unlocked) by the caller. + */ +static bool +audio_output_open(struct audio_output *ao, + const struct audio_format *audio_format, + const struct music_pipe *mp) +{ + bool open; + + assert(ao != NULL); + assert(ao->allow_play); + assert(audio_format_valid(audio_format)); + assert(mp != NULL); + + if (ao->fail_timer != NULL) { + g_timer_destroy(ao->fail_timer); + ao->fail_timer = NULL; + } + + if (ao->open && + audio_format_equals(audio_format, &ao->in_audio_format)) { + assert(ao->pipe == mp || + (ao->always_on && ao->pause)); + + if (ao->pause) { + ao->chunk = NULL; + ao->pipe = mp; + + /* unpause with the CANCEL command; this is a + hack, but suits well for forcing the thread + to leave the ao_pause() thread, and we need + to flush the device buffer anyway */ + + /* we're not using audio_output_cancel() here, + because that function is asynchronous */ + ao_command(ao, AO_COMMAND_CANCEL); + } + + return true; + } + + ao->in_audio_format = *audio_format; + ao->chunk = NULL; + + 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->mixer != NULL) { + GError *error = NULL; + + if (!mixer_open(ao->mixer, &error)) { + g_warning("Failed to open mixer for '%s': %s", + ao->name, error->message); + g_error_free(error); + } + } + + return open; +} + +/** + * Same as audio_output_close(), but expects the lock to be held by + * the caller. + */ +static void +audio_output_close_locked(struct audio_output *ao) +{ + assert(ao != NULL); + assert(ao->allow_play); + + if (ao->mixer != NULL) + mixer_auto_close(ao->mixer); + + assert(!ao->open || ao->fail_timer == NULL); + + if (ao->open) + ao_command(ao, AO_COMMAND_CLOSE); + else if (ao->fail_timer != NULL) { + g_timer_destroy(ao->fail_timer); + ao->fail_timer = NULL; + } +} + +bool +audio_output_update(struct audio_output *ao, + const struct audio_format *audio_format, + const struct music_pipe *mp) +{ + assert(mp != NULL); + + g_mutex_lock(ao->mutex); + + if (ao->enabled && ao->really_enabled) { + if (ao->fail_timer == NULL || + g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) { + bool success = audio_output_open(ao, audio_format, mp); + g_mutex_unlock(ao->mutex); + return success; + } + } else if (audio_output_is_open(ao)) + audio_output_close_locked(ao); + + g_mutex_unlock(ao->mutex); + return false; +} + +void +audio_output_play(struct audio_output *ao) +{ + g_mutex_lock(ao->mutex); + + assert(ao->allow_play); + + if (audio_output_is_open(ao)) + g_cond_signal(ao->cond); + + g_mutex_unlock(ao->mutex); +} + +void audio_output_pause(struct audio_output *ao) +{ + if (ao->mixer != NULL && ao->plugin->pause == NULL) + /* the device has no pause mode: close the mixer, + unless its "global" flag is set (checked by + mixer_auto_close()) */ + mixer_auto_close(ao->mixer); + + g_mutex_lock(ao->mutex); + assert(ao->allow_play); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_PAUSE); + g_mutex_unlock(ao->mutex); +} + +void +audio_output_drain_async(struct audio_output *ao) +{ + g_mutex_lock(ao->mutex); + assert(ao->allow_play); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_DRAIN); + g_mutex_unlock(ao->mutex); +} + +void audio_output_cancel(struct audio_output *ao) +{ + g_mutex_lock(ao->mutex); + + if (audio_output_is_open(ao)) { + ao->allow_play = false; + ao_command_async(ao, AO_COMMAND_CANCEL); + } + + g_mutex_unlock(ao->mutex); +} + +void +audio_output_allow_play(struct audio_output *ao) +{ + g_mutex_lock(ao->mutex); + + ao->allow_play = true; + if (audio_output_is_open(ao)) + g_cond_signal(ao->cond); + + g_mutex_unlock(ao->mutex); +} + +void +audio_output_release(struct audio_output *ao) +{ + if (ao->always_on) + audio_output_pause(ao); + else + audio_output_close(ao); +} + +void audio_output_close(struct audio_output *ao) +{ + assert(ao != NULL); + assert(!ao->open || ao->fail_timer == NULL); + + g_mutex_lock(ao->mutex); + audio_output_close_locked(ao); + g_mutex_unlock(ao->mutex); +} + +void audio_output_finish(struct audio_output *ao) +{ + audio_output_close(ao); + + assert(ao->fail_timer == NULL); + + if (ao->thread != NULL) { + assert(ao->allow_play); + ao_lock_command(ao, AO_COMMAND_KILL); + g_thread_join(ao->thread); + ao->thread = NULL; + } + + audio_output_free(ao); +} diff --git a/src/OutputControl.hxx b/src/OutputControl.hxx new file mode 100644 index 000000000..90769180f --- /dev/null +++ b/src/OutputControl.hxx @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2013 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_OUTPUT_CONTROL_HXX +#define MPD_OUTPUT_CONTROL_HXX + +#include + +#include + +struct audio_output; +struct audio_format; +struct config_param; +struct music_pipe; +struct player_control; + +static inline GQuark +audio_output_quark(void) +{ + return g_quark_from_static_string("audio_output"); +} + +/** + * Enables the device. + */ +void +audio_output_enable(struct audio_output *ao); + +/** + * Disables the device. + */ +void +audio_output_disable(struct audio_output *ao); + +/** + * Opens or closes the device, depending on the "enabled" flag. + * + * @return true if the device is open + */ +bool +audio_output_update(struct audio_output *ao, + const struct audio_format *audio_format, + const struct music_pipe *mp); + +void +audio_output_play(struct audio_output *ao); + +void audio_output_pause(struct audio_output *ao); + +void +audio_output_drain_async(struct audio_output *ao); + +/** + * Clear the "allow_play" flag and send the "CANCEL" command + * asynchronously. To finish the operation, the caller has to call + * audio_output_allow_play(). + */ +void audio_output_cancel(struct audio_output *ao); + +/** + * Set the "allow_play" and signal the thread. + */ +void +audio_output_allow_play(struct audio_output *ao); + +void audio_output_close(struct audio_output *ao); + +/** + * Closes the audio output, but if the "always_on" flag is set, put it + * into pause mode instead. + */ +void +audio_output_release(struct audio_output *ao); + +void audio_output_finish(struct audio_output *ao); + +#endif diff --git a/src/OutputError.hxx b/src/OutputError.hxx new file mode 100644 index 000000000..451df9857 --- /dev/null +++ b/src/OutputError.hxx @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2013 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_OUTPUT_ERROR_HXX +#define MPD_OUTPUT_ERROR_HXX + +#include + +/** + * Quark for GError.domain. + */ +G_GNUC_CONST +static inline GQuark +output_quark(void) +{ + return g_quark_from_static_string("output"); +} + +#endif diff --git a/src/OutputFinish.cxx b/src/OutputFinish.cxx new file mode 100644 index 000000000..ac6ad6977 --- /dev/null +++ b/src/OutputFinish.cxx @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +extern "C" { +#include "output_internal.h" +#include "output_plugin.h" +#include "mixer_control.h" +#include "filter_plugin.h" +} + +#include + +void +ao_base_finish(struct audio_output *ao) +{ + assert(!ao->open); + assert(ao->fail_timer == NULL); + assert(ao->thread == NULL); + + if (ao->mixer != NULL) + mixer_free(ao->mixer); + + g_cond_free(ao->cond); + g_mutex_free(ao->mutex); + + if (ao->replay_gain_filter != NULL) + filter_free(ao->replay_gain_filter); + + if (ao->other_replay_gain_filter != NULL) + filter_free(ao->other_replay_gain_filter); + + filter_free(ao->filter); + + pcm_buffer_deinit(&ao->cross_fade_buffer); +} + +void +audio_output_free(struct audio_output *ao) +{ + assert(!ao->open); + assert(ao->fail_timer == NULL); + assert(ao->thread == NULL); + + ao_plugin_finish(ao); +} diff --git a/src/OutputInit.cxx b/src/OutputInit.cxx new file mode 100644 index 000000000..5fc800d19 --- /dev/null +++ b/src/OutputInit.cxx @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputControl.hxx" +#include "OutputList.hxx" + +extern "C" { +#include "output_api.h" +#include "output_internal.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_config.h" +#include "filter/chain_filter_plugin.h" +#include "filter/autoconvert_filter_plugin.h" +#include "filter/replay_gain_filter_plugin.h" +} + +#include + +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "output" + +#define AUDIO_OUTPUT_TYPE "type" +#define AUDIO_OUTPUT_NAME "name" +#define AUDIO_OUTPUT_FORMAT "format" +#define AUDIO_FILTERS "filters" + +static const struct audio_output_plugin * +audio_output_detect(GError **error) +{ + g_warning("Attempt to detect audio output device"); + + audio_output_plugins_for_each(plugin) { + if (plugin->test_default_device == NULL) + continue; + + g_warning("Attempting to detect a %s audio device", + plugin->name); + if (ao_plugin_test_default_device(plugin)) + return plugin; + } + + g_set_error(error, audio_output_quark(), 0, + "Unable to detect an audio device"); + 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(struct audio_output *ao, + const struct config_param *param, + const struct mixer_plugin *plugin, + struct filter *filter_chain, + GError **error_r) +{ + 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, ao, param, error_r); + + case MIXER_TYPE_SOFTWARE: + mixer = mixer_new(&software_mixer_plugin, NULL, NULL, NULL); + assert(mixer != NULL); + + filter_chain_append(filter_chain, + software_mixer_get_filter(mixer)); + return mixer; + } + + assert(false); + return NULL; +} + +bool +ao_base_init(struct audio_output *ao, + const struct audio_output_plugin *plugin, + const struct config_param *param, GError **error_r) +{ + assert(ao != NULL); + assert(plugin != NULL); + assert(plugin->finish != NULL); + assert(plugin->open != NULL); + assert(plugin->close != NULL); + assert(plugin->play != NULL); + + GError *error = NULL; + + if (param) { + const char *p; + + ao->name = config_get_block_string(param, AUDIO_OUTPUT_NAME, + NULL); + if (ao->name == NULL) { + g_set_error(error_r, audio_output_quark(), 0, + "Missing \"name\" configuration"); + return false; + } + + p = config_get_block_string(param, AUDIO_OUTPUT_FORMAT, + NULL); + if (p != NULL) { + bool success = + audio_format_parse(&ao->config_audio_format, + p, true, error_r); + if (!success) + return false; + } else + audio_format_clear(&ao->config_audio_format); + } else { + ao->name = "default detected output"; + + audio_format_clear(&ao->config_audio_format); + } + + ao->plugin = plugin; + ao->tags = config_get_block_bool(param, "tags", true); + ao->always_on = config_get_block_bool(param, "always_on", false); + ao->enabled = config_get_block_bool(param, "enabled", true); + ao->really_enabled = false; + ao->open = false; + ao->pause = false; + ao->allow_play = true; + ao->fail_timer = NULL; + + pcm_buffer_init(&ao->cross_fade_buffer); + + /* set up the filter chain */ + + ao->filter = filter_chain_new(); + assert(ao->filter != NULL); + + /* create the normalization filter (if configured) */ + + if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) { + struct filter *normalize_filter = + filter_new(&normalize_filter_plugin, NULL, NULL); + assert(normalize_filter != NULL); + + filter_chain_append(ao->filter, + autoconvert_filter_new(normalize_filter)); + } + + filter_chain_parse(ao->filter, + config_get_block_string(param, AUDIO_FILTERS, ""), + &error + ); + + // It's not really fatal - Part of the filter chain has been set up already + // and even an empty one will work (if only with unexpected behaviour) + if (error != NULL) { + g_warning("Failed to initialize filter chain for '%s': %s", + ao->name, error->message); + g_error_free(error); + } + + ao->thread = NULL; + ao->command = AO_COMMAND_NONE; + ao->mutex = g_mutex_new(); + ao->cond = g_cond_new(); + + ao->mixer = NULL; + ao->replay_gain_filter = NULL; + ao->other_replay_gain_filter = NULL; + + /* done */ + + return true; +} + +static bool +audio_output_setup(struct audio_output *ao, const struct config_param *param, + GError **error_r) +{ + + /* create the replay_gain filter */ + + const char *replay_gain_handler = + config_get_block_string(param, "replay_gain_handler", + "software"); + + if (strcmp(replay_gain_handler, "none") != 0) { + ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, NULL); + assert(ao->replay_gain_filter != NULL); + + ao->replay_gain_serial = 0; + + ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, NULL); + assert(ao->other_replay_gain_filter != NULL); + + ao->other_replay_gain_serial = 0; + } else { + ao->replay_gain_filter = NULL; + ao->other_replay_gain_filter = NULL; + } + + /* set up the mixer */ + + GError *error = NULL; + ao->mixer = audio_output_load_mixer(ao, param, + ao->plugin->mixer_plugin, + ao->filter, &error); + if (ao->mixer == NULL && error != NULL) { + g_warning("Failed to initialize hardware mixer for '%s': %s", + ao->name, error->message); + g_error_free(error); + } + + /* use the hardware mixer for replay gain? */ + + if (strcmp(replay_gain_handler, "mixer") == 0) { + if (ao->mixer != NULL) + replay_gain_filter_set_mixer(ao->replay_gain_filter, + ao->mixer, 100); + else + g_warning("No such mixer for output '%s'", ao->name); + } else if (strcmp(replay_gain_handler, "software") != 0 && + ao->replay_gain_filter != NULL) { + g_set_error(error_r, audio_output_quark(), 0, + "Invalid \"replay_gain_handler\" value"); + return false; + } + + /* 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); + + return true; +} + +struct audio_output * +audio_output_new(const struct config_param *param, + struct player_control *pc, + GError **error_r) +{ + const struct audio_output_plugin *plugin; + + if (param) { + const char *p; + + p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL); + if (p == NULL) { + g_set_error(error_r, audio_output_quark(), 0, + "Missing \"type\" configuration"); + return nullptr; + } + + plugin = audio_output_plugin_get(p); + if (plugin == NULL) { + g_set_error(error_r, audio_output_quark(), 0, + "No such audio output plugin: %s", p); + return nullptr; + } + } else { + g_warning("No \"%s\" defined in config file\n", + CONF_AUDIO_OUTPUT); + + plugin = audio_output_detect(error_r); + if (plugin == NULL) + return nullptr; + + g_message("Successfully detected a %s audio device", + plugin->name); + } + + struct audio_output *ao = ao_plugin_init(plugin, param, error_r); + if (ao == NULL) + return NULL; + + if (!audio_output_setup(ao, param, error_r)) { + ao_plugin_finish(ao); + return NULL; + } + + ao->player_control = pc; + return ao; +} diff --git a/src/OutputList.cxx b/src/OutputList.cxx new file mode 100644 index 000000000..3e469385f --- /dev/null +++ b/src/OutputList.cxx @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputList.hxx" +#include "output_api.h" +#include "output/alsa_output_plugin.h" +#include "output/ao_output_plugin.h" +#include "output/ffado_output_plugin.h" +#include "output/fifo_output_plugin.h" +#include "output/httpd_output_plugin.h" +#include "output/jack_output_plugin.h" +#include "output/mvp_output_plugin.h" +#include "output/null_output_plugin.h" +#include "output/openal_output_plugin.h" +#include "output/oss_output_plugin.h" +#include "output/osx_output_plugin.h" +#include "output/pipe_output_plugin.h" +#include "output/pulse_output_plugin.h" +#include "output/recorder_output_plugin.h" +#include "output/roar_output_plugin.h" +#include "output/shout_output_plugin.h" +#include "output/solaris_output_plugin.h" +#include "output/winmm_output_plugin.h" + +const struct audio_output_plugin *const audio_output_plugins[] = { +#ifdef HAVE_SHOUT + &shout_output_plugin, +#endif + &null_output_plugin, +#ifdef HAVE_FIFO + &fifo_output_plugin, +#endif +#ifdef ENABLE_PIPE_OUTPUT + &pipe_output_plugin, +#endif +#ifdef HAVE_ALSA + &alsa_output_plugin, +#endif +#ifdef HAVE_ROAR + &roar_output_plugin, +#endif +#ifdef HAVE_AO + &ao_output_plugin, +#endif +#ifdef HAVE_OSS + &oss_output_plugin, +#endif +#ifdef HAVE_OPENAL + &openal_output_plugin, +#endif +#ifdef HAVE_OSX + &osx_output_plugin, +#endif +#ifdef ENABLE_SOLARIS_OUTPUT + &solaris_output_plugin, +#endif +#ifdef HAVE_PULSE + &pulse_output_plugin, +#endif +#ifdef HAVE_MVP + &mvp_output_plugin, +#endif +#ifdef HAVE_JACK + &jack_output_plugin, +#endif +#ifdef ENABLE_HTTPD_OUTPUT + &httpd_output_plugin, +#endif +#ifdef ENABLE_RECORDER_OUTPUT + &recorder_output_plugin, +#endif +#ifdef ENABLE_WINMM_OUTPUT + &winmm_output_plugin, +#endif +#ifdef ENABLE_FFADO_OUTPUT + &ffado_output_plugin, +#endif + NULL +}; + +const struct audio_output_plugin * +audio_output_plugin_get(const char *name) +{ + audio_output_plugins_for_each(plugin) + if (strcmp(plugin->name, name) == 0) + return plugin; + + return NULL; +} diff --git a/src/OutputList.hxx b/src/OutputList.hxx new file mode 100644 index 000000000..b7716c67e --- /dev/null +++ b/src/OutputList.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2013 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_OUTPUT_LIST_HXX +#define MPD_OUTPUT_LIST_HXX + +extern const struct audio_output_plugin *const audio_output_plugins[]; + +const struct audio_output_plugin * +audio_output_plugin_get(const char *name); + +#define audio_output_plugins_for_each(plugin) \ + for (const struct audio_output_plugin *plugin, \ + *const*output_plugin_iterator = &audio_output_plugins[0]; \ + (plugin = *output_plugin_iterator) != NULL; ++output_plugin_iterator) + +#endif diff --git a/src/OutputPlugin.cxx b/src/OutputPlugin.cxx new file mode 100644 index 000000000..9aa0f7792 --- /dev/null +++ b/src/OutputPlugin.cxx @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +extern "C" { +#include "output_plugin.h" +} + +#include "output_internal.h" + +struct audio_output * +ao_plugin_init(const struct audio_output_plugin *plugin, + const struct config_param *param, + GError **error) +{ + assert(plugin != NULL); + assert(plugin->init != NULL); + + return plugin->init(param, error); +} + +void +ao_plugin_finish(struct audio_output *ao) +{ + ao->plugin->finish(ao); +} + +bool +ao_plugin_enable(struct audio_output *ao, GError **error_r) +{ + return ao->plugin->enable != NULL + ? ao->plugin->enable(ao, error_r) + : true; +} + +void +ao_plugin_disable(struct audio_output *ao) +{ + if (ao->plugin->disable != NULL) + ao->plugin->disable(ao); +} + +bool +ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error) +{ + return ao->plugin->open(ao, audio_format, error); +} + +void +ao_plugin_close(struct audio_output *ao) +{ + ao->plugin->close(ao); +} + +unsigned +ao_plugin_delay(struct audio_output *ao) +{ + return ao->plugin->delay != NULL + ? ao->plugin->delay(ao) + : 0; +} + +void +ao_plugin_send_tag(struct audio_output *ao, const struct tag *tag) +{ + if (ao->plugin->send_tag != NULL) + ao->plugin->send_tag(ao, tag); +} + +size_t +ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error) +{ + return ao->plugin->play(ao, chunk, size, error); +} + +void +ao_plugin_drain(struct audio_output *ao) +{ + if (ao->plugin->drain != NULL) + ao->plugin->drain(ao); +} + +void +ao_plugin_cancel(struct audio_output *ao) +{ + if (ao->plugin->cancel != NULL) + ao->plugin->cancel(ao); +} + +bool +ao_plugin_pause(struct audio_output *ao) +{ + return ao->plugin->pause != NULL && ao->plugin->pause(ao); +} diff --git a/src/OutputThread.cxx b/src/OutputThread.cxx new file mode 100644 index 000000000..75394703b --- /dev/null +++ b/src/OutputThread.cxx @@ -0,0 +1,690 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputThread.hxx" + +extern "C" { +#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 "notify.h" +} + +#include "mpd_error.h" +#include "gcc.h" + +#include + +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "output" + +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, &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); + 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) +{ + assert(audio_format_valid(audio_format)); + + /* 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_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->pipe != NULL); + assert(ao->chunk == NULL); + assert(audio_format_valid(&ao->in_audio_format)); + + if (ao->fail_timer != NULL) { + /* this can only happen when this + output thread fails while + audio_output_open() is run in the + player thread */ + g_timer_destroy(ao->fail_timer); + ao->fail_timer = 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; + } + + assert(audio_format_valid(filter_audio_format)); + + 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, &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; + + ao->chunk = NULL; + ao->open = false; + + g_mutex_unlock(ao->mutex); + + if (drain) + ao_plugin_drain(ao); + else + ao_plugin_cancel(ao); + + ao_plugin_close(ao); + ao_filter_close(ao); + + g_mutex_lock(ao->mutex); + + 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; + + 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); + 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); +} + +/** + * Wait until the output's delay reaches zero. + * + * @return true if playback should be continued, false if a command + * was issued + */ +static bool +ao_wait(struct audio_output *ao) +{ + while (true) { + unsigned delay = ao_plugin_delay(ao); + if (delay == 0) + return true; + + GTimeVal tv; + g_get_current_time(&tv); + g_time_val_add(&tv, delay * 1000); + (void)g_cond_timed_wait(ao->cond, ao->mutex, &tv); + + if (ao->command != AO_COMMAND_NONE) + return false; + } +} + +static const void * +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)); + + const void *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 void * +ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, + size_t *length_r) +{ + GError *error = NULL; + + size_t length; + const void *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 void *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; + + void *dest = pcm_buffer_get(&ao->cross_fade_buffer, + other_length); + memcpy(dest, other_data, other_length); + if (!pcm_mix(dest, data, length, + sample_format(ao->in_audio_format.format), + 1.0 - chunk->mix_ratio)) { + g_warning("Cannot cross-fade format %s", + sample_format_to_string(sample_format(ao->in_audio_format.format))); + return NULL; + } + + 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 (ao->tags && gcc_unlikely(chunk->tag != NULL)) { + g_mutex_unlock(ao->mutex); + ao_plugin_send_tag(ao, chunk->tag); + g_mutex_lock(ao->mutex); + } + + size_t size; +#if GCC_CHECK_VERSION(4,7) + /* workaround -Wmaybe-uninitialized false positive */ + size = 0; +#endif + const char *data = (const char *)ao_filter_chunk(ao, chunk, &size); + if (data == NULL) { + ao_close(ao, false); + + /* 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; + + if (!ao_wait(ao)) + break; + + g_mutex_unlock(ao->mutex); + nbytes = ao_plugin_play(ao, 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_close(ao, false); + + /* don't automatically reopen this device for + 10 seconds */ + assert(ao->fail_timer == NULL); + ao->fail_timer = g_timer_new(); + + return false; + } + + assert(nbytes <= size); + assert(nbytes % audio_format_frame_size(&ao->out_audio_format) == 0); + + data += nbytes; + size -= nbytes; + } + + return true; +} + +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); + + 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; + + success = ao_play_chunk(ao, chunk); + if (!success) { + assert(ao->chunk == NULL); + break; + } + + assert(ao->chunk == chunk); + chunk = chunk->next; + } + + ao->chunk_finished = true; + + g_mutex_unlock(ao->mutex); + player_lock_signal(ao->player_control); + g_mutex_lock(ao->mutex); + + return true; +} + +static void ao_pause(struct audio_output *ao) +{ + bool ret; + + g_mutex_unlock(ao->mutex); + ao_plugin_cancel(ao); + g_mutex_lock(ao->mutex); + + ao->pause = true; + ao_command_finished(ao); + + do { + if (!ao_wait(ao)) + break; + + g_mutex_unlock(ao->mutex); + ret = ao_plugin_pause(ao); + g_mutex_lock(ao->mutex); + + if (!ret) { + ao_close(ao, false); + break; + } + } while (ao->command == AO_COMMAND_NONE); + + ao->pause = false; +} + +static gpointer audio_output_task(gpointer arg) +{ + struct audio_output *ao = (struct audio_output *)arg; + + g_mutex_lock(ao->mutex); + + while (1) { + switch (ao->command) { + case AO_COMMAND_NONE: + break; + + case AO_COMMAND_ENABLE: + ao_enable(ao); + ao_command_finished(ao); + break; + + case AO_COMMAND_DISABLE: + ao_disable(ao); + ao_command_finished(ao); + break; + + case AO_COMMAND_OPEN: + ao_open(ao); + ao_command_finished(ao); + break; + + case AO_COMMAND_REOPEN: + ao_reopen(ao); + ao_command_finished(ao); + break; + + case AO_COMMAND_CLOSE: + assert(ao->open); + assert(ao->pipe != NULL); + + ao_close(ao, false); + ao_command_finished(ao); + break; + + case AO_COMMAND_PAUSE: + if (!ao->open) { + /* the output has failed after + audio_output_all_pause() has + submitted the PAUSE command; bail + out */ + ao_command_finished(ao); + break; + } + + ao_pause(ao); + /* don't "break" here: this might cause + ao_play() to be called when command==CLOSE + ends the paused state - "continue" checks + 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); + g_mutex_lock(ao->mutex); + } + + ao_command_finished(ao); + continue; + + case AO_COMMAND_CANCEL: + ao->chunk = NULL; + + if (ao->open) { + g_mutex_unlock(ao->mutex); + ao_plugin_cancel(ao); + g_mutex_lock(ao->mutex); + } + + ao_command_finished(ao); + continue; + + case AO_COMMAND_KILL: + ao->chunk = NULL; + ao_command_finished(ao); + g_mutex_unlock(ao->mutex); + return NULL; + } + + if (ao->open && ao->allow_play && ao_play(ao)) + /* don't wait for an event if there are more + chunks in the pipe */ + continue; + + if (ao->command == AO_COMMAND_NONE) + g_cond_wait(ao->cond, ao->mutex); + } +} + +void audio_output_thread_start(struct audio_output *ao) +{ + GError *e = NULL; + + assert(ao->command == AO_COMMAND_NONE); + + if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e))) + MPD_ERROR("Failed to spawn output task: %s\n", e->message); +} diff --git a/src/OutputThread.hxx b/src/OutputThread.hxx new file mode 100644 index 000000000..1a7932162 --- /dev/null +++ b/src/OutputThread.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2013 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_OUTPUT_THREAD_HXX +#define MPD_OUTPUT_THREAD_HXX + +struct audio_output; + +void audio_output_thread_start(struct audio_output *ao); + +#endif diff --git a/src/output_all.c b/src/output_all.c deleted file mode 100644 index b2ef1561f..000000000 --- a/src/output_all.c +++ /dev/null @@ -1,601 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_all.h" -#include "output_error.h" -#include "output_internal.h" -#include "output_control.h" -#include "chunk.h" -#include "conf.h" -#include "pipe.h" -#include "buffer.h" -#include "player_control.h" -#include "mpd_error.h" -#include "notify.h" - -#ifndef NDEBUG -#include "chunk.h" -#endif - -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "output" - -static struct audio_format input_audio_format; - -static struct audio_output **audio_outputs; -static unsigned int num_audio_outputs; - -/** - * The #music_buffer object where consumed chunks are returned. - */ -static struct music_buffer *g_music_buffer; - -/** - * The #music_pipe object which feeds all audio outputs. It is filled - * by audio_output_all_play(). - */ -static struct music_pipe *g_mp; - -/** - * The "elapsed_time" stamp of the most recently finished chunk. - */ -static float audio_output_all_elapsed_time = -1.0; - -unsigned int audio_output_count(void) -{ - return num_audio_outputs; -} - -struct audio_output * -audio_output_get(unsigned i) -{ - assert(i < num_audio_outputs); - - assert(audio_outputs[i] != NULL); - - return audio_outputs[i]; -} - -struct audio_output * -audio_output_find(const char *name) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_output_get(i); - - if (strcmp(ao->name, name) == 0) - return ao; - } - - /* name not found */ - return NULL; -} - -static unsigned -audio_output_config_count(void) -{ - unsigned int nr = 0; - const struct config_param *param = NULL; - - while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param))) - nr++; - if (!nr) - nr = 1; /* we'll always have at least one device */ - return nr; -} - -void -audio_output_all_init(struct player_control *pc) -{ - const struct config_param *param = NULL; - unsigned int i; - GError *error = NULL; - - notify_init(&audio_output_client_notify); - - num_audio_outputs = audio_output_config_count(); - audio_outputs = g_new(struct audio_output *, num_audio_outputs); - - for (i = 0; i < num_audio_outputs; i++) - { - unsigned int j; - - param = config_get_next_param(CONF_AUDIO_OUTPUT, param); - - /* only allow param to be NULL if there just one audioOutput */ - assert(param || (num_audio_outputs == 1)); - - struct audio_output *output = audio_output_new(param, pc, &error); - if (output == NULL) { - if (param != NULL) - MPD_ERROR("line %i: %s", - param->line, error->message); - else - MPD_ERROR("%s", error->message); - } - - audio_outputs[i] = output; - - /* require output names to be unique: */ - for (j = 0; j < i; j++) { - if (!strcmp(output->name, audio_outputs[j]->name)) { - MPD_ERROR("output devices with identical " - "names: %s\n", output->name); - } - } - } -} - -void -audio_output_all_finish(void) -{ - unsigned int i; - - for (i = 0; i < num_audio_outputs; i++) { - audio_output_disable(audio_outputs[i]); - audio_output_finish(audio_outputs[i]); - } - - g_free(audio_outputs); - audio_outputs = NULL; - num_audio_outputs = 0; - - notify_deinit(&audio_output_client_notify); -} - -void -audio_output_all_enable_disable(void) -{ - for (unsigned i = 0; i < num_audio_outputs; i++) { - struct audio_output *ao = audio_outputs[i]; - bool enabled; - - g_mutex_lock(ao->mutex); - enabled = ao->really_enabled; - g_mutex_unlock(ao->mutex); - - if (ao->enabled != enabled) { - if (ao->enabled) - audio_output_enable(ao); - else - audio_output_disable(ao); - } - } -} - -/** - * Determine if all (active) outputs have finished the current - * command. - */ -static bool -audio_output_all_finished(void) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_outputs[i]; - bool not_finished; - - g_mutex_lock(ao->mutex); - not_finished = audio_output_is_open(ao) && - !audio_output_command_is_finished(ao); - g_mutex_unlock(ao->mutex); - - if (not_finished) - return false; - } - - return true; -} - -static void audio_output_wait_all(void) -{ - while (!audio_output_all_finished()) - notify_wait(&audio_output_client_notify); -} - -/** - * Signals all audio outputs which are open. - */ -static void -audio_output_allow_play_all(void) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) - audio_output_allow_play(audio_outputs[i]); -} - -static void -audio_output_reset_reopen(struct audio_output *ao) -{ - g_mutex_lock(ao->mutex); - - if (!ao->open && ao->fail_timer != NULL) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = NULL; - } - - g_mutex_unlock(ao->mutex); -} - -/** - * Resets the "reopen" flag on all audio devices. MPD should - * immediately retry to open the device instead of waiting for the - * timeout when the user wants to start playback. - */ -static void -audio_output_all_reset_reopen(void) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_outputs[i]; - - audio_output_reset_reopen(ao); - } -} - -/** - * Opens all output devices which are enabled, but closed. - * - * @return true if there is at least open output device which is open - */ -static bool -audio_output_all_update(void) -{ - unsigned int i; - bool ret = false; - - if (!audio_format_defined(&input_audio_format)) - return false; - - for (i = 0; i < num_audio_outputs; ++i) - ret = audio_output_update(audio_outputs[i], - &input_audio_format, g_mp) || ret; - - return ret; -} - -bool -audio_output_all_play(struct music_chunk *chunk, GError **error_r) -{ - bool ret; - unsigned int i; - - assert(g_music_buffer != NULL); - assert(g_mp != NULL); - assert(chunk != NULL); - assert(music_chunk_check_format(chunk, &input_audio_format)); - - ret = audio_output_all_update(); - if (!ret) { - /* TODO: obtain real error */ - g_set_error(error_r, output_quark(), 0, - "Failed to open audio output"); - return false; - } - - music_pipe_push(g_mp, chunk); - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_play(audio_outputs[i]); - - return true; -} - -bool -audio_output_all_open(const struct audio_format *audio_format, - struct music_buffer *buffer, - GError **error_r) -{ - bool ret = false, enabled = false; - unsigned int i; - - assert(audio_format != NULL); - assert(buffer != NULL); - assert(g_music_buffer == NULL || g_music_buffer == buffer); - assert((g_mp == NULL) == (g_music_buffer == NULL)); - - g_music_buffer = buffer; - - /* the audio format must be the same as existing chunks in the - pipe */ - assert(g_mp == NULL || music_pipe_check_format(g_mp, audio_format)); - - if (g_mp == NULL) - g_mp = music_pipe_new(); - else - /* if the pipe hasn't been cleared, the the audio - format must not have changed */ - assert(music_pipe_empty(g_mp) || - audio_format_equals(audio_format, - &input_audio_format)); - - input_audio_format = *audio_format; - - audio_output_all_reset_reopen(); - audio_output_all_enable_disable(); - audio_output_all_update(); - - for (i = 0; i < num_audio_outputs; ++i) { - if (audio_outputs[i]->enabled) - enabled = true; - - if (audio_outputs[i]->open) - ret = true; - } - - if (!enabled) - g_set_error(error_r, output_quark(), 0, - "All audio outputs are disabled"); - else if (!ret) - /* TODO: obtain real error */ - g_set_error(error_r, output_quark(), 0, - "Failed to open audio output"); - - if (!ret) - /* close all devices if there was an error */ - audio_output_all_close(); - - return ret; -} - -/** - * Has the specified audio output already consumed this chunk? - */ -static bool -chunk_is_consumed_in(const struct audio_output *ao, - const struct music_chunk *chunk) -{ - if (!ao->open) - return true; - - if (ao->chunk == NULL) - return false; - - assert(chunk == ao->chunk || music_pipe_contains(g_mp, ao->chunk)); - - if (chunk != ao->chunk) { - assert(chunk->next != NULL); - return true; - } - - return ao->chunk_finished && chunk->next == NULL; -} - -/** - * Has this chunk been consumed by all audio outputs? - */ -static bool -chunk_is_consumed(const struct music_chunk *chunk) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) { - const struct audio_output *ao = audio_outputs[i]; - bool consumed; - - g_mutex_lock(ao->mutex); - consumed = chunk_is_consumed_in(ao, chunk); - g_mutex_unlock(ao->mutex); - - if (!consumed) - return false; - } - - return true; -} - -/** - * There's only one chunk left in the pipe (#g_mp), and all audio - * outputs have consumed it already. Clear the reference. - */ -static void -clear_tail_chunk(G_GNUC_UNUSED const struct music_chunk *chunk, bool *locked) -{ - assert(chunk->next == NULL); - assert(music_pipe_contains(g_mp, chunk)); - - for (unsigned i = 0; i < num_audio_outputs; ++i) { - struct audio_output *ao = audio_outputs[i]; - - /* this mutex will be unlocked by the caller when it's - ready */ - g_mutex_lock(ao->mutex); - locked[i] = ao->open; - - if (!locked[i]) { - g_mutex_unlock(ao->mutex); - continue; - } - - assert(ao->chunk == chunk); - assert(ao->chunk_finished); - ao->chunk = NULL; - } -} - -unsigned -audio_output_all_check(void) -{ - const struct music_chunk *chunk; - bool is_tail; - struct music_chunk *shifted; - bool locked[num_audio_outputs]; - - assert(g_music_buffer != NULL); - assert(g_mp != NULL); - - while ((chunk = music_pipe_peek(g_mp)) != NULL) { - assert(!music_pipe_empty(g_mp)); - - if (!chunk_is_consumed(chunk)) - /* at least one output is not finished playing - this chunk */ - return music_pipe_size(g_mp); - - if (chunk->length > 0 && chunk->times >= 0.0) - /* only update elapsed_time if the chunk - provides a defined value */ - audio_output_all_elapsed_time = chunk->times; - - is_tail = chunk->next == NULL; - if (is_tail) - /* this is the tail of the pipe - clear the - chunk reference in all outputs */ - clear_tail_chunk(chunk, locked); - - /* remove the chunk from the pipe */ - shifted = music_pipe_shift(g_mp); - assert(shifted == chunk); - - if (is_tail) - /* unlock all audio outputs which were locked - by clear_tail_chunk() */ - for (unsigned i = 0; i < num_audio_outputs; ++i) - if (locked[i]) - g_mutex_unlock(audio_outputs[i]->mutex); - - /* return the chunk to the buffer */ - music_buffer_return(g_music_buffer, shifted); - } - - return 0; -} - -bool -audio_output_all_wait(struct player_control *pc, unsigned threshold) -{ - player_lock(pc); - - if (audio_output_all_check() < threshold) { - player_unlock(pc); - return true; - } - - player_wait(pc); - player_unlock(pc); - - return audio_output_all_check() < threshold; -} - -void -audio_output_all_pause(void) -{ - unsigned int i; - - audio_output_all_update(); - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_pause(audio_outputs[i]); - - audio_output_wait_all(); -} - -void -audio_output_all_drain(void) -{ - for (unsigned i = 0; i < num_audio_outputs; ++i) - audio_output_drain_async(audio_outputs[i]); - - audio_output_wait_all(); -} - -void -audio_output_all_cancel(void) -{ - unsigned int i; - - /* send the cancel() command to all audio outputs */ - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_cancel(audio_outputs[i]); - - audio_output_wait_all(); - - /* clear the music pipe and return all chunks to the buffer */ - - if (g_mp != NULL) - music_pipe_clear(g_mp, g_music_buffer); - - /* the audio outputs are now waiting for a signal, to - synchronize the cleared music pipe */ - - audio_output_allow_play_all(); - - /* invalidate elapsed_time */ - - audio_output_all_elapsed_time = -1.0; -} - -void -audio_output_all_close(void) -{ - unsigned int i; - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_close(audio_outputs[i]); - - if (g_mp != NULL) { - assert(g_music_buffer != NULL); - - music_pipe_clear(g_mp, g_music_buffer); - music_pipe_free(g_mp); - g_mp = NULL; - } - - g_music_buffer = NULL; - - audio_format_clear(&input_audio_format); - - audio_output_all_elapsed_time = -1.0; -} - -void -audio_output_all_release(void) -{ - unsigned int i; - - for (i = 0; i < num_audio_outputs; ++i) - audio_output_release(audio_outputs[i]); - - if (g_mp != NULL) { - assert(g_music_buffer != NULL); - - music_pipe_clear(g_mp, g_music_buffer); - music_pipe_free(g_mp); - g_mp = NULL; - } - - g_music_buffer = NULL; - - audio_format_clear(&input_audio_format); - - audio_output_all_elapsed_time = -1.0; -} - -void -audio_output_all_song_border(void) -{ - /* clear the elapsed_time pointer at the beginning of a new - song */ - audio_output_all_elapsed_time = 0.0; -} - -float -audio_output_all_get_elapsed_time(void) -{ - return audio_output_all_elapsed_time; -} diff --git a/src/output_command.c b/src/output_command.c deleted file mode 100644 index 3988f350a..000000000 --- a/src/output_command.c +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Glue functions for controlling the audio outputs over the MPD - * protocol. These functions perform extra validation on all - * parameters, because they might be from an untrusted source. - * - */ - -#include "config.h" -#include "output_command.h" -#include "output_all.h" -#include "output_internal.h" -#include "output_plugin.h" -#include "mixer_control.h" -#include "player_control.h" -#include "idle.h" - -extern unsigned audio_output_state_version; - -bool -audio_output_enable_index(unsigned idx) -{ - struct audio_output *ao; - - if (idx >= audio_output_count()) - return false; - - ao = audio_output_get(idx); - if (ao->enabled) - return true; - - ao->enabled = true; - idle_add(IDLE_OUTPUT); - - pc_update_audio(ao->player_control); - - ++audio_output_state_version; - - return true; -} - -bool -audio_output_disable_index(unsigned idx) -{ - struct audio_output *ao; - struct mixer *mixer; - - if (idx >= audio_output_count()) - return false; - - ao = audio_output_get(idx); - if (!ao->enabled) - return true; - - ao->enabled = false; - idle_add(IDLE_OUTPUT); - - mixer = ao->mixer; - if (mixer != NULL) { - mixer_close(mixer); - idle_add(IDLE_MIXER); - } - - pc_update_audio(ao->player_control); - - ++audio_output_state_version; - - return true; -} diff --git a/src/output_command.h b/src/output_command.h deleted file mode 100644 index eda30acc8..000000000 --- a/src/output_command.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Glue functions for controlling the audio outputs over the MPD - * protocol. These functions perform extra validation on all - * parameters, because they might be from an untrusted source. - * - */ - -#ifndef OUTPUT_COMMAND_H -#define OUTPUT_COMMAND_H - -#include - -/** - * Enables an audio output. Returns false if the specified output - * does not exist. - */ -bool -audio_output_enable_index(unsigned idx); - -/** - * Disables an audio output. Returns false if the specified output - * does not exist. - */ -bool -audio_output_disable_index(unsigned idx); - -#endif diff --git a/src/output_control.c b/src/output_control.c deleted file mode 100644 index 7b95be49b..000000000 --- a/src/output_control.c +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_control.h" -#include "output_api.h" -#include "output_internal.h" -#include "output_thread.h" -#include "mixer_control.h" -#include "mixer_plugin.h" -#include "filter_plugin.h" -#include "notify.h" - -#include -#include - -enum { - /** after a failure, wait this number of seconds before - automatically reopening the device */ - REOPEN_AFTER = 10, -}; - -struct notify audio_output_client_notify; - -/** - * Waits for command completion. - * - * @param ao the #audio_output instance; must be locked - */ -static void ao_command_wait(struct audio_output *ao) -{ - while (ao->command != AO_COMMAND_NONE) { - g_mutex_unlock(ao->mutex); - notify_wait(&audio_output_client_notify); - g_mutex_lock(ao->mutex); - } -} - -/** - * Sends a command to the #audio_output object, but does not wait for - * completion. - * - * @param ao the #audio_output instance; must be locked - */ -static void ao_command_async(struct audio_output *ao, - enum audio_output_command cmd) -{ - assert(ao->command == AO_COMMAND_NONE); - ao->command = cmd; - g_cond_signal(ao->cond); -} - -/** - * Sends a command to the #audio_output object and waits for - * completion. - * - * @param ao the #audio_output instance; must be locked - */ -static void -ao_command(struct audio_output *ao, enum audio_output_command cmd) -{ - ao_command_async(ao, cmd); - ao_command_wait(ao); -} - -/** - * Lock the #audio_output object and execute the command - * synchronously. - */ -static void -ao_lock_command(struct audio_output *ao, enum audio_output_command cmd) -{ - g_mutex_lock(ao->mutex); - ao_command(ao, cmd); - g_mutex_unlock(ao->mutex); -} - -void -audio_output_enable(struct audio_output *ao) -{ - if (ao->thread == NULL) { - if (ao->plugin->enable == NULL) { - /* don't bother to start the thread now if the - device doesn't even have a enable() method; - just assign the variable and we're done */ - ao->really_enabled = true; - return; - } - - audio_output_thread_start(ao); - } - - ao_lock_command(ao, AO_COMMAND_ENABLE); -} - -void -audio_output_disable(struct audio_output *ao) -{ - if (ao->thread == NULL) { - if (ao->plugin->disable == NULL) - ao->really_enabled = false; - else - /* if there's no thread yet, the device cannot - be enabled */ - assert(!ao->really_enabled); - - return; - } - - ao_lock_command(ao, AO_COMMAND_DISABLE); -} - -/** - * Object must be locked (and unlocked) by the caller. - */ -static bool -audio_output_open(struct audio_output *ao, - const struct audio_format *audio_format, - const struct music_pipe *mp) -{ - bool open; - - assert(ao != NULL); - assert(ao->allow_play); - assert(audio_format_valid(audio_format)); - assert(mp != NULL); - - if (ao->fail_timer != NULL) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = NULL; - } - - if (ao->open && - audio_format_equals(audio_format, &ao->in_audio_format)) { - assert(ao->pipe == mp || - (ao->always_on && ao->pause)); - - if (ao->pause) { - ao->chunk = NULL; - ao->pipe = mp; - - /* unpause with the CANCEL command; this is a - hack, but suits well for forcing the thread - to leave the ao_pause() thread, and we need - to flush the device buffer anyway */ - - /* we're not using audio_output_cancel() here, - because that function is asynchronous */ - ao_command(ao, AO_COMMAND_CANCEL); - } - - return true; - } - - ao->in_audio_format = *audio_format; - ao->chunk = NULL; - - 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->mixer != NULL) { - GError *error = NULL; - - if (!mixer_open(ao->mixer, &error)) { - g_warning("Failed to open mixer for '%s': %s", - ao->name, error->message); - g_error_free(error); - } - } - - return open; -} - -/** - * Same as audio_output_close(), but expects the lock to be held by - * the caller. - */ -static void -audio_output_close_locked(struct audio_output *ao) -{ - assert(ao != NULL); - assert(ao->allow_play); - - if (ao->mixer != NULL) - mixer_auto_close(ao->mixer); - - assert(!ao->open || ao->fail_timer == NULL); - - if (ao->open) - ao_command(ao, AO_COMMAND_CLOSE); - else if (ao->fail_timer != NULL) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = NULL; - } -} - -bool -audio_output_update(struct audio_output *ao, - const struct audio_format *audio_format, - const struct music_pipe *mp) -{ - assert(mp != NULL); - - g_mutex_lock(ao->mutex); - - if (ao->enabled && ao->really_enabled) { - if (ao->fail_timer == NULL || - g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) { - bool success = audio_output_open(ao, audio_format, mp); - g_mutex_unlock(ao->mutex); - return success; - } - } else if (audio_output_is_open(ao)) - audio_output_close_locked(ao); - - g_mutex_unlock(ao->mutex); - return false; -} - -void -audio_output_play(struct audio_output *ao) -{ - g_mutex_lock(ao->mutex); - - assert(ao->allow_play); - - if (audio_output_is_open(ao)) - g_cond_signal(ao->cond); - - g_mutex_unlock(ao->mutex); -} - -void audio_output_pause(struct audio_output *ao) -{ - if (ao->mixer != NULL && ao->plugin->pause == NULL) - /* the device has no pause mode: close the mixer, - unless its "global" flag is set (checked by - mixer_auto_close()) */ - mixer_auto_close(ao->mixer); - - g_mutex_lock(ao->mutex); - assert(ao->allow_play); - if (audio_output_is_open(ao)) - ao_command_async(ao, AO_COMMAND_PAUSE); - g_mutex_unlock(ao->mutex); -} - -void -audio_output_drain_async(struct audio_output *ao) -{ - g_mutex_lock(ao->mutex); - assert(ao->allow_play); - if (audio_output_is_open(ao)) - ao_command_async(ao, AO_COMMAND_DRAIN); - g_mutex_unlock(ao->mutex); -} - -void audio_output_cancel(struct audio_output *ao) -{ - g_mutex_lock(ao->mutex); - - if (audio_output_is_open(ao)) { - ao->allow_play = false; - ao_command_async(ao, AO_COMMAND_CANCEL); - } - - g_mutex_unlock(ao->mutex); -} - -void -audio_output_allow_play(struct audio_output *ao) -{ - g_mutex_lock(ao->mutex); - - ao->allow_play = true; - if (audio_output_is_open(ao)) - g_cond_signal(ao->cond); - - g_mutex_unlock(ao->mutex); -} - -void -audio_output_release(struct audio_output *ao) -{ - if (ao->always_on) - audio_output_pause(ao); - else - audio_output_close(ao); -} - -void audio_output_close(struct audio_output *ao) -{ - assert(ao != NULL); - assert(!ao->open || ao->fail_timer == NULL); - - g_mutex_lock(ao->mutex); - audio_output_close_locked(ao); - g_mutex_unlock(ao->mutex); -} - -void audio_output_finish(struct audio_output *ao) -{ - audio_output_close(ao); - - assert(ao->fail_timer == NULL); - - if (ao->thread != NULL) { - assert(ao->allow_play); - ao_lock_command(ao, AO_COMMAND_KILL); - g_thread_join(ao->thread); - ao->thread = NULL; - } - - audio_output_free(ao); -} diff --git a/src/output_control.h b/src/output_control.h deleted file mode 100644 index 874a53518..000000000 --- a/src/output_control.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_CONTROL_H -#define MPD_OUTPUT_CONTROL_H - -#include - -#include -#include - -struct audio_output; -struct audio_format; -struct config_param; -struct music_pipe; -struct player_control; - -static inline GQuark -audio_output_quark(void) -{ - return g_quark_from_static_string("audio_output"); -} - -/** - * Enables the device. - */ -void -audio_output_enable(struct audio_output *ao); - -/** - * Disables the device. - */ -void -audio_output_disable(struct audio_output *ao); - -/** - * Opens or closes the device, depending on the "enabled" flag. - * - * @return true if the device is open - */ -bool -audio_output_update(struct audio_output *ao, - const struct audio_format *audio_format, - const struct music_pipe *mp); - -void -audio_output_play(struct audio_output *ao); - -void audio_output_pause(struct audio_output *ao); - -void -audio_output_drain_async(struct audio_output *ao); - -/** - * Clear the "allow_play" flag and send the "CANCEL" command - * asynchronously. To finish the operation, the caller has to call - * audio_output_allow_play(). - */ -void audio_output_cancel(struct audio_output *ao); - -/** - * Set the "allow_play" and signal the thread. - */ -void -audio_output_allow_play(struct audio_output *ao); - -void audio_output_close(struct audio_output *ao); - -/** - * Closes the audio output, but if the "always_on" flag is set, put it - * into pause mode instead. - */ -void -audio_output_release(struct audio_output *ao); - -void audio_output_finish(struct audio_output *ao); - -#endif diff --git a/src/output_error.h b/src/output_error.h deleted file mode 100644 index ccc784f89..000000000 --- a/src/output_error.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2003-2012 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_OUTPUT_ERROR_H -#define MPD_OUTPUT_ERROR_H - -#include - -/** - * Quark for GError.domain. - */ -G_GNUC_CONST -static inline GQuark -output_quark(void) -{ - return g_quark_from_static_string("output"); -} - -#endif diff --git a/src/output_finish.c b/src/output_finish.c deleted file mode 100644 index e11b43675..000000000 --- a/src/output_finish.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_internal.h" -#include "output_plugin.h" -#include "mixer_control.h" -#include "filter_plugin.h" - -#include - -void -ao_base_finish(struct audio_output *ao) -{ - assert(!ao->open); - assert(ao->fail_timer == NULL); - assert(ao->thread == NULL); - - if (ao->mixer != NULL) - mixer_free(ao->mixer); - - g_cond_free(ao->cond); - g_mutex_free(ao->mutex); - - if (ao->replay_gain_filter != NULL) - filter_free(ao->replay_gain_filter); - - if (ao->other_replay_gain_filter != NULL) - filter_free(ao->other_replay_gain_filter); - - filter_free(ao->filter); - - pcm_buffer_deinit(&ao->cross_fade_buffer); -} - -void -audio_output_free(struct audio_output *ao) -{ - assert(!ao->open); - assert(ao->fail_timer == NULL); - assert(ao->thread == NULL); - - ao_plugin_finish(ao); -} diff --git a/src/output_init.c b/src/output_init.c deleted file mode 100644 index a6d191920..000000000 --- a/src/output_init.c +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_control.h" -#include "output_api.h" -#include "output_internal.h" -#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_config.h" -#include "filter/chain_filter_plugin.h" -#include "filter/autoconvert_filter_plugin.h" -#include "filter/replay_gain_filter_plugin.h" - -#include - -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "output" - -#define AUDIO_OUTPUT_TYPE "type" -#define AUDIO_OUTPUT_NAME "name" -#define AUDIO_OUTPUT_FORMAT "format" -#define AUDIO_FILTERS "filters" - -static const struct audio_output_plugin * -audio_output_detect(GError **error) -{ - g_warning("Attempt to detect audio output device"); - - audio_output_plugins_for_each(plugin) { - if (plugin->test_default_device == NULL) - continue; - - g_warning("Attempting to detect a %s audio device", - plugin->name); - if (ao_plugin_test_default_device(plugin)) - return plugin; - } - - g_set_error(error, audio_output_quark(), 0, - "Unable to detect an audio device"); - 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(struct audio_output *ao, - const struct config_param *param, - const struct mixer_plugin *plugin, - struct filter *filter_chain, - GError **error_r) -{ - 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, ao, param, error_r); - - case MIXER_TYPE_SOFTWARE: - mixer = mixer_new(&software_mixer_plugin, NULL, NULL, NULL); - assert(mixer != NULL); - - filter_chain_append(filter_chain, - software_mixer_get_filter(mixer)); - return mixer; - } - - assert(false); - return NULL; -} - -bool -ao_base_init(struct audio_output *ao, - const struct audio_output_plugin *plugin, - const struct config_param *param, GError **error_r) -{ - assert(ao != NULL); - assert(plugin != NULL); - assert(plugin->finish != NULL); - assert(plugin->open != NULL); - assert(plugin->close != NULL); - assert(plugin->play != NULL); - - GError *error = NULL; - - if (param) { - const char *p; - - ao->name = config_get_block_string(param, AUDIO_OUTPUT_NAME, - NULL); - if (ao->name == NULL) { - g_set_error(error_r, audio_output_quark(), 0, - "Missing \"name\" configuration"); - return false; - } - - p = config_get_block_string(param, AUDIO_OUTPUT_FORMAT, - NULL); - if (p != NULL) { - bool success = - audio_format_parse(&ao->config_audio_format, - p, true, error_r); - if (!success) - return false; - } else - audio_format_clear(&ao->config_audio_format); - } else { - ao->name = "default detected output"; - - audio_format_clear(&ao->config_audio_format); - } - - ao->plugin = plugin; - ao->tags = config_get_block_bool(param, "tags", true); - ao->always_on = config_get_block_bool(param, "always_on", false); - ao->enabled = config_get_block_bool(param, "enabled", true); - ao->really_enabled = false; - ao->open = false; - ao->pause = false; - ao->allow_play = true; - ao->fail_timer = NULL; - - pcm_buffer_init(&ao->cross_fade_buffer); - - /* set up the filter chain */ - - ao->filter = filter_chain_new(); - assert(ao->filter != NULL); - - /* create the normalization filter (if configured) */ - - if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) { - struct filter *normalize_filter = - filter_new(&normalize_filter_plugin, NULL, NULL); - assert(normalize_filter != NULL); - - filter_chain_append(ao->filter, - autoconvert_filter_new(normalize_filter)); - } - - filter_chain_parse(ao->filter, - config_get_block_string(param, AUDIO_FILTERS, ""), - &error - ); - - // It's not really fatal - Part of the filter chain has been set up already - // and even an empty one will work (if only with unexpected behaviour) - if (error != NULL) { - g_warning("Failed to initialize filter chain for '%s': %s", - ao->name, error->message); - g_error_free(error); - } - - ao->thread = NULL; - ao->command = AO_COMMAND_NONE; - ao->mutex = g_mutex_new(); - ao->cond = g_cond_new(); - - ao->mixer = NULL; - ao->replay_gain_filter = NULL; - ao->other_replay_gain_filter = NULL; - - /* done */ - - return true; -} - -static bool -audio_output_setup(struct audio_output *ao, const struct config_param *param, - GError **error_r) -{ - - /* create the replay_gain filter */ - - const char *replay_gain_handler = - config_get_block_string(param, "replay_gain_handler", - "software"); - - if (strcmp(replay_gain_handler, "none") != 0) { - ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin, - param, NULL); - assert(ao->replay_gain_filter != NULL); - - ao->replay_gain_serial = 0; - - ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin, - param, NULL); - assert(ao->other_replay_gain_filter != NULL); - - ao->other_replay_gain_serial = 0; - } else { - ao->replay_gain_filter = NULL; - ao->other_replay_gain_filter = NULL; - } - - /* set up the mixer */ - - GError *error = NULL; - ao->mixer = audio_output_load_mixer(ao, param, - ao->plugin->mixer_plugin, - ao->filter, &error); - if (ao->mixer == NULL && error != NULL) { - g_warning("Failed to initialize hardware mixer for '%s': %s", - ao->name, error->message); - g_error_free(error); - } - - /* use the hardware mixer for replay gain? */ - - if (strcmp(replay_gain_handler, "mixer") == 0) { - if (ao->mixer != NULL) - replay_gain_filter_set_mixer(ao->replay_gain_filter, - ao->mixer, 100); - else - g_warning("No such mixer for output '%s'", ao->name); - } else if (strcmp(replay_gain_handler, "software") != 0 && - ao->replay_gain_filter != NULL) { - g_set_error(error_r, audio_output_quark(), 0, - "Invalid \"replay_gain_handler\" value"); - return false; - } - - /* 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); - - return true; -} - -struct audio_output * -audio_output_new(const struct config_param *param, - struct player_control *pc, - GError **error_r) -{ - const struct audio_output_plugin *plugin; - - if (param) { - const char *p; - - p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL); - if (p == NULL) { - g_set_error(error_r, audio_output_quark(), 0, - "Missing \"type\" configuration"); - return false; - } - - plugin = audio_output_plugin_get(p); - if (plugin == NULL) { - g_set_error(error_r, audio_output_quark(), 0, - "No such audio output plugin: %s", p); - return false; - } - } else { - g_warning("No \"%s\" defined in config file\n", - CONF_AUDIO_OUTPUT); - - plugin = audio_output_detect(error_r); - if (plugin == NULL) - return false; - - g_message("Successfully detected a %s audio device", - plugin->name); - } - - struct audio_output *ao = ao_plugin_init(plugin, param, error_r); - if (ao == NULL) - return NULL; - - if (!audio_output_setup(ao, param, error_r)) { - ao_plugin_finish(ao); - return NULL; - } - - ao->player_control = pc; - return ao; -} diff --git a/src/output_list.c b/src/output_list.c deleted file mode 100644 index 835c02bba..000000000 --- a/src/output_list.c +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_list.h" -#include "output_api.h" -#include "output/alsa_output_plugin.h" -#include "output/ao_output_plugin.h" -#include "output/ffado_output_plugin.h" -#include "output/fifo_output_plugin.h" -#include "output/httpd_output_plugin.h" -#include "output/jack_output_plugin.h" -#include "output/mvp_output_plugin.h" -#include "output/null_output_plugin.h" -#include "output/openal_output_plugin.h" -#include "output/oss_output_plugin.h" -#include "output/osx_output_plugin.h" -#include "output/pipe_output_plugin.h" -#include "output/pulse_output_plugin.h" -#include "output/recorder_output_plugin.h" -#include "output/roar_output_plugin.h" -#include "output/shout_output_plugin.h" -#include "output/solaris_output_plugin.h" -#include "output/winmm_output_plugin.h" - -const struct audio_output_plugin *const audio_output_plugins[] = { -#ifdef HAVE_SHOUT - &shout_output_plugin, -#endif - &null_output_plugin, -#ifdef HAVE_FIFO - &fifo_output_plugin, -#endif -#ifdef ENABLE_PIPE_OUTPUT - &pipe_output_plugin, -#endif -#ifdef HAVE_ALSA - &alsa_output_plugin, -#endif -#ifdef HAVE_ROAR - &roar_output_plugin, -#endif -#ifdef HAVE_AO - &ao_output_plugin, -#endif -#ifdef HAVE_OSS - &oss_output_plugin, -#endif -#ifdef HAVE_OPENAL - &openal_output_plugin, -#endif -#ifdef HAVE_OSX - &osx_output_plugin, -#endif -#ifdef ENABLE_SOLARIS_OUTPUT - &solaris_output_plugin, -#endif -#ifdef HAVE_PULSE - &pulse_output_plugin, -#endif -#ifdef HAVE_MVP - &mvp_output_plugin, -#endif -#ifdef HAVE_JACK - &jack_output_plugin, -#endif -#ifdef ENABLE_HTTPD_OUTPUT - &httpd_output_plugin, -#endif -#ifdef ENABLE_RECORDER_OUTPUT - &recorder_output_plugin, -#endif -#ifdef ENABLE_WINMM_OUTPUT - &winmm_output_plugin, -#endif -#ifdef ENABLE_FFADO_OUTPUT - &ffado_output_plugin, -#endif - NULL -}; - -const struct audio_output_plugin * -audio_output_plugin_get(const char *name) -{ - audio_output_plugins_for_each(plugin) - if (strcmp(plugin->name, name) == 0) - return plugin; - - return NULL; -} diff --git a/src/output_list.h b/src/output_list.h deleted file mode 100644 index 185ada716..000000000 --- a/src/output_list.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_LIST_H -#define MPD_OUTPUT_LIST_H - -extern const struct audio_output_plugin *const audio_output_plugins[]; - -const struct audio_output_plugin * -audio_output_plugin_get(const char *name); - -#define audio_output_plugins_for_each(plugin) \ - for (const struct audio_output_plugin *plugin, \ - *const*output_plugin_iterator = &audio_output_plugins[0]; \ - (plugin = *output_plugin_iterator) != NULL; ++output_plugin_iterator) - -#endif diff --git a/src/output_plugin.c b/src/output_plugin.c deleted file mode 100644 index 221570c1c..000000000 --- a/src/output_plugin.c +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "output_plugin.h" -#include "output_internal.h" - -struct audio_output * -ao_plugin_init(const struct audio_output_plugin *plugin, - const struct config_param *param, - GError **error) -{ - assert(plugin != NULL); - assert(plugin->init != NULL); - - return plugin->init(param, error); -} - -void -ao_plugin_finish(struct audio_output *ao) -{ - ao->plugin->finish(ao); -} - -bool -ao_plugin_enable(struct audio_output *ao, GError **error_r) -{ - return ao->plugin->enable != NULL - ? ao->plugin->enable(ao, error_r) - : true; -} - -void -ao_plugin_disable(struct audio_output *ao) -{ - if (ao->plugin->disable != NULL) - ao->plugin->disable(ao); -} - -bool -ao_plugin_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error) -{ - return ao->plugin->open(ao, audio_format, error); -} - -void -ao_plugin_close(struct audio_output *ao) -{ - ao->plugin->close(ao); -} - -unsigned -ao_plugin_delay(struct audio_output *ao) -{ - return ao->plugin->delay != NULL - ? ao->plugin->delay(ao) - : 0; -} - -void -ao_plugin_send_tag(struct audio_output *ao, const struct tag *tag) -{ - if (ao->plugin->send_tag != NULL) - ao->plugin->send_tag(ao, tag); -} - -size_t -ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error) -{ - return ao->plugin->play(ao, chunk, size, error); -} - -void -ao_plugin_drain(struct audio_output *ao) -{ - if (ao->plugin->drain != NULL) - ao->plugin->drain(ao); -} - -void -ao_plugin_cancel(struct audio_output *ao) -{ - if (ao->plugin->cancel != NULL) - ao->plugin->cancel(ao); -} - -bool -ao_plugin_pause(struct audio_output *ao) -{ - return ao->plugin->pause != NULL && ao->plugin->pause(ao); -} diff --git a/src/output_thread.c b/src/output_thread.c deleted file mode 100644 index cd1a8a878..000000000 --- a/src/output_thread.c +++ /dev/null @@ -1,685 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "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 "mpd_error.h" -#include "notify.h" -#include "gcc.h" - -#include - -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "output" - -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, &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); - 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) -{ - assert(audio_format_valid(audio_format)); - - /* 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_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->pipe != NULL); - assert(ao->chunk == NULL); - assert(audio_format_valid(&ao->in_audio_format)); - - if (ao->fail_timer != NULL) { - /* this can only happen when this - output thread fails while - audio_output_open() is run in the - player thread */ - g_timer_destroy(ao->fail_timer); - ao->fail_timer = 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; - } - - assert(audio_format_valid(filter_audio_format)); - - 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, &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; - - ao->chunk = NULL; - ao->open = false; - - g_mutex_unlock(ao->mutex); - - if (drain) - ao_plugin_drain(ao); - else - ao_plugin_cancel(ao); - - ao_plugin_close(ao); - ao_filter_close(ao); - - g_mutex_lock(ao->mutex); - - 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; - - 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); - 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); -} - -/** - * Wait until the output's delay reaches zero. - * - * @return true if playback should be continued, false if a command - * was issued - */ -static bool -ao_wait(struct audio_output *ao) -{ - while (true) { - unsigned delay = ao_plugin_delay(ao); - if (delay == 0) - return true; - - GTimeVal tv; - g_get_current_time(&tv); - g_time_val_add(&tv, delay * 1000); - (void)g_cond_timed_wait(ao->cond, ao->mutex, &tv); - - if (ao->command != AO_COMMAND_NONE) - return false; - } -} - -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)); - - 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); - if (!pcm_mix(dest, data, length, ao->in_audio_format.format, - 1.0 - chunk->mix_ratio)) { - g_warning("Cannot cross-fade format %s", - sample_format_to_string(ao->in_audio_format.format)); - return NULL; - } - - 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 (ao->tags && gcc_unlikely(chunk->tag != NULL)) { - g_mutex_unlock(ao->mutex); - ao_plugin_send_tag(ao, chunk->tag); - g_mutex_lock(ao->mutex); - } - - size_t size; -#if GCC_CHECK_VERSION(4,7) - /* workaround -Wmaybe-uninitialized false positive */ - size = 0; -#endif - const char *data = ao_filter_chunk(ao, chunk, &size); - if (data == NULL) { - ao_close(ao, false); - - /* 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; - - if (!ao_wait(ao)) - break; - - g_mutex_unlock(ao->mutex); - nbytes = ao_plugin_play(ao, 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_close(ao, false); - - /* don't automatically reopen this device for - 10 seconds */ - assert(ao->fail_timer == NULL); - ao->fail_timer = g_timer_new(); - - return false; - } - - assert(nbytes <= size); - assert(nbytes % audio_format_frame_size(&ao->out_audio_format) == 0); - - data += nbytes; - size -= nbytes; - } - - return true; -} - -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); - - 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; - - success = ao_play_chunk(ao, chunk); - if (!success) { - assert(ao->chunk == NULL); - break; - } - - assert(ao->chunk == chunk); - chunk = chunk->next; - } - - ao->chunk_finished = true; - - g_mutex_unlock(ao->mutex); - player_lock_signal(ao->player_control); - g_mutex_lock(ao->mutex); - - return true; -} - -static void ao_pause(struct audio_output *ao) -{ - bool ret; - - g_mutex_unlock(ao->mutex); - ao_plugin_cancel(ao); - g_mutex_lock(ao->mutex); - - ao->pause = true; - ao_command_finished(ao); - - do { - if (!ao_wait(ao)) - break; - - g_mutex_unlock(ao->mutex); - ret = ao_plugin_pause(ao); - g_mutex_lock(ao->mutex); - - if (!ret) { - ao_close(ao, false); - break; - } - } while (ao->command == AO_COMMAND_NONE); - - ao->pause = false; -} - -static gpointer audio_output_task(gpointer arg) -{ - struct audio_output *ao = arg; - - g_mutex_lock(ao->mutex); - - while (1) { - switch (ao->command) { - case AO_COMMAND_NONE: - break; - - case AO_COMMAND_ENABLE: - ao_enable(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_DISABLE: - ao_disable(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_OPEN: - ao_open(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_REOPEN: - ao_reopen(ao); - ao_command_finished(ao); - break; - - case AO_COMMAND_CLOSE: - assert(ao->open); - assert(ao->pipe != NULL); - - ao_close(ao, false); - ao_command_finished(ao); - break; - - case AO_COMMAND_PAUSE: - if (!ao->open) { - /* the output has failed after - audio_output_all_pause() has - submitted the PAUSE command; bail - out */ - ao_command_finished(ao); - break; - } - - ao_pause(ao); - /* don't "break" here: this might cause - ao_play() to be called when command==CLOSE - ends the paused state - "continue" checks - 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); - g_mutex_lock(ao->mutex); - } - - ao_command_finished(ao); - continue; - - case AO_COMMAND_CANCEL: - ao->chunk = NULL; - - if (ao->open) { - g_mutex_unlock(ao->mutex); - ao_plugin_cancel(ao); - g_mutex_lock(ao->mutex); - } - - ao_command_finished(ao); - continue; - - case AO_COMMAND_KILL: - ao->chunk = NULL; - ao_command_finished(ao); - g_mutex_unlock(ao->mutex); - return NULL; - } - - if (ao->open && ao->allow_play && ao_play(ao)) - /* don't wait for an event if there are more - chunks in the pipe */ - continue; - - if (ao->command == AO_COMMAND_NONE) - g_cond_wait(ao->cond, ao->mutex); - } -} - -void audio_output_thread_start(struct audio_output *ao) -{ - GError *e = NULL; - - assert(ao->command == AO_COMMAND_NONE); - - if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e))) - MPD_ERROR("Failed to spawn output task: %s\n", e->message); -} diff --git a/src/output_thread.h b/src/output_thread.h deleted file mode 100644 index 5ad9a7527..000000000 --- a/src/output_thread.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_OUTPUT_THREAD_H -#define MPD_OUTPUT_THREAD_H - -struct audio_output; - -void audio_output_thread_start(struct audio_output *ao); - -#endif -- cgit v1.2.3