Add sample rate synchronization and device hogging to core audio plugin
which ensures mpd do bit perfect playback on OS X
This commit is contained in:
parent
2a2ac35b98
commit
ac4b83046a
@ -42,6 +42,7 @@ struct OSXOutput {
|
||||
const char *device_name;
|
||||
const char *channel_map;
|
||||
|
||||
AudioDeviceID dev_id;
|
||||
AudioComponentInstance au;
|
||||
AudioStreamBasicDescription asbd;
|
||||
|
||||
@ -113,6 +114,22 @@ osx_output_init(const ConfigBlock &block, Error &error)
|
||||
|
||||
osx_output_configure(oo, block);
|
||||
|
||||
AudioObjectPropertyAddress aopa = {
|
||||
kAudioHardwarePropertyDefaultOutputDevice,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
AudioDeviceID dev_id = kAudioDeviceUnknown;
|
||||
UInt32 dev_id_size = sizeof(dev_id);
|
||||
AudioObjectGetPropertyData(kAudioObjectSystemObject,
|
||||
&aopa,
|
||||
0,
|
||||
NULL,
|
||||
&dev_id_size,
|
||||
&dev_id);
|
||||
oo->dev_id = dev_id;
|
||||
|
||||
return &oo->base;
|
||||
}
|
||||
|
||||
@ -244,6 +261,129 @@ done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
osx_output_sync_device_sample_rate(AudioDeviceID dev_id, AudioStreamBasicDescription desc)
|
||||
{
|
||||
FormatDebug(osx_output_domain, "Syncing sample rate.");
|
||||
AudioObjectPropertyAddress aopa = {
|
||||
kAudioDevicePropertyAvailableNominalSampleRates,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
UInt32 property_size;
|
||||
OSStatus err = AudioObjectGetPropertyDataSize(dev_id,
|
||||
&aopa,
|
||||
0,
|
||||
NULL,
|
||||
&property_size);
|
||||
|
||||
int count = property_size/sizeof(AudioValueRange);
|
||||
AudioValueRange ranges[count];
|
||||
property_size = sizeof(ranges);
|
||||
err = AudioObjectGetPropertyData(dev_id,
|
||||
&aopa,
|
||||
0,
|
||||
NULL,
|
||||
&property_size,
|
||||
&ranges);
|
||||
// Get the maximum sample rate as fallback.
|
||||
Float64 sample_rate = .0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (ranges[i].mMaximum > sample_rate)
|
||||
sample_rate = ranges[i].mMaximum;
|
||||
}
|
||||
|
||||
// Now try to see if the device support our format sample rate.
|
||||
// For some high quality media samples, the frame rate may exceed
|
||||
// device capability. In this case, we let CoreAudio downsample
|
||||
// by decimation with an integer factor ranging from 1 to 4.
|
||||
for (int f = 4; f > 0; f--) {
|
||||
Float64 rate = desc.mSampleRate / f;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (ranges[i].mMinimum <= rate
|
||||
&& rate <= ranges[i].mMaximum) {
|
||||
sample_rate = rate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aopa.mSelector = kAudioDevicePropertyNominalSampleRate,
|
||||
|
||||
err = AudioObjectSetPropertyData(dev_id,
|
||||
&aopa,
|
||||
0,
|
||||
NULL,
|
||||
sizeof(&desc.mSampleRate),
|
||||
&sample_rate);
|
||||
if (err != noErr) {
|
||||
FormatWarning(osx_output_domain,
|
||||
"Failed to synchronize the sample rate: %d",
|
||||
err);
|
||||
} else {
|
||||
FormatDebug(osx_output_domain,
|
||||
"Sample rate synced to %f Hz.",
|
||||
sample_rate);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
osx_output_hog_device(AudioDeviceID dev_id, bool hog)
|
||||
{
|
||||
pid_t hog_pid;
|
||||
AudioObjectPropertyAddress aopa = {
|
||||
kAudioDevicePropertyHogMode,
|
||||
kAudioObjectPropertyScopeOutput,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
UInt32 size = sizeof(hog_pid);
|
||||
OSStatus err = AudioObjectGetPropertyData(dev_id,
|
||||
&aopa,
|
||||
0,
|
||||
NULL,
|
||||
&size,
|
||||
&hog_pid);
|
||||
if (err != noErr) {
|
||||
FormatDebug(osx_output_domain,
|
||||
"Cannot get hog information: %d",
|
||||
err);
|
||||
return;
|
||||
}
|
||||
if (hog) {
|
||||
if (hog_pid != -1) {
|
||||
FormatDebug(osx_output_domain,
|
||||
"Device is already hogged.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (hog_pid != getpid()) {
|
||||
FormatDebug(osx_output_domain,
|
||||
"Device is not owned by this process.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
hog_pid = hog ? getpid() : -1;
|
||||
size = sizeof(hog_pid);
|
||||
err = AudioObjectSetPropertyData(dev_id,
|
||||
&aopa,
|
||||
0,
|
||||
NULL,
|
||||
size,
|
||||
&hog_pid);
|
||||
if (err != noErr) {
|
||||
FormatDebug(osx_output_domain,
|
||||
"Cannot hog the device: %d",
|
||||
err);
|
||||
} else {
|
||||
FormatDebug(osx_output_domain,
|
||||
hog_pid == -1 ? "Device is unhogged"
|
||||
: "Device is hogged");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
osx_output_set_device(OSXOutput *oo, Error &error)
|
||||
{
|
||||
@ -337,6 +477,7 @@ osx_output_set_device(OSXOutput *oo, Error &error)
|
||||
goto done;
|
||||
}
|
||||
|
||||
oo->dev_id = deviceids[i];
|
||||
FormatDebug(osx_output_domain,
|
||||
"set OS X audio output device ID=%u, name=%s",
|
||||
(unsigned)deviceids[i], name);
|
||||
@ -490,6 +631,8 @@ osx_output_enable(AudioOutput *ao, Error &error)
|
||||
return false;
|
||||
}
|
||||
|
||||
osx_output_hog_device(oo->dev_id, true);
|
||||
|
||||
AURenderCallbackStruct callback;
|
||||
callback.inputProc = osx_render;
|
||||
callback.inputProcRefCon = oo;
|
||||
@ -515,6 +658,8 @@ osx_output_disable(AudioOutput *ao)
|
||||
OSXOutput *oo = (OSXOutput *)ao;
|
||||
|
||||
AudioComponentInstanceDispose(oo->au);
|
||||
|
||||
osx_output_hog_device(oo->dev_id, false);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -568,6 +713,8 @@ osx_output_open(AudioOutput *ao, AudioFormat &audio_format,
|
||||
od->asbd.mBytesPerFrame = od->asbd.mBytesPerPacket;
|
||||
od->asbd.mChannelsPerFrame = audio_format.channels;
|
||||
|
||||
osx_output_sync_device_sample_rate(od->dev_id, od->asbd);
|
||||
|
||||
OSStatus status =
|
||||
AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0,
|
||||
|
Loading…
Reference in New Issue
Block a user