2、HDMI EDID解析:EDID数据结构、音频块描述、读取EDID的I2C协议
好,咱们直接进入正题。EDID这个东西,说白了就是显示器和音频设备的一张「身份证」。你想想看,你的电脑怎么知道插上去的电视支持什么分辨率?怎么知道它能解码Dolby还是DTS?全靠这张身份证。
我刚开始做HDMI驱动的时候,就吃过EDID的亏。有一次客户反馈说播放5.1声道没声音,我查了三天,最后发现是EDID里音频块描述的长度字段读错了。嗯,从那以后,我对EDID的解析就格外小心。
2.1 EDID的数据结构
EDID的全称是Extended Display Identification Data。标准长度是128字节,但HDMI通常需要扩展块,所以实际读取的是256字节甚至更多。
咱们先看基础结构。标准EDID的128字节布局是这样的:
| 偏移地址 | 长度(字节) | 描述 |
|---|---|---|
| 0x00 | 8 | 固定头:00 FF FF FF FF FF FF 00 |
| 0x08 | 10 | 厂商ID和产品ID |
| 0x12 | 2 | EDID版本号(通常为01) |
| 0x14 | 2 | EDID修订号(通常为03或04) |
| 0x36 | 18 | 第一个详细时序描述 |
| 0x7E | 1 | 扩展块数量 |
| 0x7F | 1 | 校验和 |
这里有个关键点:0x7E字节。它告诉你有多少个扩展块。如果是0,那就只有128字节。如果是1,那就还有第二个128字节块。HDMI的音频信息,就藏在这些扩展块里。
核心要点:EDID的校验和算法很简单——所有128字节相加,低8位必须为0。我每次解析完都会做这个校验,曾经遇到过某品牌显示器校验和算错,导致驱动加载失败的情况。
2.2 音频块描述(Audio Data Block)
音频信息藏在CEA扩展块里。CEA扩展块的第一个字节是0x02,表示这是CEA-861系列的扩展块。
音频数据块(ADB)的格式是这样的:
| 字节偏移 | 位域 | 描述 |
|---|---|---|
| 0 | [7:5] = 001 | 标签:表示这是音频数据块 |
| 0 | [4:0] | 长度:后续音频描述字节数 |
| 1 | [6:3] | 音频格式代码(如1=LPCM, 2=AC-3, 3=MPEG1等) |
| 1 | [2:0] | 声道数 - 1(如5.1声道 = 6,这里存5) |
| 2 | [7:3] | 采样率支持位图(32k, 44.1k, 48k等) |
| 2 | [2:0] | 位深支持位图(16bit, 20bit, 24bit) |
举个例子,一个典型的LPCM音频块可能是这样的:
// 原始数据:0x02 0x01 0x0F 0x07
// 解析过程:
// 字节0: 0x02 → 标签=001, 长度=2
// 字节1: 0x01 → 格式=LPCM, 声道数=2(立体声)
// 字节2: 0x0F → 采样率支持32k/44.1k/48k/96k
// 字节3: 0x07 → 位深支持16/20/24bit
实战技巧:我个人习惯在解析音频块时,先检查格式代码。如果是LPCM(格式1),直接解析。如果是压缩格式(AC-3、DTS等),需要额外检查接收端是否支持。我曾经遇到一个电视,EDID里写了支持AC-3,但实际解码器是坏的,播放AC-3内容时只有噪音。
2.3 读取EDID的I2C协议
EDID是通过I2C总线读取的。HDMI的DDC通道,本质上就是一条I2C总线。地址是0x50(7位地址)或0xA0(8位地址)。
读取流程其实很简单:
- 主机发送起始信号
- 发送设备地址0xA0(写方向)
- 发送要读取的EDID偏移地址(如0x00)
- 发送重复起始信号
- 发送设备地址0xA1(读方向)
- 连续读取128字节数据
- 发送停止信号
代码实现大概是这样的:
int read_edid_block(int i2c_fd, uint8_t block_num, uint8_t *buf)
{
uint8_t offset = block_num * 128;
uint8_t addr = 0x50; // 7位I2C地址
// 写偏移地址
struct i2c_msg msgs[2];
msgs[0].addr = addr;
msgs[0].flags = 0; // 写
msgs[0].len = 1;
msgs[0].buf = &offset;
// 读数据
msgs[1].addr = addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 128;
msgs[1].buf = buf;
struct i2c_rdwr_ioctl_data data;
data.msgs = msgs;
data.nmsgs = 2;
return ioctl(i2c_fd, I2C_RDWR, &data);
}
注意:有些HDMI接收端芯片的I2C时序要求很严格。我曾经遇到过一个问题:读取EDID时,两次I2C操作之间的间隔不能超过50ms,否则芯片会进入休眠状态。解决办法是在读取前先发一个虚拟的写操作「唤醒」芯片。
2.4 实战中的坑与避坑指南
做EDID解析这么多年,我总结了几条经验:
- 校验和必须验证:我曾经遇到过EDID校验和错误但设备能正常工作的案例。但作为驱动开发者,我们必须严格校验,否则出了问题没法定位。
- 扩展块可能不连续:有些设备会在EDID中声明有3个扩展块,但实际只实现了2个。读取第3个块时会返回全0或全FF。代码里要做好容错。
- 音频格式的优先级:如果EDID同时支持LPCM和AC-3,我个人建议优先使用LPCM。因为LPCM的解码延迟最低,兼容性最好。
- 采样率匹配:EDID里列出的采样率是「支持」的,但不代表所有采样率都能稳定工作。我建议在驱动里加一个采样率回退机制——如果48kHz不行,自动降到44.1kHz。
嗯,EDID解析这块内容其实不难,但细节特别多。你只要把数据结构吃透了,I2C协议写对了,剩下的就是各种边界情况的处理。下一章我们会讲怎么把这些解析出来的音频信息,配置到HDMI的音频数据包里去。