/* * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include "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 #include #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "output" 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] != NULL); return audio_outputs[i]; } struct audio_output * audio_output_find(const char *name) { for (unsigned i = 0; i < num_audio_outputs; ++i) { struct audio_output *ao = audio_output_get(i); if (strcmp(ao->name, name) == 0) return ao; } /* name not found */ return NULL; } static unsigned audio_output_config_count(void) { unsigned int nr = 0; const struct config_param *param = NULL; while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param))) nr++; if (!nr) nr = 1; /* we'll always have at least one device */ return nr; } void audio_output_all_init(struct player_control *pc) { const struct config_param *param = NULL; unsigned int i; Error error; num_audio_outputs = audio_output_config_count(); audio_outputs = g_new(struct 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 == NULL) { if (param != NULL) 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]); } g_free(audio_outputs); audio_outputs = NULL; 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); if (!ao->open && ao->fail_timer != NULL) { g_timer_destroy(ao->fail_timer); ao->fail_timer = NULL; } } /** * 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(enum replay_gain_mode 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 != NULL); assert(g_mp != NULL); assert(chunk != NULL); 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 == NULL || g_music_buffer == &buffer); assert((g_mp == NULL) == (g_music_buffer == NULL)); g_music_buffer = &buffer; /* the audio format must be the same as existing chunks in the pipe */ assert(g_mp == NULL || g_mp->CheckFormat(audio_format)); if (g_mp == NULL) 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 == NULL) return false; assert(chunk == ao->chunk || g_mp->Contains(ao->chunk)); if (chunk != ao->chunk) { assert(chunk->next != NULL); return true; } return ao->chunk_finished && chunk->next == NULL; } /** * Has this chunk been consumed by all audio outputs? */ static bool chunk_is_consumed(const struct music_chunk *chunk) { for (unsigned i = 0; i < num_audio_outputs; ++i) { 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 == NULL); 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 = NULL; } } unsigned audio_output_all_check(void) { const struct music_chunk *chunk; bool is_tail; struct music_chunk *shifted; bool locked[num_audio_outputs]; assert(g_music_buffer != NULL); assert(g_mp != NULL); while ((chunk = 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 == NULL; if (is_tail) /* this is the tail of the pipe - clear the chunk reference in all outputs */ clear_tail_chunk(chunk, locked); /* remove the chunk from the pipe */ shifted = 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(struct player_control *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 != NULL) 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 != NULL) { assert(g_music_buffer != NULL); g_mp->Clear(*g_music_buffer); delete g_mp; g_mp = NULL; } g_music_buffer = NULL; 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 != NULL) { assert(g_music_buffer != NULL); g_mp->Clear(*g_music_buffer); delete g_mp; g_mp = NULL; } g_music_buffer = NULL; 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; }