From f5a923b9d16e4c63942a033d1bdb2ab150aae342 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 27 Jan 2014 08:20:25 +0100 Subject: OutputAll: convert to class, move instance to class Partition Another big chunk of code for multi-player support. --- src/output/MultipleOutputs.cxx | 477 +++++++++++++++++++++++++++++++++ src/output/MultipleOutputs.hxx | 271 +++++++++++++++++++ src/output/OutputAll.cxx | 589 ----------------------------------------- src/output/OutputAll.hxx | 174 ------------ src/output/OutputCommand.cxx | 46 ++-- src/output/OutputCommand.hxx | 8 +- src/output/OutputPrint.cxx | 12 +- src/output/OutputPrint.hxx | 4 +- src/output/OutputState.cxx | 19 +- src/output/OutputState.hxx | 6 +- 10 files changed, 791 insertions(+), 815 deletions(-) create mode 100644 src/output/MultipleOutputs.cxx create mode 100644 src/output/MultipleOutputs.hxx delete mode 100644 src/output/OutputAll.cxx delete mode 100644 src/output/OutputAll.hxx (limited to 'src/output') diff --git a/src/output/MultipleOutputs.cxx b/src/output/MultipleOutputs.cxx new file mode 100644 index 000000000..50f686843 --- /dev/null +++ b/src/output/MultipleOutputs.cxx @@ -0,0 +1,477 @@ +/* + * 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 "MultipleOutputs.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 "config/ConfigData.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "notify.hxx" + +#include +#include + +MultipleOutputs::MultipleOutputs() + :buffer(nullptr), pipe(nullptr), + elapsed_time(-1) +{ +} + +MultipleOutputs::~MultipleOutputs() +{ + for (auto i : outputs) { + audio_output_disable(i); + audio_output_finish(i); + } +} + +static audio_output * +LoadOutput(PlayerControl &pc, const config_param ¶m) +{ + Error error; + audio_output *output = audio_output_new(param, pc, error); + if (output == nullptr) { + if (param.line > 0) + FormatFatalError("line %i: %s", + param.line, + error.GetMessage()); + else + FatalError(error); + } + + return output; +} + +void +MultipleOutputs::Configure(PlayerControl &pc) +{ + const config_param *param = nullptr; + while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, + param)) != nullptr) { + auto output = LoadOutput(pc, *param); + if (FindByName(output->name) != nullptr) + FormatFatalError("output devices with identical " + "names: %s", output->name); + + outputs.push_back(output); + } + + if (outputs.empty()) { + /* auto-detect device */ + const config_param empty; + auto output = LoadOutput(pc, empty); + outputs.push_back(output); + } +} + +audio_output * +MultipleOutputs::FindByName(const char *name) const +{ + for (auto i : outputs) + if (strcmp(i->name, name) == 0) + return i; + + return nullptr; +} + +void +MultipleOutputs::EnableDisable() +{ + for (auto ao : outputs) { + 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); + } + } +} + +bool +MultipleOutputs::AllFinished() const +{ + for (auto ao : outputs) { + const ScopeLock protect(ao->mutex); + if (audio_output_is_open(ao) && + !audio_output_command_is_finished(ao)) + return false; + } + + return true; +} + +void +MultipleOutputs::WaitAll() +{ + while (!AllFinished()) + audio_output_client_notify.Wait(); +} + +void +MultipleOutputs::AllowPlay() +{ + for (auto ao : outputs) + audio_output_allow_play(ao); +} + +static void +audio_output_reset_reopen(struct audio_output *ao) +{ + const ScopeLock protect(ao->mutex); + + ao->fail_timer.Reset(); +} + +void +MultipleOutputs::ResetReopen() +{ + for (auto ao : outputs) + audio_output_reset_reopen(ao); +} + +bool +MultipleOutputs::Update() +{ + bool ret = false; + + if (!input_audio_format.IsDefined()) + return false; + + for (auto ao : outputs) + ret = audio_output_update(ao, input_audio_format, *pipe) + || ret; + + return ret; +} + +void +MultipleOutputs::SetReplayGainMode(ReplayGainMode mode) +{ + for (auto ao : outputs) + audio_output_set_replay_gain_mode(ao, mode); +} + +bool +MultipleOutputs::Play(music_chunk *chunk, Error &error) +{ + assert(buffer != nullptr); + assert(pipe != nullptr); + assert(chunk != nullptr); + assert(chunk->CheckFormat(input_audio_format)); + + if (!Update()) { + /* TODO: obtain real error */ + error.Set(output_domain, "Failed to open audio output"); + return false; + } + + pipe->Push(chunk); + + for (auto ao : outputs) + audio_output_play(ao); + + return true; +} + +bool +MultipleOutputs::Open(const AudioFormat audio_format, + MusicBuffer &_buffer, + Error &error) +{ + bool ret = false, enabled = false; + + assert(buffer == nullptr || buffer == &_buffer); + assert((pipe == nullptr) == (buffer == nullptr)); + + buffer = &_buffer; + + /* the audio format must be the same as existing chunks in the + pipe */ + assert(pipe == nullptr || pipe->CheckFormat(audio_format)); + + if (pipe == nullptr) + pipe = new MusicPipe(); + else + /* if the pipe hasn't been cleared, the the audio + format must not have changed */ + assert(pipe->IsEmpty() || audio_format == input_audio_format); + + input_audio_format = audio_format; + + ResetReopen(); + EnableDisable(); + Update(); + + for (auto ao : outputs) { + if (ao->enabled) + enabled = true; + + if (ao->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 */ + Close(); + + return ret; +} + +/** + * Has the specified audio output already consumed this chunk? + */ +gcc_pure +static bool +chunk_is_consumed_in(const struct audio_output *ao, + gcc_unused const MusicPipe *pipe, + const struct music_chunk *chunk) +{ + if (!ao->open) + return true; + + if (ao->chunk == nullptr) + return false; + + assert(chunk == ao->chunk || pipe->Contains(ao->chunk)); + + if (chunk != ao->chunk) { + assert(chunk->next != nullptr); + return true; + } + + return ao->chunk_finished && chunk->next == nullptr; +} + +bool +MultipleOutputs::IsChunkConsumed(const music_chunk *chunk) const +{ + for (auto ao : outputs) { + const ScopeLock protect(ao->mutex); + if (!chunk_is_consumed_in(ao, pipe, chunk)) + return false; + } + + return true; +} + +inline void +MultipleOutputs::ClearTailChunk(gcc_unused const struct music_chunk *chunk, + bool *locked) +{ + assert(chunk->next == nullptr); + assert(pipe->Contains(chunk)); + + for (unsigned i = 0, n = outputs.size(); i != n; ++i) { + audio_output *ao = 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 +MultipleOutputs::Check() +{ + const struct music_chunk *chunk; + bool is_tail; + struct music_chunk *shifted; + bool locked[outputs.size()]; + + assert(buffer != nullptr); + assert(pipe != nullptr); + + while ((chunk = pipe->Peek()) != nullptr) { + assert(!pipe->IsEmpty()); + + if (!IsChunkConsumed(chunk)) + /* at least one output is not finished playing + this chunk */ + return pipe->GetSize(); + + if (chunk->length > 0 && chunk->times >= 0.0) + /* only update elapsed_time if the chunk + provides a defined value */ + 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 */ + ClearTailChunk(chunk, locked); + + /* remove the chunk from the pipe */ + shifted = pipe->Shift(); + assert(shifted == chunk); + + if (is_tail) + /* unlock all audio outputs which were locked + by clear_tail_chunk() */ + for (unsigned i = 0, n = outputs.size(); i != n; ++i) + if (locked[i]) + outputs[i]->mutex.unlock(); + + /* return the chunk to the buffer */ + buffer->Return(shifted); + } + + return 0; +} + +bool +MultipleOutputs::Wait(PlayerControl &pc, unsigned threshold) +{ + pc.Lock(); + + if (Check() < threshold) { + pc.Unlock(); + return true; + } + + pc.Wait(); + pc.Unlock(); + + return Check() < threshold; +} + +void +MultipleOutputs::Pause() +{ + Update(); + + for (auto ao : outputs) + audio_output_pause(ao); + + WaitAll(); +} + +void +MultipleOutputs::Drain() +{ + for (auto ao : outputs) + audio_output_drain_async(ao); + + WaitAll(); +} + +void +MultipleOutputs::Cancel() +{ + /* send the cancel() command to all audio outputs */ + + for (auto ao : outputs) + audio_output_cancel(ao); + + WaitAll(); + + /* clear the music pipe and return all chunks to the buffer */ + + if (pipe != nullptr) + pipe->Clear(*buffer); + + /* the audio outputs are now waiting for a signal, to + synchronize the cleared music pipe */ + + AllowPlay(); + + /* invalidate elapsed_time */ + + elapsed_time = -1.0; +} + +void +MultipleOutputs::Close() +{ + for (auto ao : outputs) + audio_output_close(ao); + + if (pipe != nullptr) { + assert(buffer != nullptr); + + pipe->Clear(*buffer); + delete pipe; + pipe = nullptr; + } + + buffer = nullptr; + + input_audio_format.Clear(); + + elapsed_time = -1.0; +} + +void +MultipleOutputs::Release() +{ + for (auto ao : outputs) + audio_output_release(ao); + + if (pipe != nullptr) { + assert(buffer != nullptr); + + pipe->Clear(*buffer); + delete pipe; + pipe = nullptr; + } + + buffer = nullptr; + + input_audio_format.Clear(); + + elapsed_time = -1.0; +} + +void +MultipleOutputs::SongBorder() +{ + /* clear the elapsed_time pointer at the beginning of a new + song */ + elapsed_time = 0.0; +} diff --git a/src/output/MultipleOutputs.hxx b/src/output/MultipleOutputs.hxx new file mode 100644 index 000000000..e1a8602a9 --- /dev/null +++ b/src/output/MultipleOutputs.hxx @@ -0,0 +1,271 @@ +/* + * 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 "AudioFormat.hxx" +#include "ReplayGainInfo.hxx" +#include "Compiler.h" + +#include + +#include + +struct AudioFormat; +class MusicBuffer; +class MusicPipe; +struct music_chunk; +struct PlayerControl; +struct audio_output; +class Error; + +class MultipleOutputs { + std::vector outputs; + + AudioFormat input_audio_format; + + /** + * The #MusicBuffer object where consumed chunks are returned. + */ + MusicBuffer *buffer; + + /** + * The #MusicPipe object which feeds all audio outputs. It is + * filled by audio_output_all_play(). + */ + MusicPipe *pipe; + + /** + * The "elapsed_time" stamp of the most recently finished + * chunk. + */ + float elapsed_time; + +public: + /** + * Load audio outputs from the configuration file and + * initialize them. + */ + MultipleOutputs(); + ~MultipleOutputs(); + + void Configure(PlayerControl &pc); + + /** + * Returns the total number of audio output devices, including + * those which are disabled right now. + */ + gcc_pure + unsigned Size() const { + return outputs.size(); + } + + /** + * Returns the "i"th audio output device. + */ + const audio_output &Get(unsigned i) const { + assert(i < Size()); + + return *outputs[i]; + } + + audio_output &Get(unsigned i) { + assert(i < Size()); + + return *outputs[i]; + } + + /** + * Returns the audio output device with the specified name. + * Returns nullptr if the name does not exist. + */ + gcc_pure + audio_output *FindByName(const char *name) const; + + /** + * Checks the "enabled" flag of all audio outputs, and if one has + * changed, commit the change. + */ + void EnableDisable(); + + /** + * 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 Open(const AudioFormat audio_format, MusicBuffer &_buffer, + Error &error); + + /** + * Closes all audio outputs. + */ + void Close(); + + /** + * Closes all audio outputs. Outputs with the "always_on" + * flag are put into pause mode. + */ + void Release(); + + void SetReplayGainMode(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 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 Check(); + + /** + * 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 Wait(PlayerControl &pc, unsigned threshold); + + /** + * Puts all audio outputs into pause mode. Most implementations will + * simply close it then. + */ + void Pause(); + + /** + * Drain all audio outputs. + */ + void Drain(); + + /** + * Try to cancel data which may still be in the device's buffers. + */ + void Cancel(); + + /** + * Indicate that a new song will begin now. + */ + void SongBorder(); + + /** + * 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 GetElapsedTime() const { + return elapsed_time; + } + + /** + * Returns the average volume of all available mixers (range + * 0..100). Returns -1 if no mixer can be queried. + */ + gcc_pure + int GetVolume() const; + + /** + * Sets the volume on all available mixers. + * + * @param volume the volume (range 0..100) + * @return true on success, false on failure + */ + bool SetVolume(unsigned volume); + + /** + * Similar to GetVolume(), but gets the volume only for + * software mixers. See #software_mixer_plugin. This + * function fails if no software mixer is configured. + */ + gcc_pure + int GetSoftwareVolume() const; + + /** + * Similar to SetVolume(), but sets the volume only for + * software mixers. See #software_mixer_plugin. This + * function cannot fail, because the underlying software + * mixers cannot fail either. + */ + void SetSoftwareVolume(unsigned volume); + +private: + /** + * Determine if all (active) outputs have finished the current + * command. + */ + gcc_pure + bool AllFinished() const; + + void WaitAll(); + + /** + * Signals all audio outputs which are open. + */ + void AllowPlay(); + + /** + * 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. + */ + void ResetReopen(); + + /** + * Opens all output devices which are enabled, but closed. + * + * @return true if there is at least open output device which + * is open + */ + bool Update(); + + /** + * Has this chunk been consumed by all audio outputs? + */ + bool IsChunkConsumed(const music_chunk *chunk) const; + + /** + * There's only one chunk left in the pipe (#pipe), and all + * audio outputs have consumed it already. Clear the + * reference. + */ + void ClearTailChunk(const struct music_chunk *chunk, bool *locked); +}; + +#endif diff --git a/src/output/OutputAll.cxx b/src/output/OutputAll.cxx deleted file mode 100644 index ded9fa1e1..000000000 --- a/src/output/OutputAll.cxx +++ /dev/null @@ -1,589 +0,0 @@ -/* - * 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 "config/ConfigData.hxx" -#include "config/ConfigGlobal.hxx" -#include "config/ConfigOption.hxx" -#include "notify.hxx" - -#include -#include - -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 deleted file mode 100644 index b6166eb48..000000000 --- a/src/output/OutputAll.hxx +++ /dev/null @@ -1,174 +0,0 @@ -/* - * 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 index d82b7c696..d4c1d844d 100644 --- a/src/output/OutputCommand.cxx +++ b/src/output/OutputCommand.cxx @@ -26,7 +26,7 @@ #include "config.h" #include "OutputCommand.hxx" -#include "OutputAll.hxx" +#include "MultipleOutputs.hxx" #include "OutputInternal.hxx" #include "PlayerControl.hxx" #include "mixer/MixerControl.hxx" @@ -35,21 +35,19 @@ extern unsigned audio_output_state_version; bool -audio_output_enable_index(unsigned idx) +audio_output_enable_index(MultipleOutputs &outputs, unsigned idx) { - struct audio_output *ao; - - if (idx >= audio_output_count()) + if (idx >= outputs.Size()) return false; - ao = audio_output_get(idx); - if (ao->enabled) + audio_output &ao = outputs.Get(idx); + if (ao.enabled) return true; - ao->enabled = true; + ao.enabled = true; idle_add(IDLE_OUTPUT); - ao->player_control->UpdateAudio(); + ao.player_control->UpdateAudio(); ++audio_output_state_version; @@ -57,27 +55,25 @@ audio_output_enable_index(unsigned idx) } bool -audio_output_disable_index(unsigned idx) +audio_output_disable_index(MultipleOutputs &outputs, unsigned idx) { - struct audio_output *ao; - - if (idx >= audio_output_count()) + if (idx >= outputs.Size()) return false; - ao = audio_output_get(idx); - if (!ao->enabled) + audio_output &ao = outputs.Get(idx); + if (!ao.enabled) return true; - ao->enabled = false; + ao.enabled = false; idle_add(IDLE_OUTPUT); - Mixer *mixer = ao->mixer; + Mixer *mixer = ao.mixer; if (mixer != nullptr) { mixer_close(mixer); idle_add(IDLE_MIXER); } - ao->player_control->UpdateAudio(); + ao.player_control->UpdateAudio(); ++audio_output_state_version; @@ -85,26 +81,24 @@ audio_output_disable_index(unsigned idx) } bool -audio_output_toggle_index(unsigned idx) +audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx) { - struct audio_output *ao; - - if (idx >= audio_output_count()) + if (idx >= outputs.Size()) return false; - ao = audio_output_get(idx); - const bool enabled = ao->enabled = !ao->enabled; + audio_output &ao = outputs.Get(idx); + const bool enabled = ao.enabled = !ao.enabled; idle_add(IDLE_OUTPUT); if (!enabled) { - Mixer *mixer = ao->mixer; + Mixer *mixer = ao.mixer; if (mixer != nullptr) { mixer_close(mixer); idle_add(IDLE_MIXER); } } - ao->player_control->UpdateAudio(); + ao.player_control->UpdateAudio(); ++audio_output_state_version; diff --git a/src/output/OutputCommand.hxx b/src/output/OutputCommand.hxx index 4c44dff53..53fc5c95e 100644 --- a/src/output/OutputCommand.hxx +++ b/src/output/OutputCommand.hxx @@ -27,25 +27,27 @@ #ifndef MPD_OUTPUT_COMMAND_HXX #define MPD_OUTPUT_COMMAND_HXX +class MultipleOutputs; + /** * Enables an audio output. Returns false if the specified output * does not exist. */ bool -audio_output_enable_index(unsigned idx); +audio_output_enable_index(MultipleOutputs &outputs, unsigned idx); /** * Disables an audio output. Returns false if the specified output * does not exist. */ bool -audio_output_disable_index(unsigned idx); +audio_output_disable_index(MultipleOutputs &outputs, unsigned idx); /** * Toggles an audio output. Returns false if the specified output * does not exist. */ bool -audio_output_toggle_index(unsigned idx); +audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx); #endif diff --git a/src/output/OutputPrint.cxx b/src/output/OutputPrint.cxx index 66826f140..7e0536bf5 100644 --- a/src/output/OutputPrint.cxx +++ b/src/output/OutputPrint.cxx @@ -24,22 +24,20 @@ #include "config.h" #include "OutputPrint.hxx" -#include "OutputAll.hxx" +#include "MultipleOutputs.hxx" #include "OutputInternal.hxx" #include "client/Client.hxx" void -printAudioDevices(Client &client) +printAudioDevices(Client &client, const MultipleOutputs &outputs) { - const unsigned n = audio_output_count(); - - for (unsigned i = 0; i < n; ++i) { - const struct audio_output *ao = audio_output_get(i); + for (unsigned i = 0, n = outputs.Size(); i != n; ++i) { + const audio_output &ao = outputs.Get(i); client_printf(client, "outputid: %i\n" "outputname: %s\n" "outputenabled: %i\n", - i, ao->name, ao->enabled); + i, ao.name, ao.enabled); } } diff --git a/src/output/OutputPrint.hxx b/src/output/OutputPrint.hxx index 2d94226fd..29aa2b11c 100644 --- a/src/output/OutputPrint.hxx +++ b/src/output/OutputPrint.hxx @@ -1,4 +1,3 @@ - /* * Copyright (C) 2003-2014 The Music Player Daemon Project * http://www.musicpd.org @@ -27,8 +26,9 @@ #define MPD_OUTPUT_PRINT_HXX class Client; +class MultipleOutputs; void -printAudioDevices(Client &client); +printAudioDevices(Client &client, const MultipleOutputs &outputs); #endif diff --git a/src/output/OutputState.cxx b/src/output/OutputState.cxx index 1141abeda..abd5dd2ae 100644 --- a/src/output/OutputState.cxx +++ b/src/output/OutputState.cxx @@ -24,7 +24,7 @@ #include "config.h" #include "OutputState.hxx" -#include "OutputAll.hxx" +#include "MultipleOutputs.hxx" #include "OutputInternal.hxx" #include "OutputError.hxx" #include "Log.hxx" @@ -38,27 +38,22 @@ unsigned audio_output_state_version; void -audio_output_state_save(FILE *fp) +audio_output_state_save(FILE *fp, const MultipleOutputs &outputs) { - unsigned n = audio_output_count(); - - assert(n > 0); - - for (unsigned i = 0; i < n; ++i) { - const struct audio_output *ao = audio_output_get(i); + for (unsigned i = 0, n = outputs.Size(); i != n; ++i) { + const audio_output &ao = outputs.Get(i); fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n", - ao->enabled, ao->name); + ao.enabled, ao.name); } } bool -audio_output_state_read(const char *line) +audio_output_state_read(const char *line, MultipleOutputs &outputs) { long value; char *endptr; const char *name; - struct audio_output *ao; if (!StringStartsWith(line, AUDIO_DEVICE_STATE)) return false; @@ -74,7 +69,7 @@ audio_output_state_read(const char *line) return true; name = endptr + 1; - ao = audio_output_find(name); + audio_output *ao = outputs.FindByName(name); if (ao == NULL) { FormatDebug(output_domain, "Ignoring device state for '%s'", name); diff --git a/src/output/OutputState.hxx b/src/output/OutputState.hxx index a68180ae4..32f4bbcce 100644 --- a/src/output/OutputState.hxx +++ b/src/output/OutputState.hxx @@ -27,11 +27,13 @@ #include +class MultipleOutputs; + bool -audio_output_state_read(const char *line); +audio_output_state_read(const char *line, MultipleOutputs &outputs); void -audio_output_state_save(FILE *fp); +audio_output_state_save(FILE *fp, const MultipleOutputs &outputs); /** * Generates a version number for the current state of the audio -- cgit v1.2.3