diff options
author | Max Kellermann <max@duempel.org> | 2014-01-23 23:49:50 +0100 |
---|---|---|
committer | Max Kellermann <max@duempel.org> | 2014-01-23 23:49:50 +0100 |
commit | ea5b901bcce20949a8d1fd622a7b03ff6f56ae20 (patch) | |
tree | ab37a61260fdaafb94b5e54aca54928e00c9cf01 /src/output | |
parent | f1f19841bdd291c055f59b6603f69278c66366d8 (diff) | |
download | mpd-ea5b901bcce20949a8d1fd622a7b03ff6f56ae20.tar.gz mpd-ea5b901bcce20949a8d1fd622a7b03ff6f56ae20.tar.xz mpd-ea5b901bcce20949a8d1fd622a7b03ff6f56ae20.zip |
output/*: move to output/plugins/
Diffstat (limited to 'src/output')
-rw-r--r-- | src/output/OutputAPI.hxx | 33 | ||||
-rw-r--r-- | src/output/OutputAll.cxx | 589 | ||||
-rw-r--r-- | src/output/OutputAll.hxx | 174 | ||||
-rw-r--r-- | src/output/OutputCommand.cxx | 112 | ||||
-rw-r--r-- | src/output/OutputCommand.hxx | 51 | ||||
-rw-r--r-- | src/output/OutputControl.cxx | 325 | ||||
-rw-r--r-- | src/output/OutputControl.hxx | 94 | ||||
-rw-r--r-- | src/output/OutputError.cxx | 23 | ||||
-rw-r--r-- | src/output/OutputError.hxx | 25 | ||||
-rw-r--r-- | src/output/OutputFinish.cxx | 51 | ||||
-rw-r--r-- | src/output/OutputInit.cxx | 329 | ||||
-rw-r--r-- | src/output/OutputInternal.hxx | 301 | ||||
-rw-r--r-- | src/output/OutputList.cxx | 100 | ||||
-rw-r--r-- | src/output/OutputList.hxx | 33 | ||||
-rw-r--r-- | src/output/OutputPlugin.cxx | 109 | ||||
-rw-r--r-- | src/output/OutputPlugin.hxx | 202 | ||||
-rw-r--r-- | src/output/OutputPrint.cxx | 45 | ||||
-rw-r--r-- | src/output/OutputPrint.hxx | 34 | ||||
-rw-r--r-- | src/output/OutputState.cxx | 92 | ||||
-rw-r--r-- | src/output/OutputState.hxx | 44 | ||||
-rw-r--r-- | src/output/OutputThread.cxx | 690 | ||||
-rw-r--r-- | src/output/OutputThread.hxx | 28 | ||||
-rw-r--r-- | src/output/plugins/AlsaOutputPlugin.cxx (renamed from src/output/AlsaOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/AlsaOutputPlugin.hxx (renamed from src/output/AlsaOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/AoOutputPlugin.cxx (renamed from src/output/AoOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/AoOutputPlugin.hxx (renamed from src/output/AoOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/FifoOutputPlugin.cxx (renamed from src/output/FifoOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/FifoOutputPlugin.hxx (renamed from src/output/FifoOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/HttpdClient.cxx (renamed from src/output/HttpdClient.cxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/HttpdClient.hxx (renamed from src/output/HttpdClient.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/HttpdInternal.hxx (renamed from src/output/HttpdInternal.hxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/HttpdOutputPlugin.cxx (renamed from src/output/HttpdOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/HttpdOutputPlugin.hxx (renamed from src/output/HttpdOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/JackOutputPlugin.cxx (renamed from src/output/JackOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/JackOutputPlugin.hxx (renamed from src/output/JackOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/NullOutputPlugin.cxx (renamed from src/output/NullOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/NullOutputPlugin.hxx (renamed from src/output/NullOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/OSXOutputPlugin.cxx (renamed from src/output/OSXOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/OSXOutputPlugin.hxx (renamed from src/output/OSXOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/OpenALOutputPlugin.cxx (renamed from src/output/OpenALOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/OpenALOutputPlugin.hxx (renamed from src/output/OpenALOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/OssOutputPlugin.cxx (renamed from src/output/OssOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/OssOutputPlugin.hxx (renamed from src/output/OssOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/PipeOutputPlugin.cxx (renamed from src/output/PipeOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/PipeOutputPlugin.hxx (renamed from src/output/PipeOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/PulseOutputPlugin.cxx (renamed from src/output/PulseOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/PulseOutputPlugin.hxx (renamed from src/output/PulseOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/RecorderOutputPlugin.cxx (renamed from src/output/RecorderOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/RecorderOutputPlugin.hxx (renamed from src/output/RecorderOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/RoarOutputPlugin.cxx (renamed from src/output/RoarOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/RoarOutputPlugin.hxx (renamed from src/output/RoarOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/ShoutOutputPlugin.cxx (renamed from src/output/ShoutOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/ShoutOutputPlugin.hxx (renamed from src/output/ShoutOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/SolarisOutputPlugin.cxx (renamed from src/output/SolarisOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/SolarisOutputPlugin.hxx (renamed from src/output/SolarisOutputPlugin.hxx) | 0 | ||||
-rw-r--r-- | src/output/plugins/WinmmOutputPlugin.cxx (renamed from src/output/WinmmOutputPlugin.cxx) | 2 | ||||
-rw-r--r-- | src/output/plugins/WinmmOutputPlugin.hxx (renamed from src/output/WinmmOutputPlugin.hxx) | 0 |
57 files changed, 3501 insertions, 17 deletions
diff --git a/src/output/OutputAPI.hxx b/src/output/OutputAPI.hxx new file mode 100644 index 000000000..322ed3971 --- /dev/null +++ b/src/output/OutputAPI.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 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_API_HXX +#define MPD_OUTPUT_API_HXX + +// IWYU pragma: begin_exports + +#include "OutputPlugin.hxx" +#include "OutputInternal.hxx" +#include "AudioFormat.hxx" +#include "tag/Tag.hxx" +#include "ConfigData.hxx" + +// IWYU pragma: end_exports + +#endif diff --git a/src/output/OutputAll.cxx b/src/output/OutputAll.cxx new file mode 100644 index 000000000..b3623f1af --- /dev/null +++ b/src/output/OutputAll.cxx @@ -0,0 +1,589 @@ +/* + * Copyright (C) 2003-2014 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 "OutputAll.hxx" +#include "PlayerControl.hxx" +#include "OutputInternal.hxx" +#include "OutputControl.hxx" +#include "OutputError.hxx" +#include "MusicBuffer.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" +#include "system/FatalError.hxx" +#include "util/Error.hxx" +#include "ConfigData.hxx" +#include "ConfigGlobal.hxx" +#include "ConfigOption.hxx" +#include "notify.hxx" + +#include <assert.h> +#include <string.h> + +static AudioFormat input_audio_format; + +static struct audio_output **audio_outputs; +static unsigned int num_audio_outputs; + +/** + * The #MusicBuffer object where consumed chunks are returned. + */ +static MusicBuffer *g_music_buffer; + +/** + * The #MusicPipe object which feeds all audio outputs. It is filled + * by audio_output_all_play(). + */ +static MusicPipe *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] != nullptr); + + 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 nullptr; +} + +gcc_const +static unsigned +audio_output_config_count(void) +{ + unsigned int nr = 0; + const struct config_param *param = nullptr; + + 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(PlayerControl &pc) +{ + const struct config_param *param = nullptr; + unsigned int i; + Error error; + + num_audio_outputs = audio_output_config_count(); + audio_outputs = new audio_output *[num_audio_outputs]; + + const config_param empty; + + for (i = 0; i < num_audio_outputs; i++) + { + unsigned int j; + + param = config_get_next_param(CONF_AUDIO_OUTPUT, param); + if (param == nullptr) { + /* only allow param to be nullptr if there + just one audio output */ + assert(i == 0); + assert(num_audio_outputs == 1); + + param = ∅ + } + + audio_output *output = audio_output_new(*param, pc, error); + if (output == nullptr) { + if (param != nullptr) + FormatFatalError("line %i: %s", + param->line, + error.GetMessage()); + else + FatalError(error); + } + + audio_outputs[i] = output; + + /* require output names to be unique: */ + for (j = 0; j < i; j++) { + if (!strcmp(output->name, audio_outputs[j]->name)) { + FormatFatalError("output devices with identical " + "names: %s", 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]); + } + + delete[] audio_outputs; + audio_outputs = nullptr; + num_audio_outputs = 0; +} + +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; + + ao->mutex.lock(); + enabled = ao->really_enabled; + ao->mutex.unlock(); + + 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]; + + const ScopeLock protect(ao->mutex); + if (audio_output_is_open(ao) && + !audio_output_command_is_finished(ao)) + return false; + } + + return true; +} + +static void audio_output_wait_all(void) +{ + while (!audio_output_all_finished()) + audio_output_client_notify.Wait(); +} + +/** + * 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) +{ + const ScopeLock protect(ao->mutex); + + ao->fail_timer.Reset(); +} + +/** + * 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 (!input_audio_format.IsDefined()) + 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; +} + +void +audio_output_all_set_replay_gain_mode(ReplayGainMode mode) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_set_replay_gain_mode(audio_outputs[i], mode); +} + +bool +audio_output_all_play(struct music_chunk *chunk, Error &error) +{ + bool ret; + unsigned int i; + + assert(g_music_buffer != nullptr); + assert(g_mp != nullptr); + assert(chunk != nullptr); + assert(chunk->CheckFormat(input_audio_format)); + + ret = audio_output_all_update(); + if (!ret) { + /* TODO: obtain real error */ + error.Set(output_domain, "Failed to open audio output"); + return false; + } + + g_mp->Push(chunk); + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_play(audio_outputs[i]); + + return true; +} + +bool +audio_output_all_open(const AudioFormat audio_format, + MusicBuffer &buffer, + Error &error) +{ + bool ret = false, enabled = false; + unsigned int i; + + assert(g_music_buffer == nullptr || g_music_buffer == &buffer); + assert((g_mp == nullptr) == (g_music_buffer == nullptr)); + + g_music_buffer = &buffer; + + /* the audio format must be the same as existing chunks in the + pipe */ + assert(g_mp == nullptr || g_mp->CheckFormat(audio_format)); + + if (g_mp == nullptr) + g_mp = new MusicPipe(); + else + /* if the pipe hasn't been cleared, the the audio + format must not have changed */ + assert(g_mp->IsEmpty() || 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) + error.Set(output_domain, "All audio outputs are disabled"); + else if (!ret) + /* TODO: obtain real error */ + error.Set(output_domain, "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 == nullptr) + return false; + + assert(chunk == ao->chunk || g_mp->Contains(ao->chunk)); + + if (chunk != ao->chunk) { + assert(chunk->next != nullptr); + return true; + } + + return ao->chunk_finished && chunk->next == nullptr; +} + +/** + * 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) { + struct audio_output *ao = audio_outputs[i]; + + const ScopeLock protect(ao->mutex); + if (!chunk_is_consumed_in(ao, chunk)) + 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(gcc_unused const struct music_chunk *chunk, bool *locked) +{ + assert(chunk->next == nullptr); + assert(g_mp->Contains(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 */ + ao->mutex.lock(); + locked[i] = ao->open; + + if (!locked[i]) { + ao->mutex.unlock(); + continue; + } + + assert(ao->chunk == chunk); + assert(ao->chunk_finished); + ao->chunk = nullptr; + } +} + +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 != nullptr); + assert(g_mp != nullptr); + + while ((chunk = g_mp->Peek()) != nullptr) { + assert(!g_mp->IsEmpty()); + + if (!chunk_is_consumed(chunk)) + /* at least one output is not finished playing + this chunk */ + return g_mp->GetSize(); + + 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 == nullptr; + 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 = g_mp->Shift(); + 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]) + audio_outputs[i]->mutex.unlock(); + + /* return the chunk to the buffer */ + g_music_buffer->Return(shifted); + } + + return 0; +} + +bool +audio_output_all_wait(PlayerControl &pc, unsigned threshold) +{ + pc.Lock(); + + if (audio_output_all_check() < threshold) { + pc.Unlock(); + return true; + } + + pc.Wait(); + pc.Unlock(); + + 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 != nullptr) + g_mp->Clear(*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 != nullptr) { + assert(g_music_buffer != nullptr); + + g_mp->Clear(*g_music_buffer); + delete g_mp; + g_mp = nullptr; + } + + g_music_buffer = nullptr; + + input_audio_format.Clear(); + + 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 != nullptr) { + assert(g_music_buffer != nullptr); + + g_mp->Clear(*g_music_buffer); + delete g_mp; + g_mp = nullptr; + } + + g_music_buffer = nullptr; + + input_audio_format.Clear(); + + 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/OutputAll.hxx b/src/output/OutputAll.hxx new file mode 100644 index 000000000..b6166eb48 --- /dev/null +++ b/src/output/OutputAll.hxx @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* + * Functions for dealing with all configured (enabled) audion outputs + * at once. + * + */ + +#ifndef OUTPUT_ALL_H +#define OUTPUT_ALL_H + +#include "ReplayGainInfo.hxx" +#include "Compiler.h" + +struct AudioFormat; +class MusicBuffer; +struct music_chunk; +struct PlayerControl; +class Error; + +/** + * Global initialization: load audio outputs from the configuration + * file and initialize them. + */ +void +audio_output_all_init(PlayerControl &pc); + +/** + * Global finalization: free memory occupied by audio outputs. All + */ +void +audio_output_all_finish(void); + +/** + * Returns the total number of audio output devices, including those + * who are disabled right now. + */ +gcc_const +unsigned int audio_output_count(void); + +/** + * Returns the "i"th audio output device. + */ +gcc_const +struct audio_output * +audio_output_get(unsigned i); + +/** + * Returns the audio output device with the specified name. Returns + * NULL if the name does not exist. + */ +gcc_pure +struct audio_output * +audio_output_find(const char *name); + +/** + * Checks the "enabled" flag of all audio outputs, and if one has + * changed, commit the change. + */ +void +audio_output_all_enable_disable(void); + +/** + * Opens all audio outputs which are not disabled. + * + * @param audio_format the preferred audio format + * @param buffer the #music_buffer where consumed #music_chunk objects + * should be returned + * @return true on success, false on failure + */ +bool +audio_output_all_open(AudioFormat audio_format, + MusicBuffer &buffer, + Error &error); + +/** + * Closes all audio outputs. + */ +void +audio_output_all_close(void); + +/** + * Closes all audio outputs. Outputs with the "always_on" flag are + * put into pause mode. + */ +void +audio_output_all_release(void); + +void +audio_output_all_set_replay_gain_mode(ReplayGainMode mode); + +/** + * Enqueue a #music_chunk object for playing, i.e. pushes it to a + * #MusicPipe. + * + * @param chunk the #music_chunk object to be played + * @return true on success, false if no audio output was able to play + * (all closed then) + */ +bool +audio_output_all_play(music_chunk *chunk, Error &error); + +/** + * Checks if the output devices have drained their music pipe, and + * returns the consumed music chunks to the #music_buffer. + * + * @return the number of chunks to play left in the #MusicPipe + */ +unsigned +audio_output_all_check(void); + +/** + * Checks if the size of the #MusicPipe is below the #threshold. If + * not, it attempts to synchronize with all output threads, and waits + * until another #music_chunk is finished. + * + * @param threshold the maximum number of chunks in the pipe + * @return true if there are less than #threshold chunks in the pipe + */ +bool +audio_output_all_wait(PlayerControl &pc, unsigned threshold); + +/** + * Puts all audio outputs into pause mode. Most implementations will + * simply close it then. + */ +void +audio_output_all_pause(void); + +/** + * Drain all audio outputs. + */ +void +audio_output_all_drain(void); + +/** + * Try to cancel data which may still be in the device's buffers. + */ +void +audio_output_all_cancel(void); + +/** + * Indicate that a new song will begin now. + */ +void +audio_output_all_song_border(void); + +/** + * Returns the "elapsed_time" stamp of the most recently finished + * chunk. A negative value is returned when no chunk has been + * finished yet. + */ +gcc_pure +float +audio_output_all_get_elapsed_time(void); + +#endif diff --git a/src/output/OutputCommand.cxx b/src/output/OutputCommand.cxx new file mode 100644 index 000000000..839e9bd88 --- /dev/null +++ b/src/output/OutputCommand.cxx @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2003-2014 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" +#include "OutputAll.hxx" +#include "OutputInternal.hxx" +#include "PlayerControl.hxx" +#include "MixerControl.hxx" +#include "Idle.hxx" + +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); + + ao->player_control->UpdateAudio(); + + ++audio_output_state_version; + + return true; +} + +bool +audio_output_disable_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 = false; + idle_add(IDLE_OUTPUT); + + Mixer *mixer = ao->mixer; + if (mixer != nullptr) { + mixer_close(mixer); + idle_add(IDLE_MIXER); + } + + ao->player_control->UpdateAudio(); + + ++audio_output_state_version; + + return true; +} + +bool +audio_output_toggle_index(unsigned idx) +{ + struct audio_output *ao; + + if (idx >= audio_output_count()) + return false; + + ao = audio_output_get(idx); + const bool enabled = ao->enabled = !ao->enabled; + idle_add(IDLE_OUTPUT); + + if (!enabled) { + Mixer *mixer = ao->mixer; + if (mixer != nullptr) { + mixer_close(mixer); + idle_add(IDLE_MIXER); + } + } + + ao->player_control->UpdateAudio(); + + ++audio_output_state_version; + + return true; +} diff --git a/src/output/OutputCommand.hxx b/src/output/OutputCommand.hxx new file mode 100644 index 000000000..4c44dff53 --- /dev/null +++ b/src/output/OutputCommand.hxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2014 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); + +/** + * Toggles an audio output. Returns false if the specified output + * does not exist. + */ +bool +audio_output_toggle_index(unsigned idx); + +#endif diff --git a/src/output/OutputControl.cxx b/src/output/OutputControl.cxx new file mode 100644 index 000000000..b938754fd --- /dev/null +++ b/src/output/OutputControl.cxx @@ -0,0 +1,325 @@ + +/* + * Copyright (C) 2003-2014 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" +#include "OutputInternal.hxx" +#include "OutputPlugin.hxx" +#include "OutputError.hxx" +#include "MixerControl.hxx" +#include "notify.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <assert.h> + +/** after a failure, wait this number of seconds before + automatically reopening the device */ +static constexpr unsigned 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) { + ao->mutex.unlock(); + audio_output_client_notify.Wait(); + ao->mutex.lock(); + } +} + +/** + * 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; + ao->cond.signal(); +} + +/** + * 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) +{ + const ScopeLock protect(ao->mutex); + ao_command(ao, cmd); +} + +void +audio_output_set_replay_gain_mode(struct audio_output *ao, + ReplayGainMode mode) +{ + if (ao->replay_gain_filter != nullptr) + replay_gain_filter_set_mode(ao->replay_gain_filter, mode); + if (ao->other_replay_gain_filter != nullptr) + replay_gain_filter_set_mode(ao->other_replay_gain_filter, mode); +} + +void +audio_output_enable(struct audio_output *ao) +{ + if (!ao->thread.IsDefined()) { + if (ao->plugin->enable == nullptr) { + /* 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.IsDefined()) { + if (ao->plugin->disable == nullptr) + 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 AudioFormat audio_format, + const MusicPipe &mp) +{ + bool open; + + assert(ao != nullptr); + assert(ao->allow_play); + assert(audio_format.IsValid()); + + ao->fail_timer.Reset(); + + if (ao->open && audio_format == ao->in_audio_format) { + assert(ao->pipe == &mp || + (ao->always_on && ao->pause)); + + if (ao->pause) { + ao->chunk = nullptr; + ao->pipe = ∓ + + /* 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 = nullptr; + + ao->pipe = ∓ + + if (!ao->thread.IsDefined()) + audio_output_thread_start(ao); + + ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN); + open = ao->open; + + if (open && ao->mixer != nullptr) { + Error error; + if (!mixer_open(ao->mixer, error)) + FormatWarning(output_domain, + "Failed to open mixer for '%s'", + ao->name); + } + + 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 != nullptr); + assert(ao->allow_play); + + if (ao->mixer != nullptr) + mixer_auto_close(ao->mixer); + + assert(!ao->open || !ao->fail_timer.IsDefined()); + + if (ao->open) + ao_command(ao, AO_COMMAND_CLOSE); + else + ao->fail_timer.Reset(); +} + +bool +audio_output_update(struct audio_output *ao, + const AudioFormat audio_format, + const MusicPipe &mp) +{ + const ScopeLock protect(ao->mutex); + + if (ao->enabled && ao->really_enabled) { + if (ao->fail_timer.Check(REOPEN_AFTER * 1000)) { + return audio_output_open(ao, audio_format, mp); + } + } else if (audio_output_is_open(ao)) + audio_output_close_locked(ao); + + return false; +} + +void +audio_output_play(struct audio_output *ao) +{ + const ScopeLock protect(ao->mutex); + + assert(ao->allow_play); + + if (audio_output_is_open(ao) && !ao->in_playback_loop && + !ao->woken_for_play) { + ao->woken_for_play = true; + ao->cond.signal(); + } +} + +void audio_output_pause(struct audio_output *ao) +{ + if (ao->mixer != nullptr && ao->plugin->pause == nullptr) + /* 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); + + const ScopeLock protect(ao->mutex); + + assert(ao->allow_play); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_PAUSE); +} + +void +audio_output_drain_async(struct audio_output *ao) +{ + const ScopeLock protect(ao->mutex); + + assert(ao->allow_play); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_DRAIN); +} + +void audio_output_cancel(struct audio_output *ao) +{ + const ScopeLock protect(ao->mutex); + + if (audio_output_is_open(ao)) { + ao->allow_play = false; + ao_command_async(ao, AO_COMMAND_CANCEL); + } +} + +void +audio_output_allow_play(struct audio_output *ao) +{ + const ScopeLock protect(ao->mutex); + + ao->allow_play = true; + if (audio_output_is_open(ao)) + ao->cond.signal(); +} + +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 != nullptr); + assert(!ao->open || !ao->fail_timer.IsDefined()); + + const ScopeLock protect(ao->mutex); + audio_output_close_locked(ao); +} + +void audio_output_finish(struct audio_output *ao) +{ + audio_output_close(ao); + + assert(!ao->fail_timer.IsDefined()); + + if (ao->thread.IsDefined()) { + assert(ao->allow_play); + ao_lock_command(ao, AO_COMMAND_KILL); + ao->thread.Join(); + } + + audio_output_free(ao); +} diff --git a/src/output/OutputControl.hxx b/src/output/OutputControl.hxx new file mode 100644 index 000000000..7195412ef --- /dev/null +++ b/src/output/OutputControl.hxx @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2003-2014 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 "ReplayGainInfo.hxx" + +#include <stddef.h> + +struct audio_output; +struct AudioFormat; +struct config_param; +class MusicPipe; + +void +audio_output_set_replay_gain_mode(audio_output *ao, + ReplayGainMode mode); + +/** + * Enables the device. + */ +void +audio_output_enable(audio_output *ao); + +/** + * Disables the device. + */ +void +audio_output_disable(audio_output *ao); + +/** + * Opens or closes the device, depending on the "enabled" flag. + * + * @return true if the device is open + */ +bool +audio_output_update(audio_output *ao, + AudioFormat audio_format, + const MusicPipe &mp); + +void +audio_output_play(audio_output *ao); + +void +audio_output_pause(audio_output *ao); + +void +audio_output_drain_async(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(audio_output *ao); + +/** + * Set the "allow_play" and signal the thread. + */ +void +audio_output_allow_play(audio_output *ao); + +void +audio_output_close(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(audio_output *ao); + +void +audio_output_finish(audio_output *ao); + +#endif diff --git a/src/output/OutputError.cxx b/src/output/OutputError.cxx new file mode 100644 index 000000000..9d4128912 --- /dev/null +++ b/src/output/OutputError.cxx @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2003-2014 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 "OutputError.hxx" +#include "util/Domain.hxx" + +const Domain output_domain("output"); diff --git a/src/output/OutputError.hxx b/src/output/OutputError.hxx new file mode 100644 index 000000000..e3a20142f --- /dev/null +++ b/src/output/OutputError.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2014 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 + +extern const class Domain output_domain; + +#endif diff --git a/src/output/OutputFinish.cxx b/src/output/OutputFinish.cxx new file mode 100644 index 000000000..43f0dd1ec --- /dev/null +++ b/src/output/OutputFinish.cxx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2014 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 "OutputInternal.hxx" +#include "OutputPlugin.hxx" +#include "MixerControl.hxx" +#include "FilterInternal.hxx" + +#include <assert.h> + +void +ao_base_finish(struct audio_output *ao) +{ + assert(!ao->open); + assert(!ao->fail_timer.IsDefined()); + assert(!ao->thread.IsDefined()); + + if (ao->mixer != nullptr) + mixer_free(ao->mixer); + + delete ao->replay_gain_filter; + delete ao->other_replay_gain_filter; + delete ao->filter; +} + +void +audio_output_free(struct audio_output *ao) +{ + assert(!ao->open); + assert(!ao->fail_timer.IsDefined()); + assert(!ao->thread.IsDefined()); + + ao_plugin_finish(ao); +} diff --git a/src/output/OutputInit.cxx b/src/output/OutputInit.cxx new file mode 100644 index 000000000..f5b1bdc81 --- /dev/null +++ b/src/output/OutputInit.cxx @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2003-2014 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 "OutputInternal.hxx" +#include "OutputList.hxx" +#include "OutputError.hxx" +#include "OutputAPI.hxx" +#include "FilterConfig.hxx" +#include "AudioParser.hxx" +#include "MixerList.hxx" +#include "MixerType.hxx" +#include "MixerControl.hxx" +#include "mixer/SoftwareMixerPlugin.hxx" +#include "FilterPlugin.hxx" +#include "FilterRegistry.hxx" +#include "filter/AutoConvertFilterPlugin.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" +#include "filter/ChainFilterPlugin.hxx" +#include "ConfigError.hxx" +#include "ConfigGlobal.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include <assert.h> +#include <string.h> + +#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(Error &error) +{ + LogDefault(output_domain, "Attempt to detect audio output device"); + + audio_output_plugins_for_each(plugin) { + if (plugin->test_default_device == nullptr) + continue; + + FormatDefault(output_domain, + "Attempting to detect a %s audio device", + plugin->name); + if (ao_plugin_test_default_device(plugin)) + return plugin; + } + + error.Set(output_domain, "Unable to detect an audio device"); + return nullptr; +} + +/** + * 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. + */ +gcc_pure +static enum mixer_type +audio_output_mixer_type(const config_param ¶m) +{ + /* read the local "mixer_type" setting */ + const char *p = param.GetBlockValue("mixer_type"); + if (p != nullptr) + return mixer_type_parse(p); + + /* try the local "mixer_enabled" setting next (deprecated) */ + if (!param.GetBlockValue("mixer_enabled", true)) + return MIXER_TYPE_NONE; + + /* fall back to the global "mixer_type" setting (also + deprecated) */ + return mixer_type_parse(config_get_string(CONF_MIXER_TYPE, + "hardware")); +} + +static Mixer * +audio_output_load_mixer(struct audio_output *ao, + const config_param ¶m, + const struct mixer_plugin *plugin, + Filter &filter_chain, + Error &error) +{ + Mixer *mixer; + + switch (audio_output_mixer_type(param)) { + case MIXER_TYPE_NONE: + case MIXER_TYPE_UNKNOWN: + return nullptr; + + case MIXER_TYPE_HARDWARE: + if (plugin == nullptr) + return nullptr; + + return mixer_new(plugin, ao, param, error); + + case MIXER_TYPE_SOFTWARE: + mixer = mixer_new(&software_mixer_plugin, nullptr, + config_param(), + IgnoreError()); + assert(mixer != nullptr); + + filter_chain_append(filter_chain, "software_mixer", + software_mixer_get_filter(mixer)); + return mixer; + } + + assert(false); + gcc_unreachable(); +} + +bool +ao_base_init(struct audio_output *ao, + const struct audio_output_plugin *plugin, + const config_param ¶m, Error &error) +{ + assert(ao != nullptr); + assert(plugin != nullptr); + assert(plugin->finish != nullptr); + assert(plugin->open != nullptr); + assert(plugin->close != nullptr); + assert(plugin->play != nullptr); + + if (!param.IsNull()) { + ao->name = param.GetBlockValue(AUDIO_OUTPUT_NAME); + if (ao->name == nullptr) { + error.Set(config_domain, + "Missing \"name\" configuration"); + return false; + } + + const char *p = param.GetBlockValue(AUDIO_OUTPUT_FORMAT); + if (p != nullptr) { + bool success = + audio_format_parse(ao->config_audio_format, + p, true, error); + if (!success) + return false; + } else + ao->config_audio_format.Clear(); + } else { + ao->name = "default detected output"; + + ao->config_audio_format.Clear(); + } + + ao->plugin = plugin; + ao->tags = param.GetBlockValue("tags", true); + ao->always_on = param.GetBlockValue("always_on", false); + ao->enabled = param.GetBlockValue("enabled", true); + ao->really_enabled = false; + ao->open = false; + ao->pause = false; + ao->allow_play = true; + ao->in_playback_loop = false; + ao->woken_for_play = false; + + /* set up the filter chain */ + + ao->filter = filter_chain_new(); + assert(ao->filter != nullptr); + + /* create the normalization filter (if configured) */ + + if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) { + Filter *normalize_filter = + filter_new(&normalize_filter_plugin, config_param(), + IgnoreError()); + assert(normalize_filter != nullptr); + + filter_chain_append(*ao->filter, "normalize", + autoconvert_filter_new(normalize_filter)); + } + + Error filter_error; + filter_chain_parse(*ao->filter, + param.GetBlockValue(AUDIO_FILTERS, ""), + filter_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 (filter_error.IsDefined()) + FormatError(filter_error, + "Failed to initialize filter chain for '%s'", + ao->name); + + ao->command = AO_COMMAND_NONE; + + ao->mixer = nullptr; + ao->replay_gain_filter = nullptr; + ao->other_replay_gain_filter = nullptr; + + /* done */ + + return true; +} + +static bool +audio_output_setup(struct audio_output *ao, const config_param ¶m, + Error &error) +{ + + /* create the replay_gain filter */ + + const char *replay_gain_handler = + param.GetBlockValue("replay_gain_handler", "software"); + + if (strcmp(replay_gain_handler, "none") != 0) { + ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, IgnoreError()); + assert(ao->replay_gain_filter != nullptr); + + ao->replay_gain_serial = 0; + + ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, + IgnoreError()); + assert(ao->other_replay_gain_filter != nullptr); + + ao->other_replay_gain_serial = 0; + } else { + ao->replay_gain_filter = nullptr; + ao->other_replay_gain_filter = nullptr; + } + + /* set up the mixer */ + + Error mixer_error; + ao->mixer = audio_output_load_mixer(ao, param, + ao->plugin->mixer_plugin, + *ao->filter, mixer_error); + if (ao->mixer == nullptr && mixer_error.IsDefined()) + FormatError(mixer_error, + "Failed to initialize hardware mixer for '%s'", + ao->name); + + /* use the hardware mixer for replay gain? */ + + if (strcmp(replay_gain_handler, "mixer") == 0) { + if (ao->mixer != nullptr) + replay_gain_filter_set_mixer(ao->replay_gain_filter, + ao->mixer, 100); + else + FormatError(output_domain, + "No such mixer for output '%s'", ao->name); + } else if (strcmp(replay_gain_handler, "software") != 0 && + ao->replay_gain_filter != nullptr) { + error.Set(config_domain, + "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, config_param(), + IgnoreError()); + assert(ao->convert_filter != nullptr); + + filter_chain_append(*ao->filter, "convert", ao->convert_filter); + + return true; +} + +struct audio_output * +audio_output_new(const config_param ¶m, + PlayerControl &pc, + Error &error) +{ + const struct audio_output_plugin *plugin; + + if (!param.IsNull()) { + const char *p; + + p = param.GetBlockValue(AUDIO_OUTPUT_TYPE); + if (p == nullptr) { + error.Set(config_domain, + "Missing \"type\" configuration"); + return nullptr; + } + + plugin = audio_output_plugin_get(p); + if (plugin == nullptr) { + error.Format(config_domain, + "No such audio output plugin: %s", p); + return nullptr; + } + } else { + LogWarning(output_domain, + "No 'audio_output' defined in config file"); + + plugin = audio_output_detect(error); + if (plugin == nullptr) + return nullptr; + + FormatDefault(output_domain, + "Successfully detected a %s audio device", + plugin->name); + } + + struct audio_output *ao = ao_plugin_init(plugin, param, error); + if (ao == nullptr) + return nullptr; + + if (!audio_output_setup(ao, param, error)) { + ao_plugin_finish(ao); + return nullptr; + } + + ao->player_control = &pc; + return ao; +} diff --git a/src/output/OutputInternal.hxx b/src/output/OutputInternal.hxx new file mode 100644 index 000000000..18404dc01 --- /dev/null +++ b/src/output/OutputInternal.hxx @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2003-2014 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_INTERNAL_HXX +#define MPD_OUTPUT_INTERNAL_HXX + +#include "AudioFormat.hxx" +#include "pcm/PcmBuffer.hxx" +#include "pcm/PcmDither.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "thread/Thread.hxx" +#include "system/PeriodClock.hxx" + +class Error; +class Filter; +class MusicPipe; +struct config_param; +struct PlayerControl; + +enum audio_output_command { + AO_COMMAND_NONE = 0, + AO_COMMAND_ENABLE, + AO_COMMAND_DISABLE, + AO_COMMAND_OPEN, + + /** + * This command is invoked when the input audio format + * changes. + */ + AO_COMMAND_REOPEN, + + AO_COMMAND_CLOSE, + AO_COMMAND_PAUSE, + + /** + * Drains the internal (hardware) buffers of the device. This + * operation may take a while to complete. + */ + AO_COMMAND_DRAIN, + + AO_COMMAND_CANCEL, + AO_COMMAND_KILL +}; + +struct audio_output { + /** + * The device's configured display name. + */ + const char *name; + + /** + * The plugin which implements this output device. + */ + const struct audio_output_plugin *plugin; + + /** + * The #mixer object associated with this audio output device. + * May be nullptr if none is available, or if software volume is + * configured. + */ + class Mixer *mixer; + + /** + * Will this output receive tags from the decoder? The + * default is true, but it may be configured to false to + * suppress sending tags to the output. + */ + bool tags; + + /** + * Shall this output always play something (i.e. silence), + * even when playback is stopped? + */ + bool always_on; + + /** + * Has the user enabled this device? + */ + bool enabled; + + /** + * Is this device actually enabled, i.e. the "enable" method + * has succeeded? + */ + bool really_enabled; + + /** + * Is the device (already) open and functional? + * + * This attribute may only be modified by the output thread. + * It is protected with #mutex: write accesses inside the + * output thread and read accesses outside of it may only be + * performed while the lock is held. + */ + bool open; + + /** + * Is the device paused? i.e. the output thread is in the + * ao_pause() loop. + */ + bool pause; + + /** + * When this flag is set, the output thread will not do any + * playback. It will wait until the flag is cleared. + * + * This is used to synchronize the "clear" operation on the + * shared music pipe during the CANCEL command. + */ + bool allow_play; + + /** + * True while the OutputThread is inside ao_play(). This + * means the PlayerThread does not need to wake up the + * OutputThread when new chunks are added to the MusicPipe, + * because the OutputThread is already watching that. + */ + bool in_playback_loop; + + /** + * Has the OutputThread been woken up to play more chunks? + * This is set by audio_output_play() and reset by ao_play() + * to reduce the number of duplicate wakeups. + */ + bool woken_for_play; + + /** + * If not nullptr, the device has failed, and this timer is used + * to estimate how long it should stay disabled (unless + * explicitly reopened with "play"). + */ + PeriodClock fail_timer; + + /** + * The configured audio format. + */ + AudioFormat config_audio_format; + + /** + * The audio_format in which audio data is received from the + * player thread (which in turn receives it from the decoder). + */ + AudioFormat in_audio_format; + + /** + * The audio_format which is really sent to the device. This + * is basically config_audio_format (if configured) or + * in_audio_format, but may have been modified by + * plugin->open(). + */ + AudioFormat out_audio_format; + + /** + * The buffer used to allocate the cross-fading result. + */ + PcmBuffer cross_fade_buffer; + + /** + * The dithering state for cross-fading two streams. + */ + PcmDither cross_fade_dither; + + /** + * The filter object of this audio output. This is an + * instance of chain_filter_plugin. + */ + Filter *filter; + + /** + * The replay_gain_filter_plugin instance of this audio + * output. + */ + Filter *replay_gain_filter; + + /** + * The serial number of the last replay gain info. 0 means no + * replay gain info was available. + */ + unsigned replay_gain_serial; + + /** + * The replay_gain_filter_plugin instance of this audio + * output, to be applied to the second chunk during + * cross-fading. + */ + Filter *other_replay_gain_filter; + + /** + * The serial number of the last replay gain info by the + * "other" chunk during cross-fading. + */ + unsigned other_replay_gain_serial; + + /** + * The convert_filter_plugin instance of this audio output. + * It is the last item in the filter chain, and is responsible + * for converting the input data into the appropriate format + * for this audio output. + */ + Filter *convert_filter; + + /** + * The thread handle, or nullptr if the output thread isn't + * running. + */ + Thread thread; + + /** + * The next command to be performed by the output thread. + */ + enum audio_output_command command; + + /** + * The music pipe which provides music chunks to be played. + */ + const MusicPipe *pipe; + + /** + * This mutex protects #open, #fail_timer, #chunk and + * #chunk_finished. + */ + Mutex mutex; + + /** + * This condition object wakes up the output thread after + * #command has been set. + */ + Cond cond; + + /** + * The PlayerControl object which "owns" this output. This + * object is needed to signal command completion. + */ + PlayerControl *player_control; + + /** + * The #music_chunk which is currently being played. All + * chunks before this one may be returned to the + * #music_buffer, because they are not going to be used by + * this output anymore. + */ + const struct music_chunk *chunk; + + /** + * Has the output finished playing #chunk? + */ + bool chunk_finished; +}; + +/** + * Notify object used by the thread's client, i.e. we will send a + * notify signal to this object, expecting the caller to wait on it. + */ +extern struct notify audio_output_client_notify; + +static inline bool +audio_output_is_open(const struct audio_output *ao) +{ + return ao->open; +} + +static inline bool +audio_output_command_is_finished(const struct audio_output *ao) +{ + return ao->command == AO_COMMAND_NONE; +} + +struct audio_output * +audio_output_new(const config_param ¶m, + PlayerControl &pc, + Error &error); + +bool +ao_base_init(struct audio_output *ao, + const struct audio_output_plugin *plugin, + const config_param ¶m, Error &error); + +void +ao_base_finish(struct audio_output *ao); + +void +audio_output_free(struct audio_output *ao); + +#endif diff --git a/src/output/OutputList.cxx b/src/output/OutputList.cxx new file mode 100644 index 000000000..b914e6d2e --- /dev/null +++ b/src/output/OutputList.cxx @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2003-2014 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 "OutputAPI.hxx" +#include "plugins/AlsaOutputPlugin.hxx" +#include "plugins/AoOutputPlugin.hxx" +#include "plugins/FifoOutputPlugin.hxx" +#include "plugins/HttpdOutputPlugin.hxx" +#include "plugins/JackOutputPlugin.hxx" +#include "plugins/NullOutputPlugin.hxx" +#include "plugins/OpenALOutputPlugin.hxx" +#include "plugins/OssOutputPlugin.hxx" +#include "plugins/OSXOutputPlugin.hxx" +#include "plugins/PipeOutputPlugin.hxx" +#include "plugins/PulseOutputPlugin.hxx" +#include "plugins/RecorderOutputPlugin.hxx" +#include "plugins/RoarOutputPlugin.hxx" +#include "plugins/ShoutOutputPlugin.hxx" +#include "plugins/SolarisOutputPlugin.hxx" +#include "plugins/WinmmOutputPlugin.hxx" + +#include <string.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_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 + nullptr +}; + +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 nullptr; +} diff --git a/src/output/OutputList.hxx b/src/output/OutputList.hxx new file mode 100644 index 000000000..dfcf7487c --- /dev/null +++ b/src/output/OutputList.hxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2014 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) != nullptr; ++output_plugin_iterator) + +#endif diff --git a/src/output/OutputPlugin.cxx b/src/output/OutputPlugin.cxx new file mode 100644 index 000000000..29fd6455a --- /dev/null +++ b/src/output/OutputPlugin.cxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2014 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 "OutputPlugin.hxx" +#include "OutputInternal.hxx" + +struct audio_output * +ao_plugin_init(const struct audio_output_plugin *plugin, + const config_param ¶m, + Error &error) +{ + assert(plugin != nullptr); + assert(plugin->init != nullptr); + + 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, Error &error_r) +{ + return ao->plugin->enable != nullptr + ? ao->plugin->enable(ao, error_r) + : true; +} + +void +ao_plugin_disable(struct audio_output *ao) +{ + if (ao->plugin->disable != nullptr) + ao->plugin->disable(ao); +} + +bool +ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format, + Error &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 != nullptr + ? ao->plugin->delay(ao) + : 0; +} + +void +ao_plugin_send_tag(struct audio_output *ao, const Tag *tag) +{ + if (ao->plugin->send_tag != nullptr) + ao->plugin->send_tag(ao, tag); +} + +size_t +ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error) +{ + return ao->plugin->play(ao, chunk, size, error); +} + +void +ao_plugin_drain(struct audio_output *ao) +{ + if (ao->plugin->drain != nullptr) + ao->plugin->drain(ao); +} + +void +ao_plugin_cancel(struct audio_output *ao) +{ + if (ao->plugin->cancel != nullptr) + ao->plugin->cancel(ao); +} + +bool +ao_plugin_pause(struct audio_output *ao) +{ + return ao->plugin->pause != nullptr && ao->plugin->pause(ao); +} diff --git a/src/output/OutputPlugin.hxx b/src/output/OutputPlugin.hxx new file mode 100644 index 000000000..7d77c92b2 --- /dev/null +++ b/src/output/OutputPlugin.hxx @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2003-2014 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_PLUGIN_HXX +#define MPD_OUTPUT_PLUGIN_HXX + +#include "Compiler.h" + +#include <stddef.h> + +struct config_param; +struct AudioFormat; +struct Tag; +class Error; + +/** + * A plugin which controls an audio output device. + */ +struct audio_output_plugin { + /** + * the plugin's name + */ + const char *name; + + /** + * Test if this plugin can provide a default output, in case + * none has been configured. This method is optional. + */ + bool (*test_default_device)(void); + + /** + * Configure and initialize the device, but do not open it + * yet. + * + * @param param the configuration section, or nullptr if there is + * no configuration + * @return nullptr on error, or an opaque pointer to the plugin's + * data + */ + struct audio_output *(*init)(const config_param ¶m, + Error &error); + + /** + * Free resources allocated by this device. + */ + void (*finish)(struct audio_output *data); + + /** + * Enable the device. This may allocate resources, preparing + * for the device to be opened. Enabling a device cannot + * fail: if an error occurs during that, it should be reported + * by the open() method. + * + * @return true on success, false on error + */ + bool (*enable)(struct audio_output *data, Error &error); + + /** + * Disables the device. It is closed before this method is + * called. + */ + void (*disable)(struct audio_output *data); + + /** + * Really open the device. + * + * @param audio_format the audio format in which data is going + * to be delivered; may be modified by the plugin + */ + bool (*open)(struct audio_output *data, AudioFormat &audio_format, + Error &error); + + /** + * Close the device. + */ + void (*close)(struct audio_output *data); + + /** + * Returns a positive number if the output thread shall delay + * the next call to play() or pause(). This should be + * implemented instead of doing a sleep inside the plugin, + * because this allows MPD to listen to commands meanwhile. + * + * @return the number of milliseconds to wait + */ + unsigned (*delay)(struct audio_output *data); + + /** + * Display metadata for the next chunk. Optional method, + * because not all devices can display metadata. + */ + void (*send_tag)(struct audio_output *data, const Tag *tag); + + /** + * Play a chunk of audio data. + * + * @return the number of bytes played, or 0 on error + */ + size_t (*play)(struct audio_output *data, + const void *chunk, size_t size, + Error &error); + + /** + * Wait until the device has finished playing. + */ + void (*drain)(struct audio_output *data); + + /** + * Try to cancel data which may still be in the device's + * buffers. + */ + void (*cancel)(struct audio_output *data); + + /** + * Pause the device. If supported, it may perform a special + * action, which keeps the device open, but does not play + * anything. Output plugins like "shout" might want to play + * silence during pause, so their clients won't be + * disconnected. Plugins which do not support pausing will + * simply be closed, and have to be reopened when unpaused. + * + * @return false on error (output will be closed then), true + * for continue to pause + */ + bool (*pause)(struct audio_output *data); + + /** + * The mixer plugin associated with this output plugin. This + * may be nullptr if no mixer plugin is implemented. When + * created, this mixer plugin gets the same #config_param as + * this audio output device. + */ + const struct mixer_plugin *mixer_plugin; +}; + +static inline bool +ao_plugin_test_default_device(const struct audio_output_plugin *plugin) +{ + return plugin->test_default_device != nullptr + ? plugin->test_default_device() + : false; +} + +gcc_malloc +struct audio_output * +ao_plugin_init(const struct audio_output_plugin *plugin, + const config_param ¶m, + Error &error); + +void +ao_plugin_finish(struct audio_output *ao); + +bool +ao_plugin_enable(struct audio_output *ao, Error &error); + +void +ao_plugin_disable(struct audio_output *ao); + +bool +ao_plugin_open(struct audio_output *ao, AudioFormat &audio_format, + Error &error); + +void +ao_plugin_close(struct audio_output *ao); + +gcc_pure +unsigned +ao_plugin_delay(struct audio_output *ao); + +void +ao_plugin_send_tag(struct audio_output *ao, const Tag *tag); + +size_t +ao_plugin_play(struct audio_output *ao, const void *chunk, size_t size, + Error &error); + +void +ao_plugin_drain(struct audio_output *ao); + +void +ao_plugin_cancel(struct audio_output *ao); + +bool +ao_plugin_pause(struct audio_output *ao); + +#endif diff --git a/src/output/OutputPrint.cxx b/src/output/OutputPrint.cxx new file mode 100644 index 000000000..ee4424df2 --- /dev/null +++ b/src/output/OutputPrint.cxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* + * Protocol specific code for the audio output library. + * + */ + +#include "config.h" +#include "OutputPrint.hxx" +#include "OutputAll.hxx" +#include "OutputInternal.hxx" +#include "Client.hxx" + +void +printAudioDevices(Client &client) +{ + const unsigned n = audio_output_count(); + + for (unsigned i = 0; i < n; ++i) { + const struct audio_output *ao = audio_output_get(i); + + client_printf(client, + "outputid: %i\n" + "outputname: %s\n" + "outputenabled: %i\n", + i, ao->name, ao->enabled); + } +} diff --git a/src/output/OutputPrint.hxx b/src/output/OutputPrint.hxx new file mode 100644 index 000000000..2d94226fd --- /dev/null +++ b/src/output/OutputPrint.hxx @@ -0,0 +1,34 @@ + +/* + * Copyright (C) 2003-2014 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. + */ + +/* + * Protocol specific code for the audio output library. + * + */ + +#ifndef MPD_OUTPUT_PRINT_HXX +#define MPD_OUTPUT_PRINT_HXX + +class Client; + +void +printAudioDevices(Client &client); + +#endif diff --git a/src/output/OutputState.cxx b/src/output/OutputState.cxx new file mode 100644 index 000000000..1141abeda --- /dev/null +++ b/src/output/OutputState.cxx @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* + * Saving and loading the audio output states to/from the state file. + * + */ + +#include "config.h" +#include "OutputState.hxx" +#include "OutputAll.hxx" +#include "OutputInternal.hxx" +#include "OutputError.hxx" +#include "Log.hxx" +#include "util/StringUtil.hxx" + +#include <assert.h> +#include <stdlib.h> + +#define AUDIO_DEVICE_STATE "audio_device_state:" + +unsigned audio_output_state_version; + +void +audio_output_state_save(FILE *fp) +{ + unsigned n = audio_output_count(); + + assert(n > 0); + + for (unsigned i = 0; i < n; ++i) { + const struct audio_output *ao = audio_output_get(i); + + fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n", + ao->enabled, ao->name); + } +} + +bool +audio_output_state_read(const char *line) +{ + long value; + char *endptr; + const char *name; + struct audio_output *ao; + + if (!StringStartsWith(line, AUDIO_DEVICE_STATE)) + return false; + + line += sizeof(AUDIO_DEVICE_STATE) - 1; + + value = strtol(line, &endptr, 10); + if (*endptr != ':' || (value != 0 && value != 1)) + return false; + + if (value != 0) + /* state is "enabled": no-op */ + return true; + + name = endptr + 1; + ao = audio_output_find(name); + if (ao == NULL) { + FormatDebug(output_domain, + "Ignoring device state for '%s'", name); + return true; + } + + ao->enabled = false; + return true; +} + +unsigned +audio_output_state_get_version(void) +{ + return audio_output_state_version; +} diff --git a/src/output/OutputState.hxx b/src/output/OutputState.hxx new file mode 100644 index 000000000..a68180ae4 --- /dev/null +++ b/src/output/OutputState.hxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* + * Saving and loading the audio output states to/from the state file. + * + */ + +#ifndef MPD_OUTPUT_STATE_HXX +#define MPD_OUTPUT_STATE_HXX + +#include <stdio.h> + +bool +audio_output_state_read(const char *line); + +void +audio_output_state_save(FILE *fp); + +/** + * Generates a version number for the current state of the audio + * outputs. This is used by timer_save_state_file() to determine + * whether the state has changed and the state file should be saved. + */ +unsigned +audio_output_state_get_version(void); + +#endif diff --git a/src/output/OutputThread.cxx b/src/output/OutputThread.cxx new file mode 100644 index 000000000..f7e28b5f4 --- /dev/null +++ b/src/output/OutputThread.cxx @@ -0,0 +1,690 @@ +/* + * Copyright (C) 2003-2014 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" +#include "OutputInternal.hxx" +#include "OutputAPI.hxx" +#include "OutputError.hxx" +#include "pcm/PcmMix.hxx" +#include "notify.hxx" +#include "FilterInternal.hxx" +#include "filter/ConvertFilterPlugin.hxx" +#include "filter/ReplayGainFilterPlugin.hxx" +#include "PlayerControl.hxx" +#include "MusicPipe.hxx" +#include "MusicChunk.hxx" +#include "thread/Util.hxx" +#include "thread/Name.hxx" +#include "system/FatalError.hxx" +#include "util/Error.hxx" +#include "Log.hxx" +#include "Compiler.h" + +#include <assert.h> +#include <string.h> + +static void ao_command_finished(struct audio_output *ao) +{ + assert(ao->command != AO_COMMAND_NONE); + ao->command = AO_COMMAND_NONE; + + ao->mutex.unlock(); + audio_output_client_notify.Signal(); + ao->mutex.lock(); +} + +static bool +ao_enable(struct audio_output *ao) +{ + Error error; + bool success; + + if (ao->really_enabled) + return true; + + ao->mutex.unlock(); + success = ao_plugin_enable(ao, error); + ao->mutex.lock(); + if (!success) { + FormatError(error, + "Failed to enable \"%s\" [%s]", + ao->name, ao->plugin->name); + 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; + + ao->mutex.unlock(); + ao_plugin_disable(ao); + ao->mutex.lock(); + } +} + +static AudioFormat +ao_filter_open(struct audio_output *ao, AudioFormat &format, + Error &error_r) +{ + assert(format.IsValid()); + + /* the replay_gain filter cannot fail here */ + if (ao->replay_gain_filter != nullptr && + !ao->replay_gain_filter->Open(format, error_r).IsDefined()) + return AudioFormat::Undefined(); + + if (ao->other_replay_gain_filter != nullptr && + !ao->other_replay_gain_filter->Open(format, error_r).IsDefined()) { + if (ao->replay_gain_filter != nullptr) + ao->replay_gain_filter->Close(); + return AudioFormat::Undefined(); + } + + const AudioFormat af = ao->filter->Open(format, error_r); + if (!af.IsDefined()) { + if (ao->replay_gain_filter != nullptr) + ao->replay_gain_filter->Close(); + if (ao->other_replay_gain_filter != nullptr) + ao->other_replay_gain_filter->Close(); + } + + return af; +} + +static void +ao_filter_close(struct audio_output *ao) +{ + if (ao->replay_gain_filter != nullptr) + ao->replay_gain_filter->Close(); + if (ao->other_replay_gain_filter != nullptr) + ao->other_replay_gain_filter->Close(); + + ao->filter->Close(); +} + +static void +ao_open(struct audio_output *ao) +{ + bool success; + Error error; + struct audio_format_string af_string; + + assert(!ao->open); + assert(ao->pipe != nullptr); + assert(ao->chunk == nullptr); + assert(ao->in_audio_format.IsValid()); + + ao->fail_timer.Reset(); + + /* enable the device (just in case the last enable has failed) */ + + if (!ao_enable(ao)) + /* still no luck */ + return; + + /* open the filter */ + + const AudioFormat filter_audio_format = + ao_filter_open(ao, ao->in_audio_format, error); + if (!filter_audio_format.IsDefined()) { + FormatError(error, "Failed to open filter for \"%s\" [%s]", + ao->name, ao->plugin->name); + + ao->fail_timer.Update(); + return; + } + + assert(filter_audio_format.IsValid()); + + ao->out_audio_format = filter_audio_format; + ao->out_audio_format.ApplyMask(ao->config_audio_format); + + ao->mutex.unlock(); + success = ao_plugin_open(ao, ao->out_audio_format, error); + ao->mutex.lock(); + + assert(!ao->open); + + if (!success) { + FormatError(error, "Failed to open \"%s\" [%s]", + ao->name, ao->plugin->name); + + ao_filter_close(ao); + ao->fail_timer.Update(); + return; + } + + if (!convert_filter_set(ao->convert_filter, ao->out_audio_format, + error)) { + FormatError(error, "Failed to convert for \"%s\" [%s]", + ao->name, ao->plugin->name); + + ao_filter_close(ao); + ao->fail_timer.Update(); + return; + } + + ao->open = true; + + FormatDebug(output_domain, + "opened plugin=%s name=\"%s\" audio_format=%s", + ao->plugin->name, ao->name, + audio_format_to_string(ao->out_audio_format, &af_string)); + + if (ao->in_audio_format != ao->out_audio_format) + FormatDebug(output_domain, "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 = nullptr; + + ao->chunk = nullptr; + ao->open = false; + + ao->mutex.unlock(); + + if (drain) + ao_plugin_drain(ao); + else + ao_plugin_cancel(ao); + + ao_plugin_close(ao); + ao_filter_close(ao); + + ao->mutex.lock(); + + FormatDebug(output_domain, "closed plugin=%s name=\"%s\"", + ao->plugin->name, ao->name); +} + +static void +ao_reopen_filter(struct audio_output *ao) +{ + Error error; + + ao_filter_close(ao); + const AudioFormat filter_audio_format = + ao_filter_open(ao, ao->in_audio_format, error); + if (!filter_audio_format.IsDefined() || + !convert_filter_set(ao->convert_filter, ao->out_audio_format, + error)) { + FormatError(error, + "Failed to open filter for \"%s\" [%s]", + ao->name, ao->plugin->name); + + /* 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 = nullptr; + + ao->chunk = nullptr; + ao->open = false; + ao->fail_timer.Update(); + + ao->mutex.unlock(); + ao_plugin_close(ao); + ao->mutex.lock(); + + return; + } +} + +static void +ao_reopen(struct audio_output *ao) +{ + if (!ao->config_audio_format.IsFullyDefined()) { + if (ao->open) { + const MusicPipe *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; + ao->out_audio_format.ApplyMask(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; + + (void)ao->cond.timed_wait(ao->mutex, delay); + + if (ao->command != AO_COMMAND_NONE) + return false; + } +} + +static const void * +ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, + Filter *replay_gain_filter, + unsigned *replay_gain_serial_p, + size_t *length_r) +{ + assert(chunk != nullptr); + assert(!chunk->IsEmpty()); + assert(chunk->CheckFormat(ao->in_audio_format)); + + const void *data = chunk->data; + size_t length = chunk->length; + + (void)ao; + + assert(length % ao->in_audio_format.GetFrameSize() == 0); + + if (length > 0 && replay_gain_filter != nullptr) { + 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 + : nullptr); + *replay_gain_serial_p = chunk->replay_gain_serial; + } + + Error error; + data = replay_gain_filter->FilterPCM(data, length, + &length, error); + if (data == nullptr) { + FormatError(error, "\"%s\" [%s] failed to filter", + ao->name, ao->plugin->name); + return nullptr; + } + } + + *length_r = length; + return data; +} + +static const void * +ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, + size_t *length_r) +{ + size_t length; + const void *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter, + &ao->replay_gain_serial, &length); + if (data == nullptr) + return nullptr; + + if (length == 0) { + /* empty chunk, nothing to do */ + *length_r = 0; + return data; + } + + /* cross-fade */ + + if (chunk->other != nullptr) { + 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 == nullptr) + return nullptr; + + 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 = ao->cross_fade_buffer.Get(other_length); + memcpy(dest, other_data, other_length); + if (!pcm_mix(ao->cross_fade_dither, dest, data, length, + ao->in_audio_format.format, + 1.0 - chunk->mix_ratio)) { + FormatError(output_domain, + "Cannot cross-fade format %s", + sample_format_to_string(ao->in_audio_format.format)); + return nullptr; + } + + data = dest; + length = other_length; + } + + /* apply filter chain */ + + Error error; + data = ao->filter->FilterPCM(data, length, &length, error); + if (data == nullptr) { + FormatError(error, "\"%s\" [%s] failed to filter", + ao->name, ao->plugin->name); + return nullptr; + } + + *length_r = length; + return data; +} + +static bool +ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) +{ + assert(ao != nullptr); + assert(ao->filter != nullptr); + + if (ao->tags && gcc_unlikely(chunk->tag != nullptr)) { + ao->mutex.unlock(); + ao_plugin_send_tag(ao, chunk->tag); + ao->mutex.lock(); + } + + 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 == nullptr) { + ao_close(ao, false); + + /* don't automatically reopen this device for 10 + seconds */ + ao->fail_timer.Update(); + return false; + } + + Error error; + + while (size > 0 && ao->command == AO_COMMAND_NONE) { + size_t nbytes; + + if (!ao_wait(ao)) + break; + + ao->mutex.unlock(); + nbytes = ao_plugin_play(ao, data, size, error); + ao->mutex.lock(); + if (nbytes == 0) { + /* play()==0 means failure */ + FormatError(error, "\"%s\" [%s] failed to play", + ao->name, ao->plugin->name); + + ao_close(ao, false); + + /* don't automatically reopen this device for + 10 seconds */ + assert(!ao->fail_timer.IsDefined()); + ao->fail_timer.Update(); + + return false; + } + + assert(nbytes <= size); + assert(nbytes % ao->out_audio_format.GetFrameSize() == 0); + + data += nbytes; + size -= nbytes; + } + + return true; +} + +static const struct music_chunk * +ao_next_chunk(struct audio_output *ao) +{ + return ao->chunk != nullptr + /* continue the previous play() call */ + ? ao->chunk->next + /* get the first chunk from the pipe */ + : ao->pipe->Peek(); +} + +/** + * 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 != nullptr); + + chunk = ao_next_chunk(ao); + if (chunk == nullptr) + /* no chunk available */ + return false; + + ao->chunk_finished = false; + + assert(!ao->in_playback_loop); + ao->in_playback_loop = true; + + while (chunk != nullptr && ao->command == AO_COMMAND_NONE) { + assert(!ao->chunk_finished); + + ao->chunk = chunk; + + success = ao_play_chunk(ao, chunk); + if (!success) { + assert(ao->chunk == nullptr); + break; + } + + assert(ao->chunk == chunk); + chunk = chunk->next; + } + + assert(ao->in_playback_loop); + ao->in_playback_loop = false; + + ao->chunk_finished = true; + + ao->mutex.unlock(); + ao->player_control->LockSignal(); + ao->mutex.lock(); + + return true; +} + +static void ao_pause(struct audio_output *ao) +{ + bool ret; + + ao->mutex.unlock(); + ao_plugin_cancel(ao); + ao->mutex.lock(); + + ao->pause = true; + ao_command_finished(ao); + + do { + if (!ao_wait(ao)) + break; + + ao->mutex.unlock(); + ret = ao_plugin_pause(ao); + ao->mutex.lock(); + + if (!ret) { + ao_close(ao, false); + break; + } + } while (ao->command == AO_COMMAND_NONE); + + ao->pause = false; +} + +static void +audio_output_task(void *arg) +{ + struct audio_output *ao = (struct audio_output *)arg; + + FormatThreadName("output:%s", ao->name); + + SetThreadRealtime(); + + ao->mutex.lock(); + + 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 != nullptr); + + 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 == nullptr); + assert(ao->pipe->Peek() == nullptr); + + ao->mutex.unlock(); + ao_plugin_drain(ao); + ao->mutex.lock(); + } + + ao_command_finished(ao); + continue; + + case AO_COMMAND_CANCEL: + ao->chunk = nullptr; + + if (ao->open) { + ao->mutex.unlock(); + ao_plugin_cancel(ao); + ao->mutex.lock(); + } + + ao_command_finished(ao); + continue; + + case AO_COMMAND_KILL: + ao->chunk = nullptr; + ao_command_finished(ao); + ao->mutex.unlock(); + return; + } + + 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) { + ao->woken_for_play = false; + ao->cond.wait(ao->mutex); + } + } +} + +void audio_output_thread_start(struct audio_output *ao) +{ + assert(ao->command == AO_COMMAND_NONE); + + Error error; + if (!ao->thread.Start(audio_output_task, ao, error)) + FatalError(error); +} diff --git a/src/output/OutputThread.hxx b/src/output/OutputThread.hxx new file mode 100644 index 000000000..1cdbd65f2 --- /dev/null +++ b/src/output/OutputThread.hxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 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(audio_output *ao); + +#endif diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/plugins/AlsaOutputPlugin.cxx index 87370ba99..f2e4fc643 100644 --- a/src/output/AlsaOutputPlugin.cxx +++ b/src/output/plugins/AlsaOutputPlugin.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "AlsaOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "MixerList.hxx" #include "pcm/PcmExport.hxx" #include "util/Manual.hxx" diff --git a/src/output/AlsaOutputPlugin.hxx b/src/output/plugins/AlsaOutputPlugin.hxx index 63508e041..63508e041 100644 --- a/src/output/AlsaOutputPlugin.hxx +++ b/src/output/plugins/AlsaOutputPlugin.hxx diff --git a/src/output/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx index e6cd0b916..efc1e0c6e 100644 --- a/src/output/AoOutputPlugin.cxx +++ b/src/output/plugins/AoOutputPlugin.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "AoOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" diff --git a/src/output/AoOutputPlugin.hxx b/src/output/plugins/AoOutputPlugin.hxx index cbf2fd589..cbf2fd589 100644 --- a/src/output/AoOutputPlugin.hxx +++ b/src/output/plugins/AoOutputPlugin.hxx diff --git a/src/output/FifoOutputPlugin.cxx b/src/output/plugins/FifoOutputPlugin.cxx index 9e5a1d5d2..5f14bcbbe 100644 --- a/src/output/FifoOutputPlugin.cxx +++ b/src/output/plugins/FifoOutputPlugin.cxx @@ -20,7 +20,7 @@ #include "config.h" #include "FifoOutputPlugin.hxx" #include "ConfigError.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "Timer.hxx" #include "fs/AllocatedPath.hxx" #include "fs/FileSystem.hxx" diff --git a/src/output/FifoOutputPlugin.hxx b/src/output/plugins/FifoOutputPlugin.hxx index 394ec3ae9..394ec3ae9 100644 --- a/src/output/FifoOutputPlugin.hxx +++ b/src/output/plugins/FifoOutputPlugin.hxx diff --git a/src/output/HttpdClient.cxx b/src/output/plugins/HttpdClient.cxx index d761bdf57..d761bdf57 100644 --- a/src/output/HttpdClient.cxx +++ b/src/output/plugins/HttpdClient.cxx diff --git a/src/output/HttpdClient.hxx b/src/output/plugins/HttpdClient.hxx index f94f05769..f94f05769 100644 --- a/src/output/HttpdClient.hxx +++ b/src/output/plugins/HttpdClient.hxx diff --git a/src/output/HttpdInternal.hxx b/src/output/plugins/HttpdInternal.hxx index 2ef0831ba..506730d11 100644 --- a/src/output/HttpdInternal.hxx +++ b/src/output/plugins/HttpdInternal.hxx @@ -25,7 +25,7 @@ #ifndef MPD_OUTPUT_HTTPD_INTERNAL_H #define MPD_OUTPUT_HTTPD_INTERNAL_H -#include "OutputInternal.hxx" +#include "../OutputInternal.hxx" #include "Timer.hxx" #include "thread/Mutex.hxx" #include "event/ServerSocket.hxx" diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/plugins/HttpdOutputPlugin.cxx index 322c9a61e..6921cb808 100644 --- a/src/output/HttpdOutputPlugin.cxx +++ b/src/output/plugins/HttpdOutputPlugin.cxx @@ -21,7 +21,7 @@ #include "HttpdOutputPlugin.hxx" #include "HttpdInternal.hxx" #include "HttpdClient.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "encoder/EncoderPlugin.hxx" #include "encoder/EncoderList.hxx" #include "system/Resolver.hxx" diff --git a/src/output/HttpdOutputPlugin.hxx b/src/output/plugins/HttpdOutputPlugin.hxx index 78218e5f0..78218e5f0 100644 --- a/src/output/HttpdOutputPlugin.hxx +++ b/src/output/plugins/HttpdOutputPlugin.hxx diff --git a/src/output/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx index c65ae5225..5a0d2bf16 100644 --- a/src/output/JackOutputPlugin.cxx +++ b/src/output/plugins/JackOutputPlugin.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "JackOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "ConfigError.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" diff --git a/src/output/JackOutputPlugin.hxx b/src/output/plugins/JackOutputPlugin.hxx index ee3fe9238..ee3fe9238 100644 --- a/src/output/JackOutputPlugin.hxx +++ b/src/output/plugins/JackOutputPlugin.hxx diff --git a/src/output/NullOutputPlugin.cxx b/src/output/plugins/NullOutputPlugin.cxx index 0b6476239..c336d86e6 100644 --- a/src/output/NullOutputPlugin.cxx +++ b/src/output/plugins/NullOutputPlugin.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "NullOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "Timer.hxx" struct NullOutput { diff --git a/src/output/NullOutputPlugin.hxx b/src/output/plugins/NullOutputPlugin.hxx index 05b8ef3d8..05b8ef3d8 100644 --- a/src/output/NullOutputPlugin.hxx +++ b/src/output/plugins/NullOutputPlugin.hxx diff --git a/src/output/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx index 6c8467752..c247336d7 100644 --- a/src/output/OSXOutputPlugin.cxx +++ b/src/output/plugins/OSXOutputPlugin.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "OSXOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "util/DynamicFifoBuffer.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" diff --git a/src/output/OSXOutputPlugin.hxx b/src/output/plugins/OSXOutputPlugin.hxx index 0de10f83e..0de10f83e 100644 --- a/src/output/OSXOutputPlugin.hxx +++ b/src/output/plugins/OSXOutputPlugin.hxx diff --git a/src/output/OpenALOutputPlugin.cxx b/src/output/plugins/OpenALOutputPlugin.cxx index 52a2c9070..f590f0ea0 100644 --- a/src/output/OpenALOutputPlugin.cxx +++ b/src/output/plugins/OpenALOutputPlugin.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "OpenALOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" diff --git a/src/output/OpenALOutputPlugin.hxx b/src/output/plugins/OpenALOutputPlugin.hxx index eb43d1aa5..eb43d1aa5 100644 --- a/src/output/OpenALOutputPlugin.hxx +++ b/src/output/plugins/OpenALOutputPlugin.hxx diff --git a/src/output/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx index 24f3f48b5..cdf055df9 100644 --- a/src/output/OssOutputPlugin.cxx +++ b/src/output/plugins/OssOutputPlugin.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "OssOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "MixerList.hxx" #include "system/fd_util.h" #include "util/Error.hxx" diff --git a/src/output/OssOutputPlugin.hxx b/src/output/plugins/OssOutputPlugin.hxx index 4762fa652..4762fa652 100644 --- a/src/output/OssOutputPlugin.hxx +++ b/src/output/plugins/OssOutputPlugin.hxx diff --git a/src/output/PipeOutputPlugin.cxx b/src/output/plugins/PipeOutputPlugin.cxx index 66d5a28ae..802e1ba4d 100644 --- a/src/output/PipeOutputPlugin.cxx +++ b/src/output/plugins/PipeOutputPlugin.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "PipeOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "ConfigError.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" diff --git a/src/output/PipeOutputPlugin.hxx b/src/output/plugins/PipeOutputPlugin.hxx index 42b01b9f7..42b01b9f7 100644 --- a/src/output/PipeOutputPlugin.hxx +++ b/src/output/plugins/PipeOutputPlugin.hxx diff --git a/src/output/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx index dd1906f53..c133d9796 100644 --- a/src/output/PulseOutputPlugin.cxx +++ b/src/output/plugins/PulseOutputPlugin.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "PulseOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "MixerList.hxx" #include "mixer/PulseMixerPlugin.hxx" #include "util/Error.hxx" diff --git a/src/output/PulseOutputPlugin.hxx b/src/output/plugins/PulseOutputPlugin.hxx index 9df557282..9df557282 100644 --- a/src/output/PulseOutputPlugin.hxx +++ b/src/output/plugins/PulseOutputPlugin.hxx diff --git a/src/output/RecorderOutputPlugin.cxx b/src/output/plugins/RecorderOutputPlugin.cxx index cebbc2cec..16fe2c692 100644 --- a/src/output/RecorderOutputPlugin.cxx +++ b/src/output/plugins/RecorderOutputPlugin.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "RecorderOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "encoder/EncoderPlugin.hxx" #include "encoder/EncoderList.hxx" #include "ConfigError.hxx" diff --git a/src/output/RecorderOutputPlugin.hxx b/src/output/plugins/RecorderOutputPlugin.hxx index 4fac911a1..4fac911a1 100644 --- a/src/output/RecorderOutputPlugin.hxx +++ b/src/output/plugins/RecorderOutputPlugin.hxx diff --git a/src/output/RoarOutputPlugin.cxx b/src/output/plugins/RoarOutputPlugin.cxx index 9634379c5..7c1c41b47 100644 --- a/src/output/RoarOutputPlugin.cxx +++ b/src/output/plugins/RoarOutputPlugin.cxx @@ -20,7 +20,7 @@ #include "config.h" #include "RoarOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "MixerList.hxx" #include "thread/Mutex.hxx" #include "util/Error.hxx" diff --git a/src/output/RoarOutputPlugin.hxx b/src/output/plugins/RoarOutputPlugin.hxx index 27c5dc420..27c5dc420 100644 --- a/src/output/RoarOutputPlugin.hxx +++ b/src/output/plugins/RoarOutputPlugin.hxx diff --git a/src/output/ShoutOutputPlugin.cxx b/src/output/plugins/ShoutOutputPlugin.cxx index cccd3e0ba..e0ec6ce3d 100644 --- a/src/output/ShoutOutputPlugin.cxx +++ b/src/output/plugins/ShoutOutputPlugin.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "ShoutOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "encoder/EncoderPlugin.hxx" #include "encoder/EncoderList.hxx" #include "ConfigError.hxx" diff --git a/src/output/ShoutOutputPlugin.hxx b/src/output/plugins/ShoutOutputPlugin.hxx index d437e0b0d..d437e0b0d 100644 --- a/src/output/ShoutOutputPlugin.hxx +++ b/src/output/plugins/ShoutOutputPlugin.hxx diff --git a/src/output/SolarisOutputPlugin.cxx b/src/output/plugins/SolarisOutputPlugin.cxx index c06c848d1..38ed2e314 100644 --- a/src/output/SolarisOutputPlugin.cxx +++ b/src/output/plugins/SolarisOutputPlugin.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "SolarisOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "system/fd_util.h" #include "util/Error.hxx" diff --git a/src/output/SolarisOutputPlugin.hxx b/src/output/plugins/SolarisOutputPlugin.hxx index 9ce848a40..9ce848a40 100644 --- a/src/output/SolarisOutputPlugin.hxx +++ b/src/output/plugins/SolarisOutputPlugin.hxx diff --git a/src/output/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx index bc4ca9386..87861180f 100644 --- a/src/output/WinmmOutputPlugin.cxx +++ b/src/output/plugins/WinmmOutputPlugin.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "WinmmOutputPlugin.hxx" -#include "OutputAPI.hxx" +#include "../OutputAPI.hxx" #include "pcm/PcmBuffer.hxx" #include "MixerList.hxx" #include "util/Error.hxx" diff --git a/src/output/WinmmOutputPlugin.hxx b/src/output/plugins/WinmmOutputPlugin.hxx index 1409a2e8c..1409a2e8c 100644 --- a/src/output/WinmmOutputPlugin.hxx +++ b/src/output/plugins/WinmmOutputPlugin.hxx |