通过遵循以下一般原则,可以显著减少音频驱动程序对系统的性能影响:
尽量减少在正常操作期间运行的代码。
仅在必要时运行代码。
考虑系统资源消耗总量(而不仅仅是 CPU 加载)。
针对速度和大小优化代码。
此外,WavePci 微型端口驱动程序还必须解决多个特定于音频设备的性能问题。 以下讨论主要涉及音频呈现问题,但一些建议的技术也适用于音频捕获。
流服务机制
在讨论性能优化之前,需要了解有关 WavePci 流服务机制的一些背景知识。
处理波次呈现或捕获流时,音频设备需要微型端口驱动程序定期提供服务。 当流有可用的新映射时,驱动程序会将这些映射添加到流的 DMA 队列。 驱动程序还会从队列中删除已处理的任何映射。 有关映射的信息,请参阅 WavePci 延迟。
为了执行服务,微型端口驱动程序提供延迟过程调用 (DPC) 或中断服务例程 (ISR),具体取决于间隔是由系统计时器设置还是由 DMA 驱动的中断设置。 在后一种情况下,DMA 硬件通常会在每次传输完一定数量的流数据时触发中断。
每次执行 DPC 或 ISR 时,它都会确定需要提供服务的流。 DPC 或 ISR 会调用 IPortWavePci::Notify 方法来为流提供服务。 此方法采用流的服务组作为调用参数,该参数是 IServiceGroup 类型的对象。 Notify 方法会调用服务组的 RequestService 方法(请参阅 IServiceSink::RequestService)。
一个服务组对象包含一组服务接收器,这些接收器是 IServiceSink 类型的对象。 IServiceGroup 派生自 IServiceSink,并且两个接口都具有 RequestService 方法。 当 Notify 方法调用服务组的 RequestService 方法时,服务组将在组中的每个服务接收器上调用 RequestService 方法来进行响应。
一个流的服务组至少包含一个服务接收器,端口驱动程序会在创建流后立即将该接收器添加到服务组。 端口驱动程序调用微型端口驱动程序的 IMiniportWavePci::NewStream 方法,以获取指向服务组的指针。 服务接收器的 RequestService 方法是端口驱动程序的流特定服务例程。 此例程执行以下任务:
调用微型端口驱动程序的 IMiniportWavePciStream::Service 方法。
触发自上次执行服务例程以来流上的任何新挂起位置或时钟事件。
如 KS 事件中所述,客户端可以进行注册,以在流到达特定位置或时钟到达特定时间戳时收到通知。 NewStream 方法具有不提供服务组的选择,在这种情况下,端口驱动程序会设置自己的计时器来标记服务例程调用之间的间隔。
与 NewStream 方法一样,微型端口驱动程序的 IMiniportWavePci::Init 方法还会输出指向服务组的指针。 在 Init 调用之后,端口驱动程序会将其服务接收器添加到服务组。 此特定服务接收器包含整个筛选器的服务例程。 (上一段描述了与筛选器上的引脚关联的流的服务接收器。)此服务例程会调用微型端口驱动程序的 IMiniportWavePci::Service 方法。 每次 DPC 或 ISR 使用筛选器的服务组调用 Notify 时,都会执行该服务例程。 Init 方法具有不提供服务组的选项,在这种情况下,端口驱动程序永远不会调用其筛选器服务例程。
硬件中断
某些微型端口驱动程序生成过多或不够多的硬件中断。 在某些采用 DirectSound 硬件加速的 WavePci 呈现设备中,仅当提供的映射即将耗尽且呈现引擎面临枯竭风险时,才会发生硬件中断。 在其他硬件加速的 WavePci 设备中,每个映射完成或其他相对较小的间隔都会发生硬件中断。 在这种情况下,ISR 经常发现自己无事可做,但每个中断在寄存器交换和缓存重新加载时仍会消耗系统资源。 提高驱动程序性能的第一步是尽可能减少中断数,从而不会造成枯竭风险。 消除不必要的中断后,可以通过将 ISR 设计为更高效地执行来实现额外的性能提升。
在某些驱动程序中,每次发生硬件中断时都会调用流的 Notify 方法,因此 ISR 会浪费时间,而不管流是否实际正在运行。 如果没有流处于 RUN 状态,则 DMA 将处于非活动状态,并且将浪费尝试获取任何流中的新事件的映射、释放映射或检查所用的时间。 在高效的驱动程序中,ISR 会在调用流的 Notify 方法之前验证流是否正在运行。
但是,具有这种 ISR 的驱动程序需要确保在流退出 RUN 状态时触发流上的任何挂起事件。 否则,可能会延迟或丢失事件。 此问题仅在早于 Microsoft Windows XP 的操作系统上进行 RUN 到 PAUSE 过渡时出现。 在 Windows XP 及更高版本中,当流将状态从 RUN 更改为 PAUSE 时,端口驱动程序会自动发出任何未完成位置事件的信号。 但是,在较旧的操作系统中,微型端口驱动程序负责在流暂停后立即最后一次调用 Notify,以触发任何未完成事件。 有关详细信息,请参阅下面的“PAUSE/ACQUIRE 优化”。
典型的 WavePci 微型端口驱动程序从 KMixer 系统驱动程序管理单个播放流。 KMixer 的当前实现至少使用三个映射 IRP 来缓冲播放流。 每个 IRP 都包含足够的缓冲区存储,大约可存储 10 毫秒的音频。 如果每次 DMA 控制器完成 IRP 中的最终映射时,微型端口驱动程序都触发硬件中断,则中断应以相当规律的 10 毫秒间隔进行,这很频繁,足以使 DMA 队列无法枯竭。
计时器 DPC
如果驱动程序管理任何硬件加速的 DirectSound 流,它应使用计时器 DPC(请参阅计时器对象和 DPC),而不是 DMA 驱动的硬件中断。 同样,PCI 上的 WavePci 设备卡具有板载计时器,可以使用计时器驱动的硬件中断,而不是 DPC。
对于 DirectSound 缓冲区,可以将整个缓冲区附加到单个 IRP。 如果缓冲区很大,并且微型端口驱动程序仅在到达缓冲区末尾时才计划硬件中断,则连续中断可能会相隔很远,使得 DMA 队列枯竭。 此外,如果驱动程序管理大量硬件加速的 DirectSound 流,并且每个流都会生成自己的中断,则所有中断的累积影响可能会降低系统性能。 在这些情况下,微型端口驱动程序应避免使用硬件中断来计划单个流的服务。 相反,它应为计划以定期计时器生成的间隔运行的单一 DPC 中的所有流提供服务。
通过将计时器间隔设置为 10 毫秒,连续 DPC 执行之间的间隔类似于之前针对单个 KMixer 播放流中的硬件中断描述的间隔。 因此,除了硬件加速的 DirectSound 流外,DPC 还可以处理 KMixer 播放流。
当最后一个流退出 RUN 状态时,微型端口驱动程序应禁用计时器 DPC,以避免浪费系统 CPU 周期。 禁用 DPC 后,驱动程序就应确保刷新以前正在运行的流上挂起的任何时钟或位置事件。 在 Windows 98/Me 和 Windows 2000 中,驱动程序应调用 Notify,以触发要暂停的流上的任何挂起事件。 在 Windows XP 及更高版本中,当流退出 RUN 状态时,操作系统会自动触发任何挂起的事件,而无需微型端口驱动程序进行干预。
PAUSE/ACQUIRE 优化
在 Windows 98/Me 和 Windows 2000 中,WavePci 端口驱动程序的流服务例程(RequestService 方法)始终生成对微型端口驱动程序的 IMiniportWavePciStream::Service 方法的调用,而不考虑流是否处于 RUN 状态。 在这些操作系统中,Service 方法应在花费时间执行实际工作之前检查流是否正在运行。 (但是,如果微型端口驱动程序的 DPC 或 ISR 已优化为仅针对正在运行的流调用 Notify,运行的流,将此检查添加到 Service 方法可能会变得冗余。)
在 Windows XP 及更高版本中,无需进行此优化,因为 Notify 方法仅在正在运行的流上调用 Service 方法。
使用 IPreFetchOffset 接口
DirectSound 用户熟悉播放光标和写入光标的双重概念。 播放光标指示在从设备发出的数据流中的位置(驱动程序当前在 DAC 上对样本的最佳估计)。 写入位置是客户端写入其他数据的下一个安全位置流中的位置。 对于 WavePci,默认假设是写入光标位于请求的最后一个映射的末尾。 如果微型端口驱动程序已获取大量未完成的映射,则播放光标和写入光标之间的偏移量可能非常大,足以使某些 WHQL 音频位置测试失败。 在 Windows XP 及更高版本中,IPreFetchOffset 接口解决了这些问题。
微型端口驱动程序使用 IPreFetchOffset 指定总线主硬件的预提取特征,这在很大程度上取决于硬件 FIFO 大小。 音频子系统使用此数据设置播放光标和写入光标之间的常量偏移量。 此常量偏移量可能会显著小于默认偏移量,它利用这样一个事实,即即使在将映射交给硬件之后,也可以将数据写入映射,只要播放光标距离写入数据的位置足够远即可。 (此语句假定驱动程序不会复制或操作映射中的数据。)典型的偏移量可能按 64 个样本的阶数,具体取决于引擎设计。 由于偏移量很小,WavePci 驱动程序可以会完全响应且功能正常,同时仍请求大量映射。
请注意,DirectSound 当前将硬件加速引脚的写入光标拉长 10 毫秒。
有关详细信息,请参阅预提取偏移量。
处理映射中的数据
如果可能,请避免硬件驱动程序触摸映射中的数据。 应将映射中包含的任何软件处理都拆分为独立于硬件驱动程序的软件筛选器。 让硬件驱动程序执行此类处理时,会降低其效率并造成延迟问题。
硬件驱动程序应努力对其实际硬件功能保持透明。 驱动程序不应声称为实际在软件中执行的数据转换提供硬件支持。
同步基元
如果驱动程序的代码旨在避免尽可能避免被阻止,则驱动程序现在和将来不太可能出现死锁或性能问题。 具体而言,驱动程序的执行线程应努力运行到完成状态,而没有在等待另一个线程或资源时停止的风险。 例如,驱动程序线程可以使用 InterlockedXxx函数(例如,请参阅 InterlockedIncrement),协调对某些共享资源的访问权限,而不会面临被阻止的风险。
尽管这些技术非常强大,但可能无法从执行路径安全地删除所有自旋锁、互斥体和其他阻塞同步基元。 明智地使用 InterlockedXxx 函数,了解无限期等待可能会导致数据枯竭。
最重要的是,不要创建自定义同步基元。 可能会根据需要修改内置 Windows 基元(互斥体、自旋锁等),以支持新的计划程序功能,并且几乎可以肯定使用自定义构造的驱动程序将来无法正常工作。
IPinCount 接口
在 Windows XP 及更高版本中,IPinCount 接口提供了一种方法,让微型端口驱动程序可更准确地考虑分配引脚时消耗的硬件资源。 通过调用微型端口驱动程序的 IPinCount::PinCount 方法,端口驱动程序可执行以下操作:
向微型端口驱动程序公开筛选器的当前引脚计数(由端口驱动程序维护)。
使微型端口驱动程序有机会修改引脚计数,以动态反映硬件资源的当前可用性。
对于某些音频设备,具有不同属性(3-D、立体声/单声道等)的波次流在消耗的硬件资源数量方面也可能有不同的“权重”。 打开或关闭“轻型”流时,驱动程序会将可用引脚计数递增或递减一。 但是,打开“重型”流时,微型端口驱动程序可能需要将可用引脚计数递减二,而不是递减一,以便更准确地指示可以使用剩余资源创建的引脚数。
关闭重型流时,需按相反顺序执行此过程。 可用引脚计数可能会增加一以上,以反映可以根据新释放的资源创建两个或多个轻型流的事实。