音频驱动程序流式传输接口

本文档介绍了 Fuchsia 中的音频驱动程序公开的音频流式传输接口。旨在作为用户和驱动程序作者的参考,并明确定义驱动程序必须实现且用户必须遵循的接口协定。

概览

音频流是由驱动程序服务发布的设备节点,供应用用于在 Fuchsia 设备上捕获或渲染音频。系统中的每个流(输入或输出)都表示可由设备接收或传输的数字音频信息的流。流是动态的,系统可能随时创建或销毁。在任何给定的时间点存在哪些流以及哪些控制其生命周期被认为是音频政策和编解码器管理问题,本文档不作讨论。此外,音频输出流中的信息只有该流的应用所有者拥有。音频混音不是由音频流接口提供的服务。

定义

术语 定义
示例 一个讲话人在某个时刻发出的声音或一个麦克风捕捉到的声音。
LPCM 线性脉冲编码调制。所有 Fuchsia 未压缩音频流中的音频样本的具体表示形式。LPCM 音频样本表示音频信号在某个瞬间的振幅,编码音频的数值在呈现或捕获设备的振幅级别上线性分布。这与 A-law 和 μ-law 编码相反,它们具有从数值到振幅级别的非线性映射。
渠道 在音频流中,将由单个讲话者呈现或由音频流中的单个麦克风捕获的信息子集。
框架 在某个时刻捕获/渲染的音频流的每个声道的一组音频样本。
帧速率 也称为“采样率”。生成或使用音频帧的速率(以 Hz 为单位)。 常见的采样率包括 44.1 KHz、48 KHz、96 KHz 等。
客户端、用户或应用 本文档中这两个术语可以互换使用。它们是指使用这些接口与音频驱动程序/设备进行通信的模块。

基本操作

与音频流设备的通信是使用通过频道发送的消息执行的。应用会打开设备流的设备节点,并通过发出 FIDL 请求来获取通道。获取该通道后,设备节点可能会被关闭。与流的所有后续通信均使用通道进行。

流信道用于大多数命令和控制任务,包括:

  • 功能审讯
  • 格式协商
  • 硬件增益控制
  • 确定外延延迟时间
  • 插头检测通知
  • 门禁功能检测和信号

为了在音频流上实际发送或接收音频信息,必须先设置要使用的特定格式。对成功的 CreateRingBuffer 操作的响应将包含一个新的“环形缓冲区”通道。环形缓冲区通道可用于从可映射到应用地址空间并酌情发送或接收音频数据的流(以 VMO 形式提供)请求共享缓冲区。通常,在环形缓冲区通道上执行的操作包括:

  • 请求共享缓冲区
  • 开始和停止流播放和捕获
  • 接收播放和拍摄进度通知
  • 在音频输出时钟使用的振荡器与支持单调时钟的振荡器不同时接收时钟恢复信息

操作详情

设备节点

音频流设备节点必须由使用下表中提供的协议预处理器符号的驱动程序发布。这会导致系统将数据流设备节点发布到表中指定的位置。应用可以监控这些目录,以便在驱动程序发布新信息流时发现新信息流。

直播类型 协议 位置
输入 ZX_PROTOCOL_AUDIO_INPUT /dev/class/audio-input
输出 ZX_PROTOCOL_AUDIO_OUTPUT /dev/class/audio-output

建立直播频道

打开设备节点后,客户端应用可以使用 fuchsia.hardware.audio.Device/GetChannel FIDL 消息获取用于后续通信的流通道。

在客户端终止流信道

客户端可以随时通过对流通道调用 zx_handle_close(...) 来终止与流的连接。驱动程序必须关闭使用此流通道建立的所有活跃环形缓冲区通道,并且必须尽一切努力让进程中所有正在进行的流式传输操作平稳地停止。

在流和环形缓冲区通道上发送和接收消息

stream_config.fidlring_buffer.fidl 中定义了可通过信息流和环形缓冲区通道发送或接收的所有消息和消息载荷。可以使用 zx_channel_write(...) 系统调用将消息发送到驱动程序。如果按预期响应,可以使用 zx_channel_read(...) 系统调用读取该响应。不过,最佳做法是使用 zx_port_queue(...) 系统调用为您的 channel(s) 端口将数据包加入队列,并使用 zx_port_wait(...) 系统调用来确定您的那组通道何时有要读取的消息(预期响应或异步通知)。 不同的语言绑定了各种绑定,以发送和接收 FIDL 消息。特别是对于 C++ 驱动程序,还有一个库 SimpleAudioStream 可帮助在 C++ 中创建驱动程序。此库使用新的 C++ 绑定来发送和接收 FIDL 消息。

格式协商

示例格式

Format 相关的协议消息允许驱动程序向客户端列出其支持的格式。支持的格式可能包括多种速率、每个样本的比特数等。每个驱动程序都会公布其可以支持的内容,而客户端会规定每个驱动程序要使用哪种格式。

如需了解给定驱动程序支持哪些格式,客户端会使用 GetSupportedFormats 函数。驱动程序以 SupportedFormats 的矢量进行回复,其中每个 SupportedFormats 都包含一个带有以下参数的 PcmSupportedFormats

  • 通道数的向量。这里列出了驱动程序支持的声道数量,例如 <2,4,6,8>。支持两个或四个通道的驱动程序会报告包含两个元素 <2,4> 的矢量。必须按升序排列。
  • 样本格式的矢量,例如 PCM_SIGNED
  • 费率矢量。帧速率,例如 44100、48000 和 96000。必须按升序排列。
  • 每个样本的字节数。必须按升序排列。
  • 每个样本的位矢量。样本宽度,可以小于可用总字节数,例如 4 字节样本中的 24 位。必须按升序排列。

如果驱动程序不支持的所有组合都不能用一个 PcmSupportedFormats 进行描述,驱动程序会在返回的矢量中返回多个 PcmSupportedFormats。例如,如果一个 PcmSupportedFormats 允许在 48KHz 下进行 16 或 32 位样本,在 96KHz 下允许 16 位样本,但不允许在 96KHz 下进行 32 位样本,则驱动程序会回复 2 个 PcmSupportedFormats<<16bits,32bits>,<48KHz>><<16bits>,<96KHz>>。为简单起见,此示例会忽略除了速率和每个样本的位数外的其他参数。如果驱动程序支持 48KHz 或 96KHz 的 16 位或 32 位样本,则驱动程序会回复 1 个 PcmSupportedFormats<<16bits,32bits>,<48KHz,96KHz>>

此外,假设每个样本的位数始终小于或等于每个样本 8 * 字节。因此,驱动程序可以报告 <<2bytes_per_sample,4bytes_per_sample>,<16bits_per_sample,32bits_per_sample>>,这并不意味着它报告 2 字节样本上每个样本 32 位是有效的,而是仅指定了 3 种有效组合:

  • 具有 16 个有效位的 2 字节采样通道
  • 具有 32 个有效位的 4 字节样本
  • 具有 16 个有效位的 4 字节样本

客户端根据驱动程序在 GetSupportedFormats 回复中提供的信息、客户端支持的内容和任何其他要求,指定要与 CreateRingBuffer 函数搭配使用的格式。此函数接受一个参数,用于指定:

  • 多个频道。这是缓冲区中可用的通道数。
  • 要使用的通道的位掩码。这些是缓冲区中供驱动程序使用的通道。例如,对于立体声,这必须是启用了 2 位的 0x3 的位掩码,即同时使用通道 0 和通道 1。
  • 示例格式。
  • 帧速率。
  • 每个样本的字节数。
  • 每个样本的位数。

注意:

  • 默认情况下,假定多字节采样格式使用主机字节序。
  • PCM_FLOAT 编码专门使用 IEEE 754 浮点表示法。
  • 默认情况下,假定非浮点 PCM 编码使用二补码有符号整数表示。例如,16 位 PCM 样本格式的位值范围为 [0x8000, 0x7FFF],其中 0x0000 表示扬声器偏移零。如果使用 PCM_UNSIGNED 样本格式,位值范围将介于 [0x0000, 0xFFFF] 之间,其中 0x8000 表示零偏移。
  • 在较大的通道中编码较小的样本大小时(例如在 32 中编码 20 位或 24 位),会使用 32 位容器的最高有效位,而忽略最低有效位(左对齐)。例如,20 位样本将映射到 [12,31] 范围(容器将忽略 [0,11] 的位)。

设置所需的流式传输格式

为了选择流格式,应用会通过流通道发送 CreateRingBuffer 消息。在消息中,应用会指定要使用的格式。

客户端指定将执行流式传输操作的新环形缓冲区通道。如果先前的环形缓冲区通道已建立且仍处于活跃状态,驱动程序必须关闭此通道,并尽一切努力让进程中所有正在进行的流式传输操作安静下来。

确定外部延迟时间

音频流的外部延迟时间是指输出音频从系统的互连传输到扬声器本身所需的时间,或者入站音频从麦克风传输到系统的互连所用的时间。例如,假设某个外部编解码器使用 TDM 互连连接到系统:如果此互连在接收 TDM 帧和在扬声器本身呈现该帧之间引入了 4 帧延迟,则此音频路径的外部延迟时间相当于 4 个音频帧的时长。

外部延迟会在对 WatchDelayInfoDelayInfo 响应的 external_delay 字段中报告。司机应尽最大努力准确报告司机所知的所有延迟原因的总和。有关此延迟的信息通常可以在编解码器数据表中找到,以 Intel HDA 或 USB 音频规范等协议的编解码器属性动态报告,或者由使用 HDMI 或 DisplayPort 互连时使用 EDID 等机制的下行设备报告。

确定开启延迟

音频流的 turn_on_delay 是指在发出 Start 命令后,环形缓冲区上的音频样本在扬声器上实际开始播放所需的时间,或在发出 Start 命令之后,来自麦克风的音频也开始录制到环形缓冲区中。例如,如果我们有一个外部编解码器连接到系统,为节省电量,为了节省电量,为了节省电量,为了节省电量,为了节省电量,为了节省电量,我们关闭了它,在发出 Start 命令后,提供环形缓冲区的驱动程序将通过 start_time 进行回复,表明环形缓冲区上的位置何时开始移动,以及音频样本何时开始发送到外部编解码器。不过,无论 external_delay(请参阅上面的 Determining external latency)的情况,外部编解码器都可能仍通过低功耗模式充电。RingBufferProperties 表中指定的 turn_on_delay 是驱动程序对让外部编解码器实际输出音频样本所需的时间量的最佳上限。其他原因也可能造成延迟,例如,编解码器为了避免故障而内置了上升延迟,或者驱动程序抽象化了蓝牙通信,导致远程设备开始播放音频样本的延迟。由于 turn_on_delay 是一个估算值,因此音频可能会在 turn_on_delay 过去之前开始播放或捕获,因此,如果无法获得播放或捕获的初始音频样本,必须考虑此延迟。

external_delay 表示音频样本到达扬声器或麦克风延迟的时间。turn_on_delay 不会延迟音频样本,也不表示任何缓冲,而是表示音频样本可能实际上不会播放/录制的时间量。turn_on_delay 不会影响呈现时间的计算,但确实会影响呈现是否发生。对于播放,我们可以将这些延迟音频样本呈现到扬声器中,如下所示:

                   |<--- external delay --->|
             |S|--|T|----------------------|P|----|O|
                   |<------- turn on delay ------->|

其中,S 表示 Start 发出的时间,T 表示环形缓冲区中的位置开始移动的时间,即从 Start 命令返回的 start_timeP 表示扬声器在扬声器中的呈现时间,O 表示放大器完成开启且声音可听的时间。由于 O 超过 P,因此此处的 turn_on_delay 影响了在演讲者的实际演示中。

随着时间的推移,我们可以直观地看到环形缓冲区 C 上的当前位置,低于下一个进度,并且样本现在显示在扬声器中,P 超过放大器开启时间 O

                     |<--- external delay --->|
   |S|--|T|---------|C|-----------------|O|--|P|
         |<------- turn on delay ------->|

                                            |<--- external delay --->|
   |S|--|T|-----------------------------|O|-|C|---------------------|P|
         |<------- turn on delay ------->|

硬件增益控制

硬件增益控制功能报告

为了确定流的增益控制功能,如果尚未确定,应用将通过流信道发送 GetProperties 消息。此消息无需提供任何参数。驾驶员会返回一个 StreamProperties,包括获取权能等等。无论流硬件是否能够执行任何增益控制,所有流驱动程序都必须响应此消息。所有增益值均使用以 dB 表示的 32 位浮点数表示。

驱动程序会使用值指示当前数据流的增益控制功能来响应此消息。当前增益设置使用布尔值(指示音频流是否可以静音)、布尔值(指示音频流是否可以 AGC)、最小和最大增益设置以及 gain_step_db 来表示。gain_step_db 表示可以从最小增益值开始计数的最小增益控制增量。

例如,如果某个放大器有 5 个增益步长,每个均为 7.5 dB,增益最大值为 0 dB,则表示这一范围为 (-30.0, 0.0),步长为 7.5。 能够进行功能连续增益控制的放大器可以将其增益步长编码为 0.0。

无论静音功能如何,固定增益流的驱动程序都必须将其最小和最大增益报告为 (0.0, 0.0)。在这种情况下,gain_step_db 没有意义,但驾驶员应将其报告为 0.0。

设置硬件增益控制级别

为了更改流的当前增益设置,应用需要通过流信道发送 SetGain 消息。此消息包含一个参数 GainState,该参数指示要配置的增益参数,包括应应用于音频流的 dB 增益、静音和 AGC 启用。

假设请求有效,驱动程序应将请求四舍五入为支持的最近的增益步长。例如,如果音频流可以使用 0.5 dB 的增益步长在 -60.0 到 0.0 dB 的范围内控制其增益,那么将增益设置为 -33.3 dB 的请求应应用 -33.5 的增益。向同一音频流请求 -33.2 dB 的增益应应用 -33.0 的增益。

获取状态通知

客户端可以使用 WatchGainState 命令请求数据流向其发送有关增益状态变化的异步通知。驱动程序将回复客户端发送的第一个 |WatchGainState|,而不会响应后续的客户端 |WatchGainState| 调用,直到增益状态从最近报告的状态发生变化为止。

插头检测

除了为响应与总线连接或断开连接而发布/取消发布视频流之外,视频流还可以在任何给定时间点被插上或拔出。例如,一组 USB 耳机可能会在连接到 USB 时发布新的输出流,但从插头检测的角度选择“硬连接”。另一个具有标准 3.5 毫米唱机插孔的 USB 音频适配器可能会在使用 USB 连接时发布输出流,但会在用户插头和拔出具有 3.5 毫米耳机插孔的模拟设备时选择更改其插接状态。

可通过插头检测消息处理查询流的当前插接状态以及注册插头状态变更异步通知(如果支持)的功能。

插头检测功能

为了确定流的插头检测功能,如果尚未这样做,应用将通过流信道发送 GetProperties 命令。驱动程序返回 StreamProperties,其中包括 plug_detect_capabilities 中的插头检测功能以及其他字段。

目前定义的有效插件检测功能标志包括:

  • 当流硬件被视为“硬件有线”时,系统会设置 HARDWIRED。换言之,只要设备已发布,该流就会被视为已连接。例如,一组内置扬声器、一对 USB 耳机或不具备插头检测功能的可插拔音频设备。
  • 如果流硬件能够异步检测设备的插头状态更改,并能在客户端请求这些通知的情况下发送通知消息,则系统会设置 CAN_ASYNC_NOTIFY

插头状态通知

如果驱动程序在 StreamProperties 中发送了 CAN_ASYNC_NOTIFY 标志,则客户端可以使用 WatchPlugState 命令请求流式向他们发送插头状态变化的异步通知。也就是说,未设置 CAN_ASYNC_NOTIFY 标志的流的驱动程序可随意忽略应用发送的 WatchPlugState。设置了 CAN_ASYNC_NOTIFY 的驱动程序将回复客户端发送的第一个 |WatchPlugState|,而不会响应后续的客户端 |WatchPlugState| 调用,直到插头状态从最近报告的状态发生变化。

直播目的和关联

环形缓冲区信道

概览

一旦应用成功设置流的格式,它将在响应中收到一个新的通道,它表示它与流的环形缓冲区的连接。客户端使用环形缓冲区通道建立共享内存缓冲区,以及开始和停止播放和捕获音频流数据。

环形缓冲区内容由客户端(用于播放)和驱动程序端(用于录制)生成。因此,客户端是播放的提供方和录制的使用方,驱动程序是录制的提供方和播放的使用方。环形缓冲区内容可以直接由音频硬件使用或生成,也可以由驱动程序对每个样本进行软件处理。

环形缓冲区数据生成从成功响应 Start 命令后指定的时间点开始以标称速率继续生成。不过请注意,环形缓冲区几乎肯定会在内存总线和音频硬件之间建立某种形式的 FIFO 缓冲区,这会导致其在流中预读(在播放的情况下),或可能会持有数据(在捕获的情况下)。客户端在开始操作之前必须查询此缓冲区的大小,这样才能知道为防止音频干扰,它们需要保持在流的标称读取/写入位置上的时间。另请注意,由于系统的共享缓冲区性质,以及驱动程序可能会直接从此缓冲区向硬件执行 DMA 操作,对于在不是自动缓存一致的架构上运行的客户端来说,务必要在将播放数据写入缓冲区后正确回写其缓存,或者在读取捕获的数据之前使缓存失效,这一点非常重要。有关环形缓冲区数据传输的说明,请参阅 ring_buffer.fidlRingBufferProperties 中的 driver_transfer_bytes

获取共享缓冲区

如需发送或接收音频,应用必须先建立共享内存缓冲区。通过环形缓冲区通道发送 CreateRingBuffer 请求,可以做到这一点。只能在环形缓冲区停止时执行此操作。

如果使用 CreateRingBuffer 创建的通道因已建立缓冲区且环形缓冲区已启动而被驱动程序关闭,则不得停止环形缓冲区,或舍弃现有的共享内存。如果应用在已建立缓冲区,同时环形缓冲区停止时请求新的缓冲区,则必须将现有缓冲区 ii 视为无效,即旧的缓冲区现已消失。

请求环形缓冲区时,应用必须指定两个参数:min_framesclock_recovery_notifications_per_ring

min_frames

客户端需要为环形缓冲区分配的音频帧数下限。驱动程序可能会增大此缓冲区的大小以满足硬件要求。客户端必须使用返回的 VMO 大小(以字节为单位)来确定环形缓冲区的实际大小。客户端不得假定缓冲区大小(由驱动程序确定)恰好与他们请求的缓冲区大小。驱动程序必须确保环形缓冲区的大小是音频帧的整数数量。

clock_recovery_notifications_per_ring

客户端希望驱动程序在每个周期通过环形缓冲区发送的位置更新通知(可选数量),这些通知旨在用于恢复时钟。驾驶员只能发送这些响应对 WatchClockRecoveryPositionInfo 请求。驱动程序应尝试在整个通知环中以一致的方式显示通知;但是,客户端不能依赖完全均匀的更新通知间距。

ring_buffer

如果请求成功,驱动程序必须向 VMO 返回一个句柄,该句柄允许应用使用 zx_vmar_map 将 VMO 映射到其地址空间,并在播放时读取/写入缓冲区中的数据,或者在捕获时仅读取缓冲区中的数据。

num_frames

如果请求成功,驱动程序还将返回其将在缓冲区中使用的音频的实际帧数。返回的 VMO 的大小(由 zx_vmo_get_size() 报告)不得大于此帧数(转换为字节时)。此数字可能大于客户端发出的 min_frames 请求,但不得小于此数字。

启动和停止环形缓冲区

客户端可以使用 StartStop 命令请求启动或停止环形缓冲区。尝试启动已经启动的数据流必须被视为失败。尝试停止已停止的数据流应被视为成功。环形缓冲区只有在使用 CreateRingBuffer 操作建立共享缓冲区之后,才能停止或启动。

成功启动数据流后,驱动程序必须在响应的 start_time 字段中提供其硬件开始传输或捕获数据流的时间的最佳估算值。该时间戳必须从使用 zx_clock_get_monotonic() 系统调用公开的时钟中获取。除了环形缓冲区的 FIFO 深度属性外,此时间戳还允许应用发送或接收流数据,而无需驱动程序的定期位置更新。除了流信道提供的外接延迟时间估算值之外,应用还可让应用跨多个数据流甚至多设备同步呈现音频信息(前提是使用外部时间同步协议在已同步设备同类群组之间同步monotonic时间轴)。

成功启动音频流后,驱动程序必须保证在启动响应加入环形缓冲区通道之前,不发送位置通知。

成功停止流后,驱动程序必须保证在停止响应加入队列后,不会将任何位置通知加入环形缓冲区通道。

位置通知

如果客户端在 CreateRingBuffer 操作中通过非零 clock_recovery_notifications_per_ring 发出请求,驱动程序将定期向客户端发送更新,告知它其在缓冲区中的当前生产或消耗位置。此位置在回复 WatchClockRecoveryPositionInfo 消息时发送的 RingBufferPositionInfo 结构体的 position 字段中以字节表示。该消息还包含一个 timestamp 字段,其中包含该字节位置有效的时间(以 zx::time 表示)。只有在 GetVmo 函数中指定了 clock_recovery_notifications_per_ring 且已返回 GetVmo 函数后,才能发送 WatchClockRecoveryPositionInfo 请求。请注意,这些位置通知指示驱动程序在缓冲区中使用或生成了数据,而非标称的播放或捕获位置(有时分别称为“写入游标”或“读取游标”)。它们到达的时间无法保证完全一致,因此不应将其用于恢复时钟。不过,对应对(timestampposition)值本身旨在用于恢复音频流的时钟。如果客户端发现驱动程序的消耗超出了环形缓冲区中已写入播放数据的点,则未定义音频呈现。客户端应增加其时钟准备时间,并确保将来在数据流中领先于这一点。同样,捕获音频的客户端不应尝试读取超出驱动程序发送的最新位置通知所指明的环形缓冲区中点之外的位置。

驱动程序播放和捕获位置必须始终从环形缓冲区字节 0 开始,紧跟在成功的 Start 命令之后。当环形缓冲区位置到达 VMO 的末尾时(如 zx_vmo_get_size(...) 所指示),环形缓冲区位置会回回为零。驱动程序无需使用或生成整数数量的音频帧的数据。如果客户端的数据流位置概念取决于位置通知,则应谨慎地请求为每个环发送足够数量的通知(至少 2 个),并以足够快的速度处理这些通知,以免发生别名。

时钟恢复和同步

收到 AUDIO_STREAM_CMD_GET_CLOCK_DOMAIN 消息后,驱动程序必须使用包含该设备的时钟域标识符进行响应。如果音频设备锁定到本地系统单调时钟,并且未公开其速率微调机制,那么应返回值 0 来表示本地 CLOCK_MONOTONIC 域。客户端可以使用此信息(除了 AUDIO_RB_POSITION_NOTIFY 消息)来简化恢复音频设备时钟的过程。

错误通知

客户端意外终止

如果环形缓冲区控制通道的客户端因任何原因关闭,驱动程序必须立即关闭控制通道并关闭环形缓冲区,以免再发出或捕获任何音频。虽然我们鼓励驾驶员以一种能够优雅地转换为无声的方式这样做,但他们必须确保音频流进入静音状态,而不是循环播放。完成向静音的过渡后,与播放或捕获相关的资源便可释放并由驱动程序重复使用。

这样,如果播放客户端意外终止,系统将关闭客户端通道,导致音频播放停止而不是继续循环播放。