OutputAll: convert to class, move instance to class Partition
Another big chunk of code for multi-player support.
This commit is contained in:
		| @@ -961,7 +961,7 @@ OUTPUT_API_SRC = \ | ||||
| 	src/output/OutputAPI.hxx \ | ||||
| 	src/output/OutputInternal.hxx \ | ||||
| 	src/output/OutputList.cxx src/output/OutputList.hxx \ | ||||
| 	src/output/OutputAll.cxx src/output/OutputAll.hxx \ | ||||
| 	src/output/MultipleOutputs.cxx src/output/MultipleOutputs.hxx \ | ||||
| 	src/output/OutputThread.cxx src/output/OutputThread.hxx \ | ||||
| 	src/output/OutputError.cxx src/output/OutputError.hxx \ | ||||
| 	src/output/OutputControl.cxx src/output/OutputControl.hxx \ | ||||
| @@ -985,7 +985,7 @@ MIXER_API_SRC = \ | ||||
| 	src/mixer/MixerList.hxx \ | ||||
| 	src/mixer/MixerControl.cxx src/mixer/MixerControl.hxx \ | ||||
| 	src/mixer/MixerType.cxx src/mixer/MixerType.hxx \ | ||||
| 	src/mixer/MixerAll.cxx src/mixer/MixerAll.hxx \ | ||||
| 	src/mixer/MixerAll.cxx \ | ||||
| 	src/mixer/MixerInternal.hxx | ||||
|  | ||||
| libmixer_plugins_a_SOURCES = \ | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -20,6 +20,7 @@ | ||||
| #include "config.h" | ||||
| #include "Partition.hxx" | ||||
| #include "DetachedSong.hxx" | ||||
| #include "output/MultipleOutputs.hxx" | ||||
|  | ||||
| void | ||||
| Partition::DatabaseModified() | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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), | ||||
|   | ||||
| @@ -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(); | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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()); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| @@ -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); | ||||
|   | ||||
| @@ -24,15 +24,19 @@ | ||||
|  | ||||
| #include <stdio.h> | ||||
|  | ||||
| class MultipleOutputs; | ||||
|  | ||||
| void volume_init(void); | ||||
|  | ||||
| gcc_pure | ||||
| int volume_level_get(void); | ||||
|  | ||||
| bool volume_level_change(unsigned volume); | ||||
| int | ||||
| volume_level_get(const MultipleOutputs &outputs); | ||||
|  | ||||
| bool | ||||
| read_sw_volume_state(const char *line); | ||||
| volume_level_change(MultipleOutputs &outputs, unsigned volume); | ||||
|  | ||||
| bool | ||||
| read_sw_volume_state(const char *line, MultipleOutputs &outputs); | ||||
|  | ||||
| void save_sw_volume_state(FILE *fp); | ||||
|  | ||||
|   | ||||
							
								
								
									
										477
									
								
								src/output/MultipleOutputs.cxx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										477
									
								
								src/output/MultipleOutputs.cxx
									
									
									
									
									
										Normal file
									
								
							| @@ -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 ¶m) | ||||
| { | ||||
| 	Error error; | ||||
| 	audio_output *output = audio_output_new(param, pc, error); | ||||
| 	if (output == nullptr) { | ||||
| 		if (param.line > 0) | ||||
| 			FormatFatalError("line %i: %s", | ||||
| 					 param.line, | ||||
| 					 error.GetMessage()); | ||||
| 		else | ||||
| 			FatalError(error); | ||||
| 	} | ||||
|  | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| void | ||||
| MultipleOutputs::Configure(PlayerControl &pc) | ||||
| { | ||||
| 	const config_param *param = nullptr; | ||||
| 	while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, | ||||
| 					      param)) != nullptr) { | ||||
| 		auto output = LoadOutput(pc, *param); | ||||
| 		if (FindByName(output->name) != nullptr) | ||||
| 			FormatFatalError("output devices with identical " | ||||
| 					 "names: %s", output->name); | ||||
|  | ||||
| 		outputs.push_back(output); | ||||
| 	} | ||||
|  | ||||
| 	if (outputs.empty()) { | ||||
| 		/* auto-detect device */ | ||||
| 		const config_param empty; | ||||
| 		auto output = LoadOutput(pc, empty); | ||||
| 		outputs.push_back(output); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| audio_output * | ||||
| MultipleOutputs::FindByName(const char *name) const | ||||
| { | ||||
| 	for (auto i : outputs) | ||||
| 		if (strcmp(i->name, name) == 0) | ||||
| 			return i; | ||||
|  | ||||
| 	return nullptr; | ||||
| } | ||||
|  | ||||
| void | ||||
| MultipleOutputs::EnableDisable() | ||||
| { | ||||
| 	for (auto ao : outputs) { | ||||
| 		bool enabled; | ||||
|  | ||||
| 		ao->mutex.lock(); | ||||
| 		enabled = ao->really_enabled; | ||||
| 		ao->mutex.unlock(); | ||||
|  | ||||
| 		if (ao->enabled != enabled) { | ||||
| 			if (ao->enabled) | ||||
| 				audio_output_enable(ao); | ||||
| 			else | ||||
| 				audio_output_disable(ao); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool | ||||
| MultipleOutputs::AllFinished() const | ||||
| { | ||||
| 	for (auto ao : outputs) { | ||||
| 		const ScopeLock protect(ao->mutex); | ||||
| 		if (audio_output_is_open(ao) && | ||||
| 		    !audio_output_command_is_finished(ao)) | ||||
| 			return false; | ||||
| 	} | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| void | ||||
| MultipleOutputs::WaitAll() | ||||
| { | ||||
| 	while (!AllFinished()) | ||||
| 		audio_output_client_notify.Wait(); | ||||
| } | ||||
|  | ||||
| void | ||||
| MultipleOutputs::AllowPlay() | ||||
| { | ||||
| 	for (auto ao : outputs) | ||||
| 		audio_output_allow_play(ao); | ||||
| } | ||||
|  | ||||
| static void | ||||
| audio_output_reset_reopen(struct audio_output *ao) | ||||
| { | ||||
| 	const ScopeLock protect(ao->mutex); | ||||
|  | ||||
| 	ao->fail_timer.Reset(); | ||||
| } | ||||
|  | ||||
| void | ||||
| MultipleOutputs::ResetReopen() | ||||
| { | ||||
| 	for (auto ao : outputs) | ||||
| 		audio_output_reset_reopen(ao); | ||||
| } | ||||
|  | ||||
| bool | ||||
| MultipleOutputs::Update() | ||||
| { | ||||
| 	bool ret = false; | ||||
|  | ||||
| 	if (!input_audio_format.IsDefined()) | ||||
| 		return false; | ||||
|  | ||||
| 	for (auto ao : outputs) | ||||
| 		ret = audio_output_update(ao, input_audio_format, *pipe) | ||||
| 			|| ret; | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| void | ||||
| MultipleOutputs::SetReplayGainMode(ReplayGainMode mode) | ||||
| { | ||||
| 	for (auto ao : outputs) | ||||
| 		audio_output_set_replay_gain_mode(ao, mode); | ||||
| } | ||||
|  | ||||
| bool | ||||
| MultipleOutputs::Play(music_chunk *chunk, Error &error) | ||||
| { | ||||
| 	assert(buffer != nullptr); | ||||
| 	assert(pipe != nullptr); | ||||
| 	assert(chunk != nullptr); | ||||
| 	assert(chunk->CheckFormat(input_audio_format)); | ||||
|  | ||||
| 	if (!Update()) { | ||||
| 		/* TODO: obtain real error */ | ||||
| 		error.Set(output_domain, "Failed to open audio output"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	pipe->Push(chunk); | ||||
|  | ||||
| 	for (auto ao : outputs) | ||||
| 		audio_output_play(ao); | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool | ||||
| MultipleOutputs::Open(const AudioFormat audio_format, | ||||
| 		      MusicBuffer &_buffer, | ||||
| 		      Error &error) | ||||
| { | ||||
| 	bool ret = false, enabled = false; | ||||
|  | ||||
| 	assert(buffer == nullptr || buffer == &_buffer); | ||||
| 	assert((pipe == nullptr) == (buffer == nullptr)); | ||||
|  | ||||
| 	buffer = &_buffer; | ||||
|  | ||||
| 	/* the audio format must be the same as existing chunks in the | ||||
| 	   pipe */ | ||||
| 	assert(pipe == nullptr || pipe->CheckFormat(audio_format)); | ||||
|  | ||||
| 	if (pipe == nullptr) | ||||
| 		pipe = new MusicPipe(); | ||||
| 	else | ||||
| 		/* if the pipe hasn't been cleared, the the audio | ||||
| 		   format must not have changed */ | ||||
| 		assert(pipe->IsEmpty() || audio_format == input_audio_format); | ||||
|  | ||||
| 	input_audio_format = audio_format; | ||||
|  | ||||
| 	ResetReopen(); | ||||
| 	EnableDisable(); | ||||
| 	Update(); | ||||
|  | ||||
| 	for (auto ao : outputs) { | ||||
| 		if (ao->enabled) | ||||
| 			enabled = true; | ||||
|  | ||||
| 		if (ao->open) | ||||
| 			ret = true; | ||||
| 	} | ||||
|  | ||||
| 	if (!enabled) | ||||
| 		error.Set(output_domain, "All audio outputs are disabled"); | ||||
| 	else if (!ret) | ||||
| 		/* TODO: obtain real error */ | ||||
| 		error.Set(output_domain, "Failed to open audio output"); | ||||
|  | ||||
| 	if (!ret) | ||||
| 		/* close all devices if there was an error */ | ||||
| 		Close(); | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Has the specified audio output already consumed this chunk? | ||||
|  */ | ||||
| gcc_pure | ||||
| static bool | ||||
| chunk_is_consumed_in(const struct audio_output *ao, | ||||
| 		     gcc_unused const MusicPipe *pipe, | ||||
| 		     const struct music_chunk *chunk) | ||||
| { | ||||
| 	if (!ao->open) | ||||
| 		return true; | ||||
|  | ||||
| 	if (ao->chunk == nullptr) | ||||
| 		return false; | ||||
|  | ||||
| 	assert(chunk == ao->chunk || pipe->Contains(ao->chunk)); | ||||
|  | ||||
| 	if (chunk != ao->chunk) { | ||||
| 		assert(chunk->next != nullptr); | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	return ao->chunk_finished && chunk->next == nullptr; | ||||
| } | ||||
|  | ||||
| bool | ||||
| MultipleOutputs::IsChunkConsumed(const music_chunk *chunk) const | ||||
| { | ||||
| 	for (auto ao : outputs) { | ||||
| 		const ScopeLock protect(ao->mutex); | ||||
| 		if (!chunk_is_consumed_in(ao, pipe, chunk)) | ||||
| 			return false; | ||||
| 	} | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| inline void | ||||
| MultipleOutputs::ClearTailChunk(gcc_unused const struct music_chunk *chunk, | ||||
| 				bool *locked) | ||||
| { | ||||
| 	assert(chunk->next == nullptr); | ||||
| 	assert(pipe->Contains(chunk)); | ||||
|  | ||||
| 	for (unsigned i = 0, n = outputs.size(); i != n; ++i) { | ||||
| 		audio_output *ao = outputs[i]; | ||||
|  | ||||
| 		/* this mutex will be unlocked by the caller when it's | ||||
| 		   ready */ | ||||
| 		ao->mutex.lock(); | ||||
| 		locked[i] = ao->open; | ||||
|  | ||||
| 		if (!locked[i]) { | ||||
| 			ao->mutex.unlock(); | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		assert(ao->chunk == chunk); | ||||
| 		assert(ao->chunk_finished); | ||||
| 		ao->chunk = nullptr; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| unsigned | ||||
| MultipleOutputs::Check() | ||||
| { | ||||
| 	const struct music_chunk *chunk; | ||||
| 	bool is_tail; | ||||
| 	struct music_chunk *shifted; | ||||
| 	bool locked[outputs.size()]; | ||||
|  | ||||
| 	assert(buffer != nullptr); | ||||
| 	assert(pipe != nullptr); | ||||
|  | ||||
| 	while ((chunk = pipe->Peek()) != nullptr) { | ||||
| 		assert(!pipe->IsEmpty()); | ||||
|  | ||||
| 		if (!IsChunkConsumed(chunk)) | ||||
| 			/* at least one output is not finished playing | ||||
| 			   this chunk */ | ||||
| 			return pipe->GetSize(); | ||||
|  | ||||
| 		if (chunk->length > 0 && chunk->times >= 0.0) | ||||
| 			/* only update elapsed_time if the chunk | ||||
| 			   provides a defined value */ | ||||
| 			elapsed_time = chunk->times; | ||||
|  | ||||
| 		is_tail = chunk->next == nullptr; | ||||
| 		if (is_tail) | ||||
| 			/* this is the tail of the pipe - clear the | ||||
| 			   chunk reference in all outputs */ | ||||
| 			ClearTailChunk(chunk, locked); | ||||
|  | ||||
| 		/* remove the chunk from the pipe */ | ||||
| 		shifted = pipe->Shift(); | ||||
| 		assert(shifted == chunk); | ||||
|  | ||||
| 		if (is_tail) | ||||
| 			/* unlock all audio outputs which were locked | ||||
| 			   by clear_tail_chunk() */ | ||||
| 			for (unsigned i = 0, n = outputs.size(); i != n; ++i) | ||||
| 				if (locked[i]) | ||||
| 					outputs[i]->mutex.unlock(); | ||||
|  | ||||
| 		/* return the chunk to the buffer */ | ||||
| 		buffer->Return(shifted); | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| bool | ||||
| MultipleOutputs::Wait(PlayerControl &pc, unsigned threshold) | ||||
| { | ||||
| 	pc.Lock(); | ||||
|  | ||||
| 	if (Check() < threshold) { | ||||
| 		pc.Unlock(); | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	pc.Wait(); | ||||
| 	pc.Unlock(); | ||||
|  | ||||
| 	return Check() < threshold; | ||||
| } | ||||
|  | ||||
| void | ||||
| MultipleOutputs::Pause() | ||||
| { | ||||
| 	Update(); | ||||
|  | ||||
| 	for (auto ao : outputs) | ||||
| 		audio_output_pause(ao); | ||||
|  | ||||
| 	WaitAll(); | ||||
| } | ||||
|  | ||||
| void | ||||
| MultipleOutputs::Drain() | ||||
| { | ||||
| 	for (auto ao : outputs) | ||||
| 		audio_output_drain_async(ao); | ||||
|  | ||||
| 	WaitAll(); | ||||
| } | ||||
|  | ||||
| void | ||||
| MultipleOutputs::Cancel() | ||||
| { | ||||
| 	/* send the cancel() command to all audio outputs */ | ||||
|  | ||||
| 	for (auto ao : outputs) | ||||
| 		audio_output_cancel(ao); | ||||
|  | ||||
| 	WaitAll(); | ||||
|  | ||||
| 	/* clear the music pipe and return all chunks to the buffer */ | ||||
|  | ||||
| 	if (pipe != nullptr) | ||||
| 		pipe->Clear(*buffer); | ||||
|  | ||||
| 	/* the audio outputs are now waiting for a signal, to | ||||
| 	   synchronize the cleared music pipe */ | ||||
|  | ||||
| 	AllowPlay(); | ||||
|  | ||||
| 	/* invalidate elapsed_time */ | ||||
|  | ||||
| 	elapsed_time = -1.0; | ||||
| } | ||||
|  | ||||
| void | ||||
| MultipleOutputs::Close() | ||||
| { | ||||
| 	for (auto ao : outputs) | ||||
| 		audio_output_close(ao); | ||||
|  | ||||
| 	if (pipe != nullptr) { | ||||
| 		assert(buffer != nullptr); | ||||
|  | ||||
| 		pipe->Clear(*buffer); | ||||
| 		delete pipe; | ||||
| 		pipe = nullptr; | ||||
| 	} | ||||
|  | ||||
| 	buffer = nullptr; | ||||
|  | ||||
| 	input_audio_format.Clear(); | ||||
|  | ||||
| 	elapsed_time = -1.0; | ||||
| } | ||||
|  | ||||
| void | ||||
| MultipleOutputs::Release() | ||||
| { | ||||
| 	for (auto ao : outputs) | ||||
| 		audio_output_release(ao); | ||||
|  | ||||
| 	if (pipe != nullptr) { | ||||
| 		assert(buffer != nullptr); | ||||
|  | ||||
| 		pipe->Clear(*buffer); | ||||
| 		delete pipe; | ||||
| 		pipe = nullptr; | ||||
| 	} | ||||
|  | ||||
| 	buffer = nullptr; | ||||
|  | ||||
| 	input_audio_format.Clear(); | ||||
|  | ||||
| 	elapsed_time = -1.0; | ||||
| } | ||||
|  | ||||
| void | ||||
| MultipleOutputs::SongBorder() | ||||
| { | ||||
| 	/* clear the elapsed_time pointer at the beginning of a new | ||||
| 	   song */ | ||||
| 	elapsed_time = 0.0; | ||||
| } | ||||
							
								
								
									
										271
									
								
								src/output/MultipleOutputs.hxx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										271
									
								
								src/output/MultipleOutputs.hxx
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -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 = ∅ | ||||
| 		} | ||||
|  | ||||
| 		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; | ||||
| } | ||||
| @@ -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 | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -74,8 +74,10 @@ find_named_config_block(ConfigOption option, const char *name) | ||||
| 	return NULL; | ||||
| } | ||||
|  | ||||
| PlayerControl::PlayerControl(gcc_unused unsigned _buffer_chunks, | ||||
| 			       gcc_unused unsigned _buffered_before_play) {} | ||||
| PlayerControl::PlayerControl(gcc_unused MultipleOutputs &_outputs, | ||||
| 			     gcc_unused unsigned _buffer_chunks, | ||||
| 			     gcc_unused unsigned _buffered_before_play) | ||||
| 	:outputs(_outputs) {} | ||||
| PlayerControl::~PlayerControl() {} | ||||
|  | ||||
| static struct audio_output * | ||||
| @@ -89,7 +91,8 @@ load_audio_output(const char *name) | ||||
| 		return nullptr; | ||||
| 	} | ||||
|  | ||||
| 	static struct PlayerControl dummy_player_control(32, 4); | ||||
| 	static struct PlayerControl dummy_player_control(*(MultipleOutputs *)nullptr, | ||||
| 							 32, 4); | ||||
|  | ||||
| 	Error error; | ||||
| 	struct audio_output *ao = | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Max Kellermann
					Max Kellermann