大多数浏览器和
Developer App 均支持流媒体播放。
-
语音处理的新功能
了解如何使用 Apple 语音处理 API 在 IP 语音应用中实现最佳的音频体验。我们将向你展示如何检测某人在静音状态下说话、如何调整其他音频的抑制行为等等。
章节
- 0:00 - Introduction
- 3:19 - Other audio ducking
- 7:55 - Muted talker detection
- 11:37 - Muted talker detection for macOS
资源
相关视频
WWDC23
-
下载
♪ ♪
Julian:大家好 欢迎收看 “语音处理的新功能” 我是 Core Audio 团队的 Julian VoIP App 已经变得 比以往任何时候都重要 它帮助人们与同事 朋友和家人保持联系 语音聊天的音频质量 在提供良好的用户体验方面 起着关键作用 要实现在任何情况下 都把音频处理地很清晰 非常重要 也非常具有挑战性 这就是 Apple 提供语音处理 API 的原因 这样 当任何人使用你的 App 聊天时 无论他们处于什么样的声环境 无论使用什么 Apple 产品 无论连接了什么音频配件 他们始终都可以享受到 绝佳的音频体验 Apple 的语音处理 API 被广泛应用在很多 App 中 包括我们自己的 FaceTime 通话和电话 App 它提供了一流的音频信号处理 包括回声消除 噪音抑制 自动增益控制 以增强语音聊天音频质量 它的表现是由声学工程师 针对每种 Apple 产品型号 结合每种类型的 音频设备进行调整的 以应对它们不同的声学特性 使用 Apple 的语音处理 API 还可以让用户完全控制 App 的麦克风模式设置 包括标准 语音突显和宽谱 我们强烈建议 你在 VoIP App 中 使用 Apple 的语音处理 API Apple 的语音处理 API 有两种选择 第一个选择是叫作 AUVoiceIO 的 I/O 音频单元 也叫作 AUVoiceProcessingIO 这个选择适用于 需要与 I/O 音频单元直接互动的 App
第二个选择是 AVAudioEngine 更确切地说 是启用 AVAudioEngine 的“语音处理”模式
AVAudioEngine 是更高级的 API 一般来说 它更容易使用 它会减少你在处理音频时 要写的代码量 两个选择都会 提供相同的语音处理能力 那有什么新功能呢? 我们将首次在 Apple tvOS 上 提供语音处理 API 想了解更多细节 请查看讲座 “了解 Apple tvOS 的连续互通相机” 我们还为 AUVoiceIO 和 AVAudioEngine 添加了几个新的 API 为你提供更多的语音处理控制 帮助你实现新的功能
第一个 API 是帮助你 控制对其他音频的抑制行为 我等下为你解释这是什么意思 第二个 API 是帮助你为 App 实现 静音发音检测功能 在本次讲座中 我将重点介绍这两个新 API 的细节 我想谈的第一个 API 是 “抑制其它音频” 在我们深入讨论这个 API 之前 让我解释一下什么是其它音频 以及为什么抑制很重要 当你使用 Apple 的语音处理 API 时 我们看一下播放音频发生了什么 你的 App 提供了 一个经过 Apple 语音处理的 语音聊天音频流 并播放到输出设备上 不过 可能还有 其他音频流在同时播放 例如 你的 App 可能在播放 另一个没有经过 语音处理 API 渲染的音频流
也可能有其它 App 和你的 App 同时在播放音频 所以来自你的 App 之外的音频流 都会被 Apple 语音处理 视为“其它音频” 你的语音音频 在被播放到输出设备之前 会与其它音频混合在一起 对于语音聊天 App 播放音频 通常会优先考虑语音聊天音频 因此我们要抑制其它音频的音量 以提高语音音频的清晰度 过去 我们对其它音频 应用了固定量的抑制 这对大多数 App 来说效果很好 如果你的 App 满意现有的抑制行为 那么你不需要做任何改动 不过 我们了解到一些 App 希望对抑制行为 拥有更多的控制 这个 API 将帮助你实现这一目标
让我们先来看看 AUVoiceIO 中的这个 API 等下再看 AVAudioEngine 对 AUVoiceIO 来说 这是其它音频抑制配置的结构 它提供了对抑制的两个方面的控制: 抑制的模式 即 mEnableAdvancedDucking 和抑制的量 即 mDuckingLevel 对于 mEnableAdvancedDucking 默认情况下 它是禁用的 一旦启用 它将根据聊天参与者中 任何一方的语音活动 动态地调整抑制等级 换句话说 当任何一方用户说话时 它会应用更多的抑制 而当没有用户说话时 它就会降低抑制 这和 FaceTime 通话 同播共享中的抑制非常类似 FaceTime 通话双方都不说话时 媒体播放音量很高 一旦有人开始说话 媒体播放音量就会降低
接下来是 mDuckingLevel 它有四个级别的控件: 默认 (Default)、最小 (Min)、 中等 (Mid) 和最大 (Max) 默认 (Default) 抑制等级的 抑制量和我们一直应用的相同 我们将继续使用它为默认设置 最小 (Min) 抑制等级 会最小化我们应用的抑制量 换句话说 如果你想 让其他音频的音量尽可能大 你就可以使用这一设置 相反 最大 (Max) 抑制等级 会最大化我们应用的抑制量 一般来说 选择较高的抑制等级 会帮助提升语音聊天的清晰度
这两个控件可以单独使用 当结合使用时 你可以灵活地 控制抑制行为
我们已经介绍了抑制配置的作用 现在你可以创建 适合你的 App 的配置了 例如 在这里我将启用高级抑制 选择抑制等级为最小
然后我将通过 kAUVoiceIOProperty_OtherAudioDuckingConfiguration 把这个抑制配置设置为 AUVoiceIO
对 AVAudioEngine 用户来说 API 看起来非常相似 这是其他音频抑制配置的结构定义 这是抑制等级的枚举定义
要在 AVAudioEngine 中 使用这个 API 你首先要在引擎的输入节点上 启用语音处理
然后设置抑制配置
最后 在输入节点上设置配置 接下来 让我们谈谈另一个API 它可以帮助你 在你的 App 中 实现一个非常有用的功能 你是否在线上会议中 遇到过这种情况 你以为你在和同事或朋友聊天 但没过多久 你发现你在静音状态 没有人听到 你的精彩观点或有趣的故事? 是的 这很尴尬 在你的 App 中添加 静音发言检测功能是非常有用的 就像这里的 FaceTime 通话一样
这就是为什么 我们要为你提供一个 API 来检测静音状态下是否有人说话 它最初是在 iOS 15 中引入的 现在我们要将它引入到 macOS 14 和 Apple tvOS 17 以下是如何 使用该 API 的高级概述 首先 你需要向 AUVoiceIO 或 AVAudioEngine 提供一个侦听代码块 以在检测到静音状态 有人说话时接收通知 你提供的侦听代码块会在 静音时有人开始说话或停止说话时 被调用 然后实现此类通知的处理代码 例如 如果通知 显示用户在静音状态下开始说话 你可能会想提示其取消静音 最后 需要通过 AUVoiceIO 或 AVAudioEngine 的静音 API 才能实现静音
让我带你看看 AUVoiceIO 的一些代码示例 我们稍后会讲到 AVAudioEngine 的例子 首先 准备一个 处理通知的侦听代码块
该块有一个 AUVoiceIOSpeechActivityEvent 类型的参数 它可以是以下两个值之一: SpeechActivityHasStarted 或 SpeechActivityHasEnded
每当语音活动事件 在静音期间发生变化时 侦听代码块就会被调用
该块中就是你实现 如何处理该事件的地方 例如 当收到 SpeechActivityHasStarted 事件时 你可能会想提示用户取消静音 一旦你准备好了该侦听代码块 通过 kAUVoiceIOProperty_MutedSpeechActivityEventListener 向 AUVoiceIO 注册这个块
当用户要静音时 通过静音 API kAUVoiceIOProperty_MuteOutput 实现静音
你的侦听代码块只有在以下情况 才会被调用 一:用户被静音 二:语音活动状态改变时
语音活动的持续存在或持续不存在 都不会导致多余的通知
对 AVAudioEngine 用户来说 实例非常相似 在引擎的输入节点上 启用语音处理后 准备一个用于处理通知的侦听代码块
然后在输入节点上注册该侦听代码块
当用户需要静音时 使用 AVAudioEngine 的语音处理 API 来静音
现在 我们已经讨论了 用 AUVoiceIO 和 AVAudioEngine 实现检测静音状态下 是否有人说话的功能 对于那些还没有准备好 采用 Apple 的语音处理 API 的人来说 我们会提供一个替代方案 来帮助你实现这个功能
这一替代方案 只能通过 CoreAudio HAL API 在 macOS 上使用 即 Hardware Abstraction Layer API 有两个新的 HAL 属性 你可以结合使用它们 以帮助你检测语音活动 首先 通过 kAudioDevicePropertyVoiceActivityDetectionEnable 在输入设备上启用语音活动检测 然后 在 kAudioDevicePropertyVoiceActivityDetectionState 上 注册一个 HAL 属性侦听器 只要语音活动状态有变化 这个 HAL 属性侦听器就会被调用 当你的 App 被属性侦听器通知时 查询该属性以获得其当前值
现在 让我用一些代码例子 来带你了解这些
要在输入设备上启用语音活动检测 首先要构建 HAL 属性地址
然后在输入设备上 设置该属性以启用它
接下来 若要在语音活动检测状态 属性上注册一个侦听器 就要构建 HAL 属性地址 然后提供你的属性侦听器
这里的“listener_callback” 是你的侦听器函数的名称
这是一个关于 如何实现属性侦听器的例子
侦听器符合此函数签名
在这个例子中 我们假设这个侦听器 只注册了一个 HAL 属性 这意味着当它被调用时 对于哪个 HAL 属性有变化 是没有歧义的
如果你将同一个侦听器注册到了 有多个 HAL 属性的通知上 那么你必须首先 通过 inAddresses 数组来查看 到底是什么发生了变化
在处理这个通知时 查询 VoiceActivityDetectionState 属性 以获得其当前值
然后在处理该值时 实现你自己的逻辑
关于这些语音活动检测 HAL API 有一些重要的细节 首先 它是从被回声消除的 麦克风输入中 检测语音活动的 所以它是 语音聊天 App 的理想选择
其次 这种检测工作 不受进程的静音状态的影响 为了用它来实现静音检测功能 你的 App 需要实现额外的逻辑 将语音活动状态和静音状态结合起来 如果 HAL API 用户要实现静音 我们强烈建议 使用 HAL 的进程静音 API 它可以抑制菜单栏中的录音指示灯 让用户相信他们的隐私 在静音状态下得到了保护 让我们来回顾一下 今天所谈到的内容 我们谈到了 Apple 的 语音处理 API 以及我们推荐 将它用于 VoIP App 的原因 我们谈到了抑制其他音频 以及控制抑制行为的 API 并用代码举例说明了 如何通过 AUVoiceIO 和 AVAudioEngine 使用它 我们还用 AUVoiceIO 和 AVAudioEngine 的代码例子 讲了如何实现 检测静音状态下是否有人说话 对于还没有采用 Apple 的 语音处理 API 的用户 我们还展示了在 macOS 上 使用 Core Audio HAL API 的替代方案 我们期待你 利用 Apple 的语音处理 API 构建出色的 App 感谢观看! ♪ ♪
-
-
5:50 - Other audio ducking
// Insert code snipp297struct AUVoiceIOOtherAudioDuckingConfiguration { Boolean mEnableAdvancedDucking; AUVoiceIOOtherAudioDuckingLevel mDuckingLevel; };et. typedef CF_ENUM(UInt32, AUVoiceIOOtherAudioDuckingLevel) { kAUVoiceIOOtherAudioDuckingLevelDefault = 0, kAUVoiceIOOtherAudioDuckingLevelMin = 10, kAUVoiceIOOtherAudioDuckingLevelMid = 20, kAUVoiceIOOtherAudioDuckingLevelMax = 30 };
-
6:48 - Other audio ducking
const AUVoiceIOOtherAudioDuckingConfiguration duckingConfig = { .mEnableAdvancedDucking = true, .mDuckingLevel = AUVoiceIOOtherAudioDuckingLevel::kAUVoiceIOOtherAudioDuckingLevelMin }; // AUVoiceIO creation code omitted OSStatus err = AudioUnitSetProperty(auVoiceIO, kAUVoiceIOProperty_OtherAudioDuckingConfiguration, kAudioUnitScope_Global, 0, &duckingConfig, sizeof(duckingConfig));
-
6:50 - Other audio ducking
const AUVoiceIOOtherAudioDuckingConfiguration duckingConfig = { .mEnableAdvancedDucking = true, .mDuckingLevel = AUVoiceIOOtherAudioDuckingLevel::kAUVoiceIOOtherAudioDuckingLevelMin }; // AUVoiceIO creation code omitted OSStatus err = AudioUnitSetProperty(auVoiceIO, kAUVoiceIOProperty_OtherAudioDuckingConfiguration, kAudioUnitScope_Global, 0, &duckingConfig, sizeof(duckingConfig));
-
7:20 - Other audio ducking
public struct AVAudioVoiceProcessingOtherAudioDuckingConfiguration { public var enableAdvancedDucking: ObjCBool public var duckingLevel: AVAudioVoiceProcessingOtherAudioDuckingConfiguration.Level } extension AVAudioVoiceProcessingOtherAudioDuckingConfiguration { public enum Level : Int, @unchecked Sendable { case `default` = 0 case min = 10 case mid = 20 case max = 30 } }
-
7:31 - Other audio ducking
let engine = AVAudioEngine() let inputNode = engine.inputNode do { try inputNode.setVoiceProcessingEnabled(true) } catch { print("Could not enable voice processing \(error)") } let duckingConfig = AVAudioVoiceProcessingOtherAudioDuckingConfiguration(mEnableAdvancedDucking: false, mDuckingLevel: .max) inputNode.voiceProcessingOtherAudioDuckingConfiguration = duckingConfig
-
7:32 - Muted talker detection AUVoiceIO
AUVoiceIOMutedSpeechActivityEventListener listener = ^(AUVoiceIOMutedSpeechActivityEvent event) { if (event == kAUVoiceIOSpeechActivityHasStarted) { // User has started talking while muted. Prompt the user to un-mute } else if (event == kAUVoiceIOSpeechActivityHasEnded) { // User has stopped talking while muted } }; OSStatus err = AudioUnitSetProperty(auVoiceIO, kAUVoiceIOProperty_MutedSpeechActivityEventListener, kAudioUnitScope_Global, 0, &listener, sizeof(AUVoiceIOMutedSpeechActivityEventListener)); // When user mutes UInt32 muteUplinkOutput = 1; result = AudioUnitSetProperty(auVoiceIO, kAUVoiceIOProperty_MuteOutput, kAudioUnitScope_Global, 0, &muteUplinkOutput, sizeof(muteUplinkOutput));
-
11:08 - Muted talker detection AVAudioEngine
let listener = { (event : AVAudioVoiceProcessingSpeechActivityEvent) in if (event == AVAudioVoiceProcessingSpeechActivityEvent.started) { // User has started talking while muted. Prompt the user to un-mute } else if (event == AVAudioVoiceProcessingSpeechActivityEvent.ended) { // User has stopped talking while muted } } inputNode.setMutedSpeechActivityEventListener(listener) // When user mutes inputNode.isVoiceProcessingInputMuted = true
-
12:31 - Voice activity detection - implementation with HAL APIs
// Enable Voice Activity Detection on the input device const AudioObjectPropertyAddress kVoiceActivityDetectionEnable{ kAudioDevicePropertyVoiceActivityDetectionEnable, kAudioDevicePropertyScopeInput, kAudioObjectPropertyElementMain }; OSStatus status = kAudioHardwareNoError; UInt32 shouldEnable = 1; status = AudioObjectSetPropertyData(deviceID, &kVoiceActivityDetectionEnable, 0, NULL, sizeof(UInt32), &shouldEnable); // Register a listener on the Voice Activity Detection State property const AudioObjectPropertyAddress kVoiceActivityDetectionState{ kAudioDevicePropertyVoiceActivityDetectionState, kAudioDevicePropertyScopeInput, kAudioObjectPropertyElementMain }; status = AudioObjectAddPropertyListener(deviceID, &kVoiceActivityDetectionState, (AudioObjectPropertyListenerProc)listener_callback, NULL); // “listener_callback” is the name of your listener function
-
13:13 - Voice activity detection - listener_callback implementation
OSStatus listener_callback( AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress* __nullable inAddresses, void* __nullable inClientData) { // Assuming this is the only property we are listening for, therefore no need to go through inAddresses UInt32 voiceDetected = 0; UInt32 propertySize = sizeof(UInt32); OSStatus status = AudioObjectGetPropertyData(inObjectID, &kVoiceActivityState, 0, NULL, &propertySize, &voiceDetected); if (kAudioHardwareNoError == status) { if (voiceDetected == 1) { // voice activity detected } else if (voiceDetected == 0) { // voice activity not detected } } return status; };
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。