aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Main.cxx6
-rw-r--r--src/Partition.cxx1
-rw-r--r--src/Partition.hxx7
-rw-r--r--src/PlayerControl.cxx6
-rw-r--r--src/PlayerControl.hxx6
-rw-r--r--src/PlayerThread.cxx46
-rw-r--r--src/StateFile.cxx6
-rw-r--r--src/command/OtherCommands.cxx8
-rw-r--r--src/command/OutputCommands.cxx16
-rw-r--r--src/command/PlayerCommands.cxx8
-rw-r--r--src/mixer/MixerAll.cxx90
-rw-r--r--src/mixer/MixerAll.hxx64
-rw-r--r--src/mixer/Volume.cxx26
-rw-r--r--src/mixer/Volume.hxx10
-rw-r--r--src/output/MultipleOutputs.cxx477
-rw-r--r--src/output/MultipleOutputs.hxx271
-rw-r--r--src/output/OutputAll.cxx589
-rw-r--r--src/output/OutputAll.hxx174
-rw-r--r--src/output/OutputCommand.cxx46
-rw-r--r--src/output/OutputCommand.hxx8
-rw-r--r--src/output/OutputPrint.cxx12
-rw-r--r--src/output/OutputPrint.hxx4
-rw-r--r--src/output/OutputState.cxx19
-rw-r--r--src/output/OutputState.hxx6
24 files changed, 905 insertions, 1001 deletions
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 <assert.h>
-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 <stdint.h>
+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 <assert.h>
@@ -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 <stdio.h>
+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 <assert.h>
+#include <string.h>
+
+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 &param)
+{
+ 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 <vector>
+
+#include <assert.h>
+
+struct AudioFormat;
+class MusicBuffer;
+class MusicPipe;
+struct music_chunk;
+struct PlayerControl;
+struct audio_output;
+class Error;
+
+class MultipleOutputs {
+ std::vector<audio_output *> 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 <assert.h>
-#include <string.h>
-
-static AudioFormat input_audio_format;
-
-static struct audio_output **audio_outputs;
-static unsigned int num_audio_outputs;
-
-/**
- * The #MusicBuffer object where consumed chunks are returned.
- */
-static MusicBuffer *g_music_buffer;
-
-/**
- * The #MusicPipe object which feeds all audio outputs. It is filled
- * by audio_output_all_play().
- */
-static MusicPipe *g_mp;
-
-/**
- * The "elapsed_time" stamp of the most recently finished chunk.
- */
-static float audio_output_all_elapsed_time = -1.0;
-
-unsigned int audio_output_count(void)
-{
- return num_audio_outputs;
-}
-
-struct audio_output *
-audio_output_get(unsigned i)
-{
- assert(i < num_audio_outputs);
-
- assert(audio_outputs[i] != nullptr);
-
- return audio_outputs[i];
-}
-
-struct audio_output *
-audio_output_find(const char *name)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = audio_output_get(i);
-
- if (strcmp(ao->name, name) == 0)
- return ao;
- }
-
- /* name not found */
- return nullptr;
-}
-
-gcc_const
-static unsigned
-audio_output_config_count(void)
-{
- unsigned int nr = 0;
- const struct config_param *param = nullptr;
-
- while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, param)))
- nr++;
- if (!nr)
- nr = 1; /* we'll always have at least one device */
- return nr;
-}
-
-void
-audio_output_all_init(PlayerControl &pc)
-{
- const struct config_param *param = nullptr;
- unsigned int i;
- Error error;
-
- num_audio_outputs = audio_output_config_count();
- audio_outputs = new audio_output *[num_audio_outputs];
-
- const config_param empty;
-
- for (i = 0; i < num_audio_outputs; i++)
- {
- unsigned int j;
-
- param = config_get_next_param(CONF_AUDIO_OUTPUT, param);
- if (param == nullptr) {
- /* only allow param to be nullptr if there
- just one audio output */
- assert(i == 0);
- assert(num_audio_outputs == 1);
-
- param = &empty;
- }
-
- audio_output *output = audio_output_new(*param, pc, error);
- if (output == nullptr) {
- if (param != nullptr)
- FormatFatalError("line %i: %s",
- param->line,
- error.GetMessage());
- else
- FatalError(error);
- }
-
- audio_outputs[i] = output;
-
- /* require output names to be unique: */
- for (j = 0; j < i; j++) {
- if (!strcmp(output->name, audio_outputs[j]->name)) {
- FormatFatalError("output devices with identical "
- "names: %s", output->name);
- }
- }
- }
-}
-
-void
-audio_output_all_finish(void)
-{
- unsigned int i;
-
- for (i = 0; i < num_audio_outputs; i++) {
- audio_output_disable(audio_outputs[i]);
- audio_output_finish(audio_outputs[i]);
- }
-
- delete[] audio_outputs;
- audio_outputs = nullptr;
- num_audio_outputs = 0;
-}
-
-void
-audio_output_all_enable_disable(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; i++) {
- struct audio_output *ao = audio_outputs[i];
- bool enabled;
-
- ao->mutex.lock();
- enabled = ao->really_enabled;
- ao->mutex.unlock();
-
- if (ao->enabled != enabled) {
- if (ao->enabled)
- audio_output_enable(ao);
- else
- audio_output_disable(ao);
- }
- }
-}
-
-/**
- * Determine if all (active) outputs have finished the current
- * command.
- */
-static bool
-audio_output_all_finished(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = audio_outputs[i];
-
- const ScopeLock protect(ao->mutex);
- if (audio_output_is_open(ao) &&
- !audio_output_command_is_finished(ao))
- return false;
- }
-
- return true;
-}
-
-static void audio_output_wait_all(void)
-{
- while (!audio_output_all_finished())
- audio_output_client_notify.Wait();
-}
-
-/**
- * Signals all audio outputs which are open.
- */
-static void
-audio_output_allow_play_all(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i)
- audio_output_allow_play(audio_outputs[i]);
-}
-
-static void
-audio_output_reset_reopen(struct audio_output *ao)
-{
- const ScopeLock protect(ao->mutex);
-
- ao->fail_timer.Reset();
-}
-
-/**
- * Resets the "reopen" flag on all audio devices. MPD should
- * immediately retry to open the device instead of waiting for the
- * timeout when the user wants to start playback.
- */
-static void
-audio_output_all_reset_reopen(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = audio_outputs[i];
-
- audio_output_reset_reopen(ao);
- }
-}
-
-/**
- * Opens all output devices which are enabled, but closed.
- *
- * @return true if there is at least open output device which is open
- */
-static bool
-audio_output_all_update(void)
-{
- unsigned int i;
- bool ret = false;
-
- if (!input_audio_format.IsDefined())
- return false;
-
- for (i = 0; i < num_audio_outputs; ++i)
- ret = audio_output_update(audio_outputs[i],
- input_audio_format, *g_mp) || ret;
-
- return ret;
-}
-
-void
-audio_output_all_set_replay_gain_mode(ReplayGainMode mode)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i)
- audio_output_set_replay_gain_mode(audio_outputs[i], mode);
-}
-
-bool
-audio_output_all_play(struct music_chunk *chunk, Error &error)
-{
- bool ret;
- unsigned int i;
-
- assert(g_music_buffer != nullptr);
- assert(g_mp != nullptr);
- assert(chunk != nullptr);
- assert(chunk->CheckFormat(input_audio_format));
-
- ret = audio_output_all_update();
- if (!ret) {
- /* TODO: obtain real error */
- error.Set(output_domain, "Failed to open audio output");
- return false;
- }
-
- g_mp->Push(chunk);
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_play(audio_outputs[i]);
-
- return true;
-}
-
-bool
-audio_output_all_open(const AudioFormat audio_format,
- MusicBuffer &buffer,
- Error &error)
-{
- bool ret = false, enabled = false;
- unsigned int i;
-
- assert(g_music_buffer == nullptr || g_music_buffer == &buffer);
- assert((g_mp == nullptr) == (g_music_buffer == nullptr));
-
- g_music_buffer = &buffer;
-
- /* the audio format must be the same as existing chunks in the
- pipe */
- assert(g_mp == nullptr || g_mp->CheckFormat(audio_format));
-
- if (g_mp == nullptr)
- g_mp = new MusicPipe();
- else
- /* if the pipe hasn't been cleared, the the audio
- format must not have changed */
- assert(g_mp->IsEmpty() || audio_format == input_audio_format);
-
- input_audio_format = audio_format;
-
- audio_output_all_reset_reopen();
- audio_output_all_enable_disable();
- audio_output_all_update();
-
- for (i = 0; i < num_audio_outputs; ++i) {
- if (audio_outputs[i]->enabled)
- enabled = true;
-
- if (audio_outputs[i]->open)
- ret = true;
- }
-
- if (!enabled)
- error.Set(output_domain, "All audio outputs are disabled");
- else if (!ret)
- /* TODO: obtain real error */
- error.Set(output_domain, "Failed to open audio output");
-
- if (!ret)
- /* close all devices if there was an error */
- audio_output_all_close();
-
- return ret;
-}
-
-/**
- * Has the specified audio output already consumed this chunk?
- */
-static bool
-chunk_is_consumed_in(const struct audio_output *ao,
- const struct music_chunk *chunk)
-{
- if (!ao->open)
- return true;
-
- if (ao->chunk == nullptr)
- return false;
-
- assert(chunk == ao->chunk || g_mp->Contains(ao->chunk));
-
- if (chunk != ao->chunk) {
- assert(chunk->next != nullptr);
- return true;
- }
-
- return ao->chunk_finished && chunk->next == nullptr;
-}
-
-/**
- * Has this chunk been consumed by all audio outputs?
- */
-static bool
-chunk_is_consumed(const struct music_chunk *chunk)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = audio_outputs[i];
-
- const ScopeLock protect(ao->mutex);
- if (!chunk_is_consumed_in(ao, chunk))
- return false;
- }
-
- return true;
-}
-
-/**
- * There's only one chunk left in the pipe (#g_mp), and all audio
- * outputs have consumed it already. Clear the reference.
- */
-static void
-clear_tail_chunk(gcc_unused const struct music_chunk *chunk, bool *locked)
-{
- assert(chunk->next == nullptr);
- assert(g_mp->Contains(chunk));
-
- for (unsigned i = 0; i < num_audio_outputs; ++i) {
- struct audio_output *ao = audio_outputs[i];
-
- /* this mutex will be unlocked by the caller when it's
- ready */
- ao->mutex.lock();
- locked[i] = ao->open;
-
- if (!locked[i]) {
- ao->mutex.unlock();
- continue;
- }
-
- assert(ao->chunk == chunk);
- assert(ao->chunk_finished);
- ao->chunk = nullptr;
- }
-}
-
-unsigned
-audio_output_all_check(void)
-{
- const struct music_chunk *chunk;
- bool is_tail;
- struct music_chunk *shifted;
- bool locked[num_audio_outputs];
-
- assert(g_music_buffer != nullptr);
- assert(g_mp != nullptr);
-
- while ((chunk = g_mp->Peek()) != nullptr) {
- assert(!g_mp->IsEmpty());
-
- if (!chunk_is_consumed(chunk))
- /* at least one output is not finished playing
- this chunk */
- return g_mp->GetSize();
-
- if (chunk->length > 0 && chunk->times >= 0.0)
- /* only update elapsed_time if the chunk
- provides a defined value */
- audio_output_all_elapsed_time = chunk->times;
-
- is_tail = chunk->next == nullptr;
- if (is_tail)
- /* this is the tail of the pipe - clear the
- chunk reference in all outputs */
- clear_tail_chunk(chunk, locked);
-
- /* remove the chunk from the pipe */
- shifted = g_mp->Shift();
- assert(shifted == chunk);
-
- if (is_tail)
- /* unlock all audio outputs which were locked
- by clear_tail_chunk() */
- for (unsigned i = 0; i < num_audio_outputs; ++i)
- if (locked[i])
- audio_outputs[i]->mutex.unlock();
-
- /* return the chunk to the buffer */
- g_music_buffer->Return(shifted);
- }
-
- return 0;
-}
-
-bool
-audio_output_all_wait(PlayerControl &pc, unsigned threshold)
-{
- pc.Lock();
-
- if (audio_output_all_check() < threshold) {
- pc.Unlock();
- return true;
- }
-
- pc.Wait();
- pc.Unlock();
-
- return audio_output_all_check() < threshold;
-}
-
-void
-audio_output_all_pause(void)
-{
- unsigned int i;
-
- audio_output_all_update();
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_pause(audio_outputs[i]);
-
- audio_output_wait_all();
-}
-
-void
-audio_output_all_drain(void)
-{
- for (unsigned i = 0; i < num_audio_outputs; ++i)
- audio_output_drain_async(audio_outputs[i]);
-
- audio_output_wait_all();
-}
-
-void
-audio_output_all_cancel(void)
-{
- unsigned int i;
-
- /* send the cancel() command to all audio outputs */
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_cancel(audio_outputs[i]);
-
- audio_output_wait_all();
-
- /* clear the music pipe and return all chunks to the buffer */
-
- if (g_mp != nullptr)
- g_mp->Clear(*g_music_buffer);
-
- /* the audio outputs are now waiting for a signal, to
- synchronize the cleared music pipe */
-
- audio_output_allow_play_all();
-
- /* invalidate elapsed_time */
-
- audio_output_all_elapsed_time = -1.0;
-}
-
-void
-audio_output_all_close(void)
-{
- unsigned int i;
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_close(audio_outputs[i]);
-
- if (g_mp != nullptr) {
- assert(g_music_buffer != nullptr);
-
- g_mp->Clear(*g_music_buffer);
- delete g_mp;
- g_mp = nullptr;
- }
-
- g_music_buffer = nullptr;
-
- input_audio_format.Clear();
-
- audio_output_all_elapsed_time = -1.0;
-}
-
-void
-audio_output_all_release(void)
-{
- unsigned int i;
-
- for (i = 0; i < num_audio_outputs; ++i)
- audio_output_release(audio_outputs[i]);
-
- if (g_mp != nullptr) {
- assert(g_music_buffer != nullptr);
-
- g_mp->Clear(*g_music_buffer);
- delete g_mp;
- g_mp = nullptr;
- }
-
- g_music_buffer = nullptr;
-
- input_audio_format.Clear();
-
- audio_output_all_elapsed_time = -1.0;
-}
-
-void
-audio_output_all_song_border(void)
-{
- /* clear the elapsed_time pointer at the beginning of a new
- song */
- audio_output_all_elapsed_time = 0.0;
-}
-
-float
-audio_output_all_get_elapsed_time(void)
-{
- return audio_output_all_elapsed_time;
-}
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 <stdio.h>
+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