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 的设计灵感来自英特尔 Moorestown SOC 的 2 年经验,需要进行许多修正才能将 API 上游到主线内核,而不是暂存树,并使其可供其他人使用。
要求¶
主要要求是
字节计数和时间之间的分离。压缩格式可能每个文件、每个帧都有一个标头,或者根本没有标头。有效负载大小可能因帧而异。因此,在处理压缩数据时,无法可靠地估计音频缓冲区的持续时间。需要专门的机制来允许可靠的音视频同步,这需要精确报告任何给定时间渲染的样本数量。
处理多种格式。PCM 数据只需要指定采样率、通道数和每个样本的位数。相比之下,压缩数据有多种格式。音频 DSP 还可以支持嵌入固件的有限数量的音频编码器和解码器,或者可以通过动态下载库来支持更多选择。
专注于主要格式。此 API 为音频和视频捕获和播放提供了对最常用格式的支持。随着音频压缩技术的进步,很可能会添加新的格式。
处理多种配置。即使对于像 AAC 这样的给定格式,某些实现也可能支持 AAC 多声道,但 HE-AAC 立体声。同样,WMA10 M3 级可能需要太多的内存和 CPU 周期。新的 API 需要提供一种通用的方法来列出这些格式。
仅渲染/捕获。此 API 不提供任何硬件加速方式,在这种方式中,PCM 样本被提供回用户空间以进行额外的处理。此 API 专注于将压缩数据流式传输到 DSP,并假设解码后的样本被路由到物理输出或逻辑后端。
隐藏复杂性。现有的用户空间多媒体框架都具有每个压缩格式的现有枚举/结构。此新 API 假设存在一个平台特定的兼容层来公开、转换和利用音频 DSP 的功能,例如 Android HAL 或 PulseAudio 接收器。从构造上讲,不应期望常规应用程序使用此 API。
设计¶
新的 API 与 PCM API 共享许多用于流量控制的概念。启动、暂停、恢复、排空和停止命令具有相同的语义,无论内容是什么。
在 ALSA PCM API 中借用了划分为一组片段的内存环形缓冲区的概念。但是,只能指定字节大小。
假设由主机处理搜索/特技模式。
不支持倒带/快进的概念。提交到环形缓冲区的数据不能被无效,除非丢弃所有缓冲区。
压缩数据 API 不会假设数据如何传输到音频 DSP。从主内存到嵌入式音频集群或到外部 DSP 的 SPI 接口的 DMA 传输是可能的。与 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,在此之后发送的元数据和写入操作将对应于后续曲目
- 部分排空
当到达文件末尾时调用此例程。用户空间可以通知 DSP 已到达 EOF,现在 DSP 可以开始跳过填充延迟。此外,下一个写入数据将属于下一个曲目
无缝播放的顺序流是: - 打开 - 获取功能/编解码器功能 - 设置参数 - 设置第一个曲目的元数据 - 填充第一个曲目的数据 - 触发开始 - 用户空间完成发送所有内容, - 通过发送 set_next_track 来指示下一个曲目数据 - 设置下一个曲目的元数据 - 然后调用 partial_drain 以刷新 DSP 中的大部分缓冲区 - 填充下一个曲目的数据 - DSP 切换到第二个曲目
(注意:partial_drain 的顺序和下一个曲目的写入也可以颠倒)
无缝播放 SM¶
对于无缝播放,我们从运行状态移动到部分排空状态,然后再返回,同时设置 meta_data 并发出下一个曲目的信号
+----------+
compr_drain_notify() | |
+------------------------>| RUNNING |
| | |
| +----------+
| |
| |
| | compr_next_track()
| |
| V
| +----------+
| compr_set_params() | |
| +-----------|NEXT_TRACK|
| | | |
| | +--+-------+
| | | |
| +--------------+ |
| |
| | compr_partial_drain()
| |
| V
| +----------+
| | |
+------------------------ | PARTIAL_ |
| DRAIN |
+----------+
不支持¶
此 API 的目标不是支持 VoIP/电路交换呼叫。对动态比特率更改的支持需要在 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 演示并量化了在真实平台上音频卸载的优势。