From ac4b83046a244ba8707f2dddca5ebfeb1f828393 Mon Sep 17 00:00:00 2001 From: Yue Wang Date: Sun, 18 Sep 2016 12:52:08 -0700 Subject: [PATCH] Add sample rate synchronization and device hogging to core audio plugin which ensures mpd do bit perfect playback on OS X --- src/output/plugins/OSXOutputPlugin.cxx | 147 +++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/src/output/plugins/OSXOutputPlugin.cxx b/src/output/plugins/OSXOutputPlugin.cxx index 540d342f9..aa0fabf23 100644 --- a/src/output/plugins/OSXOutputPlugin.cxx +++ b/src/output/plugins/OSXOutputPlugin.cxx @@ -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,