第2章:音频数据包结构——Audio Sample Packet格式解析
好,咱们接着聊。上一章我们把HDMI的整体架构捋了一遍,这一章要深入一个非常具体的细节——音频数据包长什么样。说白了,就是音频数据在HDMI线里是怎么“打包”的。
我记得刚接触HDMI音频驱动时,最头疼的就是这个包结构。文档写得晦涩,寄存器手册又喜欢用缩写。后来我干脆自己画了一张图,贴在工位上,天天看。今天我把这张图“翻译”成文字,分享给你。
2.1 音频数据包的基本单位
HDMI传输音频,不是一股脑把PCM数据扔过去。它要打包成一个个小单元,叫Audio Sample Packet。每个包携带若干个音频样本。
你想想看,视频信号是连续的像素流,音频信号是离散的采样点。两者要同步,就必须有严格的时序和包结构。HDMI规范里定义了多种音频包类型,但最常用的是Audio Sample Packet (ASP)。
核心要点:每个Audio Sample Packet包含一个包头(Packet Header)和包体(Packet Body)。包头4字节,包体最多32字节。
2.2 包头结构详解
包头虽然只有4个字节,但信息量很大。我习惯把它拆成两部分看:
| 字节偏移 | 位域 | 描述 |
|---|---|---|
| Byte 0 | Bits[7:0] | Packet Type = 0x02(音频数据包标识) |
| Byte 1 | Bits[7:0] | 数据块数量(Number of Audio Samples per Channel) |
| Byte 2 | Bits[7:4] | 保留位(必须为0) |
| Byte 2 | Bits[3:0] | 音频样本字长(Sample Size) |
| Byte 3 | Bits[7:0] | 音频时钟恢复信息(N/CTS相关,后面细讲) |
这里有个坑,我当年踩过。Byte 2的低4位表示样本字长,但它的编码方式不是直接写16或24。规范里用的是索引值:
- 0x01 → 16位
- 0x02 → 20位
- 0x03 → 24位
嗯,要注意,如果你直接写0x10,接收端会解析成别的意思。我曾经因为这个bug,调试了整整两天。
2.3 包体结构——音频样本怎么放
包体才是真正装音频数据的地方。它的布局取决于两个因素:声道数和样本字长。
举个例子,最常见的2声道、16位PCM:
包体布局(32字节):
Byte 0-1: 左声道样本0(16位)
Byte 2-3: 右声道样本0(16位)
Byte 4-5: 左声道样本1(16位)
Byte 6-7: 右声道样本1(16位)
...
Byte 30-31: 右声道样本7(16位)
每个包可以放8个样本(每个声道)。如果是24位样本,每个样本占3字节,那一个包就只能放5个样本左右,剩下的字节填充0。
我的经验:实际开发中,我建议你优先用16位样本。兼容性最好,而且包利用率最高。24位虽然音质好,但很多老设备不支持。
2.4 音频时钟恢复——N/CTS机制
好,接下来是重头戏。音频时钟恢复,说白了就是让接收端知道音频的采样频率是多少。HDMI没有单独的音频时钟线,它必须从TMDS时钟里“算”出来。
怎么算?靠两个参数:N 和 CTS。
公式很简单:
音频采样频率 = (TMDS时钟频率 × N) / (128 × CTS)
嗯,别被公式吓到。我解释一下:
- TMDS时钟频率:就是HDMI链路的像素时钟,比如148.5MHz(1080p@60Hz)
- N:一个固定的整数,由音频采样频率决定
- CTS:一个动态计算的值,用来补偿时钟偏差
N的值是规范里写死的。比如:
| 音频采样频率 (kHz) | N值 |
|---|---|
| 32.0 | 4096 |
| 44.1 | 6272 |
| 48.0 | 6144 |
| 96.0 | 12288 |
| 192.0 | 24576 |
CTS怎么算?CTS = (TMDS时钟频率 × N) / (128 × 音频采样频率)。
举个例子,假设TMDS时钟是148.5MHz,音频是48kHz:
CTS = (148500000 × 6144) / (128 × 48000)
= 912384000000 / 6144000
= 148500
你看,算出来正好是148500。这个值会放在音频数据包的包头里,发送给接收端。
注意:CTS必须是整数。如果除不尽,说明你的TMDS时钟和音频采样频率不匹配。我曾经在一个项目中,因为用了非标准的视频分辨率,导致CTS算出来是小数,音频一直断断续续。最后换了标准分辨率才解决。
2.5 实际开发中的注意事项
聊了这么多理论,说点实际的。你在写音频驱动时,这几个点一定要检查:
- 包类型必须正确:包头第一个字节必须是0x02,否则接收端不认。
- 样本对齐:16位样本按2字节对齐,24位按3字节对齐。别搞混。
- N/CTS更新时机:每次音频采样频率变化时,必须重新计算并更新N和CTS。我建议在音频流开始前就设置好,不要中途改。
- 填充字节:包体没用完的字节,必须填0。有些接收端会检查这个。
避坑指南:我曾经在一个项目中,音频偶尔出现“噼啪”声。查了三天,最后发现是N/CTS更新不及时导致的。接收端用旧的CTS算出来的频率不对,音频就失真了。从那以后,我每次改音频参数都会先停掉音频流,更新完再重启。
2.6 小结
这一章我们讲了音频数据包的格式,从包头到包体,再到时钟恢复机制。说白了,HDMI音频传输的核心就是两件事:把样本打包好,把时钟信息传过去。
下一章,我们会深入音频驱动初始化流程,看看怎么在代码里把这些包发出去。到时候我会贴一些实际的寄存器配置代码,你一看就明白了。
好,今天就到这儿。有问题随时找我。