什么是AUDIO_OUTPUT_FLAG_NON_BLOCKING
如字面意思,非阻塞式写入,在以下文件中也有相关定义。
AudioOutputFlags.aidl
/**
* Write operations must return as fast as possible instead of
* being blocked until all provided data has been consumed.
*/
NON_BLOCKING = 5,
平时通过alsa调用pcm_write一般都采用的是阻塞式。但在Offload模式下,通常需要使用非阻塞式。
为什么需要非阻塞式:
1. 延长 CPU 睡眠时间
这是大缓冲区最主要的原因。
普通模式(PCM):缓冲区通常很小(比如 20ms~40ms)。这意味着 CPU 每隔几十毫秒就必须醒来一次,往缓冲区里填数据。CPU 频繁地在“工作”和“睡眠”之间切换,无法进入深度的省电状态(Deep Sleep States)。
Offload 模式:通过使用非常大的缓冲区(通常可以容纳 1秒到数秒 的音频数据),CPU 可以一次性把大量的压缩数据(如 MP3, AAC)“推”给 DSP。
结果:数据推完后,CPU 就可以彻底“躺平”睡大觉。在接下来的 2 秒钟里,CPU 都不需要处理音频,只有低功耗的 DSP 在工作。这种长周期的睡眠能显著降低功耗。
2. 压缩数据的高效率
压缩音频数据的“密度”比原始 PCM 数据大得多。
同样的 128KB 空间:
* 如果存 PCM(44.1kHz, 16bit, 双声道),只能支撑约 0.7 秒。
* 如果存 MP3(128kbps 比特率),可以支撑约 8 秒。
因为 Offload 传输的是压缩数据,大缓冲区配合高压缩比,使得 CPU 每次“醒来”填满缓冲区后,获得的“收益”(播放时长)极大,数据需要消耗的时间也相当长,如果一直阻塞等待消耗完,十分浪费CPU资源。
如何实现非阻塞式:
1.配置文件
在AudioPolicyConfiguration.xml中Offload模式下(AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD)同时配置AUDIO_OUTPUT_FLAG_NON_BLOCKING.
2.flag检测
在audio HAL层检测flag是否包含AUDIO_OUTPUT_FLAG_NON_BLOCKING,如若包含,创建对应的StreamOut,并且保存回调函数接口(callback)
3.修改write()
在write函数下实现非阻塞写入:如果有空间,写入数据,返回写入字节数。如果没空间,立即返回-EAGAIN 或 0,不要调用 `usleep` 或等待信号量。
4. 异步回调:
HAL 内部维护一个线程处理实际的硬件写入。当硬件消费了数据,空出缓冲区时,调用 `IStreamOutCallback->onWriteReady()`。当 AudioFlinger 层收到 `onWriteReady` 回调时,`AudioFlinger::PlaybackThread` 或`AudioFlinger::OffloadThread` 会被唤醒。它会重新调用 `mStream->write()`。
非阻塞模式通常还伴随着 `drain()` 操作。当数据写完时,Framework 会调用 `drain(AUDIO_DRAIN_ALL)`。HAL 在硬件播放完成后,需要回调 `IStreamOutCallback->onDrainReady()`。
其它要点:
pause与resume
状态同步:当调用 `pause` 时,HAL 应该立即停止向硬件推送数据,但不能丢弃已在硬件缓冲区中的数据。
Resume 的瞬时性:调用 `resume` 后,硬件应立即从暂停的位置恢复播放。
在非阻塞模式下,`pause` 之后不应触发数据请求回调。如果正在进行 `drain` 操作时执行 `pause`,`drain` 的完成回调通常会被挂起,直到 `resume`。
drain
这是非阻塞模式下最复杂的操作。它的目的是确保所有已写入缓冲区的音频数据都被完整播放。
异步特性:在非阻塞模式下,`drain()` 操作必须是非阻塞的。HAL 接收到指令后开始播放剩余数据,并在播放完成后发送 `AUDIO_DRAIN_READY` 回调。
类型:
`DRAIN_ALL`: 等待所有数据播放完。
`DRAIN_EARLY_NOTIFY`: 在数据接近播放完时提前通知(常用于下一曲等无缝切换)。
在 `drain` 过程中,如果收到 `flush` 或 `stop` 指令,必须取消 `drain` 等待。
在 `drain` 完成前,不要关闭 Stream。
测试验证
在应用层快速写入大量数据,检验是否立即返回,返回写入数据长度或0