简介: CSDN博客专家、《Android系统多媒体进阶实战》作者
博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中...... 】🚀
Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中...... 】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课 🚀
专题四:Android15快速自定义与集成音效实战课 🚀
专题五:Android15音频策略实战课 🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例) 🚀
人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
更多原创,欢迎关注:Android系统攻城狮

🍉🍉🍉文章目录🍉🍉🍉
-
-
- [🌻1. 前言](#🌻1. 前言)
- [🌻2. 用法与应用场景](#🌻2. 用法与应用场景)
- [🌻3. 调用流程剖析](#🌻3. 调用流程剖析)
-
- [3.1 核心步骤](#3.1 核心步骤)
- [3.2 涉及核心时序图](#3.2 涉及核心时序图)
- [🌻4. 实战应用案例](#🌻4. 实战应用案例)
- [🌻5. 用法总结](#🌻5. 用法总结)
-
🌻1. 前言
本篇目的:Android tinyalsa 深度解析之 pcm_start 调用流程与实战。
🌻2. 用法与应用场景
pcm_start 是 tinyalsa 库中用于手动触发音频流传输的核心函数。它负责将音频接口的状态正式从 PREPARED (就绪)切换到 RUNNING(运行),命令内核 ALSA 驱动立即启动 DMA 引擎。
- 用法 :
int pcm_start(struct pcm *pcm); - 返回值 :成功返回 0 ;失败返回负数,具体错误可通过
pcm_get_error获取。 - 应用场景:
- 显式流启动:在预填(Pre-roll)完缓冲区数据后,精确控制播放开始的时机。
- 低延迟同步 :在多通道或多设备同步场景下,先通过
pcm_write填满 Buffer,再统一调用pcm_start降低启动抖动。 - 录音启动:对于录音(Capture)流,必须调用 start(或隐式调用 read)来通知硬件开始采集数据。
🌻3. 调用流程剖析
3.1 核心步骤
- 状态检查 :验证
pcm句柄是否有效。如果设备当前已经处于RUNNING状态,函数通常会返回成功,避免重复启动。 - 执行内核 IOCTL :发起系统调用
ioctl(pcm->fd, SNDRV_PCM_IOCTL_START)。 - 驱动底层触发 :内核 ALSA 核心层接收到请求后,会调用硬件驱动(如 SoC 的 DMA 控制器驱动)的
trigger回调函数。 - DMA 搬运开启 :硬件开始按照
pcm_config设置的采样率和周期大小,在内存 Buffer 与 I2S/TDM 接口之间搬运音频采样点。 - 隐式调用说明 :在 Android HAL 中,开发者很少显式调用
pcm_start。这是因为在pcm_write或pcm_read的实现中,如果检测到设备处于PREPARED状态,会自动内部调用pcm_start。
关键技术:启动阈值(Start Threshold)
在 pcm_config 中,start_threshold 决定了隐式启动的时机。只有当 Buffer 中的数据量达到该阈值时,第一次 pcm_write 才会触发类似 pcm_start 的操作。而手动调用 pcm_start 则可以忽略此阈值强制开启硬件传输。
3.2 涉及核心时序图
DMA Controller / Codec Kernel ALSA Core tinyalsa (pcm_start) Audio HAL / App DMA Controller / Codec Kernel ALSA Core tinyalsa (pcm_start) Audio HAL / App 调用 pcm_start(pcm) 1. 确认状态为 PREPARED 2. ioctl(fd, SNDRV_PCM_IOCTL_START) 3. 触发硬件 Trigger (Start) DMA 搬运开始 4. 更新流状态为 RUNNING 返回 0 返回 0
🌻4. 实战应用案例
此案例展示了如何在播放音频前,先向 Buffer 预填静音数据或初始音频数据,然后显式启动硬件流,以实现极其稳定的启动相位。
cpp
#include <tinyalsa/asoundlib.h>
#include <stdio.h>
#include <string.h>
/**
* 模拟 Android 低延迟音频播放初始化
*/
int start_playback_explicitly(struct pcm *pcm, void *initial_data, unsigned int size) {
// 1. 在启动前,先将数据写入缓冲区
// 此时设备处于 PREPARED 状态,数据只是进入了内存 Buffer
if (pcm_write(pcm, initial_data, size) != 0) {
fprintf(stderr, "HAL: Pre-roll write failed: %s\n", pcm_get_error(pcm));
return -1;
}
printf("HAL: Buffer pre-filled. Triggering hardware start...\n");
/* 2. 核心调用:显式启动硬件 DMA 传输 */
if (pcm_start(pcm) != 0) {
fprintf(stderr, "HAL: Failed to start PCM: %s\n", pcm_get_error(pcm));
return -1;
}
printf("HAL: DMA engine is now RUNNING.\n");
return 0;
}
int main() {
struct pcm_config config = {
.channels = 2,
.rate = 48000,
.period_size = 512,
.period_count = 3,
.format = PCM_FORMAT_S16_LE,
.start_threshold = 0, // 设为 0 以配合手动 start
};
struct pcm *out = pcm_open(0, 0, PCM_OUT, &config);
if (!pcm_is_ready(out)) {
pcm_close(out);
return -1;
}
// 准备阶段
pcm_prepare(out);
// 预填充并启动
char silent_buffer[2048] = {0};
start_playback_explicitly(out, silent_buffer, sizeof(silent_buffer));
// 后续持续写入...
// pcm_write(out, ...);
pcm_close(out);
return 0;
}
🌻5. 用法总结
| 特性 | 详情描述 |
|---|---|
| 状态迁移 | 核心动作。将设备状态从 PREPARED 强制推向 RUNNING。 |
| 执行开销 | 极小。仅涉及一个 ioctl 命令下发到硬件寄存器,不涉及数据搬运。 |
| 启动时机 | 手动可控 。可绕过 start_threshold 的限制,实现精准的播放起始点控制。 |
| 隐式行为 | 非强制显式 。大多数情况下 pcm_write 会根据配置自动调用它。 |
| 录音流特性 | 必需性 。录音流如果处于 PREPARED 状态而不 start,pcm_read 将会一直阻塞或返回错误。 |