diff --git a/doc/mpdconf.example b/doc/mpdconf.example
index b6ecefed5..96fb12e2c 100644
--- a/doc/mpdconf.example
+++ b/doc/mpdconf.example
@@ -313,6 +313,15 @@ input {
# mixer_type "software"
#}
#
+# An example of an OS X output:
+#
+#audio_output {
+# type "osx"
+# name "My OS X Device"
+## device "Built-in Output" # optional
+## channel_map "-1,-1,0,1" # optional
+#}
+#
## Example "pipe" output:
#
#audio_output {
diff --git a/doc/user.xml b/doc/user.xml
index 3b59df677..5c6e3eff1 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -3149,6 +3149,55 @@ buffer_size: 16384
The "Mac OS X" plugin uses Apple's CoreAudio API.
+
+
+
+
+ Setting
+ Description
+
+
+
+
+
+ device
+ NAME
+
+
+ Sets the device which should be used. Uses device names as listed in the
+ "Audio Devices" window of "Audio MIDI Setup".
+
+
+
+
+ channel_map
+ SOURCE,SOURCE,...
+
+
+ Specifies a channel map. If your audio device has more than two
+ outputs this allows you to route audio to auxillary outputs. For
+ predictable results you should also specify a "format" with a fixed
+ number of channels, e.g. "*:*:2". The number of items in the channel
+ map must match the number of output channels of your output device.
+ Each list entry specifies the source for that output channel; use "-1"
+ to silence an output. For example, if you have a four-channel output
+ device and you wish to send stereo sound (format "*:*:2") to outputs 3
+ and 4 while leaving outputs 1 and 2 silent then set the channel map to
+ "-1,-1,0,1". In this example '0' and '1' denote the left and right
+ channel respectively.
+
+
+ The channel map may not refer to outputs that do not exist according
+ to the format. If the format is "*:*:1" (mono) and you have a
+ four-channel sound card then "-1,-1,0,0" (dual mono output on the
+ second pair of sound card outputs) is a valid channel map but
+ "-1,-1,0,1" is not because the second channel ('1') does not exist
+ when the output is mono.
+
+
+
+
+
diff --git a/src/output/plugins/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx
index e5d40891e..408bbc298 100644
--- a/src/output/plugins/OSXOutputPlugin.cxx
+++ b/src/output/plugins/OSXOutputPlugin.cxx
@@ -39,6 +39,7 @@ struct OSXOutput {
OSType component_subtype;
/* only applicable with kAudioUnitSubType_HALOutput */
const char *device_name;
+ const char *channel_map;
AudioComponentInstance au;
AudioStreamBasicDescription asbd;
@@ -93,6 +94,8 @@ osx_output_configure(OSXOutput *oo, const ConfigBlock &block)
/* XXX am I supposed to strdup() this? */
oo->device_name = device;
}
+
+ oo->channel_map = block.GetBlockValue("channel_map");
}
static AudioOutput *
@@ -117,6 +120,126 @@ osx_output_finish(AudioOutput *ao)
delete oo;
}
+static bool
+osx_output_parse_channel_map(
+ const char *device_name,
+ const char *channel_map_str,
+ SInt32 channel_map[],
+ UInt32 num_channels,
+ Error &error)
+{
+ char *endptr;
+ unsigned int inserted_channels = 0;
+ bool want_number = true;
+
+ while (*channel_map_str) {
+ if (inserted_channels >= num_channels) {
+ error.Format(osx_output_domain,
+ "%s: channel map contains more than %u entries or trailing garbage",
+ device_name, num_channels);
+ return false;
+ }
+
+ if (!want_number && *channel_map_str == ',') {
+ ++channel_map_str;
+ want_number = true;
+ continue;
+ }
+
+ if (want_number &&
+ (isdigit(*channel_map_str) || *channel_map_str == '-')
+ ) {
+ channel_map[inserted_channels] = strtol(channel_map_str, &endptr, 10);
+ if (channel_map[inserted_channels] < -1) {
+ error.Format(osx_output_domain,
+ "%s: channel map value %d not allowed (must be -1 or greater)",
+ device_name, channel_map[inserted_channels]);
+ return false;
+ }
+ channel_map_str = endptr;
+ want_number = false;
+ FormatDebug(osx_output_domain,
+ "%s: channel_map[%u] = %d",
+ device_name, inserted_channels, channel_map[inserted_channels]);
+ ++inserted_channels;
+ continue;
+ }
+
+ error.Format(osx_output_domain,
+ "%s: invalid character '%c' in channel map",
+ device_name, *channel_map_str);
+ return false;
+ }
+
+ if (inserted_channels < num_channels) {
+ error.Format(osx_output_domain,
+ "%s: channel map contains less than %u entries",
+ device_name, num_channels);
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+osx_output_set_channel_map(OSXOutput *oo, Error &error)
+{
+ AudioStreamBasicDescription desc;
+ OSStatus status;
+ SInt32 *channel_map = nullptr;
+ UInt32 size, num_channels;
+ char errormsg[1024];
+ bool ret = true;
+
+ size = sizeof(desc);
+ memset(&desc, 0, size);
+ status = AudioUnitGetProperty(oo->au,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output,
+ 0,
+ &desc,
+ &size);
+ if (status != noErr) {
+ osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
+ error.Format(osx_output_domain, status,
+ "%s: unable to get number of output device channels: %s",
+ oo->device_name, errormsg);
+ ret = false;
+ goto done;
+ }
+
+ num_channels = desc.mChannelsPerFrame;
+ channel_map = new SInt32[num_channels];
+ if (!osx_output_parse_channel_map(oo->device_name,
+ oo->channel_map,
+ channel_map,
+ num_channels,
+ error)
+ ) {
+ ret = false;
+ goto done;
+ }
+
+ size = num_channels * sizeof(SInt32);
+ status = AudioUnitSetProperty(oo->au,
+ kAudioOutputUnitProperty_ChannelMap,
+ kAudioUnitScope_Input,
+ 0,
+ channel_map,
+ size);
+ if (status != noErr) {
+ osx_os_status_to_cstring(status, errormsg, sizeof(errormsg));
+ error.Format(osx_output_domain, status,
+ "%s: unable to set channel map: %s", oo->device_name, errormsg);
+ ret = false;
+ goto done;
+ }
+
+done:
+ delete[] channel_map;
+ return ret;
+}
+
static bool
osx_output_set_device(OSXOutput *oo, Error &error)
{
@@ -214,6 +337,11 @@ osx_output_set_device(OSXOutput *oo, Error &error)
"set OS X audio output device ID=%u, name=%s",
(unsigned)deviceids[i], name);
+ if (oo->channel_map && !osx_output_set_channel_map(oo, error)) {
+ ret = false;
+ goto done;
+ }
+
done:
delete[] deviceids;
if (cfname)