This is a new feature that allows setting volume on a per-output basis. (mantis feature request reference: http://bugs.musicpd.org/view.php?id=4426) Original concept was to have the mixer for each output and the global mixer multiply their volume together. Looking into the source I found it would be easier to implement (and works just as well from a usability point of view) by having each output volume map directly to it's actual output and calculate the global volume as the average, and setting volume by trying to scale everything proportionately.

The implementation does do some degree of compatibility checking as not all outputs can have per-output volume control (only software mixers seem to work) and clients can tell if a given output has a settable volume by if volume is reported from the outputs command. Volume for an output is set with a new outputvolume command. Disabled outputs can have their volumes set, but they do not affect and are not affected by the global volume.

Attached is a patch that implements this feature. I have little experience in the MPD source so this probably needs more work, but it does show that this feature is probably feasible. I have been using this for the last few weeks and it seems to be working well for me, but I probably haven't covered all possible configurations.
diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx
index 8e8865f..40e5e1f 100644
--- a/src/command/AllCommands.cxx
+++ b/src/command/AllCommands.cxx
@@ -134,6 +134,7 @@ static constexpr struct command commands[] = {
 	{ "next", PERMISSION_CONTROL, 0, 0, handle_next },
 	{ "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands },
 	{ "outputs", PERMISSION_READ, 0, 0, handle_devices },
+	{ "outputvolume", PERMISSION_ADMIN, 2, 2, handle_outputvolume },
 	{ "password", PERMISSION_NONE, 1, 1, handle_password },
 	{ "pause", PERMISSION_CONTROL, 0, 1, handle_pause },
 	{ "ping", PERMISSION_NONE, 0, 0, handle_ping },
diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx
index 7bbe5f9..654b8cc 100644
--- a/src/command/OutputCommands.cxx
+++ b/src/command/OutputCommands.cxx
@@ -83,3 +83,33 @@ handle_devices(Client &client, gcc_unused Request args, Response &r)
 	printAudioDevices(r, client.partition.outputs);
 	return CommandResult::OK;
 }
+
+CommandResult
+handle_outputvolume(Client &client, Request args, Response &r)
+{
+	assert(args.size == 2);
+	unsigned device;
+	if (!args.Parse(0, device, r))
+		return CommandResult::ERROR;
+
+	if (device > client.partition.outputs.Size()-1 || device < 0) {
+		r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
+		return CommandResult::ERROR;
+	}
+
+	unsigned volume;
+	if (!args.Parse(1, volume, r))
+		return CommandResult::ERROR;
+
+	if (volume > 100 || volume < 0) {
+		r.Error(ACK_ERROR_NO_EXIST, "volume must be in the range 0 - 100");
+		return CommandResult::ERROR;
+	}
+
+	if (!audio_output_set_volume(client.partition.outputs, device, volume)) {
+		r.Error(ACK_ERROR_NO_EXIST, "could not set volume on the output");
+		return CommandResult::ERROR;
+	}
+
+	return CommandResult::OK;
+}
diff --git a/src/command/OutputCommands.hxx b/src/command/OutputCommands.hxx
index 3dd81bc..0673e59 100644
--- a/src/command/OutputCommands.hxx
+++ b/src/command/OutputCommands.hxx
@@ -38,4 +38,7 @@ handle_toggleoutput(Client &client, Request request, Response &response);
 CommandResult
 handle_devices(Client &client, Request request, Response &response);
 
+CommandResult
+handle_outputvolume(Client &client, Request request, Response &response);
+
 #endif
diff --git a/src/mixer/MixerAll.cxx b/src/mixer/MixerAll.cxx
index 835cf4e..492bd16 100644
--- a/src/mixer/MixerAll.cxx
+++ b/src/mixer/MixerAll.cxx
@@ -29,6 +29,8 @@
 
 #include <assert.h>
 
+#include <iostream>
+
 static int
 output_mixer_get_volume(const AudioOutput &ao)
 {
@@ -49,6 +51,56 @@ output_mixer_get_volume(const AudioOutput &ao)
 	return volume;
 }
 
+/**
+ * given a set of volumes, and a new average volume,
+ * set all of the volumes such that their average is the given average
+ * maintaining their relative proportions as much as possible and
+ * respecting the constraint that all volumes must stay withing 0 to 100
+ */
+static void
+set_average_volume(std::vector<int> &volumes, unsigned max, unsigned new_average)
+{
+	unsigned average = 0;
+
+	unsigned valid_count = 0;
+	for(unsigned i = 0; i<volumes.size(); i++){
+		if(volumes[i] > 0){
+			average += volumes[i];
+			valid_count++;
+		}
+	}
+	average /= valid_count;
+
+	unsigned last_average = 0;
+
+	while(new_average != average && last_average != average){
+		last_average = average;
+		average = 0;
+		for(unsigned i = 0; i<volumes.size(); i++){
+			if(volumes[i] < 0){
+				//invalid volume, bail
+				continue;
+			}
+			unsigned new_volume = new_average;
+
+			//try to maintain proportion
+			if(last_average != 0 && volumes[i] > 0){
+				new_volume = volumes[i]*new_average/last_average;
+				if(new_volume > max)
+					new_volume = max;
+				if(! (new_volume > 0) ){ //catching error conditions
+					new_volume = 0;
+				}
+			}
+
+			volumes[i] = new_volume;
+
+			average += new_volume;
+		}
+		average /= valid_count;
+	}
+}
+
 int
 MultipleOutputs::GetVolume() const
 {
@@ -97,9 +149,21 @@ MultipleOutputs::SetVolume(unsigned volume)
 	assert(volume <= 100);
 
 	bool success = false;
-	for (auto ao : outputs)
-		success = output_mixer_set_volume(*ao, volume)
-			|| success;
+
+	std::vector<int> volumes(outputs.size(), -1);
+
+	for (unsigned i = 0; i < outputs.size(); i++){
+		volumes[i] = output_mixer_get_volume(*outputs[i]);
+		if(volumes[i] == 0){
+			volumes[i] = 1;
+		}
+	}
+
+	set_average_volume(volumes, 100, volume);
+
+	for (unsigned i = 0; i < outputs.size(); i++){
+		success = output_mixer_set_volume(*outputs[i], volumes[i]) || success;
+	}
 
 	return success;
 }
@@ -142,12 +206,23 @@ MultipleOutputs::SetSoftwareVolume(unsigned volume)
 {
 	assert(volume <= PCM_VOLUME_1);
 
-	for (auto ao : outputs) {
-		const auto mixer = ao->mixer;
+	std::vector<int> volumes(outputs.size(), -1);
+
+	for (unsigned i = 0; i < outputs.size(); i++){
+		volumes[i] = output_mixer_get_volume(*outputs[i]);
+		if(volumes[i] == 0){
+			volumes[i] = 1;
+		}
+	}
+
+	set_average_volume(volumes, PCM_VOLUME_1, volume);
+
+	for (unsigned i = 0; i < outputs.size(); i++){
+		const auto mixer = outputs[i]->mixer;
 
 		if (mixer != nullptr &&
 		    (&mixer->plugin == &software_mixer_plugin ||
 		     &mixer->plugin == &null_mixer_plugin))
-			mixer_set_volume(mixer, volume, IgnoreError());
+			mixer_set_volume(mixer, volumes[i], IgnoreError());
 	}
 }
diff --git a/src/mixer/Volume.cxx b/src/mixer/Volume.cxx
index 8bc8d87..5aa6a69 100644
--- a/src/mixer/Volume.cxx
+++ b/src/mixer/Volume.cxx
@@ -87,9 +87,12 @@ volume_level_change(MultipleOutputs &outputs, unsigned volume)
 
 	volume_software_set = volume;
 
+	bool result = hardware_volume_change(outputs, volume);
+
 	idle_add(IDLE_MIXER);
+	idle_add(IDLE_OUTPUT); //because of per-output volume
 
-	return hardware_volume_change(outputs, volume);
+	return result;
 }
 
 bool
diff --git a/src/output/OutputCommand.cxx b/src/output/OutputCommand.cxx
index 83abcf2..7d0fedd 100644
--- a/src/output/OutputCommand.cxx
+++ b/src/output/OutputCommand.cxx
@@ -31,6 +31,8 @@
 #include "player/Control.hxx"
 #include "mixer/MixerControl.hxx"
 #include "Idle.hxx"
+#include "mixer/MixerInternal.hxx"
+#include "util/Error.hxx"
 
 extern unsigned audio_output_state_version;
 
@@ -104,3 +106,30 @@ audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx)
 
 	return true;
 }
+
+bool
+audio_output_set_volume(MultipleOutputs &outputs, unsigned idx, unsigned volume)
+{
+	if (idx >= outputs.Size())
+		return false;
+
+	AudioOutput &ao = outputs.Get(idx);
+	if (ao.mixer) {
+		bool it_worked = mixer_set_volume(ao.mixer, volume, IgnoreError());
+		if (!it_worked) {
+			return false;
+		}
+	}
+	else {
+		return false;
+	}
+
+	idle_add(IDLE_MIXER);
+	idle_add(IDLE_OUTPUT);
+
+	ao.player_control->UpdateAudio();
+
+	++audio_output_state_version;
+
+	return true;
+}
diff --git a/src/output/OutputCommand.hxx b/src/output/OutputCommand.hxx
index 5b53cd1..e0918b2 100644
--- a/src/output/OutputCommand.hxx
+++ b/src/output/OutputCommand.hxx
@@ -50,4 +50,11 @@ audio_output_disable_index(MultipleOutputs &outputs, unsigned idx);
 bool
 audio_output_toggle_index(MultipleOutputs &outputs, unsigned idx);
 
+/**
+ * Sets the volume of an audio output.  Returns false if the specified output
+ * does not exist, or if it's volume cannot be set.
+ */
+bool
+audio_output_set_volume(MultipleOutputs &outputs, unsigned idx, unsigned volume);
+
 #endif
diff --git a/src/output/OutputPrint.cxx b/src/output/OutputPrint.cxx
index d2ddbbf..26916f9 100644
--- a/src/output/OutputPrint.cxx
+++ b/src/output/OutputPrint.cxx
@@ -27,6 +27,8 @@
 #include "MultipleOutputs.hxx"
 #include "Internal.hxx"
 #include "client/Response.hxx"
+#include "mixer/MixerInternal.hxx"
+#include "util/Error.hxx"
 
 void
 printAudioDevices(Response &r, const MultipleOutputs &outputs)
@@ -38,5 +40,15 @@ printAudioDevices(Response &r, const MultipleOutputs &outputs)
 			 "outputname: %s\n"
 			 "outputenabled: %i\n",
 			 i, ao.name, ao.enabled);
+
+		//if they have independent volume report it
+		if(ao.mixer)
+		{
+			int volume = ao.mixer->GetVolume(IgnoreError());
+			if(volume > -1) //-1 indicates an error state
+			{
+				r.Format("outputvolume: %i\n",volume);
+			}
+		}
 	}
 }
_______________________________________________
mpd-devel mailing list
[email protected]
http://mailman.blarg.de/listinfo/mpd-devel

Reply via email to