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位地址)。

读取流程其实很简单:

  1. 主机发送起始信号
  2. 发送设备地址0xA0(写方向)
  3. 发送要读取的EDID偏移地址(如0x00)
  4. 发送重复起始信号
  5. 发送设备地址0xA1(读方向)
  6. 连续读取128字节数据
  7. 发送停止信号

代码实现大概是这样的:

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的音频数据包里去。