Highlight
Apple 在 iOS 17、macOS 14 和 tvOS 17 中为语音处理 API 新增了两个能力:其他音频闪避(Other Audio Ducking)的精细化控制,以及静音说话者检测(Muted Talker Detection),让 VoIP 应用可以像 FaceTime 一样智能调节背景音量和提示用户解除静音。
核心内容
背景音闪避:从一刀切到精细控制
做语音通话应用的开发者都遇到过这个场景:用户在通话时同时播放音乐或其他音频,背景音太大导致人声听不清。Apple 的语音处理 API 过去会自动降低其他音频的音量,但闪避量是固定的,有些应用觉得降得太多,有些觉得降得不够。
(03:16)今年 Apple 给 AUVoiceIO 和 AVAudioEngine 都新增了闪避配置 API。开发者现在可以控制两个维度:闪避风格(是否启用高级动态闪避)和闪避强度(四个级别)。
高级动态闪避的效果类似 FaceTime SharePlay:当任何一方说话时,媒体音量自动降低;没人说话时,音量恢复。这让通话和背景音频可以自然共存。
静音说话者检测:告别”我说了半天才发现没开麦”
在线会议中最尴尬的场景之一:用户兴致勃勃地讲了一堆,却发现麦克风一直是静音状态。
(07:58)Apple 在 iOS 15 就引入了静音说话者检测,今年将其扩展到了 macOS 14 和 tvOS 17。只要用户通过语音处理 API 的静音接口静音,系统就能检测到用户是否在静音状态下说话,并通知应用。应用收到通知后可以提示用户解除静音。
对于还没接入 Apple 语音处理 API 的 macOS 应用,Apple 也提供了 Core Audio HAL 层面的语音活动检测属性作为替代方案。
详细内容
其他音频闪避配置
Apple 提供了两种接入方式:底层的 AUVoiceIO(Audio Unit)和高层的 AVAudioEngine。
AUVoiceIO 方式
(05:50)AUVoiceIO 使用 C 结构体配置闪避参数:
typedef CF_ENUM(UInt32, AUVoiceIOOtherAudioDuckingLevel) {
kAUVoiceIOOtherAudioDuckingLevelDefault = 0,
kAUVoiceIOOtherAudioDuckingLevelMin = 10,
kAUVoiceIOOtherAudioDuckingLevelMid = 20,
kAUVoiceIOOtherAudioDuckingLevelMax = 30
};
struct AUVoiceIOOtherAudioDuckingConfiguration {
Boolean mEnableAdvancedDucking;
AUVoiceIOOtherAudioDuckingLevel mDuckingLevel;
};
创建并应用配置:
const AUVoiceIOOtherAudioDuckingConfiguration duckingConfig = {
.mEnableAdvancedDucking = true,
.mDuckingLevel = AUVoiceIOOtherAudioDuckingLevel::kAUVoiceIOOtherAudioDuckingLevelMin
};
OSStatus err = AudioUnitSetProperty(
auVoiceIO,
kAUVoiceIOProperty_OtherAudioDuckingConfiguration,
kAudioUnitScope_Global,
0,
&duckingConfig,
sizeof(duckingConfig)
);
关键点:
mEnableAdvancedDucking为true时,系统会根据双方语音活动动态调节闪避量mDuckingLevel有四个级别:default(与之前行为一致)、min(最小闪避,背景音最大)、mid、max(最大闪避,人声最清晰)- 两个控制维度相互独立,组合使用可覆盖大多数场景
AVAudioEngine 方式
(07:20)AVAudioEngine 提供了对应的 Swift API:
let engine = AVAudioEngine()
let inputNode = engine.inputNode
do {
try inputNode.setVoiceProcessingEnabled(true)
} catch {
print("Could not enable voice processing \(error)")
}
let duckingConfig = AVAudioVoiceProcessingOtherAudioDuckingConfiguration(
enableAdvancedDucking: false,
duckingLevel: .max
)
inputNode.voiceProcessingOtherAudioDuckingConfiguration = duckingConfig
关键点:
- 先调用
setVoiceProcessingEnabled(true)启用语音处理模式 AVAudioVoiceProcessingOtherAudioDuckingConfiguration是 Swift 结构体,参数名与 C 版本略有不同- 直接赋值给
inputNode.voiceProcessingOtherAudioDuckingConfiguration即可生效
静音说话者检测
AUVoiceIO 实现
(07:32)
AUVoiceIOMutedSpeechActivityEventListener listener = ^(AUVoiceIOMutedSpeechActivityEvent event) {
if (event == kAUVoiceIOSpeechActivityHasStarted) {
// 用户静音时开始说话,提示解除静音
} else if (event == kAUVoiceIOSpeechActivityHasEnded) {
// 用户停止说话
}
};
OSStatus err = AudioUnitSetProperty(
auVoiceIO,
kAUVoiceIOProperty_MutedSpeechActivityEventListener,
kAudioUnitScope_Global,
0,
&listener,
sizeof(AUVoiceIOMutedSpeechActivityEventListener)
);
// 用户点击静音时
UInt32 muteUplinkOutput = 1;
AudioUnitSetProperty(
auVoiceIO,
kAUVoiceIOProperty_MuteOutput,
kAudioUnitScope_Global,
0,
&muteUplinkOutput,
sizeof(muteUplinkOutput)
);
关键点:
- 监听器只在语音活动状态变化时触发,不会连续重复调用
- 必须通过
kAUVoiceIOProperty_MuteOutputAPI 实现静音,系统才能正确检测 - 事件只有两种:
kAUVoiceIOSpeechActivityHasStarted和kAUVoiceIOSpeechActivityHasEnded
AVAudioEngine 实现
(11:08)
let listener = { (event: AVAudioVoiceProcessingSpeechActivityEvent) in
if event == .started {
// 用户静音时开始说话,提示解除静音
} else if event == .ended {
// 用户停止说话
}
}
inputNode.setMutedSpeechActivityEventListener(listener)
// 用户点击静音时
inputNode.isVoiceProcessingInputMuted = true
关键点:
setMutedSpeechActivityEventListener注册监听器- 静音必须使用
isVoiceProcessingInputMuted属性,不能用其他方式 - Swift 的枚举值是
.started和.ended,比 C 版本更简洁
Core Audio HAL 语音活动检测(macOS 专用)
(12:31)对于未接入 Apple 语音处理 API 的 macOS 应用,可以使用 HAL API:
// 启用输入设备的语音活动检测
const AudioObjectPropertyAddress kVoiceActivityDetectionEnable = {
kAudioDevicePropertyVoiceActivityDetectionEnable,
kAudioDevicePropertyScopeInput,
kAudioObjectPropertyElementMain
};
UInt32 shouldEnable = 1;
AudioObjectSetPropertyData(deviceID, &kVoiceActivityDetectionEnable, 0, NULL, sizeof(UInt32), &shouldEnable);
// 注册状态监听器
const AudioObjectPropertyAddress kVoiceActivityDetectionState = {
kAudioDevicePropertyVoiceActivityDetectionState,
kAudioDevicePropertyScopeInput,
kAudioObjectPropertyElementMain
};
AudioObjectAddPropertyListener(deviceID, &kVoiceActivityDetectionState, listener_callback, NULL);
监听器实现:
OSStatus listener_callback(
AudioObjectID inObjectID,
UInt32 inNumberAddresses,
const AudioObjectPropertyAddress* inAddresses,
void* inClientData
) {
UInt32 voiceDetected = 0;
UInt32 propertySize = sizeof(UInt32);
OSStatus status = AudioObjectGetPropertyData(
inObjectID, &kVoiceActivityDetectionState, 0, NULL, &propertySize, &voiceDetected
);
if (status == kAudioHardwareNoError) {
if (voiceDetected == 1) {
// 检测到语音活动
} else {
// 未检测到语音活动
}
}
return status;
}
关键点:
- 检测的是回声消除后的麦克风输入,适合语音通话场景
- 检测与应用的静音状态无关,需要应用自己结合静音状态做判断
- 建议使用 HAL 的进程静音 API,可以熄灭菜单栏的录音指示灯,保护用户隐私
核心启发
-
做智能音量调节的通话应用
- 做什么:在语音通话中自动调节背景音乐/音效音量,说话时降低、不说话时恢复
- 为什么值得做:以前需要开发者自己写音频分析和音量控制逻辑,现在用
enableAdvancedDucking一行代码就能实现 FaceTime 级别的智能闪避 - 怎么开始:用 AVAudioEngine 启用语音处理,设置
voiceProcessingOtherAudioDuckingConfiguration,将非通话音频路由到另一个节点
-
做带”你静音了”提示的会议应用
- 做什么:检测用户静音时是否说话,在界面上弹出提示
- 为什么值得做:这是 FaceTime 已有的功能,用户对这个交互有预期。用
setMutedSpeechActivityEventListener几行代码就能实现 - 怎么开始:注册监听器后,在
.started事件中显示一个浮层提示”你正在静音状态说话”,附带一键解除静音按钮
-
做播客/直播的语音触发工具
- 做什么:利用语音活动检测实现语音触发功能,比如检测到主播说话时自动切换场景、显示字幕
- 为什么值得做:HAL API 提供的语音活动检测基于回声消除后的纯净音频,比简单的音量阈值更准确
- 怎么开始:在 macOS 上用
kAudioDevicePropertyVoiceActivityDetectionEnable启用检测,注册监听器,结合自己的业务逻辑触发动作
-
做多人游戏语音聊天
- 做什么:游戏内语音聊天时,游戏音效和队友语音可以共存,系统智能调节音量
- 为什么值得做:tvOS 17 首次支持语音处理 API,结合 Continuity Camera,Apple TV 上的游戏也能有高质量语音聊天
- 怎么开始:在 tvOS 上启用 AUVoiceIO 或 AVAudioEngine 的语音处理模式,根据游戏场景选择
min(保留游戏氛围)或max(确保指令清晰)闪避级别
关联 Session
- Discover Continuity Camera for tvOS — 了解 tvOS 上如何使用 Continuity Camera,与语音处理 API 配合使用
- Support voice input in your app — 在应用中支持语音输入
- Add SharePlay to your app — 了解 SharePlay 如何与语音处理配合实现共享媒体体验
评论
GitHub Issues · utterances