You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于打开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内存)。

核心疑问

  1. 此问题是CoreAudio的Bug,还是我们违反了线程同步规则?
  2. 是否有关于音频回调线程同步的官方文档?

复现代码

为复现问题,编写了仅基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 06:54:34