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

OS X下使用CoreAudio API无法将蓝牙耳机设为默认输出设备

macOS下CoreAudio设置AirPods为默认输出设备失败问题

问题背景

在录音程序中创建了包含AirPods蓝牙耳机和Blackhole设备的聚合音频设备,录制完成后删除该聚合设备,随后调用AudioObjectSetPropertyData()尝试将AirPods设为默认输出设备。尽管函数返回noErr成功状态,但设备并未实际成为默认输出设备;退出录音前再次检查并重试设置,依然返回成功但无实际效果。

已知条件:程序能正确记录AirPods设备名称,并通过名称获取到对应的AudioDeviceID。

相关代码

#include <CoreAudio/AudioHardware.h>
#include <CoreAudio/AudioServerPlugIn.h>

OSStatus SetDefaultOutputDeviceFromName(const char *_name = nullptr)
{
    OSStatus err = noErr;
    if(_name != nullptr)
    {
        std::string log_ = "[Destroy Aggregate Device]: Selected loopback device name: " + std::string(_name) +  "\n";
        synchronized_logger_function(log_);
        AudioDeviceID deviceID = findDeviceIDFromDeviceName(_name);
        if(deviceID == kAudioObjectUnknown) {
           std::cerr << "Error in getting Device Id from name\n";
        }
        else 
        {
            AudioObjectPropertyAddress defaultDeviceAOPA;
            defaultDeviceAOPA.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
            defaultDeviceAOPA.mScope = kAudioObjectPropertyScopeGlobal;
            defaultDeviceAOPA.mElement = kAudioObjectPropertyElementMain;

            err = AudioObjectSetPropertyData(kAudioObjectSystemObject, &defaultDeviceAOPA, 0, NULL, sizeof(AudioDeviceID), &deviceID);
            // pause again to give the changes time to take effect
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);

            if (err)
            {
                // printf("[Remove Aggregate Device]: Error setting default output device: %d\n", err);
            }           
            else
            {
                // printf("[Create Aggregate Device]: Previous device set as default output device.\n");
            }
        }
        // MA_ASSERT(deviceID != kAudioObjectUnknown);
    }
    return noErr;
}

OSStatus DestroyAggregateDevice(AudioDeviceID inDeviceToDestroy, const char *_name = nullptr)
{
    OSStatus osErr = noErr;

    //-----------------------
    // Start by getting the base audio hardware plugin
    //-----------------------

    AudioObjectPropertyAddress propertyAddress = {
        kAudioHardwarePropertyPlugInForBundleID,
        kAudioObjectPropertyScopeGlobal,
        kAudioObjectPropertyElementMain};

    UInt32 outSize = 0;

    OSStatus err = AudioObjectGetPropertyDataSize(
        kAudioObjectSystemObject,
        &propertyAddress,
        0,
        NULL,
        &outSize);

    if (err != noErr)
    {
        // Handle error
        synchronized_logger_function(std::string("[Destroy aggregate device]: Error occured in removing aggregate device\n"));
    }

    AudioValueTranslation pluginAVT;

    CFStringRef inBundleRef = CFSTR("com.apple.audio.CoreAudio");
    AudioObjectID pluginID;

    pluginAVT.mInputData = &inBundleRef;
    pluginAVT.mInputDataSize = sizeof(inBundleRef);
    pluginAVT.mOutputData = &pluginID;
    pluginAVT.mOutputDataSize = sizeof(pluginID);
    osErr = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &outSize, &pluginAVT);
    if (osErr != noErr)
        return osErr;

    //-----------------------
    // Feed the AudioDeviceID to the plugin, to destroy the aggregate device
    //-----------------------

    AudioObjectPropertyAddress pluginAOPA;
    pluginAOPA.mSelector = kAudioPlugInDestroyAggregateDevice;
    pluginAOPA.mScope = kAudioObjectPropertyScopeGlobal;
    pluginAOPA.mElement = kAudioObjectPropertyElementMain;
    UInt32 outDataSize;

    osErr = AudioObjectGetPropertyDataSize(pluginID, &pluginAOPA, 0, NULL, &outDataSize);
    if (osErr != noErr)
    {
        return osErr;
    }

    osErr = AudioObjectGetPropertyData(pluginID, &pluginAOPA, 0, NULL, &outDataSize, &inDeviceToDestroy);
    if (osErr != noErr)
    {
        return osErr;
    }
    // pause again to give the changes time to take effect
    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);

    SetDefaultOutputDeviceFromName(_name);

    return noErr;
}

排查与解决建议

1. 延长系统状态同步等待时间

销毁聚合设备后,系统音频服务可能仍在处理状态变更,立即设置默认设备可能被后续操作覆盖。建议将CFRunLoopRunInMode的超时时间从0.1秒调整至0.5-1秒,确保系统完成聚合设备的清理工作。

2. 添加设置后验证逻辑

在调用AudioObjectSetPropertyData后,立即读取当前默认设备ID进行验证,确认设置是否真的生效,若未生效则重试:

// 在SetDefaultOutputDeviceFromName函数的设置操作后添加
AudioDeviceID currentDefault;
UInt32 size = sizeof(AudioDeviceID);
err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &defaultDeviceAOPA, 0, NULL, &size, &currentDefault);
if (err == noErr && currentDefault != deviceID) {
    synchronized_logger_function("Default device verification failed, retrying...\n");
    err = AudioObjectSetPropertyData(kAudioObjectSystemObject, &defaultDeviceAOPA, 0, NULL, sizeof(AudioDeviceID), &deviceID);
}

3. 检查辅助功能权限

macOS Ventura及以上版本中,音频设备设置可能需要程序拥有「辅助功能」权限。前往「系统设置-隐私与安全性-辅助功能」,确认当前程序已被授权,若未授权则手动添加后重新测试。

4. 确认AirPods设备状态

AirPods可能处于连接不稳定状态(如重新连接中),导致设置默认设备失败。设置前可通过kAudioDevicePropertyDeviceIsRunning属性检查设备是否处于活跃可用状态。

5. 监听音频设备属性变化

通过注册kAudioHardwarePropertyDefaultOutputDevice的属性监听器,确认聚合设备完全销毁后,再执行默认设备设置操作,避免状态冲突。

内容的提问来源于stack exchange,提问作者V K

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 18:05:45