output/osx channel_map feature

This commit is contained in:
Jacob Vosmaer 2016-07-09 13:03:41 +02:00
parent b67e7df38e
commit 4cd9abe632
3 changed files with 163 additions and 0 deletions

View File

@ -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 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), users do not need to change this. The default is 256000000 / sample_rate(kHz),
or 5804 microseconds for CD-quality audio. or 5804 microseconds for CD-quality audio.
.SH OPTIONAL OS X OUTPUT PARAMETERS
.TP
.B device <dev>
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 <input,input,input...>
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 .SH FILES
.TP .TP
.BI ~/.mpdconf .BI ~/.mpdconf

View File

@ -313,6 +313,15 @@ input {
# mixer_type "software" # 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: ## Example "pipe" output:
# #
#audio_output { #audio_output {

View File

@ -39,6 +39,7 @@ struct OSXOutput {
OSType component_subtype; OSType component_subtype;
/* only applicable with kAudioUnitSubType_HALOutput */ /* only applicable with kAudioUnitSubType_HALOutput */
const char *device_name; const char *device_name;
const char *channel_map;
AudioComponentInstance au; AudioComponentInstance au;
AudioStreamBasicDescription asbd; AudioStreamBasicDescription asbd;
@ -93,6 +94,8 @@ osx_output_configure(OSXOutput *oo, const ConfigBlock &block)
/* XXX am I supposed to strdup() this? */ /* XXX am I supposed to strdup() this? */
oo->device_name = device; oo->device_name = device;
} }
oo->channel_map = block.GetBlockValue("channel_map");
} }
static AudioOutput * static AudioOutput *
@ -117,6 +120,126 @@ osx_output_finish(AudioOutput *ao)
delete oo; 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 static bool
osx_output_set_device(OSXOutput *oo, Error &error) 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", "set OS X audio output device ID=%u, name=%s",
(unsigned)deviceids[i], name); (unsigned)deviceids[i], name);
if (oo->channel_map && !osx_output_set_channel_map(oo, error)) {
ret = false;
goto done;
}
done: done:
delete[] deviceids; delete[] deviceids;
if (cfname) if (cfname)