macos - VoiceProcessingIO 音频单元将意外的输入流添加到内置输出设备 (macOS)
问题描述
我在 macOS 上开发 VoIP 应用程序,并使用 VoiceProcessingIO 音频单元进行音频处理,例如回声消除和自动增益控制。
问题是,当我启动音频单元时,Core Audio 设备列表会发生变化 - 不仅仅是通过添加 VP 音频单元用于其需要的新聚合设备,而且还因为内置输出设备(即“内置 - 在 MacBook Pro Speakers") 现在也显示为输入设备,即除了输出流之外还有一个意外的输入流。
这是我在初始化我的 VP AU 之前从 Core Audio 获得的 INPUT 设备(又名“麦克风”)列表:
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 工作时,HALLab 应用程序会显示另一件事:内置输入还有两个输入流(好吧,如果只是这样,我会幸存下来!)。但这并不表示内置输出添加了输入流,就像在我的应用程序中一样。
以下是我如何设置 VP Audio Unit 的 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;
}
解决方案
好吧,在 Apple 反馈助理回复我的请求后的 4 个月内回复了我自己的问题:
“你注意到了两件事,这两件事都是预期的并被视为 AUVP 的实施细节:
- 扬声器设备具有输入流 - 这是用于回声消除的参考抽头流。
- 内置麦克风设备下还有额外的输入流 - 这是由 AUVP 启用的原始麦克风流。
对于#1,我们建议您在根据其输入/输出流确定它是否是输入/输出设备时,特别小心对待内置扬声器和(在某些 Mac 上)耳机。
对于 #2,我们建议您忽略设备上的额外流。”
所以他们建议我做我当时做的事情:在启动 AU 之前确定内置输出设备,然后记住它;在 VP AU 操作期间忽略出现在内置设备中的任何额外流。
推荐阅读
- javascript - 使用 D3,如何制作重复区域图
- android - Android 创建指标服务
- xml - oracle中xmlconcat函数的使用方法
- reactjs - 单击按钮后反应挂钩 useInterval 重置
- google-apps-script - 如何避免使用谷歌应用脚本 range.setValues 解析数据
- javascript - Web - 定期同步和同步之间有什么区别?
- jquery - Pickaday 添加一天
- python - 如何在 Windows 10 上使用任务计划程序自动化 Python 脚本
- c# - 在c#中打破一个循环
- laravel - laravel (artisan) 不等待输入