Commit f5a923b9 authored by Max Kellermann's avatar Max Kellermann

OutputAll: convert to class, move instance to class Partition

Another big chunk of code for multi-player support.
parent 36bab6ef
...@@ -961,7 +961,7 @@ OUTPUT_API_SRC = \ ...@@ -961,7 +961,7 @@ OUTPUT_API_SRC = \
src/output/OutputAPI.hxx \ src/output/OutputAPI.hxx \
src/output/OutputInternal.hxx \ src/output/OutputInternal.hxx \
src/output/OutputList.cxx src/output/OutputList.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/OutputThread.cxx src/output/OutputThread.hxx \
src/output/OutputError.cxx src/output/OutputError.hxx \ src/output/OutputError.cxx src/output/OutputError.hxx \
src/output/OutputControl.cxx src/output/OutputControl.hxx \ src/output/OutputControl.cxx src/output/OutputControl.hxx \
...@@ -985,7 +985,7 @@ MIXER_API_SRC = \ ...@@ -985,7 +985,7 @@ MIXER_API_SRC = \
src/mixer/MixerList.hxx \ src/mixer/MixerList.hxx \
src/mixer/MixerControl.cxx src/mixer/MixerControl.hxx \ src/mixer/MixerControl.cxx src/mixer/MixerControl.hxx \
src/mixer/MixerType.cxx src/mixer/MixerType.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 src/mixer/MixerInternal.hxx
libmixer_plugins_a_SOURCES = \ libmixer_plugins_a_SOURCES = \
......
...@@ -37,7 +37,6 @@ ...@@ -37,7 +37,6 @@
#include "command/AllCommands.hxx" #include "command/AllCommands.hxx"
#include "Partition.hxx" #include "Partition.hxx"
#include "mixer/Volume.hxx" #include "mixer/Volume.hxx"
#include "output/OutputAll.hxx"
#include "tag/TagConfig.hxx" #include "tag/TagConfig.hxx"
#include "ReplayGainConfig.hxx" #include "ReplayGainConfig.hxx"
#include "Idle.hxx" #include "Idle.hxx"
...@@ -458,7 +457,7 @@ int mpd_main(int argc, char *argv[]) ...@@ -458,7 +457,7 @@ int mpd_main(int argc, char *argv[])
initialize_decoder_and_player(); initialize_decoder_and_player();
volume_init(); volume_init();
initAudioConfig(); initAudioConfig();
audio_output_all_init(instance->partition->pc); instance->partition->outputs.Configure(instance->partition->pc);
client_manager_init(); client_manager_init();
replay_gain_global_init(); replay_gain_global_init();
...@@ -500,7 +499,7 @@ int mpd_main(int argc, char *argv[]) ...@@ -500,7 +499,7 @@ int mpd_main(int argc, char *argv[])
return EXIT_FAILURE; 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)) { if (config_get_bool(CONF_AUTO_UPDATE, false)) {
#ifdef ENABLE_INOTIFY #ifdef ENABLE_INOTIFY
...@@ -567,7 +566,6 @@ int mpd_main(int argc, char *argv[]) ...@@ -567,7 +566,6 @@ int mpd_main(int argc, char *argv[])
playlist_list_global_finish(); playlist_list_global_finish();
input_stream_global_finish(); input_stream_global_finish();
audio_output_all_finish();
mapper_finish(); mapper_finish();
delete instance->partition; delete instance->partition;
command_finish(); command_finish();
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include "config.h" #include "config.h"
#include "Partition.hxx" #include "Partition.hxx"
#include "DetachedSong.hxx" #include "DetachedSong.hxx"
#include "output/MultipleOutputs.hxx"
void void
Partition::DatabaseModified() Partition::DatabaseModified()
......
...@@ -21,9 +21,11 @@ ...@@ -21,9 +21,11 @@
#define MPD_PARTITION_HXX #define MPD_PARTITION_HXX
#include "Playlist.hxx" #include "Playlist.hxx"
#include "output/MultipleOutputs.hxx"
#include "PlayerControl.hxx" #include "PlayerControl.hxx"
struct Instance; struct Instance;
class MultipleOutputs;
/** /**
* A partition of the Music Player Daemon. It is a separate unit with * A partition of the Music Player Daemon. It is a separate unit with
...@@ -34,6 +36,8 @@ struct Partition { ...@@ -34,6 +36,8 @@ struct Partition {
struct playlist playlist; struct playlist playlist;
MultipleOutputs outputs;
PlayerControl pc; PlayerControl pc;
Partition(Instance &_instance, Partition(Instance &_instance,
...@@ -41,8 +45,7 @@ struct Partition { ...@@ -41,8 +45,7 @@ struct Partition {
unsigned buffer_chunks, unsigned buffer_chunks,
unsigned buffered_before_play) unsigned buffered_before_play)
:instance(_instance), playlist(max_length), :instance(_instance), playlist(max_length),
pc(buffer_chunks, buffered_before_play) { pc(outputs, buffer_chunks, buffered_before_play) {}
}
void ClearQueue() { void ClearQueue() {
playlist.Clear(pc); playlist.Clear(pc);
......
...@@ -26,9 +26,11 @@ ...@@ -26,9 +26,11 @@
#include <assert.h> #include <assert.h>
PlayerControl::PlayerControl(unsigned _buffer_chunks, PlayerControl::PlayerControl(MultipleOutputs &_outputs,
unsigned _buffer_chunks,
unsigned _buffered_before_play) unsigned _buffered_before_play)
:buffer_chunks(_buffer_chunks), :outputs(_outputs),
buffer_chunks(_buffer_chunks),
buffered_before_play(_buffered_before_play), buffered_before_play(_buffered_before_play),
command(PlayerCommand::NONE), command(PlayerCommand::NONE),
state(PlayerState::STOP), state(PlayerState::STOP),
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include <stdint.h> #include <stdint.h>
class MultipleOutputs;
class DetachedSong; class DetachedSong;
enum class PlayerState : uint8_t { enum class PlayerState : uint8_t {
...@@ -91,6 +92,8 @@ struct player_status { ...@@ -91,6 +92,8 @@ struct player_status {
}; };
struct PlayerControl { struct PlayerControl {
MultipleOutputs &outputs;
unsigned buffer_chunks; unsigned buffer_chunks;
unsigned int buffered_before_play; unsigned int buffered_before_play;
...@@ -170,7 +173,8 @@ struct PlayerControl { ...@@ -170,7 +173,8 @@ struct PlayerControl {
*/ */
bool border_pause; bool border_pause;
PlayerControl(unsigned buffer_chunks, PlayerControl(MultipleOutputs &_outputs,
unsigned buffer_chunks,
unsigned buffered_before_play); unsigned buffered_before_play);
~PlayerControl(); ~PlayerControl();
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
#include "system/FatalError.hxx" #include "system/FatalError.hxx"
#include "CrossFade.hxx" #include "CrossFade.hxx"
#include "PlayerControl.hxx" #include "PlayerControl.hxx"
#include "output/OutputAll.hxx" #include "output/MultipleOutputs.hxx"
#include "tag/Tag.hxx" #include "tag/Tag.hxx"
#include "Idle.hxx" #include "Idle.hxx"
#include "GlobalEvents.hxx" #include "GlobalEvents.hxx"
...@@ -125,7 +125,7 @@ class Player { ...@@ -125,7 +125,7 @@ class Player {
/** /**
* The time stamp of the chunk most recently sent to the * The time stamp of the chunk most recently sent to the
* output thread. This attribute is only used if * 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 * value; the output thread can estimate the elapsed time more
* precisely. * precisely.
*/ */
...@@ -228,8 +228,8 @@ private: ...@@ -228,8 +228,8 @@ private:
bool WaitForDecoder(); bool WaitForDecoder();
/** /**
* Wrapper for audio_output_all_open(). Upon failure, it pauses the * Wrapper for MultipleOutputs::Open(). Upon failure, it
* player. * pauses the player.
* *
* @return true on success * @return true on success
*/ */
...@@ -393,7 +393,7 @@ Player::OpenOutput() ...@@ -393,7 +393,7 @@ Player::OpenOutput()
pc.state == PlayerState::PAUSE); pc.state == PlayerState::PAUSE);
Error error; Error error;
if (audio_output_all_open(play_audio_format, buffer, error)) { if (pc.outputs.Open(play_audio_format, buffer, error)) {
output_open = true; output_open = true;
paused = false; paused = false;
...@@ -444,7 +444,7 @@ Player::CheckDecoderStartup() ...@@ -444,7 +444,7 @@ Player::CheckDecoderStartup()
pc.Unlock(); pc.Unlock();
if (output_open && if (output_open &&
!audio_output_all_wait(pc, 1)) !pc.outputs.Wait(pc, 1))
/* the output devices havn't finished playing /* the output devices havn't finished playing
all chunks yet - wait for that */ all chunks yet - wait for that */
return true; return true;
...@@ -504,7 +504,7 @@ Player::SendSilence() ...@@ -504,7 +504,7 @@ Player::SendSilence()
memset(chunk->data, 0, chunk->length); memset(chunk->data, 0, chunk->length);
Error error; Error error;
if (!audio_output_all_play(chunk, error)) { if (!pc.outputs.Play(chunk, error)) {
LogError(error); LogError(error);
buffer.Return(chunk); buffer.Return(chunk);
return false; return false;
...@@ -582,7 +582,7 @@ Player::SeekDecoder() ...@@ -582,7 +582,7 @@ Player::SeekDecoder()
/* re-fill the buffer after seeking */ /* re-fill the buffer after seeking */
buffering = true; buffering = true;
audio_output_all_cancel(); pc.outputs.Cancel();
return true; return true;
} }
...@@ -599,7 +599,7 @@ Player::ProcessCommand() ...@@ -599,7 +599,7 @@ Player::ProcessCommand()
case PlayerCommand::UPDATE_AUDIO: case PlayerCommand::UPDATE_AUDIO:
pc.Unlock(); pc.Unlock();
audio_output_all_enable_disable(); pc.outputs.EnableDisable();
pc.Lock(); pc.Lock();
pc.CommandFinished(); pc.CommandFinished();
break; break;
...@@ -618,7 +618,7 @@ Player::ProcessCommand() ...@@ -618,7 +618,7 @@ Player::ProcessCommand()
paused = !paused; paused = !paused;
if (paused) { if (paused) {
audio_output_all_pause(); pc.outputs.Pause();
pc.Lock(); pc.Lock();
pc.state = PlayerState::PAUSE; pc.state = PlayerState::PAUSE;
...@@ -669,11 +669,11 @@ Player::ProcessCommand() ...@@ -669,11 +669,11 @@ Player::ProcessCommand()
case PlayerCommand::REFRESH: case PlayerCommand::REFRESH:
if (output_open && !paused) { if (output_open && !paused) {
pc.Unlock(); pc.Unlock();
audio_output_all_check(); pc.outputs.Check();
pc.Lock(); pc.Lock();
} }
pc.elapsed_time = audio_output_all_get_elapsed_time(); pc.elapsed_time = pc.outputs.GetElapsedTime();
if (pc.elapsed_time < 0.0) if (pc.elapsed_time < 0.0)
pc.elapsed_time = elapsed_time; pc.elapsed_time = elapsed_time;
...@@ -733,7 +733,7 @@ play_chunk(PlayerControl &pc, ...@@ -733,7 +733,7 @@ play_chunk(PlayerControl &pc,
/* send the chunk to the audio outputs */ /* send the chunk to the audio outputs */
if (!audio_output_all_play(chunk, error)) if (!pc.outputs.Play(chunk, error))
return false; return false;
pc.total_play_time += (double)chunk->length / pc.total_play_time += (double)chunk->length /
...@@ -744,7 +744,7 @@ play_chunk(PlayerControl &pc, ...@@ -744,7 +744,7 @@ play_chunk(PlayerControl &pc,
inline bool inline bool
Player::PlayNextChunk() 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 /* the output pipe is still large enough, don't send
another chunk */ another chunk */
return true; return true;
...@@ -883,7 +883,7 @@ Player::SongBorder() ...@@ -883,7 +883,7 @@ Player::SongBorder()
ReplacePipe(dc.pipe); ReplacePipe(dc.pipe);
audio_output_all_song_border(); pc.outputs.SongBorder();
if (!WaitForDecoder()) if (!WaitForDecoder())
return false; return false;
...@@ -933,7 +933,7 @@ Player::Run() ...@@ -933,7 +933,7 @@ Player::Run()
pc.command == PlayerCommand::EXIT || pc.command == PlayerCommand::EXIT ||
pc.command == PlayerCommand::CLOSE_AUDIO) { pc.command == PlayerCommand::CLOSE_AUDIO) {
pc.Unlock(); pc.Unlock();
audio_output_all_cancel(); pc.outputs.Cancel();
break; break;
} }
...@@ -949,7 +949,7 @@ Player::Run() ...@@ -949,7 +949,7 @@ Player::Run()
/* not enough decoded buffer space yet */ /* not enough decoded buffer space yet */
if (!paused && output_open && if (!paused && output_open &&
audio_output_all_check() < 4 && pc.outputs.Check() < 4 &&
!SendSilence()) !SendSilence())
break; break;
...@@ -1029,7 +1029,7 @@ Player::Run() ...@@ -1029,7 +1029,7 @@ Player::Run()
to the audio output */ to the audio output */
PlayNextChunk(); PlayNextChunk();
} else if (audio_output_all_check() > 0) { } else if (pc.outputs.Check() > 0) {
/* not enough data from decoder, but the /* not enough data from decoder, but the
output thread is still busy, so it's output thread is still busy, so it's
okay */ okay */
...@@ -1054,7 +1054,7 @@ Player::Run() ...@@ -1054,7 +1054,7 @@ Player::Run()
if (pipe->IsEmpty()) { if (pipe->IsEmpty()) {
/* wait for the hardware to finish /* wait for the hardware to finish
playback */ playback */
audio_output_all_drain(); pc.outputs.Drain();
break; break;
} }
} else if (output_open) { } else if (output_open) {
...@@ -1130,7 +1130,7 @@ player_task(void *arg) ...@@ -1130,7 +1130,7 @@ player_task(void *arg)
case PlayerCommand::STOP: case PlayerCommand::STOP:
pc.Unlock(); pc.Unlock();
audio_output_all_cancel(); pc.outputs.Cancel();
pc.Lock(); pc.Lock();
/* fall through */ /* fall through */
...@@ -1145,7 +1145,7 @@ player_task(void *arg) ...@@ -1145,7 +1145,7 @@ player_task(void *arg)
case PlayerCommand::CLOSE_AUDIO: case PlayerCommand::CLOSE_AUDIO:
pc.Unlock(); pc.Unlock();
audio_output_all_release(); pc.outputs.Release();
pc.Lock(); pc.Lock();
pc.CommandFinished(); pc.CommandFinished();
...@@ -1156,7 +1156,7 @@ player_task(void *arg) ...@@ -1156,7 +1156,7 @@ player_task(void *arg)
case PlayerCommand::UPDATE_AUDIO: case PlayerCommand::UPDATE_AUDIO:
pc.Unlock(); pc.Unlock();
audio_output_all_enable_disable(); pc.outputs.EnableDisable();
pc.Lock(); pc.Lock();
pc.CommandFinished(); pc.CommandFinished();
break; break;
...@@ -1166,7 +1166,7 @@ player_task(void *arg) ...@@ -1166,7 +1166,7 @@ player_task(void *arg)
dc.Quit(); dc.Quit();
audio_output_all_close(); pc.outputs.Close();
player_command_finished(pc); player_command_finished(pc);
return; return;
......
...@@ -75,7 +75,7 @@ StateFile::Write() ...@@ -75,7 +75,7 @@ StateFile::Write()
} }
save_sw_volume_state(fp); 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); playlist_state_save(fp, partition.playlist, partition.pc);
fclose(fp); fclose(fp);
...@@ -99,8 +99,8 @@ StateFile::Read() ...@@ -99,8 +99,8 @@ StateFile::Read()
const char *line; const char *line;
while ((line = file.ReadLine()) != NULL) { while ((line = file.ReadLine()) != NULL) {
success = read_sw_volume_state(line) || success = read_sw_volume_state(line, partition.outputs) ||
audio_output_state_read(line) || audio_output_state_read(line, partition.outputs) ||
playlist_state_restore(line, file, partition.playlist, playlist_state_restore(line, file, partition.playlist,
partition.pc); partition.pc);
if (!success) if (!success)
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
#include "db/PlaylistVector.hxx" #include "db/PlaylistVector.hxx"
#include "client/ClientFile.hxx" #include "client/ClientFile.hxx"
#include "client/Client.hxx" #include "client/Client.hxx"
#include "Partition.hxx"
#include "Idle.hxx" #include "Idle.hxx"
#include <assert.h> #include <assert.h>
...@@ -254,7 +255,7 @@ handle_setvol(Client &client, gcc_unused int argc, char *argv[]) ...@@ -254,7 +255,7 @@ handle_setvol(Client &client, gcc_unused int argc, char *argv[])
return CommandResult::ERROR; return CommandResult::ERROR;
} }
success = volume_level_change(level); success = volume_level_change(client.partition.outputs, level);
if (!success) { if (!success) {
command_error(client, ACK_ERROR_SYSTEM, command_error(client, ACK_ERROR_SYSTEM,
"problems setting volume"); "problems setting volume");
...@@ -276,7 +277,7 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[]) ...@@ -276,7 +277,7 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[])
return CommandResult::ERROR; return CommandResult::ERROR;
} }
const int old_volume = volume_level_get(); const int old_volume = volume_level_get(client.partition.outputs);
if (old_volume < 0) { if (old_volume < 0) {
command_error(client, ACK_ERROR_SYSTEM, "No mixer"); command_error(client, ACK_ERROR_SYSTEM, "No mixer");
return CommandResult::ERROR; return CommandResult::ERROR;
...@@ -288,7 +289,8 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[]) ...@@ -288,7 +289,8 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[])
else if (new_volume > 100) else if (new_volume > 100)
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, command_error(client, ACK_ERROR_SYSTEM,
"problems setting volume"); "problems setting volume");
return CommandResult::ERROR; return CommandResult::ERROR;
......
...@@ -23,18 +23,17 @@ ...@@ -23,18 +23,17 @@
#include "output/OutputCommand.hxx" #include "output/OutputCommand.hxx"
#include "protocol/Result.hxx" #include "protocol/Result.hxx"
#include "protocol/ArgParser.hxx" #include "protocol/ArgParser.hxx"
#include "client/Client.hxx"
#include "Partition.hxx"
CommandResult CommandResult
handle_enableoutput(Client &client, gcc_unused int argc, char *argv[]) handle_enableoutput(Client &client, gcc_unused int argc, char *argv[])
{ {
unsigned device; unsigned device;
bool ret;
if (!check_unsigned(client, &device, argv[1])) if (!check_unsigned(client, &device, argv[1]))
return CommandResult::ERROR; return CommandResult::ERROR;
ret = audio_output_enable_index(device); if (!audio_output_enable_index(client.partition.outputs, device)) {
if (!ret) {
command_error(client, ACK_ERROR_NO_EXIST, command_error(client, ACK_ERROR_NO_EXIST,
"No such audio output"); "No such audio output");
return CommandResult::ERROR; return CommandResult::ERROR;
...@@ -47,13 +46,10 @@ CommandResult ...@@ -47,13 +46,10 @@ CommandResult
handle_disableoutput(Client &client, gcc_unused int argc, char *argv[]) handle_disableoutput(Client &client, gcc_unused int argc, char *argv[])
{ {
unsigned device; unsigned device;
bool ret;
if (!check_unsigned(client, &device, argv[1])) if (!check_unsigned(client, &device, argv[1]))
return CommandResult::ERROR; return CommandResult::ERROR;
ret = audio_output_disable_index(device); if (!audio_output_disable_index(client.partition.outputs, device)) {
if (!ret) {
command_error(client, ACK_ERROR_NO_EXIST, command_error(client, ACK_ERROR_NO_EXIST,
"No such audio output"); "No such audio output");
return CommandResult::ERROR; return CommandResult::ERROR;
...@@ -69,7 +65,7 @@ handle_toggleoutput(Client &client, gcc_unused int argc, char *argv[]) ...@@ -69,7 +65,7 @@ handle_toggleoutput(Client &client, gcc_unused int argc, char *argv[])
if (!check_unsigned(client, &device, argv[1])) if (!check_unsigned(client, &device, argv[1]))
return CommandResult::ERROR; 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, command_error(client, ACK_ERROR_NO_EXIST,
"No such audio output"); "No such audio output");
return CommandResult::ERROR; return CommandResult::ERROR;
...@@ -82,7 +78,7 @@ CommandResult ...@@ -82,7 +78,7 @@ CommandResult
handle_devices(Client &client, handle_devices(Client &client,
gcc_unused int argc, gcc_unused char *argv[]) gcc_unused int argc, gcc_unused char *argv[])
{ {
printAudioDevices(client); printAudioDevices(client, client.partition.outputs);
return CommandResult::OK; return CommandResult::OK;
} }
...@@ -25,7 +25,6 @@ ...@@ -25,7 +25,6 @@
#include "db/update/UpdateGlue.hxx" #include "db/update/UpdateGlue.hxx"
#include "client/Client.hxx" #include "client/Client.hxx"
#include "mixer/Volume.hxx" #include "mixer/Volume.hxx"
#include "output/OutputAll.hxx"
#include "Partition.hxx" #include "Partition.hxx"
#include "protocol/Result.hxx" #include "protocol/Result.hxx"
#include "protocol/ArgParser.hxx" #include "protocol/ArgParser.hxx"
...@@ -140,7 +139,7 @@ handle_status(Client &client, ...@@ -140,7 +139,7 @@ handle_status(Client &client,
COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n"
COMMAND_STATUS_MIXRAMPDB ": %f\n" COMMAND_STATUS_MIXRAMPDB ": %f\n"
COMMAND_STATUS_STATE ": %s\n", COMMAND_STATUS_STATE ": %s\n",
volume_level_get(), volume_level_get(client.partition.outputs),
playlist.GetRepeat(), playlist.GetRepeat(),
playlist.GetRandom(), playlist.GetRandom(),
playlist.GetSingle(), playlist.GetSingle(),
...@@ -277,7 +276,7 @@ handle_random(Client &client, gcc_unused int argc, char *argv[]) ...@@ -277,7 +276,7 @@ handle_random(Client &client, gcc_unused int argc, char *argv[])
return CommandResult::ERROR; return CommandResult::ERROR;
client.partition.SetRandom(status); 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; return CommandResult::OK;
} }
...@@ -379,8 +378,7 @@ handle_replay_gain_mode(Client &client, ...@@ -379,8 +378,7 @@ handle_replay_gain_mode(Client &client,
return CommandResult::ERROR; 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; return CommandResult::OK;
} }
......
...@@ -18,11 +18,10 @@ ...@@ -18,11 +18,10 @@
*/ */
#include "config.h" #include "config.h"
#include "MixerAll.hxx" #include "output/MultipleOutputs.hxx"
#include "MixerControl.hxx" #include "MixerControl.hxx"
#include "MixerInternal.hxx" #include "MixerInternal.hxx"
#include "MixerList.hxx" #include "MixerList.hxx"
#include "output/OutputAll.hxx"
#include "output/OutputInternal.hxx" #include "output/OutputInternal.hxx"
#include "pcm/Volume.hxx" #include "pcm/Volume.hxx"
#include "util/Error.hxx" #include "util/Error.hxx"
...@@ -34,39 +33,33 @@ ...@@ -34,39 +33,33 @@
static constexpr Domain mixer_domain("mixer"); static constexpr Domain mixer_domain("mixer");
static int static int
output_mixer_get_volume(unsigned i) output_mixer_get_volume(const audio_output &ao)
{ {
struct audio_output *output; if (!ao.enabled)
int volume;
assert(i < audio_output_count());
output = audio_output_get(i);
if (!output->enabled)
return -1; return -1;
Mixer *mixer = output->mixer; Mixer *mixer = ao.mixer;
if (mixer == nullptr) if (mixer == nullptr)
return -1; return -1;
Error error; Error error;
volume = mixer_get_volume(mixer, error); int volume = mixer_get_volume(mixer, error);
if (volume < 0 && error.IsDefined()) if (volume < 0 && error.IsDefined())
FormatError(error, FormatError(error,
"Failed to read mixer for '%s'", "Failed to read mixer for '%s'",
output->name); ao.name);
return volume; return volume;
} }
int int
mixer_all_get_volume(void) MultipleOutputs::GetVolume() const
{ {
unsigned count = audio_output_count(), ok = 0; unsigned ok = 0;
int volume, total = 0; int total = 0;
for (unsigned i = 0; i < count; i++) { for (auto ao : outputs) {
volume = output_mixer_get_volume(i); int volume = output_mixer_get_volume(*ao);
if (volume >= 0) { if (volume >= 0) {
total += volume; total += volume;
++ok; ++ok;
...@@ -80,59 +73,47 @@ mixer_all_get_volume(void) ...@@ -80,59 +73,47 @@ mixer_all_get_volume(void)
} }
static bool 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); assert(volume <= 100);
output = audio_output_get(i); if (!ao.enabled)
if (!output->enabled)
return false; return false;
Mixer *mixer = output->mixer; Mixer *mixer = ao.mixer;
if (mixer == nullptr) if (mixer == nullptr)
return false; return false;
Error error; Error error;
success = mixer_set_volume(mixer, volume, error); bool success = mixer_set_volume(mixer, volume, error);
if (!success && error.IsDefined()) if (!success && error.IsDefined())
FormatError(error, FormatError(error,
"Failed to set mixer for '%s'", "Failed to set mixer for '%s'",
output->name); ao.name);
return success; return success;
} }
bool bool
mixer_all_set_volume(unsigned volume) MultipleOutputs::SetVolume(unsigned volume)
{ {
bool success = false;
unsigned count = audio_output_count();
assert(volume <= 100); assert(volume <= 100);
for (unsigned i = 0; i < count; i++) bool success = false;
success = output_mixer_set_volume(i, volume) for (auto ao : outputs)
success = output_mixer_set_volume(*ao, volume)
|| success; || success;
return success; return success;
} }
static int static int
output_mixer_get_software_volume(unsigned i) output_mixer_get_software_volume(const audio_output &ao)
{ {
struct audio_output *output; if (!ao.enabled)
assert(i < audio_output_count());
output = audio_output_get(i);
if (!output->enabled)
return -1; return -1;
Mixer *mixer = output->mixer; Mixer *mixer = ao.mixer;
if (mixer == nullptr || !mixer->IsPlugin(software_mixer_plugin)) if (mixer == nullptr || !mixer->IsPlugin(software_mixer_plugin))
return -1; return -1;
...@@ -140,13 +121,13 @@ output_mixer_get_software_volume(unsigned i) ...@@ -140,13 +121,13 @@ output_mixer_get_software_volume(unsigned i)
} }
int int
mixer_all_get_software_volume(void) MultipleOutputs::GetSoftwareVolume() const
{ {
unsigned count = audio_output_count(), ok = 0; unsigned ok = 0;
int volume, total = 0; int total = 0;
for (unsigned i = 0; i < count; i++) { for (auto ao : outputs) {
volume = output_mixer_get_software_volume(i); int volume = output_mixer_get_software_volume(*ao);
if (volume >= 0) { if (volume >= 0) {
total += volume; total += volume;
++ok; ++ok;
...@@ -160,16 +141,15 @@ mixer_all_get_software_volume(void) ...@@ -160,16 +141,15 @@ mixer_all_get_software_volume(void)
} }
void void
mixer_all_set_software_volume(unsigned volume) MultipleOutputs::SetSoftwareVolume(unsigned volume)
{ {
unsigned count = audio_output_count();
assert(volume <= PCM_VOLUME_1); assert(volume <= PCM_VOLUME_1);
for (unsigned i = 0; i < count; i++) { for (auto ao : outputs) {
struct audio_output *output = audio_output_get(i); const auto mixer = ao->mixer;
if (output->mixer != nullptr &&
output->mixer->plugin == &software_mixer_plugin) if (mixer != nullptr &&
mixer_set_volume(output->mixer, volume, IgnoreError()); mixer->plugin == &software_mixer_plugin)
mixer_set_volume(mixer, volume, IgnoreError());
} }
} }
/*
* 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
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
#include "config.h" #include "config.h"
#include "Volume.hxx" #include "Volume.hxx"
#include "MixerAll.hxx" #include "output/MultipleOutputs.hxx"
#include "Idle.hxx" #include "Idle.hxx"
#include "GlobalEvents.hxx" #include "GlobalEvents.hxx"
#include "util/StringUtil.hxx" #include "util/StringUtil.hxx"
...@@ -59,36 +59,40 @@ void volume_init(void) ...@@ -59,36 +59,40 @@ void volume_init(void)
GlobalEvents::Register(GlobalEvents::MIXER, mixer_event_callback); GlobalEvents::Register(GlobalEvents::MIXER, mixer_event_callback);
} }
int volume_level_get(void) int
volume_level_get(const MultipleOutputs &outputs)
{ {
if (last_hardware_volume >= 0 && if (last_hardware_volume >= 0 &&
!hardware_volume_clock.CheckUpdate(1000)) !hardware_volume_clock.CheckUpdate(1000))
/* throttle access to hardware mixers */ /* throttle access to hardware mixers */
return last_hardware_volume; return last_hardware_volume;
last_hardware_volume = mixer_all_get_volume(); last_hardware_volume = outputs.GetVolume();
return last_hardware_volume; return last_hardware_volume;
} }
static bool software_volume_change(unsigned volume) static bool
software_volume_change(MultipleOutputs &outputs, unsigned volume)
{ {
assert(volume <= 100); assert(volume <= 100);
volume_software_set = volume; volume_software_set = volume;
mixer_all_set_software_volume(volume); outputs.SetSoftwareVolume(volume);
return true; return true;
} }
static bool hardware_volume_change(unsigned volume) static bool
hardware_volume_change(MultipleOutputs &outputs, unsigned volume)
{ {
/* reset the cache */ /* reset the cache */
last_hardware_volume = -1; 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); assert(volume <= 100);
...@@ -96,11 +100,11 @@ bool volume_level_change(unsigned volume) ...@@ -96,11 +100,11 @@ bool volume_level_change(unsigned volume)
idle_add(IDLE_MIXER); idle_add(IDLE_MIXER);
return hardware_volume_change(volume); return hardware_volume_change(outputs, volume);
} }
bool bool
read_sw_volume_state(const char *line) read_sw_volume_state(const char *line, MultipleOutputs &outputs)
{ {
char *end = nullptr; char *end = nullptr;
long int sv; long int sv;
...@@ -111,7 +115,7 @@ read_sw_volume_state(const char *line) ...@@ -111,7 +115,7 @@ read_sw_volume_state(const char *line)
line += sizeof(SW_VOLUME_STATE) - 1; line += sizeof(SW_VOLUME_STATE) - 1;
sv = strtol(line, &end, 10); sv = strtol(line, &end, 10);
if (*end == 0 && sv >= 0 && sv <= 100) if (*end == 0 && sv >= 0 && sv <= 100)
software_volume_change(sv); software_volume_change(outputs, sv);
else else
FormatWarning(volume_domain, FormatWarning(volume_domain,
"Can't parse software volume: %s", line); "Can't parse software volume: %s", line);
......
...@@ -24,15 +24,19 @@ ...@@ -24,15 +24,19 @@
#include <stdio.h> #include <stdio.h>
class MultipleOutputs;
void volume_init(void); void volume_init(void);
gcc_pure 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 bool
read_sw_volume_state(const char *line); read_sw_volume_state(const char *line, MultipleOutputs &outputs);
void save_sw_volume_state(FILE *fp); void save_sw_volume_state(FILE *fp);
......
/*
* 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;
}
...@@ -26,58 +26,92 @@ ...@@ -26,58 +26,92 @@
#ifndef OUTPUT_ALL_H #ifndef OUTPUT_ALL_H
#define OUTPUT_ALL_H #define OUTPUT_ALL_H
#include "AudioFormat.hxx"
#include "ReplayGainInfo.hxx" #include "ReplayGainInfo.hxx"
#include "Compiler.h" #include "Compiler.h"
#include <vector>
#include <assert.h>
struct AudioFormat; struct AudioFormat;
class MusicBuffer; class MusicBuffer;
class MusicPipe;
struct music_chunk; struct music_chunk;
struct PlayerControl; struct PlayerControl;
struct audio_output;
class Error; class Error;
/** class MultipleOutputs {
* Global initialization: load audio outputs from the configuration std::vector<audio_output *> outputs;
* file and initialize them.
AudioFormat input_audio_format;
/**
* The #MusicBuffer object where consumed chunks are returned.
*/ */
void MusicBuffer *buffer;
audio_output_all_init(PlayerControl &pc);
/** /**
* Global finalization: free memory occupied by audio outputs. All * The #MusicPipe object which feeds all audio outputs. It is
* filled by audio_output_all_play().
*/ */
void MusicPipe *pipe;
audio_output_all_finish(void);
/**
* 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 * Returns the total number of audio output devices, including
* who are disabled right now. * those which are disabled right now.
*/ */
gcc_const gcc_pure
unsigned int audio_output_count(void); unsigned Size() const {
return outputs.size();
}
/** /**
* Returns the "i"th audio output device. * Returns the "i"th audio output device.
*/ */
gcc_const const audio_output &Get(unsigned i) const {
struct audio_output * assert(i < Size());
audio_output_get(unsigned i);
/** return *outputs[i];
* Returns the audio output device with the specified name. Returns }
* NULL if the name does not exist.
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 gcc_pure
struct audio_output * audio_output *FindByName(const char *name) const;
audio_output_find(const char *name);
/** /**
* Checks the "enabled" flag of all audio outputs, and if one has * Checks the "enabled" flag of all audio outputs, and if one has
* changed, commit the change. * changed, commit the change.
*/ */
void void EnableDisable();
audio_output_all_enable_disable(void);
/** /**
* Opens all audio outputs which are not disabled. * Opens all audio outputs which are not disabled.
* *
* @param audio_format the preferred audio format * @param audio_format the preferred audio format
...@@ -85,28 +119,23 @@ audio_output_all_enable_disable(void); ...@@ -85,28 +119,23 @@ audio_output_all_enable_disable(void);
* should be returned * should be returned
* @return true on success, false on failure * @return true on success, false on failure
*/ */
bool bool Open(const AudioFormat audio_format, MusicBuffer &_buffer,
audio_output_all_open(AudioFormat audio_format,
MusicBuffer &buffer,
Error &error); Error &error);
/** /**
* Closes all audio outputs. * Closes all audio outputs.
*/ */
void void Close();
audio_output_all_close(void);
/** /**
* Closes all audio outputs. Outputs with the "always_on" flag are * Closes all audio outputs. Outputs with the "always_on"
* put into pause mode. * flag are put into pause mode.
*/ */
void void Release();
audio_output_all_release(void);
void void SetReplayGainMode(ReplayGainMode mode);
audio_output_all_set_replay_gain_mode(ReplayGainMode mode);
/** /**
* Enqueue a #music_chunk object for playing, i.e. pushes it to a * Enqueue a #music_chunk object for playing, i.e. pushes it to a
* #MusicPipe. * #MusicPipe.
* *
...@@ -114,19 +143,17 @@ audio_output_all_set_replay_gain_mode(ReplayGainMode mode); ...@@ -114,19 +143,17 @@ audio_output_all_set_replay_gain_mode(ReplayGainMode mode);
* @return true on success, false if no audio output was able to play * @return true on success, false if no audio output was able to play
* (all closed then) * (all closed then)
*/ */
bool bool Play(music_chunk *chunk, Error &error);
audio_output_all_play(music_chunk *chunk, Error &error);
/** /**
* Checks if the output devices have drained their music pipe, and * Checks if the output devices have drained their music pipe, and
* returns the consumed music chunks to the #music_buffer. * returns the consumed music chunks to the #music_buffer.
* *
* @return the number of chunks to play left in the #MusicPipe * @return the number of chunks to play left in the #MusicPipe
*/ */
unsigned unsigned Check();
audio_output_all_check(void);
/** /**
* Checks if the size of the #MusicPipe is below the #threshold. If * Checks if the size of the #MusicPipe is below the #threshold. If
* not, it attempts to synchronize with all output threads, and waits * not, it attempts to synchronize with all output threads, and waits
* until another #music_chunk is finished. * until another #music_chunk is finished.
...@@ -134,41 +161,111 @@ audio_output_all_check(void); ...@@ -134,41 +161,111 @@ audio_output_all_check(void);
* @param threshold the maximum number of chunks in the pipe * @param threshold the maximum number of chunks in the pipe
* @return true if there are less than #threshold chunks in the pipe * @return true if there are less than #threshold chunks in the pipe
*/ */
bool bool Wait(PlayerControl &pc, unsigned threshold);
audio_output_all_wait(PlayerControl &pc, unsigned threshold);
/** /**
* Puts all audio outputs into pause mode. Most implementations will * Puts all audio outputs into pause mode. Most implementations will
* simply close it then. * simply close it then.
*/ */
void void Pause();
audio_output_all_pause(void);
/** /**
* Drain all audio outputs. * Drain all audio outputs.
*/ */
void void Drain();
audio_output_all_drain(void);
/** /**
* Try to cancel data which may still be in the device's buffers. * Try to cancel data which may still be in the device's buffers.
*/ */
void void Cancel();
audio_output_all_cancel(void);
/** /**
* Indicate that a new song will begin now. * Indicate that a new song will begin now.
*/ */
void void SongBorder();
audio_output_all_song_border(void);
/** /**
* Returns the "elapsed_time" stamp of the most recently finished * Returns the "elapsed_time" stamp of the most recently finished
* chunk. A negative value is returned when no chunk has been * chunk. A negative value is returned when no chunk has been
* finished yet. * finished yet.
*/ */
gcc_pure gcc_pure
float float GetElapsedTime() const {
audio_output_all_get_elapsed_time(void); 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 #endif
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
#include "config.h" #include "config.h"
#include "OutputCommand.hxx" #include "OutputCommand.hxx"
#include "OutputAll.hxx" #include "MultipleOutputs.hxx"
#include "OutputInternal.hxx" #include "OutputInternal.hxx"
#include "PlayerControl.hxx" #include "PlayerControl.hxx"
#include "mixer/MixerControl.hxx" #include "mixer/MixerControl.hxx"
...@@ -35,21 +35,19 @@ ...@@ -35,21 +35,19 @@
extern unsigned audio_output_state_version; extern unsigned audio_output_state_version;
bool bool
audio_output_enable_index(unsigned idx) audio_output_enable_index(MultipleOutputs &outputs, unsigned idx)
{ {
struct audio_output *ao; if (idx >= outputs.Size())
if (idx >= audio_output_count())
return false; return false;
ao = audio_output_get(idx); audio_output &ao = outputs.Get(idx);
if (ao->enabled) if (ao.enabled)
return true; return true;
ao->enabled = true; ao.enabled = true;
idle_add(IDLE_OUTPUT); idle_add(IDLE_OUTPUT);
ao->player_control->UpdateAudio(); ao.player_control->UpdateAudio();
++audio_output_state_version; ++audio_output_state_version;
...@@ -57,27 +55,25 @@ audio_output_enable_index(unsigned idx) ...@@ -57,27 +55,25 @@ audio_output_enable_index(unsigned idx)
} }
bool bool
audio_output_disable_index(unsigned idx) audio_output_disable_index(MultipleOutputs &outputs, unsigned idx)
{ {
struct audio_output *ao; if (idx >= outputs.Size())
if (idx >= audio_output_count())
return false; return false;
ao = audio_output_get(idx); audio_output &ao = outputs.Get(idx);
if (!ao->enabled) if (!ao.enabled)
return true; return true;
ao->enabled = false; ao.enabled = false;
idle_add(IDLE_OUTPUT); idle_add(IDLE_OUTPUT);
Mixer *mixer = ao->mixer; Mixer *mixer = ao.mixer;
if (mixer != nullptr) { if (mixer != nullptr) {
mixer_close(mixer); mixer_close(mixer);
idle_add(IDLE_MIXER); idle_add(IDLE_MIXER);
} }
ao->player_control->UpdateAudio(); ao.player_control->UpdateAudio();
++audio_output_state_version; ++audio_output_state_version;
...@@ -85,26 +81,24 @@ audio_output_disable_index(unsigned idx) ...@@ -85,26 +81,24 @@ audio_output_disable_index(unsigned idx)
} }
bool bool
audio_output_toggle_index(unsigned idx) audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
{ {
struct audio_output *ao; if (idx >= outputs.Size())
if (idx >= audio_output_count())
return false; return false;
ao = audio_output_get(idx); audio_output &ao = outputs.Get(idx);
const bool enabled = ao->enabled = !ao->enabled; const bool enabled = ao.enabled = !ao.enabled;
idle_add(IDLE_OUTPUT); idle_add(IDLE_OUTPUT);
if (!enabled) { if (!enabled) {
Mixer *mixer = ao->mixer; Mixer *mixer = ao.mixer;
if (mixer != nullptr) { if (mixer != nullptr) {
mixer_close(mixer); mixer_close(mixer);
idle_add(IDLE_MIXER); idle_add(IDLE_MIXER);
} }
} }
ao->player_control->UpdateAudio(); ao.player_control->UpdateAudio();
++audio_output_state_version; ++audio_output_state_version;
......
...@@ -27,25 +27,27 @@ ...@@ -27,25 +27,27 @@
#ifndef MPD_OUTPUT_COMMAND_HXX #ifndef MPD_OUTPUT_COMMAND_HXX
#define MPD_OUTPUT_COMMAND_HXX #define MPD_OUTPUT_COMMAND_HXX
class MultipleOutputs;
/** /**
* Enables an audio output. Returns false if the specified output * Enables an audio output. Returns false if the specified output
* does not exist. * does not exist.
*/ */
bool 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 * Disables an audio output. Returns false if the specified output
* does not exist. * does not exist.
*/ */
bool 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 * Toggles an audio output. Returns false if the specified output
* does not exist. * does not exist.
*/ */
bool bool
audio_output_toggle_index(unsigned idx); audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx);
#endif #endif
...@@ -24,22 +24,20 @@ ...@@ -24,22 +24,20 @@
#include "config.h" #include "config.h"
#include "OutputPrint.hxx" #include "OutputPrint.hxx"
#include "OutputAll.hxx" #include "MultipleOutputs.hxx"
#include "OutputInternal.hxx" #include "OutputInternal.hxx"
#include "client/Client.hxx" #include "client/Client.hxx"
void void
printAudioDevices(Client &client) printAudioDevices(Client &client, const MultipleOutputs &outputs)
{ {
const unsigned n = audio_output_count(); for (unsigned i = 0, n = outputs.Size(); i != n; ++i) {
const audio_output &ao = outputs.Get(i);
for (unsigned i = 0; i < n; ++i) {
const struct audio_output *ao = audio_output_get(i);
client_printf(client, client_printf(client,
"outputid: %i\n" "outputid: %i\n"
"outputname: %s\n" "outputname: %s\n"
"outputenabled: %i\n", "outputenabled: %i\n",
i, ao->name, ao->enabled); i, ao.name, ao.enabled);
} }
} }
/* /*
* Copyright (C) 2003-2014 The Music Player Daemon Project * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
...@@ -27,8 +26,9 @@ ...@@ -27,8 +26,9 @@
#define MPD_OUTPUT_PRINT_HXX #define MPD_OUTPUT_PRINT_HXX
class Client; class Client;
class MultipleOutputs;
void void
printAudioDevices(Client &client); printAudioDevices(Client &client, const MultipleOutputs &outputs);
#endif #endif
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
#include "config.h" #include "config.h"
#include "OutputState.hxx" #include "OutputState.hxx"
#include "OutputAll.hxx" #include "MultipleOutputs.hxx"
#include "OutputInternal.hxx" #include "OutputInternal.hxx"
#include "OutputError.hxx" #include "OutputError.hxx"
#include "Log.hxx" #include "Log.hxx"
...@@ -38,27 +38,22 @@ ...@@ -38,27 +38,22 @@
unsigned audio_output_state_version; unsigned audio_output_state_version;
void void
audio_output_state_save(FILE *fp) audio_output_state_save(FILE *fp, const MultipleOutputs &outputs)
{ {
unsigned n = audio_output_count(); for (unsigned i = 0, n = outputs.Size(); i != n; ++i) {
const audio_output &ao = outputs.Get(i);
assert(n > 0);
for (unsigned i = 0; i < n; ++i) {
const struct audio_output *ao = audio_output_get(i);
fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n", fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n",
ao->enabled, ao->name); ao.enabled, ao.name);
} }
} }
bool bool
audio_output_state_read(const char *line) audio_output_state_read(const char *line, MultipleOutputs &outputs)
{ {
long value; long value;
char *endptr; char *endptr;
const char *name; const char *name;
struct audio_output *ao;
if (!StringStartsWith(line, AUDIO_DEVICE_STATE)) if (!StringStartsWith(line, AUDIO_DEVICE_STATE))
return false; return false;
...@@ -74,7 +69,7 @@ audio_output_state_read(const char *line) ...@@ -74,7 +69,7 @@ audio_output_state_read(const char *line)
return true; return true;
name = endptr + 1; name = endptr + 1;
ao = audio_output_find(name); audio_output *ao = outputs.FindByName(name);
if (ao == NULL) { if (ao == NULL) {
FormatDebug(output_domain, FormatDebug(output_domain,
"Ignoring device state for '%s'", name); "Ignoring device state for '%s'", name);
......
...@@ -27,11 +27,13 @@ ...@@ -27,11 +27,13 @@
#include <stdio.h> #include <stdio.h>
class MultipleOutputs;
bool bool
audio_output_state_read(const char *line); audio_output_state_read(const char *line, MultipleOutputs &outputs);
void 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 * Generates a version number for the current state of the audio
......
...@@ -74,8 +74,10 @@ find_named_config_block(ConfigOption option, const char *name) ...@@ -74,8 +74,10 @@ find_named_config_block(ConfigOption option, const char *name)
return NULL; return NULL;
} }
PlayerControl::PlayerControl(gcc_unused unsigned _buffer_chunks, PlayerControl::PlayerControl(gcc_unused MultipleOutputs &_outputs,
gcc_unused unsigned _buffered_before_play) {} gcc_unused unsigned _buffer_chunks,
gcc_unused unsigned _buffered_before_play)
:outputs(_outputs) {}
PlayerControl::~PlayerControl() {} PlayerControl::~PlayerControl() {}
static struct audio_output * static struct audio_output *
...@@ -89,7 +91,8 @@ load_audio_output(const char *name) ...@@ -89,7 +91,8 @@ load_audio_output(const char *name)
return nullptr; return nullptr;
} }
static struct PlayerControl dummy_player_control(32, 4); static struct PlayerControl dummy_player_control(*(MultipleOutputs *)nullptr,
32, 4);
Error error; Error error;
struct audio_output *ao = struct audio_output *ao =
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment