diff --git a/doc/mpd.conf.5 b/doc/mpd.conf.5 index bfec4cd1a..5fe58b839 100644 --- a/doc/mpd.conf.5 +++ b/doc/mpd.conf.5 @@ -251,6 +251,32 @@ errors on bandwidth-limited devices. Some users have reported good results with this set to 50000, but not all devices support values this high. Most users do not need to change this. The default is 256000000 / sample_rate(kHz), or 5804 microseconds for CD-quality audio. +.SH OPTIONAL OS X OUTPUT PARAMETERS +.TP +.B device +This specifies the device to use for audio output. The default is +"default". Use the names listed in the "Audio Devices" window of +"Audio MIDI Setup". +.TP +.B channel_map +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. .SH FILES .TP .BI ~/.mpdconf 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/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)