ALSA 压缩卸载 API

Pierre-Louis.Bossart <pierre-louis.bossart@linux.intel.com>

Vinod Koul <vinod.koul@linux.intel.com>

概述

自早期以来,ALSA API 的定义就着眼于 PCM 支持或恒定比特率有效载荷,例如 IEC61937。帧中的参数和返回值是常态,这使得扩展现有 API 以支持压缩数据流具有挑战性。

近年来,音频数字信号处理器 (DSP) 集成到了片上系统设计中,并且 DSP 也集成到了音频编解码器中。与基于主机的处理相比,在此类 DSP 上处理压缩数据可以显著降低功耗。 Linux 对此类硬件的支持一直不太好,主要是因为主线内核中缺少通用的 API。

与其通过更改 ALSA PCM 接口的 API 来打破兼容性,不如引入一个新的“压缩数据”API,为音频 DSP 提供控制和数据流接口。

此 API 的设计灵感来自 Intel Moorestown SOC 的 2 年经验,需要进行许多更正才能将 API 上游到主线内核,而不是分段树,并使其可供其他人使用。

要求

主要要求是

  • 字节计数和时间之间的分离。压缩格式可能具有每个文件、每个帧的标头,或者根本没有标头。有效载荷大小可能因帧而异。 因此,在处理压缩数据时,无法可靠地估计音频缓冲区的持续时间。 需要专门的机制来实现可靠的音视频同步,这需要准确报告在任何给定时间呈现的样本数量。

  • 处理多种格式。 PCM 数据只需要指定采样率、通道数和每个样本的位数。 相比之下,压缩数据有多种格式。 音频 DSP 还可以通过固件中嵌入的有限数量的音频编码器和解码器提供支持,或者可以通过动态下载库来支持更多选择。

  • 关注主要格式。 此 API 提供对用于音频和视频捕获和播放的最流行格式的支持。 随着音频压缩技术的进步,可能会添加新的格式。

  • 处理多种配置。 即使对于给定的格式(如 AAC),某些实现也可能支持 AAC 多声道,但支持 HE-AAC 立体声。 同样,WMA10 M3 级别可能需要太多的内存和 CPU 周期。 新的 API 需要提供一种通用的方式来列出这些格式。

  • 仅渲染/抓取。 此 API 不提供任何硬件加速的方式,在这种加速中,PCM 样本被提供回用户空间以进行额外的处理。 相反,此 API 侧重于将压缩数据流式传输到 DSP,并假设解码后的样本被路由到物理输出或逻辑后端。

  • 隐藏复杂性。 现有的用户空间多媒体框架都为每种压缩格式提供了现有的枚举/结构。 这个新的 API 假定存在一个平台特定的兼容层来公开、转换和利用音频 DSP 的功能,例如 Android HAL 或 PulseAudio sinks。 通过构造,常规应用程序不应该使用此 API。

设计

新的 API 与 PCM API 在流量控制方面共享许多概念。 启动、暂停、恢复、排空和停止命令具有相同的语义,无论内容是什么。

内存环形缓冲区划分为一组片段的概念是从 ALSA PCM API 借用的。 但是,只能指定字节大小。

搜索/技巧模式被假定由主机处理。

不支持倒带/快进的概念。 提交到环形缓冲区的数据无法失效,除非删除所有缓冲区。

压缩数据 API 不对数据如何传输到音频 DSP 做任何假设。 从主内存到嵌入式音频集群的 DMA 传输或到外部 DSP 的 SPI 接口是可能的。 与 ALSA PCM 情况一样,公开了一组核心例程; 每个驱动程序实现者都必须编写对一组强制性例程的支持,并且可能需要使用可选的例程。

主要的补充是

get_caps

此例程返回支持的音频格式列表。 查询捕获流上的编解码器将返回编码器,而解码器将列出用于播放流。

get_codec_caps

对于每个编解码器,此例程返回一个功能列表。 目的是确保所有功能都对应于有效的设置,并最大限度地减少配置失败的风险。 例如,对于像 AAC 这样复杂的编解码器,支持的通道数可能取决于特定的配置文件。 如果这些功能是用单个描述符公开的,则可能会出现特定的配置文件/通道/格式组合不受支持的情况。 同样,嵌入式 DSP 的内存和 CPU 周期有限,某些实现可能会使功能列表动态化并依赖于现有的工作负载。 除了编解码器设置外,此例程还会返回实现处理的最小缓冲区大小。 此信息可以是 DMA 缓冲区大小、同步所需的字节数等函数,并且可以由用户空间使用来定义在播放开始之前需要在环形缓冲区中写入多少内容。

set_params

此例程设置为特定编解码器选择的配置。 参数中最重要的字段是编解码器类型; 在大多数情况下,解码器将忽略其他字段,而编码器将严格遵守设置

get_params

此例程返回 DSP 使用的实际设置。 对设置的更改应仍然是例外。

get_timestamp

时间戳变成了一个多字段结构。 它列出了传输的字节数、处理的样本数和渲染/抓取的样本数。 所有这些值都可用于确定平均比特率,确定是否需要重新填充环形缓冲区或由于 DSP 上的解码/编码/ IO 引起的延迟。

请注意,编解码器/配置文件/模式列表是从 OpenMAX AL 规范派生的,而不是重新发明轮子。 修改包括: - 添加 FLAC 和 IEC 格式 - 合并编码器/解码器功能 - 配置文件/模式列为位掩码以使描述符更紧凑 - 添加解码器的 set_params(OpenMAX AL 中缺少) - 添加 AMR/AMR-WB 编码模式(OpenMAX AL 中缺少) - 添加 WMA 的格式信息 - 添加所需的编码选项(从 OpenMAX IL 派生) - 添加 rateControlSupported(OpenMAX AL 中缺少)

状态机

压缩音频流状态机如下所述

                                      +----------+
                                      |          |
                                      |   OPEN   |
                                      |          |
                                      +----------+
                                           |
                                           |
                                           | compr_set_params()
                                           |
                                           v
       compr_free()                  +----------+
+------------------------------------|          |
|                                    |   SETUP  |
|          +-------------------------|          |<-------------------------+
|          |       compr_write()     +----------+                          |
|          |                              ^                                |
|          |                              | compr_drain_notify()           |
|          |                              |        or                      |
|          |                              |     compr_stop()               |
|          |                              |                                |
|          |                         +----------+                          |
|          |                         |          |                          |
|          |                         |   DRAIN  |                          |
|          |                         |          |                          |
|          |                         +----------+                          |
|          |                              ^                                |
|          |                              |                                |
|          |                              | compr_drain()                  |
|          |                              |                                |
|          v                              |                                |
|    +----------+                    +----------+                          |
|    |          |    compr_start()   |          |        compr_stop()      |
|    | PREPARE  |------------------->|  RUNNING |--------------------------+
|    |          |                    |          |                          |
|    +----------+                    +----------+                          |
|          |                            |    ^                             |
|          |compr_free()                |    |                             |
|          |              compr_pause() |    | compr_resume()              |
|          |                            |    |                             |
|          v                            v    |                             |
|    +----------+                   +----------+                           |
|    |          |                   |          |         compr_stop()      |
+--->|   FREE   |                   |  PAUSE   |---------------------------+
     |          |                   |          |
     +----------+                   +----------+

无缝播放

在播放专辑时,解码器能够跳过编码器延迟和填充,并直接从一个音轨内容移动到另一个音轨内容。 最终用户可以将此感知为无缝播放,因为我们在从一个音轨切换到另一个音轨时没有静音。

此外,由于编码,可能会出现低强度噪声。 使用所有类型的压缩数据很难达到完美的无缝效果,但在大多数音乐内容中效果很好。 解码器需要知道编码器延迟和编码器填充。 因此,我们需要将其传递给 DSP。 此元数据是从 ID3/MP4 标头中提取的,默认情况下不存在于比特流中,因此需要一个新的接口将此信息传递给 DSP。 此外,DSP 和用户空间需要从一个音轨切换到另一个音轨,并开始使用第二个音轨的数据。

主要的补充是

set_metadata

此例程设置编码器延迟和编码器填充。 解码器可以使用此功能来去除静音。 这需要在写入音轨中的数据之前进行设置。

set_next_track

此例程告诉 DSP 在此之后发送的元数据和写入操作将对应于后续音轨

partial drain

当到达文件结尾时调用此函数。 用户空间可以通知 DSP 已到达 EOF,现在 DSP 可以开始跳过填充延迟。 此外,下一个写入数据将属于下一个音轨

无缝的顺序流程将是: - 打开 - 获取 caps / codec caps - 设置 params - 设置第一个音轨的元数据 - 填充第一个音轨的数据 - 触发启动 - 用户空间完成发送所有内容, - 通过发送 set_next_track 来指示下一个音轨数据 - 设置下一个音轨的元数据 - 然后调用 partial_drain 以刷新 DSP 中的大部分缓冲区 - 填充下一个音轨的数据 - DSP 切换到第二个音轨

(注意:partial_drain 和下一个音轨的写入顺序也可以颠倒)

无缝播放 SM

对于无缝播放,我们将从运行状态移动到 partial drain 并返回,同时设置 meta_data 并发出下一个音轨的信号

                          +----------+
  compr_drain_notify()    |          |
+------------------------>|  RUNNING |
|                         |          |
|                         +----------+
|                              |
|                              |
|                              | compr_next_track()
|                              |
|                              V
|                         +----------+
|    compr_set_params()   |          |
|             +-----------|NEXT_TRACK|
|             |           |          |
|             |           +--+-------+
|             |              | |
|             +--------------+ |
|                              |
|                              | compr_partial_drain()
|                              |
|                              V
|                         +----------+
|                         |          |
+------------------------ | PARTIAL_ |
                          |  DRAIN   |
                          +----------+

不支持

  • 对 VoIP/电路交换呼叫的支持不是此 API 的目标。 对动态比特率更改的支持将需要在 DSP 和主机堆栈之间进行紧密耦合,从而限制功耗。

  • 不支持丢包隐藏。 这将需要一个额外的接口来让解码器在传输期间丢失帧时合成数据。 这可能会在将来添加。

  • 此 API 不处理音量控制/路由。 暴露压缩数据接口的设备将被视为常规 ALSA 设备; 音量更改和路由信息将通过常规 ALSA kcontrol 提供。

  • 嵌入式音频效果。 无论输入是 PCM 还是压缩的,都应以相同的方式启用此类效果。

  • 多声道 IEC 编码。 不清楚是否需要此功能。

  • 如上所述,不支持编码/解码加速。 可以将解码器的输出路由到捕获流,甚至实现转码功能。 此路由将使用 ALSA kcontrol 启用。

  • 音频策略/资源管理。 此 API 不提供任何钩子来查询音频 DSP 的利用率,也不提供任何抢占机制。

  • 没有欠载/超载的概念。 由于写入的字节本质上是压缩的,并且写入/读取的数据不会直接转换为时间的渲染输出,因此这不处理欠载/超载,并且可能在用户库中处理

鸣谢

  • 感谢 Mark Brown 和 Liam Girdwood 讨论对此 API 的需求

  • 感谢 Harsha Priya 在 intel_sst 压缩 API 方面的工作

  • 感谢 Rakesh Ughreja 提供的宝贵反馈

  • 感谢 Sing Nallasellan、Sikkandar Madar 和 Prasanna Samaga 演示和量化音频卸载在真实平台上的优势。