这篇写什么?
配置麦克风采集时,常会看到类似:
mic_cfg.period_size = 960;
mic_cfg.period_count = 8;
很多人只记住「数字越大越稳」,却说不清:延迟多少、能扛多久卡顿、回调一秒几次、和 CPU 什么关系。
本文用 16 kHz、4 通道、16bit 这组常见语音参数,把 ALSA(以及同类 PCM 驱动)里的 帧 → period → 环形缓冲 一次算清楚,并补上调参时要用到的专业概念。
一句话:
period_size = 一周期多少帧(决定延迟与回调频率)
period_count = 环形缓冲有几段(决定能扛多久处理抖动)
总缓冲时间 ≈ period_size × period_count / sample_rate

一、先统一几个「单位」
| 名词 | 含义 |
|---|---|
| 采样点(sample) | 某一个声道上的一个数值(如 16bit 整数) |
| 帧(frame) | 同一时刻所有声道各取一个采样点。N 通道 → 1 帧里有 N 个采样点 |
| period(周期) | 驱动按「一块」通知上层的数据量,单位通常是 帧数 |
| 环形缓冲(ring buffer) | 由多个 period 首尾相接组成;硬件写、软件读(或反过来),指针绕圈前进 |
对 4 通道、16bit(2 字节/采样):
1 帧 = 4 通道 × 2 字节 = 8 字节
这就是后面所有「字节数」换算的基础。
和「交错 / 非交错」的关系:交错(interleaved)时,一帧在内存里是
L R L R …或ch0 ch1 ch2 ch3紧挨着;算字节时仍是帧数 × 通道 × 每采样字节。
二、period_size:一周期多少帧
mic_cfg.period_size = 960; // 单位:帧
含义:
- 每个 period 包含 960 帧 音频
- 不是「960 个采样点」——若误当成采样点,4 通道时会算错 4 倍
一周期有多少字节?
字节/周期 = period_size × channels × bytes_per_sample
= 960 × 4 × 2
= 7680 字节
单周期延迟(最常用公式)
单周期延迟 = period_size / sample_rate
= 960 / 16000
= 0.06 s = 60 ms
直观理解:硬件大约每攒满 60 ms 的数据,就「敲一下门」——触发一次读回调 / 一次 period 就绪。
| 影响 | 说明 |
|---|---|
| period 越小 | 延迟越低,实时性更好,但回调更勤,CPU 与调度开销↑ |
| period 越大 | 延迟更高,回调更稀,CPU 更省,但手感/交互变钝 |
语音唤醒、通话类:几十毫秒级 period 很常见;乐器监听、低延迟监控有时会压到 5~15 ms(对驱动与线程调度要求更高)。
三、period_count:环形缓冲有几段
mic_cfg.period_count = 8;
含义:整个环形缓冲区由 8 个 period 串起来。
总缓冲大小(字节)
总字节 = period_size × period_count × frame_size
= 960 × 8 × 8
= 61440 字节 ≈ 60 KB
其中 frame_size = channels × bytes_per_sample = 8。
总缓冲时间(能扛多久抖动)
总缓冲时间 = (period_size × period_count) / sample_rate
= (960 × 8) / 16000
= 480 ms
直观理解:软件偶尔忙了几十~一两百毫秒,只要读指针还没被写指针追上,就 不容易 underrun/overrun;缓冲越大越「扛得住」,但:
- 捕获(录音)路径上,过大缓冲会让「听到/算法看到」的数据更「晚」
- 播放路径上,过大缓冲会增加可感延迟
period_count 常见取值 2~8(再大往往收益递减,还浪费内存、拉长最坏延迟)。
四、回调频率与 CPU
回调频率 ≈ sample_rate / period_size
= 16000 / 960
≈ 16.67 Hz
即大约 每秒 16~17 次 进入 audioMicDevCallback(或等价读线程唤醒)。
| 频率 | 典型观感 |
|---|---|
| 很低(如 <10 Hz) | CPU 省,但单次处理块大,抖动时更容易积压 |
| 适中(约 10~50 Hz) | 语音采集常用区间 |
| 很高(如 >100~200 Hz) | 低延迟场景常见,但线程唤醒、锁、拷贝成本明显上升 |
注意:CPU 开销不只看频率,还看 每次回调里做了什么(拷贝、重采样、AEC、FFT)。频率低但每次做重算法,同样会打满核。
五、当前设置(960 / 8 @ 16 kHz)速查表
| 指标 | 数值 | 评价(语音场景) |
|---|---|---|
| 单周期延迟 | 60 ms | 对唤醒/ASR 通常可接受 |
| 总缓冲 | 480 ms | 能应对处理波动与短暂调度延迟 |
| 回调频率 | ≈16.7 Hz | 不会过高,CPU 友好 |
| 一周期数据 | 7680 B | 便于按块做算法 |
| 总缓冲 | ≈60 KB | 内存占用很小 |
一句话:延迟适中、缓冲够用、回调不疯——适合多数 16 kHz 多麦采集,而不是极限低延迟监听。
六、再补几条专业概念(调参时很有用)
1)延迟其实有好几层
不要只看 period_size / fs:
| 层级 | 常见内容 |
|---|---|
| 硬件 / DMA | 驱动内部 FIFO、DMA period |
| ALSA 应用缓冲 | period_size × period_count |
| 框架 | AudioFlinger / TinyALSA / 自研 ring |
| 算法 | AEC 参考对齐、FFT 窗、VAD 平滑 |
端到端延迟 ≈ 以上之和。只改 period_size 却感觉「差不多」,往往是别层更大。
2)xrun:overrun 与 underrun
| 方向 | 名称 | 含义 |
|---|---|---|
| 录音(capture) | overrun | 写太快 / 读太慢,旧数据被盖掉 → 丢样、咔哒、算法断续 |
| 播放(playback) | underrun | 读太快 / 写太慢,缓冲空了 → 爆音、静音空洞 |
增大 period_count(总缓冲)通常能降低 xrun 概率;减小 period_size 会提高实时性,但给软件的「反应时间」更短,xrun 风险上升。
3)为什么 period 常选「整毫秒 / 对齐」的数?
例如 16 kHz 下:
| period_size | 时间 |
|---|---|
| 160 | 10 ms |
| 320 | 20 ms |
| 480 | 30 ms |
| 960 | 60 ms |
很多算法按 10/20 ms 一帧设计;960 正好是 3×320 或 6×160,方便按块切分,也方便和 48 kHz 侧的 2880 帧(60 ms)做时间对齐。
4)和「块大小(blocksize)」的关系
在部分 SDK / 引擎里叫 framesPerBuffer、blockSize,本质同类:
时间块 = blockSize / sample_rate
ALSA 再多一层:period_count 决定 环形里有多少个这样的块。
5)调参经验口诀
先定采样率与通道(业务决定)
→ 再定可接受延迟(period_size)
→ 再在实测 xrun / CPU 之间调 period_count
→ 用长时间拷机验证,而不是只听几秒
经验区间(语音 16 kHz):
- 延迟敏感:period ≈ 10~20 ms,count 4~8
- 稳妥唤醒/降噪:period ≈ 20~60 ms,count 4~8(本文 60 ms × 8 属于偏稳妥)
- 若经常 overrun:先查回调是否在做重活 / 是否被同核抢占,再考虑加大 count 或略加大 period
6)和 Android Audio HAL 的对应直觉
上层常看到的 bufferSizeInFrames、period_size(tinyalsa)等,最终仍落到「多少帧一块、几块围成环」。分析卡顿时:既要看 单块延迟,也要看 环有多深,还要结合 atrace 看线程 CPU——回调线程是否经常跑满、是否被别的线程卡住。
七、快速自算模板
把你的三个数填进去即可:
fs = 16000 // 采样率
ch = 4
bps = 2 // 16bit
period = 960 // 帧
count = 8
frame_bytes = ch * bps
period_bytes = period * frame_bytes
buffer_bytes = period * count * frame_bytes
latency_ms = 1000.0 * period / fs
buffer_ms = 1000.0 * period * count / fs
callback_hz = fs / (double)period
换 48 kHz、2 通道、32bit 时,同一套公式照抄,只改三个输入。
八、小结
| 参数 | 主要决定 |
|---|---|
| period_size | 单次通知的数据量 → 延迟 + 回调频率 |
| period_count | 环有多深 → 抗抖动能力 + 最坏缓冲延迟 |
| 二者乘积 / fs | 总可缓冲时间 |
当前 960 / 8 @ 16 kHz、4ch 16bit:约 60 ms 一拍、480 ms 总缓冲、16.7 Hz 回调——对语音采集是一套平衡点,而不是极限低延迟配置。
调参时记住:小 period 换实时,大 count 换稳定;出问题先分清是 延迟太大 还是 xrun / CPU 忙,再用公式定量,而不是只改一个「感觉」。
相关阅读
评论
还没有评论,来做第一个吧
赞同、吐槽、提问都行,评论区开放