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)