2. 解析EDID中的时序列表

任务说明

本案例需要从EDID 1.3/1.4的128字节扩展块中,解析出标准时序(Standard Timing,ST)和详细时序描述(Detailed Timing Descriptor,DTD),并列出显示器支持的分辨率与刷新率组合。输入为原始EDID字节数组,输出为可读的时序列表。

EDID时序数据结构

EDID 1.3/1.4的128字节块中,时序信息分布在三个区域:

  • 标准时序(偏移54~71,共8组):每组2字节。第1字节为水平有效像素/8,第2字节高4位为宽高比(0=16:10,1=4:3,2=5:4,3=16:9),低4位为垂直刷新率(实际值=该值+60)。若第1字节为0x01且第2字节为0x01,表示该组无效。
  • 详细时序描述(偏移54~125,共4组,每组18字节):每组前2字节为像素时钟(单位10kHz)。若像素时钟为0,则该组不是DTD,而是显示器描述(如显示器名称、范围限制等),需跳过。DTD中水平有效像素位于偏移+2(低字节)和+4(高字节bit7:4),垂直有效行数位于偏移+5(低字节)和+7(高字节bit7:4)。
  • 标准时序与DTD的优先级:ST提供基础分辨率/刷新率组合,DTD提供精确的像素时钟和时序参数。本案例仅提取分辨率与刷新率,不解析完整时序参数。

实现代码


#include <stdio.h>
#include <stdint.h>
#include <string.h>

// 从EDID中解析标准时序
void parse_standard_timings(const uint8_t *edid) {
    printf("=== 标准时序 (Standard Timings) ===\n");
    int count = 0;
    for (int i = 0; i < 8; i++) {
        int offset = 54 + i * 2;
        uint8_t h_pixels_byte = edid[offset];
        uint8_t aspect_rate = edid[offset + 1];
        
        // 无效条目检查:0x01 0x01 表示该组未使用
        if (h_pixels_byte == 0x01 && aspect_rate == 0x01) {
            continue;
        }
        
        // 水平像素 = 第1字节 * 8
        int h_pixels = h_pixels_byte * 8;
        // 宽高比编码
        int aspect_code = (aspect_rate >> 4) & 0x0F;
        // 刷新率 = 低4位 + 60
        int v_rate = (aspect_rate & 0x0F) + 60;
        
        const char *aspect_str;
        int v_pixels = 0;
        switch (aspect_code) {
            case 0: aspect_str = "16:10"; v_pixels = h_pixels * 10 / 16; break;
            case 1: aspect_str = "4:3";   v_pixels = h_pixels * 3 / 4;  break;
            case 2: aspect_str = "5:4";   v_pixels = h_pixels * 4 / 5;  break;
            case 3: aspect_str = "16:9";  v_pixels = h_pixels * 9 / 16; break;
            default: aspect_str = "未知"; v_pixels = 0; break;
        }
        
        printf("  %dx%d @ %dHz (比例 %s)\n", h_pixels, v_pixels, v_rate, aspect_str);
        count++;
    }
    if (count == 0) {
        printf("  (无有效标准时序)\n");
    }
}

// 从EDID中解析详细时序描述(DTD)
void parse_detailed_timings(const uint8_t *edid) {
    printf("\n=== 详细时序描述 (Detailed Timing Descriptors) ===\n");
    int count = 0;
    for (int i = 0; i < 4; i++) {
        int offset = 54 + i * 18;
        
        // 像素时钟(单位10kHz),若为0则该组不是DTD
        uint16_t pixel_clock_10khz = edid[offset] | (edid[offset + 1] << 8);
        if (pixel_clock_10khz == 0) {
            continue;
        }
        
        // 水平有效像素:低字节在offset+2,高4位在offset+4的bit7:4
        int h_pixels = edid[offset + 2] | ((edid[offset + 4] & 0xF0) << 4);
        // 垂直有效行数:低字节在offset+5,高4位在offset+7的bit7:4
        int v_lines = edid[offset + 5] | ((edid[offset + 7] & 0xF0) << 4);
        
        // 像素时钟转换为MHz
        double pixel_clock_mhz = pixel_clock_10khz / 100.0;
        
        printf("  %dx%d @ 像素时钟 %.2f MHz\n", h_pixels, v_lines, pixel_clock_mhz);
        count++;
    }
    if (count == 0) {
        printf("  (无有效详细时序)\n");
    }
}

int main() {
    // 示例EDID数据(128字节,仅展示关键部分,其余填充0)
    // 实际使用时替换为从显示器读取的完整EDID
    uint8_t edid[128] = {0};
    
    // 填充标准时序区(偏移54~71)
    // 第1组:1920x1080 @ 60Hz (16:9)
    edid[54] = 240;  // 1920/8 = 240
    edid[55] = (3 << 4) | 0;  // 16:9, 60Hz (0+60)
    
    // 第2组:1280x720 @ 60Hz (16:9)
    edid[56] = 160;  // 1280/8 = 160
    edid[57] = (3 << 4) | 0;  // 16:9, 60Hz
    
    // 第3组:1024x768 @ 75Hz (4:3)
    edid[58] = 128;  // 1024/8 = 128
    edid[59] = (1 << 4) | 15;  // 4:3, 75Hz (15+60)
    
    // 其余标准时序组保持0x01 0x01(无效)
    for (int i = 3; i < 8; i++) {
        edid[54 + i * 2] = 0x01;
        edid[55 + i * 2] = 0x01;
    }
    
    // 填充第1个DTD(偏移54~71):1920x1080 @ 60Hz
    // 像素时钟 = 148.5 MHz = 14850 * 10kHz
    edid[54] = 0x50;  // 14850 & 0xFF = 0x50
    edid[55] = 0x3A;  // 14850 >> 8 = 0x3A
    // 水平有效像素 1920 = 0x0780
    edid[56] = 0x80;  // 低字节
    edid[58] = 0x07;  // 高字节放在offset+4的高4位
    edid[58] = (edid[58] & 0x0F) | (0x07 << 4);  // 实际应写入offset+4
    // 修正:水平高4位在offset+4的bit7:4
    edid[58] = 0x70;  // 0x07 << 4 = 0x70
    
    // 垂直有效行数 1080 = 0x0438
    edid[59] = 0x38;  // 低字节
    edid[61] = 0x04;  // 高字节放在offset+7的高4位
    edid[61] = 0x40;  // 0x04 << 4 = 0x40
    
    // 第2个DTD(偏移72~89):1280x720 @ 60Hz
    // 像素时钟 = 74.25 MHz = 7425 * 10kHz
    edid[72] = 0x01;  // 7425 & 0xFF = 0x01
    edid[73] = 0x1D;  // 7425 >> 8 = 0x1D
    // 水平 1280 = 0x0500
    edid[74] = 0x00;
    edid[76] = 0x50;  // 0x05 << 4
    // 垂直 720 = 0x02D0
    edid[77] = 0xD0;
    edid[79] = 0x20;  // 0x02 << 4
    
    // 第3、4个DTD设为0(无效)
    memset(edid + 90, 0, 36);
    
    // 解析并输出
    parse_standard_timings(edid);
    parse_detailed_timings(edid);
    
    return 0;
}

编译与运行


gcc -o edid_parse edid_parse.c
./edid_parse

预期输出


=== 标准时序 (Standard Timings) ===
  1920x1080 @ 60Hz (比例 16:9)
  1280x720 @ 60Hz (比例 16:9)
  1024x768 @ 75Hz (比例 4:3)

=== 详细时序描述 (Detailed Timing Descriptors) ===
  1920x1080 @ 像素时钟 148.50 MHz
  1280x720 @ 像素时钟 74.25 MHz

验证方法

将上述代码中的示例EDID替换为从真实显示器读取的EDID数据(可通过Linux的edid-decode命令或I2C读取),编译运行后观察输出。判定标准:

  • 标准时序中列出的分辨率/刷新率与EDID中ST区域编码一致;
  • DTD中列出的分辨率与像素时钟与EDID中DTD区域数据一致;
  • 无效条目(0x01 0x01或像素时钟为0)被正确跳过,不产生错误输出。
EDID 128字节块布局 头部 (0~7) 厂商/版本 基本参数 (8~17) 亮度/色度 色度信息 (18~24) RGB/白点 已建立时序 (25~34) 厂商保留 (35~53) 标准时序区域 (偏移 54~71) — 8组 × 2字节 每组: [水平像素/8] [宽高比(4bit) | 刷新率-60(4bit)] 详细时序描述区域 (偏移 54~125) — 4组 × 18字节 每组: [像素时钟(2B)] [水平有效(2B)] [水平消隐(2B)] [垂直有效(2B)] [垂直消隐(2B)] ... 像素时钟=0 → 该组为显示器描述(名称/范围),非DTD 扩展标志 (126) | 校验和 (127) 解析流程 读取偏移54~71 判断0x01 0x01无效? 计算分辨率与刷新率并输出

关键说明

  • 标准时序中的垂直分辨率由宽高比推算,并非EDID直接存储,因此实际显示器的精确垂直像素可能略有差异(如16:9的1366x768在ST中编码为1360x768)。
  • DTD中的像素时钟可用于计算精确刷新率:刷新率 = 像素时钟 / (水平总像素 × 垂直总行数)。本案例仅提取分辨率,如需精确刷新率需解析完整时序参数。
  • 实际应用中,EDID可能包含多个扩展块(如CEA-861),本案例仅处理128字节的EDID 1.3/1.4基础块。