关于打开CoreAudio设备时出现死锁问题的技术问询
CoreAudio设备启停时的偶发死锁问题分析
问题现象
部分JUCE用户(包括本人)在打开CoreAudio设备时遇到高频偶发死锁,已造成实际业务影响。具体死锁场景为:
- 主线程调用
AudioDeviceStart()时,等待CoreAudio内部互斥锁; - 音频回调线程则等待应用层用于同步主线程与回调的专属互斥锁;
- 由于音频回调无法返回,
AudioDeviceStart()也无法结束,形成双向持锁的死锁状态。
排查结论
经测试验证,得出以下结论:
- 若每秒以不同采样率重新打开设备,可在5分钟内稳定复现问题,推测为竞态条件导致;
- 仅Mac内置音频设备受影响(含扬声器与外接耳机);
- Apple Silicon与Intel架构的Mac均有用户反馈该问题;
- 仅当以与设备当前采样率不同的参数打开设备(需重新配置设备)时才会触发;
- 测试环境:macOS 12.4(Monterey)、2021款16英寸MacBook Pro(Apple M1 Max、32GB内存)。
核心疑问
- 此问题是CoreAudio的Bug,还是我们违反了线程同步规则?
- 是否有关于音频回调线程同步的官方文档?
复现代码
为复现问题,编写了仅基于CoreAudio的测试工具,模拟JUCE调用CoreAudio API的逻辑:
#include <CoreAudio/AudioHardware.h> #include <chrono> #include <iostream> #include <thread> OSStatus audioIOProc ( AudioObjectID inDevice, const AudioTimeStamp* inNow, const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime, void* __nullable inClientData) { if (!outOutputData) return noErr; if (!inClientData) return noErr; // Synchronise audio callbacks with the main thread, which is not ok during processing but fine when starting or // stopping a device. std::lock_guard lock (*static_cast<std::mutex*> (inClientData)); // Silence the output buffers the quick and rough way. for (UInt32 i = 0; i < outOutputData->mNumberBuffers; ++i) { auto buffer = outOutputData->mBuffers[i]; for (int j = 0; j < buffer.mDataByteSize; ++j) { static_cast<uint8_t*> (buffer.mData)[j] = 0; } } return noErr; } int main (int argc, const char* argv[]) { AudioObjectPropertyAddress property; property.mSelector = kAudioHardwarePropertyDefaultOutputDevice; property.mScope = kAudioObjectPropertyScopeGlobal; property.mElement = kAudioObjectPropertyElementMain; UInt32 propertySize = 0; if (AudioObjectGetPropertyDataSize (kAudioObjectSystemObject, &property, 0, nullptr, &propertySize) != noErr) throw std::runtime_error ("Failed to get kAudioObjectSystemObject"); AudioObjectID defaultOutputDevice = 0; if (AudioObjectGetPropertyData ( kAudioObjectSystemObject, &property, 0, nullptr, &propertySize, &defaultOutputDevice)) throw std::runtime_error ("Failed to get kAudioObjectSystemObject"); AudioDeviceIOProcID procID = nullptr; std::mutex mutex; if (AudioDeviceCreateIOProcID (defaultOutputDevice, audioIOProc, &mutex, &procID) != noErr) throw std::runtime_error ("Failed to create procid"); int counter = 0; bool sampleRateToggle = false; while (true) { property.mSelector = kAudioDevicePropertyNominalSampleRate; property.mScope = kAudioObjectPropertyScopeGlobal; property.mElement = kAudioObjectPropertyElementMaster; double sampleRate = sampleRateToggle ? 48000.0 : 44100.0; if (AudioObjectSetPropertyData (defaultOutputDevice, &property, 0, nullptr, sizeof sampleRate, &sampleRate) != noErr) throw std::runtime_error ("Failed to set samplerate"); sampleRateToggle = !sampleRateToggle; { std::lock_guard lock (mutex); if (AudioDeviceStart (defaultOutputDevice, procID) != noErr) throw std::runtime_error ("Failed to start device"); } using namespace std::chrono_literals; std::this_thread::sleep_for (1000ms); std::cout << "Opened device (" << ++counter << ")" << std::endl; { std::lock_guard lock (mutex); if (AudioDeviceStop (defaultOutputDevice, nullptr) != noErr) throw std::runtime_error ("Failed to stop device"); } } // We never reach this, but hey... if (AudioDeviceDestroyIOProcID (defaultOutputDevice, procID) != noErr) throw std::runtime_error ("Failed to destroy procid"); return 0; }
死锁堆栈跟踪
死锁发生时的线程堆栈如下:
com.apple.main-thread: __psynch_mutexwait 0x000000019c1dd738 _pthread_mutex_firstfit_lock_wait 0x000000019c215384 _pthread_mutex_firstfit_lock_slow 0x000000019c212cf8 HALB_Mutex::Lock() 0x000000019e1ac898 HALC_ProxyIOContext::StopIOProc(int (*)(unsigned int, AudioTimeStamp const*, AudioBufferList const*, AudioTimeStamp const*, AudioBufferList*, AudioTimeStamp const*, void*)) 0x000000019ddb9838 HAL_HardwarePlugIn_DeviceStop(AudioHardwarePlugInInterface**, unsigned int, int (*)(unsigned int, AudioTimeStamp const*, AudioBufferList const*, AudioTimeStamp const*, AudioBufferList*, AudioTimeStamp const*, void*)) 0x000000019dd838bc HALDevice::StopIOProc(int (*)(unsigned int, AudioTimeStamp const*, AudioBufferList const*, AudioTimeStamp const*, AudioBufferList*, AudioTimeStamp const*, void*)) 0x000000019e1a6028 AudioDeviceStop 0x000000019dbffdb8 main CoreAudioMain.cpp:94 start 0x000000010410908c com.apple.audio.IOThread.client: __psynch_mutexwait 0x000000019c1dd738 _pthread_mutex_firstfit_lock_wait 0x000000019c215384 _pthread_mutex_firstfit_lock_slow 0x000000019c212cf8 std::__1::mutex::lock() 0x000000019c1691a8 std::lock_guard::lock_guard(std::mutex &) __mutex_base:90 std::lock_guard::lock_guard(std::mutex &) __mutex_base:90 audioIOProc(unsigned int, const AudioTimeStamp *, const AudioBufferList *, const AudioTimeStamp *, AudioBufferList *, const AudioTimeStamp *, void *) CoreAudioMain.cpp:22 HALC_ProxyIOContext::IOWorkLoop() 0x000000019ddb7db4 invocation function for block in HALC_ProxyIOContext::HALC_ProxyIOContext(unsigned int, unsigned int) 0x000000019ddb5efc HALB_IOThread::Entry(void*) 0x000000019df82304 _pthread_start 0x000000019c21826c
补充说明
相关问题可参考JUCE论坛的对应讨论帖内容。
内容的提问来源于stack exchange,提问作者Ruurd Adema
相关产品推荐
相关产品推荐

