多语音助手

多语音助手平台为 Windows 中的其他语音助手提供支持。 这允许在 Windows 设备上提供其他助手,例如电脑和可穿戴设备(如 HoloLens)。 在使用一组受支持的关键字模式的同一设备上,可以激活多个语音助手。

注意

从 Windows 10 版本 1903 开始,支持多个语音助手。

有关实现 Windows Cortana 的信息,请参阅语音激活

语音激活

语音激活是一项功能,让用户可通过说出特定短语,从各种设备电源状态调用语音识别引擎。

实现语音激活是一个重要项目,也是 SoC 供应商完成的任务。 OEM 可以联系其 SoC 供应商,了解有关其 SoC 实现语音激活的信息。

语音激活允许用户使用语音在活动上下文(即当前屏幕上的内容)之外快速参与语音助手体验。 用户通常希望能够即时访问体验,而无需与设备进行物理交互或触控设备。 对于 Xbox 用户,这可能是因为不想查找和连接控制器。 对于电脑用户,他们可能需要快速访问体验,而无需执行多个鼠标、触控和/或键盘操作,就像厨房中的计算机一样。

语音激活由关键字检测工具 (KWS) 提供支持,当检测到关键短语时该检测工具会做出反应。 关键短语可能包括“你好 Contoso”等关键字。关键字检测描述通过硬件或软件检测关键字。

关键短语自身(“你好 Contoso”)可作为暂存命令表达,或者后跟一个包含链接命令的语音操作(“你好 Contoso,我的下一次会议在哪里开?”)

Microsoft 提供 OS 默认关键字检测工具(软件关键字检测工具),用于在硬件关键字检测不可用的情况下提供语音助手体验。 虽然此功能目前可用于 Cortana,但可能需要其他 Microsoft 配置,才能载入其他语音助手来执行两阶段关键字检测。 有关详细信息,请联系 AskMVA@Microsoft.com

如果 KWS 从低功率状态唤醒设备,则解决方案称为语音唤醒 (WoV)。 有关详细信息,请参阅本文后面部分的语音唤醒

术语词汇表

此术语表汇总了与语音激活相关的术语。

Term 示例/定义
暂存命令 示例:你好 Contoso <暂停,等待助手 UI> 天气怎样? 这有时称为“双枪命令”或“仅关键字”。
链接命令 示例:你好 Contoso,天气怎样? 这有时称为“单枪命令”。
语音激活 示例:“你好 Contoso”在预定义的激活关键短语中检测到关键字的场景。
语音唤醒 (WoV) 支持语音激活的技术,从屏幕关闭、低功率状态到屏幕打开、全功率状态。
来自新式待机的 WoV 从新式待机 (S0ix) 屏幕关闭状态到屏幕打开、全功率 (S0) 状态的语音唤醒。
新式待机 Windows 低功率空闲基础结构 - Windows 10 中连接待机 (CS) 的后续任务。 新式待机的第一种状态是屏幕关闭时。 最深的睡眠状态是处于 DRIPS/复原状态时。 有关详细信息,请参阅新式待机
KWS 关键字检测工具 - 提供“你好 Contoso”检测的算法。
SW KWS 软件关键字检测工具 – 在主机 (CPU) 上运行的 KWS 实现。 对于“你好小娜”,Windows 中包含 SW KWS。
HW KWS 硬件关键字检测工具 – 在硬件上卸载运行的 KWS 实现。
突发缓冲区 用于存储在 KWS 检测时可以“突发”的 PCM 数据的循环缓冲区,以便包括触发 KWS 检测的所有音频。
事件检测器 OEM 适配器 充当 Windows 语音助手堆栈和驱动程序之间的中介的用户模式组件。
模型 KWS 算法使用的声学模型数据文件。 数据文件是静态文件。 模型已本地化,每个区域设置一个。
MVA 多语音代理 - 支持多个代理的 HWKWS DDI。
SVA 单语音代理 - 以前的 HWKWS DDI,它仅支持单个代理 (Cortana)。

集成硬件关键字检测工具

若要实现硬件关键字检测工具 (HW KWS),SoC 供应商必须完成以下任务。

硬件卸载关键字检测工具 (HW KWS) WoV 要求

  • HW KWS WoV 在 S0 工作状态和 S0 睡眠状态(也称为新式待机)期间受支持。
  • 从 S3 开始不支持 HW KWS WoV。

AEC

DSP 可以在捕获突发音频时执行 AEC,也可以稍后通过软件 APO 来完成。 若要使用 KWS 突发数据执行软件 AEC,必须从捕获突发数据时获得对应的环回音频。 为此,已创建突发输出的自定义音频格式,该格式会将环回音频交错到突发音频数据中。

从 Windows 版本 20H1 开始,Microsoft AEC APO 知道这种交错格式,并可以将其用于执行 AEC。 有关详细信息,请参阅 KSPROPERTY_INTERLEAVEDAUDIO_FORMATINFORMATION

验证

使用语音激活管理器 2 测试验证对 KSPROPSETID_SoundDetector2 属性的 HW 支持。

示例代码概览

GitHub 上实现语音激活的音频驱动程序示例代码是 SYSVAD 虚拟音频适配器示例的一部分。 建议将此代码用作起点。

有关 SYSVAD 示例音频驱动程序的详细信息,请参阅示例音频驱动程序

关键字识别系统信息

语音激活音频堆栈支持

用于启用语音激活的音频堆栈外部接口充当语音平台和音频驱动程序的通信管道。 外部接口分为三个部分。

音频终结点属性

通常会生成音频终结点图形。 图形的处理速度比实时捕获快。 所捕获缓冲区上的时间戳仍为 true。 具体而言,时间戳将正确反映过去捕获和缓冲的数据,并且现在已突发。

蓝牙旁路音频流理论

驱动程序会像往常一样公开其捕获设备的 KS 筛选器。 此筛选器支持多个 KS 属性和 KS 事件,可配置、启用检测事件并发出相应信号。 该筛选器还包括一个标识为关键字检测工具 (KWS) 引脚的额外引脚工厂。 此引脚用于从关键字检测工具流式传输音频。

属性为:KSPROPSETID_SoundDetector2

所有 KSPROPSETID_SoundDetector2 属性都可使用 KSSOUNDDETECTORPROPERTY 数据结构调用。 此数据结构包含一个 KSPROPERTY,以及要配备、重置、检测关键字的事件 ID 等。

  • 支持的关键字类型 - KSPROPERTY_SOUNDDETECTOR_PATTERNS。 此属性由操作系统设置,用于配置要检测的关键字。
  • 关键字模式 GUID 列表 - KSPROPERTY_SOUNDDETECTOR_SUPPORTEDPATTERNS。 此属性用于获取标识受支持模式类型的 GUID 列表。
  • 配备 - KSPROPERTY_SOUNDDETECTOR_ARMED。 此读/写属性只是指示是否配备检测器的布尔状态。 OS 将其设置为使用关键字检测器。 OS 可以清除此项以解除连接。 当设置了关键字模式,并在检测到关键字后,驱动程序会自动清除此模式。 (OS 必须重新配备。)
  • 匹配结果 - KSPROPERTY_SOUNDDETECTOR_RESET 用于在启动时重置声音检测器。

在关键字检测时,发送包含 KSNOTIFICATIONID_SoundDetector 的 PNP 通知。 注意:这不是 KSEvent,而是通过 IoReportTargetDeviceChangeAsynchronous 发送的带有有效负载的 PNP 事件。

如下所示,KSNOTIFICATIONID_SoundDetector 在 ksmedia.h 中定义。

// The payload of this notification is a SOUNDDETECTOR_PATTERNHEADER
#define STATIC_KSNOTIFICATIONID_SoundDetector\
    0x6389d844, 0xbb32, 0x4c4c, 0xa8, 0x2, 0xf4, 0xb4, 0xb7, 0x7a, 0xfe, 0xad
DEFINE_GUIDSTRUCT("6389D844-BB32-4C4C-A802-F4B4B77AFEAD", KSNOTIFICATIONID_SoundDetector);
#define KSNOTIFICATIONID_SoundDetector DEFINE_GUIDNAMED(KSNOTIFICATIONID_SoundDetector)

操作顺序

系统启动

  1. OS 会发送 KSPROPERTY_SOUNDDETECTOR_RESET,以清除任何以前的检测器状态,从而将所有检测器重置为已解除配备,并清除先前设置的模式。
  2. OS 会查询 KSPROPERTY_SOUNDDETECTOR_PATTERNS,以检索事件检测器 OEM 适配器的 clsid。
  3. OS 使用事件检测器 oem 适配器来检索受支持的关键字和语言列表。
  4. OS 会注册驱动程序发送的自定义 PNP 通知
  5. OS 会设置所需的关键字模式。
  6. OS 将配备检测器

内部驱动程序和硬件操作

当配备检测器时,硬件可以持续捕获和缓冲小型 FIFO 缓冲区中的音频数据。 (此 FIFO 缓冲区的大小由本文档之外的要求决定,但通常可能为几百毫秒到几秒。)检测算法通过此缓冲区对数据流式传输执行操作。 驱动程序和硬件的设计使得在配备时,在检测到关键字之前,驱动程序与硬件之间没有交互,并且不会中断“应用程序”处理器。 这允许系统在没有其他活动的情况下达到低功率状态。

当硬件检测到关键字时,会生成中断。 在等待驱动程序提供中断时,硬件将继续将音频捕获到缓冲区中,从而确保在缓冲限制内不会丢失关键字后的任何数据。

关键字时间戳

检测关键字后,所有语音激活解决方案都必须缓冲所有用户说出的关键字,包括关键字开始前的 1.6 秒。 音频驱动程序必须提供时间戳,用于标识流中关键短语的开始和结束。

为了支持关键字开始/结束时间戳,DSP 软件可能需要根据 DSP 时钟在内部标记事件的时间戳。 检测到关键字后,DSP 软件将与驱动程序交互,以准备 KS 事件。 驱动程序和 DSP 软件需要将 DSP 时间戳映射到 Windows 性能计数器值。 执行此操作的方法特定于硬件设计。 一种可能的解决方案是让驱动程序读取当前性能计数器,查询当前的 DSP 时间戳,再次读取当前性能计数器,然后估计性能计数器与 DSP 时间之间的关联。 然后,鉴于相关性,驱动程序可以将关键字 DSP 时间戳映射到 Windows 性能计数器时间戳。

IEvent 检测器 OEM 适配器接口

OEM 提供一个 COM 对象实现,该实现充当 OS 和驱动程序之间的中介,帮助计算或分析通过 KSPROPERTY_SOUNDDETECTOR_PATTERNSKSPROPERTY_SOUNDDETECTOR_MATCHRESULT 写入和读取到音频驱动程序的不透明数据。

COM 对象的 CLSID 是由 KSPROPERTY_SOUNDDETECTOR_SUPPORTEDPATTERNS 返回的检测器模式类型 GUID。 OS 会调用传递模式类型 GUID 的 CoCreateInstance 来实例化与关键字模式类型兼容的相应 COM 对象,并在对象的 IEventDetectorOemAdapter 接口上调用方法。

COM 线程模型要求

OEM 的实现可以选择任何 COM 线程模型。

IEventDetectorOemAdapter

接口设计会尝试使对象实现保持无状态。 换句话说,实现不应要求在方法调用之间存储任何状态。 事实上,内部 C++ 类可能不需要除一般实现 COM 对象所需的成员变量之外的任何成员变量。

方法

实现以下方法。

WAVERT 的增强功能

微型端口接口被定义为由 WaveRT 微型端口驱动程序实现。 这些接口提供了简化音频驱动程序、提高 OS 音频管道性能和可靠性或支持新方案的方法。 定义了一个 PnP 设备接口属性,从而允许驱动程序向 OS 提供其缓冲区大小约束的静态表达式。

缓冲区大小

在 OS、驱动程序和硬件之间移动音频数据时,驱动程序在各种约束下运行。 这些约束可能是由于在内存和硬件之间移动数据的物理硬件传输,和/或由于硬件或关联的 DSP 中的信号处理模块所致。

HW-KWS 解决方案必须支持至少 100 毫秒和高达 200 毫秒的音频捕获大小。

通过在具有 KS 流式处理引脚的 KS 筛选器的 KSCATEGORY_AUDIO PnP 设备接口上设置 DEVPKEY_KsAudio_PacketSize_Constraints2 设备属性,驱动程序可表达缓冲区大小约束。 启用 KS 筛选器接口时,此属性应保持有效且稳定。 OS 可以随时读取此值,而无需打开驱动程序的句柄并在驱动程序上调用。

DEVPKEY_KsAudio_PacketSize_Constraints2

DEVPKEY_KsAudio_PacketSize_Constraints2 属性值包含描述物理硬件约束的 KSAUDIO_PACKETSIZE_CONSTRAINTS2 结构(即由于将数据从 WaveRT 缓冲区传输到音频硬件的机制)。 该结构包括一个由 0 个或多个 KSAUDIO_PACKETSIZE_PROCESSINGMODE_CONSTRAINT 结构构成的数组,描述特定于任何信号处理模式的约束。 驱动程序会在调用 PcRegisterSubdevice 之前设置此属性,或者为其流式传输引脚启用其 KS 筛选器接口。

IMiniportWaveRTInputStream

驱动程序会实现此接口,以便更好地协调从驱动程序到 OS 的音频数据流。 如果此接口在捕获流上可用,则 OS 会在此接口上使用方法访问 WaveRT 缓冲区中的数据。 有关详细信息,请参阅 IMiniportWaveRTInputStream::GetReadPacket

IMiniportWaveRTOutputStream

WaveRT 微型端口(可选)会实现此接口,以便从 OS 收到写入进度并返回精确的流位置。 有关详细信息,请参阅 IMiniportWaveRTOutputStream::SetWritePacketIMiniportWaveRTOutputStream::GetOutputStreamPresentationPositionIMiniportWaveRTOutputStream::GetPacketCount

性能计数器时间戳

多个驱动程序例程返回 Windows 性能计数器时间戳,反映设备捕获或显示样本的时间。

在具有复杂 DSP 管道和信号处理的设备中,计算准确的时间戳可能具有挑战性,应该考虑周到地完成。 时间戳不应仅反映将样本传输到 Windows 或从 OS 传输到 DSP 的时间。

  • 在 DSP 中,使用一些内部 DSP 时钟跟踪样本时间戳。
  • 在驱动程序与 DSP 之间,计算 Windows 性能计数器与 DSP 时钟之间的关联。 此操作的过程可以是非常简单(但不太精确)到相当复杂或耗时(但更精确)。
  • 除非考虑这些延迟,否则考虑因信号处理算法或者管道或硬件传输而产生的任何持续延迟。

突发读取操作

本部分介绍突发读取的 OS 和驱动程序交互。 只要驱动程序支持基于数据包的流式处理 WaveRT 模型(包括 IMiniportWaveRTInputStream::GetReadPacket 函数),就会在语音激活方案之外出现突发读取。

将讨论两个突发示例读取方案。 在一种方案中,如果微型端口支持引脚类别 KSNODETYPE_AUDIO_KEYWORDDETECTOR,则在检测到关键字时,驱动程序将开始捕获和内部缓冲数据。 在另一种方案中,如果 OS 未调用 IMiniportWaveRTInputStream::GetReadPacket 来足够快地读取数据,驱动程序可以选择在 WaveRT 缓冲区外部对数据进行内部缓冲。

若要突发在转换到 KSSTATE_RUN 之前捕获的数据,驱动程序必须保留准确的样本时间戳信息,以及缓冲的捕获数据。 时间戳标识所捕获样本的采样即时。

  1. 流过渡到 KSSTATE_RUN 后,驱动程序会立即设置缓冲区通知事件,因为它已有数据可用。

  2. 在此事件中,OS 会调用 GetReadPacket(),以获取有关可用数据的信息。

    a. 驱动程序会返回有效捕获数据的数据包数(从 KSSTATE_STOP 过渡到 KSSTATE_RUN 后的第一个数据包为 0),OS 可以从中派生 WaveRT 缓冲区中的数据包位置,以及相对于流开始的数据包位置。

    b. 驱动程序还会返回与数据包中第一个样本的采样即时相对应的性能计数器值。 请注意,此性能计数器值可能相对较旧,具体取决于硬件或驱动程序(在 WaveRT 缓冲区外部)中缓冲了多少捕获数据。

    c. 如果有更多未读缓冲数据可用,驱动程序将:i. 立即将该数据传输到 WaveRT 缓冲区的可用空间(即 GetReadPacket 返回的数据包未使用的空间),为 MoreData 返回 true,并在从此例程返回之前设置缓冲区通知事件。 或,ii. 程序硬件将下一个数据包突发到 WaveRT 缓冲区的可用空间,为 MoreData 返回 false,并在传输完成后设置缓冲区事件。

  3. OS 使用 GetReadPacket() 返回的信息,从 WaveRT 缓冲区读取数据。

  4. OS 等待下一个缓冲区通知事件。 如果驱动程序在步骤 (2c) 中设置缓冲区通知,则等待可能会立即终止。

  5. 如果驱动程序未在步骤 (2c) 中立即设置事件,驱动程序会在将更多捕获的数据传输到 WaveRT 缓冲区并使其可供 OS 读取后设置该事件

  6. 转到 (2)。

对于 KSNODETYPE_AUDIO_KEYWORDDETECTOR关键字检测器引脚,驱动程序应为至少 5000 毫秒的音频数据分配足够的内部突发缓冲。 如果 OS 在缓冲区溢出之前无法在引脚上创建流,则驱动程序可能会结束内部缓冲活动并释放关联资源。

语音唤醒

语音唤醒 (WoV) 使用户能够通过说出特定的关键字(如“你好 Contoso”),从低功率状态到全功率状态和屏幕打开,激活和查询语音识别引擎。

此功能允许设备在设备空闲且屏幕关闭时始终侦听用户的语音。 这是因为与普通麦克风录制相比,侦听模式使用的功率要少得多。 WoV 允许链接语音短语(如“你好 Contoso,我的下一个预约是什么时间”),以免提调用语音助手的响应。

音频堆栈负责传达唤醒数据(扬声器 ID、关键字触发器、有关置信度的上下文信息),以及通知感兴趣的客户端检测到关键字。

新式待机系统上的验证

使用 HLK 中的使用交流电源进行新式待机语音唤醒基本测试使用直流电源进行新式待机语音唤醒基本测试,可以在新式待机系统上验证来自系统空闲状态的 WoV。 这些测试会检查系统是否具有硬件关键字检测工具 (HW-KWS),是否能够进入最深运行时空闲平台状态 (DRIPS),以及是否能够利用语音命令从新式待机唤醒,其中系统恢复延迟小于或等于一秒。

ACX 和 MVA

音频类 eXtension (ACX) 将为音频域定义 Windows 驱动程序框架 (WDF) 类扩展。 有关 ACX 的详细信息,请参阅 ACX 音频类扩展概述ACX 对象的摘要。 本部分介绍如何使用 ACX 实现 MVA。

ACX 为关键字检测工具使用相同的 KS 基础结构,从而添加一个抽象层来简化驱动程序实现。 使用 ACX 时,将按上述方式使用相同的 OEM DLL,并且保持不变。 ACX 和 Portcls 都需要 IEventDetectorOEMAdapter 接口,并且两者的 OEM 适配器实现没有区别。

AcxKeywordSpotterCreate 函数用于创建与线路设备对象父项关联的 ACX 关键字检测工具不透明对象 (ACXKEYWORDSPOTTER)。

ACXKEYWORDSPOTTER 对象用于替换所有 KSPROPERTY_SOUNDDETECTOR 调用,从而简化 KWS 实现。 可将其用于将 KWS 元素和 KWS 引脚添加到 ACX 线路的过程。 关联的回调负责获取模式、配备、解除配备和重置。 它使用初始化的 ACX_KEYWORDSPOTTER_CONFIG 结构来描述关键字检测工具的配置。

ACX_KEYWORDSPOTTER_CONFIG 结构采用定义以下回调的 ACX_KEYWORDSPOTTER_CALLBACKS 结构

EvtAcxKeywordSpotterRetrieveArm - ACX_KEYWORDSPOTTER_RETRIEVE_ARM 回调。

EvtAcxKeywordSpotterAssignArm - ACX_KEYWORDSPOTTER_ASSIGN_ARM 回调。

EvtAcxKeywordSpotterAssignPatterns - ACX_KEYWORDSPOTTER_ASSIGN_PATTERNS 回调。

EvtAcxKeywordSpotterAssignReset - ACX_KEYWORDSPOTTER_ASSIGN_RESET 回调。

ACX PNP 事件

ACX PNP 事件取代了 KSNOTIFICATIONID_SoundDetector,简化了检测通知事件。 ACX_PNPEVENT_CONFIG_INIT 函数会初始化 ACX_PNPEVENT_CONFIG 结构。 此函数不使用任何输入。

ACX KWS 示例代码

ACX KWS 示例代码显示回调的初始化、关键字元素和关键字检测工具的创建。

{
    NTSTATUS                        status;
    WDF_OBJECT_ATTRIBUTES           attributes;
    ACX_KEYWORDSPOTTER_CALLBACKS    keywordSpotterCallbacks;
    ACX_KEYWORDSPOTTER_CONFIG       keywordSpotterCfg;
    PCODEC_KEYWORDSPOTTER_CONTEXT   keywordSpotterCtx;
    ACX_PNPEVENT_CONFIG             keywordEventCfg;
    ACXPNPEVENT                     keywordEvent;

    PAGED_CODE();

    ACX_KEYWORDSPOTTER_CALLBACKS_INIT(&keywordSpotterCallbacks);
    keywordSpotterCallbacks.EvtAcxKeywordSpotterRetrieveArm = CodecC_EvtAcxKeywordSpotterRetrieveArm;
    keywordSpotterCallbacks.EvtAcxKeywordSpotterAssignArm = CodecC_EvtAcxKeywordSpotterAssignArm;
    keywordSpotterCallbacks.EvtAcxKeywordSpotterAssignPatterns = CodecC_EvtAcxKeywordSpotterAssignPatterns;
    keywordSpotterCallbacks.EvtAcxKeywordSpotterAssignReset = CodecC_EvtAcxKeywordSpotterAssignReset;
    
    ACX_KEYWORDSPOTTER_CONFIG_INIT(&keywordSpotterCfg);
    keywordSpotterCfg.Pattern = &CONTOSO_KEYWORDCONFIGURATION_IDENTIFIER2;
    keywordSpotterCfg.Callbacks = &keywordSpotterCallbacks;
    
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_KEYWORDSPOTTER_CONTEXT);
    attributes.ParentObject = Circuit;

接下来,AcxKeywordSpotterCreate 函数用于创建 ACX 关键字检测工具对象,并将其与现有线路相关联。

    status = AcxKeywordSpotterCreate(Circuit, &attributes, &keywordSpotterCfg, Element);
    if (!NT_SUCCESS(status))
    {
        ASSERT(FALSE);
        goto exit;
    }

然后确定关键字检测工具上下文,并将其用于在 NonPagedPoolNx 内存中创建 KeywordDetector。

    
    keywordSpotterCtx = GetCodecKeywordSpotterContext(*Element);
    ASSERT(keywordSpotterCtx);
    
    keywordSpotterCtx->KeywordDetector = (PVOID) new(NonPagedPoolNx, DRIVER_TAG) CKeywordDetector();
    if (keywordSpotterCtx->KeywordDetector == NULL)
    {
        status = STATUS_INSUFFICIENT_RESOURCES;
        ASSERT(FALSE);
        goto exit;
    }

在此示例代码中,添加到上下文中的 CKeywordDetector 类仅作为示例实现提供,该实现模拟示例驱动程序中的关键字检测。 CKeywordDetector 类不是 ACX 框架的一部分,也不是 ACX 上 MVA 实现的必需部分,但可以为开发实际关键字检测工具提供良好的起点。

最后,配置并创建 ACX PnP 事件。

   
    ACX_PNPEVENT_CONFIG_INIT(&keywordEventCfg);
    
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, CODEC_PNPEVENT_CONTEXT);
    attributes.ParentObject = *Element;
    status = AcxPnpEventCreate(Device, *Element, &attributes, &keywordEventCfg, &keywordEvent);
    if (!NT_SUCCESS(status))
    {
        ASSERT(FALSE);
        goto exit;
    }

    keywordSpotterCtx->Event = keywordEvent;

    //
    // Done. 
    //
    status = STATUS_SUCCESS;

}

具有复杂引脚行为的线路,包括 KWS

对于具有复杂引脚行为的线路,例如具有主机引擎和/或 KWS 的线路,驱动程序应禁用 ACX 执行流桥处理,而是创建没有 inmode 的流桥。 此方法可防止 ACX 自动将流关联到流桥。

另请参阅

语音激活