VoiceProcessingIO 音频单元向内置输出设备 (macOS) 添加意外的输入流

问题描述 投票:0回答:1

我在 macOS 上开发 VoIP 应用程序,并使用 VoiceProcessingIO 音频单元进行音频处理,例如回声消除和自动增益控制。

问题是,当我初始化音频单元时,核心音频设备列表发生变化 - 不仅仅是添加 VP 音频单元满足其需要的新聚合设备,而且还因为内置输出设备(即“内置 - MacBook”) Pro 扬声器”)现在也显示为输入设备,即除了输出流之外还有意外的输入流。

这是我在初始化 VP AU 之前从 Core Audio 获得的输入设备(又名“麦克风”)列表:

DEVICE:      INPUT   45      BlackHole_UID
DEVICE:      INPUT   93      BuiltInMicrophoneDevice

这与我的 VP AU 初始化时的列表相同:

DEVICE:      INPUT   45      BlackHole_UID
DEVICE:      INPUT   93      BuiltInMicrophoneDevice
DEVICE:      INPUT   86      BuiltInSpeakerDevice /// WHY?
DEVICE:      INPUT   98      VPAUAggregateAudioDevice-0x101046040

这非常令人沮丧,因为我需要在应用程序中显示设备列表,尽管我可以大胆地从设备列表中过滤掉聚合设备(无论如何它们都不能与 VP AU 一起使用),但我无法排除我们的内置 MacBook 扬声器设备。

也许你们中的某个人已经经历过这个并且知道发生了什么事以及是否可以解决这个问题。我需要注意一些 kAudioObjectPropertyXX 以从输入列表中排除设备。当然,这可能是苹果方面的一个错误/功能,我只需要想办法解决这个问题。

VP AU 运行良好,尽管使用了设备,但问题仍然存在(我在内置和外部/USB/蓝牙上都尝试过)。该问题在我可以测试的所有 macOS 版本上都会重现,从 10.13 开始到 11.0 结束。这也会在不同的 Mac 和连接的不同音频设备组上重现。我很好奇,关于该问题的可用信息几乎为零,这让我想到我做错了什么。

更奇怪的是,当 VP AU 工作时,HALLLab 应用程序指示另一件事:内置输入有两个以上的输入流(好吧,如果只是这样的话,我会幸存下来!)。但它并不表明内置输出已添加输入流,就像在我的应用程序中一样。

以下是关于如何设置 VP 音频单元的 cpp 代码摘录:

#define MAX_FRAMES_PER_CALLBACK 1024
        
AudioComponentInstance AvHwVoIP::getComponentInstance(OSType type, OSType subType) {
        AudioComponentDescription desc = {0};
        desc.componentFlags = 0;
        desc.componentFlagsMask = 0;
        desc.componentManufacturer = kAudioUnitManufacturer_Apple;
        desc.componentSubType =  subType;
        desc.componentType    = type;
        
        AudioComponent ioComponent = AudioComponentFindNext(NULL, &desc);
        AudioComponentInstance unit;
        OSStatus status = AudioComponentInstanceNew(ioComponent, &unit);
        if (status != noErr) {
            printf("Error: %d\n", status);
        }
        return unit;
    }
    
    void AvHwVoIP::enableIO(uint32_t enableIO, AudioUnit auDev) {
        
        UInt32 no = 0;
        
        setAudioUnitProperty(auDev,
                             kAudioOutputUnitProperty_EnableIO,
                             kAudioUnitScope_Input,
                             1,
                             &enableIO,
                             sizeof(enableIO));
        
        setAudioUnitProperty(auDev,
                             kAudioOutputUnitProperty_EnableIO,
                             kAudioUnitScope_Output,
                             0,
                             &enableIO,
                             sizeof(enableIO));
    }
    
    void AvHwVoIP::setDeviceAsCurrent(AudioUnit auDev, AudioUnitElement element, AudioObjectID devId) {
        //Set the Current Device to the AUHAL.
        //this should be done only after IO has been enabled on the AUHAL.
        setAudioUnitProperty(auDev,
                             kAudioOutputUnitProperty_CurrentDevice,
                             element == 0 ? kAudioUnitScope_Output : kAudioUnitScope_Input,
                             element,
                             &devId,
                             sizeof(AudioDeviceID));
    }
    
    void AvHwVoIP::setAudioUnitProperty(AudioUnit auDev,
                                           AudioUnitPropertyID inID,
                                            AudioUnitScope inScope,
                                            AudioUnitElement inElement,
                                            const void* __nullable inData,
                                            uint32_t inDataSize) {
    
        OSStatus status = AudioUnitSetProperty(auDev, inID, inScope, inElement, inData, inDataSize);
        if (noErr != status) {
            std::cout << "****** ::setAudioUnitProperty failed" << std::endl;
        }
        
    }
    
    void AvHwVoIP::start() {
        m_auVoiceProcesing = getComponentInstance(kAudioUnitType_Output, kAudioUnitSubType_VoiceProcessingIO);
        enableIO(1, m_auVoiceProcesing);
        m_format_description = SetAudioUnitStreamFormatFloat(m_auVoiceProcesing);
        SetAudioUnitCallbacks(m_auVoiceProcesing);
        setDeviceAsCurrent(m_auVoiceProcesing, 0,  m_renderDeviceID);//output device AudioDeviceID here
        setDeviceAsCurrent(m_auVoiceProcesing, 1,  m_capDeviceID);//input device AudioDeviceID here
        setInputLevelListener();
        setVPEnabled(true);
        setAGCEnabled(true);
        
        UInt32 maximumFramesPerSlice = 0;
        UInt32 size = sizeof(maximumFramesPerSlice);
        OSStatus s1 = AudioUnitGetProperty(m_auVoiceProcesing, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maximumFramesPerSlice, &size);
    
        printf("max frames per callback: %d\n", maximumFramesPerSlice);
        
        maximumFramesPerSlice = MAX_FRAMES_PER_CALLBACK;
        s1 = AudioUnitSetProperty(m_auVoiceProcesing, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maximumFramesPerSlice, size);
        
        
        OSStatus status = AudioUnitInitialize(m_auVoiceProcesing);
        if (noErr != status) {
            printf("*** error AU initialize: %d", status);
        }
        
        status = AudioOutputUnitStart(m_auVoiceProcesing);
        if (noErr != status) {
            printf("*** AU start error: %d", status);
        }
    }

这是我获取设备列表的方法:

//does this device have input/output streams?    
bool hasStreamsForCategory(AudioObjectID devId, bool input)
    {
        const AudioObjectPropertyScope scope = (input == true ? kAudioObjectPropertyScopeInput : kAudioObjectPropertyScopeOutput);
        
        AudioObjectPropertyAddress propertyAddress{kAudioDevicePropertyStreams, scope, kAudioObjectPropertyElementWildcard};
    
        uint32_t dataSize = 0;
        OSStatus status = AudioObjectGetPropertyDataSize(devId,
                                                         &propertyAddress,
                                                         0,
                                                         NULL,
                                                         &dataSize);
        if (noErr != status)
            printf("%s: Error in AudioObjectGetPropertyDataSize: %d \n", __FUNCTION__, status);
    
        return (dataSize / sizeof(AudioStreamID)) > 0;
    }
    
    std::set<AudioDeviceID> scanCoreAudioDeviceUIDs(bool isInput)
    {
        std::set<AudioDeviceID> deviceIDs{};
        
        // find out how many audio devices there are
        AudioObjectPropertyAddress propertyAddress = {kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
        
        uint32_t dataSize{0};
        OSStatus err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize);
        if ( err != noErr )
        {
            printf("%s: AudioObjectGetPropertyDataSize: %d\n", __FUNCTION__, dataSize);
            return deviceIDs;//empty
        }
        
        // calculate the number of device available
        uint32_t devicesAvailable = dataSize / sizeof(AudioObjectID);
        if ( devicesAvailable < 1 )
        {
            printf("%s: Core audio available devices were not found\n", __FUNCTION__);
            return deviceIDs;//empty
        }
        
        AudioObjectID devices[devicesAvailable];//devices to get
        
        err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize, devices);
        if ( err != noErr )
        {
            printf("%s: Core audio available devices were not found\n", __FUNCTION__);
            return deviceIDs;//empty
        }
        
        const AudioObjectPropertyScope scope = (isInput == true ? kAudioObjectPropertyScopeInput : kAudioObjectPropertyScopeOutput);
        
        for (uint32_t i = 0; i < devicesAvailable; ++i)
        {
            const bool hasCorrespondingStreams = hasStreamsForCategory(devices[i], isInput);
    
            if (!hasCorrespondingStreams) {
                continue;
            }
            
            printf("DEVICE: \t %s \t %d \t %s\n", isInput ? "INPUT" : "OUTPUT", devices[i], deviceUIDFromAudioDeviceID(devices[i]).c_str());
            
            deviceIDs.insert(devices[i]);
        }//end for
        
        return deviceIDs;
    }
macos core-audio audiounit hal
1个回答
4
投票

好吧,自从我向 Apple 写过这个问题以来,在 4 个月内回复了我自己的问题,因为今天 Apple Feedback Assistant 回复了我的请求:

“您注意到了两件事,这两件事都是预期的并被视为 AUVP 的实现细节:

  1. 扬声器设备具有输入流 - 这是用于回声消除的参考抽头流。
  2. 内置麦克风设备下有额外的输入流 - 这是 AUVP 启用的原始麦克风流。

对于#1,我们建议您在根据输入/输出流确定内置扬声器和(在某些 Mac 上)耳机是否为输入/输出设备时要特别小心。

对于#2,我们建议您忽略设备上的额外流。”

所以他们建议我做我当时所做的事情:

  • 启动AU前确定内置输出设备,然后记住即可;
  • 忽略 VP AU 操作期间内置设备中出现的任何额外流。
© www.soinside.com 2019 - 2024. All rights reserved.