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. --- Makefile.am | 4 +- src/Main.cxx | 6 +- src/Partition.cxx | 1 + src/Partition.hxx | 7 +- src/PlayerControl.cxx | 6 +- src/PlayerControl.hxx | 6 +- src/PlayerThread.cxx | 46 ++-- src/StateFile.cxx | 6 +- src/command/OtherCommands.cxx | 8 +- src/command/OutputCommands.cxx | 16 +- src/command/PlayerCommands.cxx | 8 +- src/mixer/MixerAll.cxx | 90 +++---- src/mixer/MixerAll.hxx | 64 ----- src/mixer/Volume.cxx | 26 +- src/mixer/Volume.hxx | 10 +- 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 +- test/run_output.cxx | 9 +- 26 files changed, 913 insertions(+), 1006 deletions(-) delete mode 100644 src/mixer/MixerAll.hxx 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 diff --git a/Makefile.am b/Makefile.am index 0d94ddee0..23974e23c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -961,7 +961,7 @@ OUTPUT_API_SRC = \ src/output/OutputAPI.hxx \ src/output/OutputInternal.hxx \ src/output/OutputList.cxx src/output/OutputList.hxx \ - src/output/OutputAll.cxx src/output/OutputAll.hxx \ + src/output/MultipleOutputs.cxx src/output/MultipleOutputs.hxx \ src/output/OutputThread.cxx src/output/OutputThread.hxx \ src/output/OutputError.cxx src/output/OutputError.hxx \ src/output/OutputControl.cxx src/output/OutputControl.hxx \ @@ -985,7 +985,7 @@ MIXER_API_SRC = \ src/mixer/MixerList.hxx \ src/mixer/MixerControl.cxx src/mixer/MixerControl.hxx \ src/mixer/MixerType.cxx src/mixer/MixerType.hxx \ - src/mixer/MixerAll.cxx src/mixer/MixerAll.hxx \ + src/mixer/MixerAll.cxx \ src/mixer/MixerInternal.hxx libmixer_plugins_a_SOURCES = \ diff --git a/src/Main.cxx b/src/Main.cxx index f790ec574..87a35eca2 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -37,7 +37,6 @@ #include "command/AllCommands.hxx" #include "Partition.hxx" #include "mixer/Volume.hxx" -#include "output/OutputAll.hxx" #include "tag/TagConfig.hxx" #include "ReplayGainConfig.hxx" #include "Idle.hxx" @@ -458,7 +457,7 @@ int mpd_main(int argc, char *argv[]) initialize_decoder_and_player(); volume_init(); initAudioConfig(); - audio_output_all_init(instance->partition->pc); + instance->partition->outputs.Configure(instance->partition->pc); client_manager_init(); replay_gain_global_init(); @@ -500,7 +499,7 @@ int mpd_main(int argc, char *argv[]) return EXIT_FAILURE; } - audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(instance->partition->playlist.queue.random)); + instance->partition->outputs.SetReplayGainMode(replay_gain_get_real_mode(instance->partition->playlist.queue.random)); if (config_get_bool(CONF_AUTO_UPDATE, false)) { #ifdef ENABLE_INOTIFY @@ -567,7 +566,6 @@ int mpd_main(int argc, char *argv[]) playlist_list_global_finish(); input_stream_global_finish(); - audio_output_all_finish(); mapper_finish(); delete instance->partition; command_finish(); diff --git a/src/Partition.cxx b/src/Partition.cxx index ea801f827..71ded4309 100644 --- a/src/Partition.cxx +++ b/src/Partition.cxx @@ -20,6 +20,7 @@ #include "config.h" #include "Partition.hxx" #include "DetachedSong.hxx" +#include "output/MultipleOutputs.hxx" void Partition::DatabaseModified() diff --git a/src/Partition.hxx b/src/Partition.hxx index 4a5bcc6c7..3ee110654 100644 --- a/src/Partition.hxx +++ b/src/Partition.hxx @@ -21,9 +21,11 @@ #define MPD_PARTITION_HXX #include "Playlist.hxx" +#include "output/MultipleOutputs.hxx" #include "PlayerControl.hxx" struct Instance; +class MultipleOutputs; /** * A partition of the Music Player Daemon. It is a separate unit with @@ -34,6 +36,8 @@ struct Partition { struct playlist playlist; + MultipleOutputs outputs; + PlayerControl pc; Partition(Instance &_instance, @@ -41,8 +45,7 @@ struct Partition { unsigned buffer_chunks, unsigned buffered_before_play) :instance(_instance), playlist(max_length), - pc(buffer_chunks, buffered_before_play) { - } + pc(outputs, buffer_chunks, buffered_before_play) {} void ClearQueue() { playlist.Clear(pc); diff --git a/src/PlayerControl.cxx b/src/PlayerControl.cxx index 7ba703ea0..bca3cc937 100644 --- a/src/PlayerControl.cxx +++ b/src/PlayerControl.cxx @@ -26,9 +26,11 @@ #include -PlayerControl::PlayerControl(unsigned _buffer_chunks, +PlayerControl::PlayerControl(MultipleOutputs &_outputs, + unsigned _buffer_chunks, unsigned _buffered_before_play) - :buffer_chunks(_buffer_chunks), + :outputs(_outputs), + buffer_chunks(_buffer_chunks), buffered_before_play(_buffered_before_play), command(PlayerCommand::NONE), state(PlayerState::STOP), diff --git a/src/PlayerControl.hxx b/src/PlayerControl.hxx index 4847958c7..a7237167c 100644 --- a/src/PlayerControl.hxx +++ b/src/PlayerControl.hxx @@ -29,6 +29,7 @@ #include +class MultipleOutputs; class DetachedSong; enum class PlayerState : uint8_t { @@ -91,6 +92,8 @@ struct player_status { }; struct PlayerControl { + MultipleOutputs &outputs; + unsigned buffer_chunks; unsigned int buffered_before_play; @@ -170,7 +173,8 @@ struct PlayerControl { */ bool border_pause; - PlayerControl(unsigned buffer_chunks, + PlayerControl(MultipleOutputs &_outputs, + unsigned buffer_chunks, unsigned buffered_before_play); ~PlayerControl(); diff --git a/src/PlayerThread.cxx b/src/PlayerThread.cxx index 4fb045bda..d90f281c6 100644 --- a/src/PlayerThread.cxx +++ b/src/PlayerThread.cxx @@ -28,7 +28,7 @@ #include "system/FatalError.hxx" #include "CrossFade.hxx" #include "PlayerControl.hxx" -#include "output/OutputAll.hxx" +#include "output/MultipleOutputs.hxx" #include "tag/Tag.hxx" #include "Idle.hxx" #include "GlobalEvents.hxx" @@ -125,7 +125,7 @@ class Player { /** * The time stamp of the chunk most recently sent to the * output thread. This attribute is only used if - * audio_output_all_get_elapsed_time() didn't return a usable + * MultipleOutputs::GetElapsedTime() didn't return a usable * value; the output thread can estimate the elapsed time more * precisely. */ @@ -228,8 +228,8 @@ private: bool WaitForDecoder(); /** - * Wrapper for audio_output_all_open(). Upon failure, it pauses the - * player. + * Wrapper for MultipleOutputs::Open(). Upon failure, it + * pauses the player. * * @return true on success */ @@ -393,7 +393,7 @@ Player::OpenOutput() pc.state == PlayerState::PAUSE); Error error; - if (audio_output_all_open(play_audio_format, buffer, error)) { + if (pc.outputs.Open(play_audio_format, buffer, error)) { output_open = true; paused = false; @@ -444,7 +444,7 @@ Player::CheckDecoderStartup() pc.Unlock(); if (output_open && - !audio_output_all_wait(pc, 1)) + !pc.outputs.Wait(pc, 1)) /* the output devices havn't finished playing all chunks yet - wait for that */ return true; @@ -504,7 +504,7 @@ Player::SendSilence() memset(chunk->data, 0, chunk->length); Error error; - if (!audio_output_all_play(chunk, error)) { + if (!pc.outputs.Play(chunk, error)) { LogError(error); buffer.Return(chunk); return false; @@ -582,7 +582,7 @@ Player::SeekDecoder() /* re-fill the buffer after seeking */ buffering = true; - audio_output_all_cancel(); + pc.outputs.Cancel(); return true; } @@ -599,7 +599,7 @@ Player::ProcessCommand() case PlayerCommand::UPDATE_AUDIO: pc.Unlock(); - audio_output_all_enable_disable(); + pc.outputs.EnableDisable(); pc.Lock(); pc.CommandFinished(); break; @@ -618,7 +618,7 @@ Player::ProcessCommand() paused = !paused; if (paused) { - audio_output_all_pause(); + pc.outputs.Pause(); pc.Lock(); pc.state = PlayerState::PAUSE; @@ -669,11 +669,11 @@ Player::ProcessCommand() case PlayerCommand::REFRESH: if (output_open && !paused) { pc.Unlock(); - audio_output_all_check(); + pc.outputs.Check(); pc.Lock(); } - pc.elapsed_time = audio_output_all_get_elapsed_time(); + pc.elapsed_time = pc.outputs.GetElapsedTime(); if (pc.elapsed_time < 0.0) pc.elapsed_time = elapsed_time; @@ -733,7 +733,7 @@ play_chunk(PlayerControl &pc, /* send the chunk to the audio outputs */ - if (!audio_output_all_play(chunk, error)) + if (!pc.outputs.Play(chunk, error)) return false; pc.total_play_time += (double)chunk->length / @@ -744,7 +744,7 @@ play_chunk(PlayerControl &pc, inline bool Player::PlayNextChunk() { - if (!audio_output_all_wait(pc, 64)) + if (!pc.outputs.Wait(pc, 64)) /* the output pipe is still large enough, don't send another chunk */ return true; @@ -883,7 +883,7 @@ Player::SongBorder() ReplacePipe(dc.pipe); - audio_output_all_song_border(); + pc.outputs.SongBorder(); if (!WaitForDecoder()) return false; @@ -933,7 +933,7 @@ Player::Run() pc.command == PlayerCommand::EXIT || pc.command == PlayerCommand::CLOSE_AUDIO) { pc.Unlock(); - audio_output_all_cancel(); + pc.outputs.Cancel(); break; } @@ -949,7 +949,7 @@ Player::Run() /* not enough decoded buffer space yet */ if (!paused && output_open && - audio_output_all_check() < 4 && + pc.outputs.Check() < 4 && !SendSilence()) break; @@ -1029,7 +1029,7 @@ Player::Run() to the audio output */ PlayNextChunk(); - } else if (audio_output_all_check() > 0) { + } else if (pc.outputs.Check() > 0) { /* not enough data from decoder, but the output thread is still busy, so it's okay */ @@ -1054,7 +1054,7 @@ Player::Run() if (pipe->IsEmpty()) { /* wait for the hardware to finish playback */ - audio_output_all_drain(); + pc.outputs.Drain(); break; } } else if (output_open) { @@ -1130,7 +1130,7 @@ player_task(void *arg) case PlayerCommand::STOP: pc.Unlock(); - audio_output_all_cancel(); + pc.outputs.Cancel(); pc.Lock(); /* fall through */ @@ -1145,7 +1145,7 @@ player_task(void *arg) case PlayerCommand::CLOSE_AUDIO: pc.Unlock(); - audio_output_all_release(); + pc.outputs.Release(); pc.Lock(); pc.CommandFinished(); @@ -1156,7 +1156,7 @@ player_task(void *arg) case PlayerCommand::UPDATE_AUDIO: pc.Unlock(); - audio_output_all_enable_disable(); + pc.outputs.EnableDisable(); pc.Lock(); pc.CommandFinished(); break; @@ -1166,7 +1166,7 @@ player_task(void *arg) dc.Quit(); - audio_output_all_close(); + pc.outputs.Close(); player_command_finished(pc); return; diff --git a/src/StateFile.cxx b/src/StateFile.cxx index b28e8c617..7c0e24439 100644 --- a/src/StateFile.cxx +++ b/src/StateFile.cxx @@ -75,7 +75,7 @@ StateFile::Write() } save_sw_volume_state(fp); - audio_output_state_save(fp); + audio_output_state_save(fp, partition.outputs); playlist_state_save(fp, partition.playlist, partition.pc); fclose(fp); @@ -99,8 +99,8 @@ StateFile::Read() const char *line; while ((line = file.ReadLine()) != NULL) { - success = read_sw_volume_state(line) || - audio_output_state_read(line) || + success = read_sw_volume_state(line, partition.outputs) || + audio_output_state_read(line, partition.outputs) || playlist_state_restore(line, file, partition.playlist, partition.pc); if (!success) diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx index 328683d3d..359fa9fe3 100644 --- a/src/command/OtherCommands.cxx +++ b/src/command/OtherCommands.cxx @@ -45,6 +45,7 @@ #include "db/PlaylistVector.hxx" #include "client/ClientFile.hxx" #include "client/Client.hxx" +#include "Partition.hxx" #include "Idle.hxx" #include @@ -254,7 +255,7 @@ handle_setvol(Client &client, gcc_unused int argc, char *argv[]) return CommandResult::ERROR; } - success = volume_level_change(level); + success = volume_level_change(client.partition.outputs, level); if (!success) { command_error(client, ACK_ERROR_SYSTEM, "problems setting volume"); @@ -276,7 +277,7 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[]) return CommandResult::ERROR; } - const int old_volume = volume_level_get(); + const int old_volume = volume_level_get(client.partition.outputs); if (old_volume < 0) { command_error(client, ACK_ERROR_SYSTEM, "No mixer"); return CommandResult::ERROR; @@ -288,7 +289,8 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[]) else if (new_volume > 100) new_volume = 100; - if (new_volume != old_volume && !volume_level_change(new_volume)) { + if (new_volume != old_volume && + !volume_level_change(client.partition.outputs, new_volume)) { command_error(client, ACK_ERROR_SYSTEM, "problems setting volume"); return CommandResult::ERROR; diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx index 4ed3b89f1..03058d7ed 100644 --- a/src/command/OutputCommands.cxx +++ b/src/command/OutputCommands.cxx @@ -23,18 +23,17 @@ #include "output/OutputCommand.hxx" #include "protocol/Result.hxx" #include "protocol/ArgParser.hxx" +#include "client/Client.hxx" +#include "Partition.hxx" CommandResult handle_enableoutput(Client &client, gcc_unused int argc, char *argv[]) { unsigned device; - bool ret; - if (!check_unsigned(client, &device, argv[1])) return CommandResult::ERROR; - ret = audio_output_enable_index(device); - if (!ret) { + if (!audio_output_enable_index(client.partition.outputs, device)) { command_error(client, ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; @@ -47,13 +46,10 @@ CommandResult handle_disableoutput(Client &client, gcc_unused int argc, char *argv[]) { unsigned device; - bool ret; - if (!check_unsigned(client, &device, argv[1])) return CommandResult::ERROR; - ret = audio_output_disable_index(device); - if (!ret) { + if (!audio_output_disable_index(client.partition.outputs, device)) { command_error(client, ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; @@ -69,7 +65,7 @@ handle_toggleoutput(Client &client, gcc_unused int argc, char *argv[]) if (!check_unsigned(client, &device, argv[1])) return CommandResult::ERROR; - if (!audio_output_toggle_index(device)) { + if (!audio_output_toggle_index(client.partition.outputs, device)) { command_error(client, ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; @@ -82,7 +78,7 @@ CommandResult handle_devices(Client &client, gcc_unused int argc, gcc_unused char *argv[]) { - printAudioDevices(client); + printAudioDevices(client, client.partition.outputs); return CommandResult::OK; } diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx index 28258dded..5dc55b485 100644 --- a/src/command/PlayerCommands.cxx +++ b/src/command/PlayerCommands.cxx @@ -25,7 +25,6 @@ #include "db/update/UpdateGlue.hxx" #include "client/Client.hxx" #include "mixer/Volume.hxx" -#include "output/OutputAll.hxx" #include "Partition.hxx" #include "protocol/Result.hxx" #include "protocol/ArgParser.hxx" @@ -140,7 +139,7 @@ handle_status(Client &client, COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" COMMAND_STATUS_MIXRAMPDB ": %f\n" COMMAND_STATUS_STATE ": %s\n", - volume_level_get(), + volume_level_get(client.partition.outputs), playlist.GetRepeat(), playlist.GetRandom(), playlist.GetSingle(), @@ -277,7 +276,7 @@ handle_random(Client &client, gcc_unused int argc, char *argv[]) return CommandResult::ERROR; client.partition.SetRandom(status); - audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client.partition.GetRandom())); + client.partition.outputs.SetReplayGainMode(replay_gain_get_real_mode(client.partition.GetRandom())); return CommandResult::OK; } @@ -379,8 +378,7 @@ handle_replay_gain_mode(Client &client, return CommandResult::ERROR; } - audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client.playlist.queue.random)); - + client.partition.outputs.SetReplayGainMode(replay_gain_get_real_mode(client.playlist.queue.random)); return CommandResult::OK; } diff --git a/src/mixer/MixerAll.cxx b/src/mixer/MixerAll.cxx index 3cc92baee..483660a45 100644 --- a/src/mixer/MixerAll.cxx +++ b/src/mixer/MixerAll.cxx @@ -18,11 +18,10 @@ */ #include "config.h" -#include "MixerAll.hxx" +#include "output/MultipleOutputs.hxx" #include "MixerControl.hxx" #include "MixerInternal.hxx" #include "MixerList.hxx" -#include "output/OutputAll.hxx" #include "output/OutputInternal.hxx" #include "pcm/Volume.hxx" #include "util/Error.hxx" @@ -34,39 +33,33 @@ static constexpr Domain mixer_domain("mixer"); static int -output_mixer_get_volume(unsigned i) +output_mixer_get_volume(const audio_output &ao) { - struct audio_output *output; - int volume; - - assert(i < audio_output_count()); - - output = audio_output_get(i); - if (!output->enabled) + if (!ao.enabled) return -1; - Mixer *mixer = output->mixer; + Mixer *mixer = ao.mixer; if (mixer == nullptr) return -1; Error error; - volume = mixer_get_volume(mixer, error); + int volume = mixer_get_volume(mixer, error); if (volume < 0 && error.IsDefined()) FormatError(error, "Failed to read mixer for '%s'", - output->name); + ao.name); return volume; } int -mixer_all_get_volume(void) +MultipleOutputs::GetVolume() const { - unsigned count = audio_output_count(), ok = 0; - int volume, total = 0; + unsigned ok = 0; + int total = 0; - for (unsigned i = 0; i < count; i++) { - volume = output_mixer_get_volume(i); + for (auto ao : outputs) { + int volume = output_mixer_get_volume(*ao); if (volume >= 0) { total += volume; ++ok; @@ -80,59 +73,47 @@ mixer_all_get_volume(void) } static bool -output_mixer_set_volume(unsigned i, unsigned volume) +output_mixer_set_volume(audio_output &ao, unsigned volume) { - struct audio_output *output; - bool success; - - assert(i < audio_output_count()); assert(volume <= 100); - output = audio_output_get(i); - if (!output->enabled) + if (!ao.enabled) return false; - Mixer *mixer = output->mixer; + Mixer *mixer = ao.mixer; if (mixer == nullptr) return false; Error error; - success = mixer_set_volume(mixer, volume, error); + bool success = mixer_set_volume(mixer, volume, error); if (!success && error.IsDefined()) FormatError(error, "Failed to set mixer for '%s'", - output->name); + ao.name); return success; } bool -mixer_all_set_volume(unsigned volume) +MultipleOutputs::SetVolume(unsigned volume) { - bool success = false; - unsigned count = audio_output_count(); - assert(volume <= 100); - for (unsigned i = 0; i < count; i++) - success = output_mixer_set_volume(i, volume) + bool success = false; + for (auto ao : outputs) + success = output_mixer_set_volume(*ao, volume) || success; return success; } static int -output_mixer_get_software_volume(unsigned i) +output_mixer_get_software_volume(const audio_output &ao) { - struct audio_output *output; - - assert(i < audio_output_count()); - - output = audio_output_get(i); - if (!output->enabled) + if (!ao.enabled) return -1; - Mixer *mixer = output->mixer; + Mixer *mixer = ao.mixer; if (mixer == nullptr || !mixer->IsPlugin(software_mixer_plugin)) return -1; @@ -140,13 +121,13 @@ output_mixer_get_software_volume(unsigned i) } int -mixer_all_get_software_volume(void) +MultipleOutputs::GetSoftwareVolume() const { - unsigned count = audio_output_count(), ok = 0; - int volume, total = 0; + unsigned ok = 0; + int total = 0; - for (unsigned i = 0; i < count; i++) { - volume = output_mixer_get_software_volume(i); + for (auto ao : outputs) { + int volume = output_mixer_get_software_volume(*ao); if (volume >= 0) { total += volume; ++ok; @@ -160,16 +141,15 @@ mixer_all_get_software_volume(void) } void -mixer_all_set_software_volume(unsigned volume) +MultipleOutputs::SetSoftwareVolume(unsigned volume) { - unsigned count = audio_output_count(); - assert(volume <= PCM_VOLUME_1); - for (unsigned i = 0; i < count; i++) { - struct audio_output *output = audio_output_get(i); - if (output->mixer != nullptr && - output->mixer->plugin == &software_mixer_plugin) - mixer_set_volume(output->mixer, volume, IgnoreError()); + for (auto ao : outputs) { + const auto mixer = ao->mixer; + + if (mixer != nullptr && + mixer->plugin == &software_mixer_plugin) + mixer_set_volume(mixer, volume, IgnoreError()); } } diff --git a/src/mixer/MixerAll.hxx b/src/mixer/MixerAll.hxx deleted file mode 100644 index f868f64b5..000000000 --- a/src/mixer/MixerAll.hxx +++ /dev/null @@ -1,64 +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. - */ - -/** \file - * - * Functions which affect the mixers of all audio outputs. - */ - -#ifndef MPD_MIXER_ALL_HXX -#define MPD_MIXER_ALL_HXX - -#include "Compiler.h" - -/** - * Returns the average volume of all available mixers (range 0..100). - * Returns -1 if no mixer can be queried. - */ -gcc_pure -int -mixer_all_get_volume(void); - -/** - * Sets the volume on all available mixers. - * - * @param volume the volume (range 0..100) - * @return true on success, false on failure - */ -bool -mixer_all_set_volume(unsigned volume); - -/** - * Similar to mixer_all_get_volume(), but gets the volume only for - * software mixers. See #software_mixer_plugin. This function fails - * if no software mixer is configured. - */ -gcc_pure -int -mixer_all_get_software_volume(void); - -/** - * Similar to mixer_all_set_volume(), 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 -mixer_all_set_software_volume(unsigned volume); - -#endif diff --git a/src/mixer/Volume.cxx b/src/mixer/Volume.cxx index 4c897b21f..aaae5d9ee 100644 --- a/src/mixer/Volume.cxx +++ b/src/mixer/Volume.cxx @@ -19,7 +19,7 @@ #include "config.h" #include "Volume.hxx" -#include "MixerAll.hxx" +#include "output/MultipleOutputs.hxx" #include "Idle.hxx" #include "GlobalEvents.hxx" #include "util/StringUtil.hxx" @@ -59,36 +59,40 @@ void volume_init(void) GlobalEvents::Register(GlobalEvents::MIXER, mixer_event_callback); } -int volume_level_get(void) +int +volume_level_get(const MultipleOutputs &outputs) { if (last_hardware_volume >= 0 && !hardware_volume_clock.CheckUpdate(1000)) /* throttle access to hardware mixers */ return last_hardware_volume; - last_hardware_volume = mixer_all_get_volume(); + last_hardware_volume = outputs.GetVolume(); return last_hardware_volume; } -static bool software_volume_change(unsigned volume) +static bool +software_volume_change(MultipleOutputs &outputs, unsigned volume) { assert(volume <= 100); volume_software_set = volume; - mixer_all_set_software_volume(volume); + outputs.SetSoftwareVolume(volume); return true; } -static bool hardware_volume_change(unsigned volume) +static bool +hardware_volume_change(MultipleOutputs &outputs, unsigned volume) { /* reset the cache */ last_hardware_volume = -1; - return mixer_all_set_volume(volume); + return outputs.SetVolume(volume); } -bool volume_level_change(unsigned volume) +bool +volume_level_change(MultipleOutputs &outputs, unsigned volume) { assert(volume <= 100); @@ -96,11 +100,11 @@ bool volume_level_change(unsigned volume) idle_add(IDLE_MIXER); - return hardware_volume_change(volume); + return hardware_volume_change(outputs, volume); } bool -read_sw_volume_state(const char *line) +read_sw_volume_state(const char *line, MultipleOutputs &outputs) { char *end = nullptr; long int sv; @@ -111,7 +115,7 @@ read_sw_volume_state(const char *line) line += sizeof(SW_VOLUME_STATE) - 1; sv = strtol(line, &end, 10); if (*end == 0 && sv >= 0 && sv <= 100) - software_volume_change(sv); + software_volume_change(outputs, sv); else FormatWarning(volume_domain, "Can't parse software volume: %s", line); diff --git a/src/mixer/Volume.hxx b/src/mixer/Volume.hxx index 06d3551eb..b86328d88 100644 --- a/src/mixer/Volume.hxx +++ b/src/mixer/Volume.hxx @@ -24,15 +24,19 @@ #include +class MultipleOutputs; + void volume_init(void); gcc_pure -int volume_level_get(void); +int +volume_level_get(const MultipleOutputs &outputs); -bool volume_level_change(unsigned volume); +bool +volume_level_change(MultipleOutputs &outputs, unsigned volume); bool -read_sw_volume_state(const char *line); +read_sw_volume_state(const char *line, MultipleOutputs &outputs); void save_sw_volume_state(FILE *fp); 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 diff --git a/test/run_output.cxx b/test/run_output.cxx index 522db5e1e..fe377158d 100644 --- a/test/run_output.cxx +++ b/test/run_output.cxx @@ -74,8 +74,10 @@ find_named_config_block(ConfigOption option, const char *name) return NULL; } -PlayerControl::PlayerControl(gcc_unused unsigned _buffer_chunks, - gcc_unused unsigned _buffered_before_play) {} +PlayerControl::PlayerControl(gcc_unused MultipleOutputs &_outputs, + gcc_unused unsigned _buffer_chunks, + gcc_unused unsigned _buffered_before_play) + :outputs(_outputs) {} PlayerControl::~PlayerControl() {} static struct audio_output * @@ -89,7 +91,8 @@ load_audio_output(const char *name) return nullptr; } - static struct PlayerControl dummy_player_control(32, 4); + static struct PlayerControl dummy_player_control(*(MultipleOutputs *)nullptr, + 32, 4); Error error; struct audio_output *ao = -- cgit v1.2.3