aboutsummaryrefslogtreecommitdiffstats
path: root/src/output/OutputAll.cxx
diff options
context:
space:
mode:
authorMax Kellermann <max@duempel.org>2014-01-23 23:49:50 +0100
committerMax Kellermann <max@duempel.org>2014-01-23 23:49:50 +0100
commitea5b901bcce20949a8d1fd622a7b03ff6f56ae20 (patch)
treeab37a61260fdaafb94b5e54aca54928e00c9cf01 /src/output/OutputAll.cxx
parentf1f19841bdd291c055f59b6603f69278c66366d8 (diff)
downloadmpd-ea5b901bcce20949a8d1fd622a7b03ff6f56ae20.tar.gz
mpd-ea5b901bcce20949a8d1fd622a7b03ff6f56ae20.tar.xz
mpd-ea5b901bcce20949a8d1fd622a7b03ff6f56ae20.zip
output/*: move to output/plugins/
Diffstat (limited to 'src/output/OutputAll.cxx')
-rw-r--r--src/output/OutputAll.cxx589
1 files changed, 589 insertions, 0 deletions
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 = &empty;
+ }
+
+ 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;
+}