4. EDID扩展块解析

任务说明

本案例实现一个EDID解析器,读取原始EDID二进制数据,识别扩展块是否存在,并解析CEA-861扩展块中的音频数据块(Audio Data Block)和视频数据块(Video Data Block),提取关键参数。输入为EDID的128字节原始数据(以十六进制字符串提供),输出为解析后的结构化信息。

EDID扩展块结构

标准EDID长度为128字节。若第126字节(偏移0x7E)的值不为0,则表示存在扩展块。CEA-861扩展块通常位于第二个128字节块(偏移128~255),其结构如下:

  • 偏移0:扩展块标签(0x02表示CEA-861)
  • 偏移1:保留字节(通常为0x03)
  • 偏移2:DBC(Data Block Collection)起始位置
  • 偏移3:FEATURES(支持特性)
  • 偏移4~DBC起始-1:详细时序描述符(可跳过)
  • DBC起始处开始:依次排列数据块,每个数据块首字节高3位为块类型,低5位为块长度(字节数,不含首字节)

数据块类型定义

  • 0x02:视频数据块(Video Data Block)——每个字节表示一个VIC(Video Identification Code)
  • 0x01:音频数据块(Audio Data Block)——每3字节描述一种音频格式:
    • 字节0:高3位为音频格式(1=LPCM,2=AC-3,3=MPEG1,4=MP3,5=MPEG2,6=AAC,7=DTS,8=ATRAC,9=One Bit Audio,10=Dolby Digital+,11=DTS-HD,12=MAT(DD+),13=DST,14=WMA Pro,15=参考格式)
    • 字节0:低4位为声道数减1(即声道数=低4位+1)
    • 字节1:采样频率位图(bit0=32kHz,bit1=44.1kHz,bit2=48kHz,bit3=88.2kHz,bit4=96kHz,bit5=176.4kHz,bit6=192kHz)
    • 字节2:位深/码率(对LPCM:bit0~2表示位深,0=16bit,1=20bit,2=24bit)
  • 0x03:扬声器分配数据块(Speaker Allocation Data Block)
  • 0x04:VESA视频时序块(VESA Video Timing Block)
  • 0x05:预留
  • 0x60:扩展标签数据块(Extended Tag Data Block)

代码实现


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

#define EDID_BASE_SIZE 128
#define CEA_EXT_TAG 0x02

// 音频格式名称
const char* audio_fmt_name(uint8_t fmt) {
    switch(fmt) {
        case 1: return "LPCM";
        case 2: return "AC-3";
        case 3: return "MPEG1";
        case 4: return "MP3";
        case 5: return "MPEG2";
        case 6: return "AAC";
        case 7: return "DTS";
        case 8: return "ATRAC";
        case 9: return "One Bit Audio";
        case 10: return "Dolby Digital+";
        case 11: return "DTS-HD";
        case 12: return "MAT(DD+)";
        case 13: return "DST";
        case 14: return "WMA Pro";
        case 15: return "Reference";
        default: return "Unknown";
    }
}

void parse_cea_block(const uint8_t* ext, int len) {
    if (len < 4) {
        printf("CEA扩展块长度不足\n");
        return;
    }
    uint8_t dbc_start = ext[2];
    if (dbc_start < 4 || dbc_start >= len) {
        printf("DBC起始位置无效: %d\n", dbc_start);
        return;
    }
    printf("CEA扩展块: DBC起始=%d, 特性=0x%02X\n", dbc_start, ext[3]);
    
    int pos = dbc_start;
    while (pos < len) {
        uint8_t tag_len = ext[pos];
        uint8_t tag = (tag_len >> 5) & 0x07;
        uint8_t dlen = tag_len & 0x1F;
        pos++;
        if (pos + dlen > len) {
            printf("数据块越界: tag=%d, len=%d\n", tag, dlen);
            break;
        }
        
        if (tag == 0x02) { // 视频数据块
            printf("视频数据块 (长度=%d): ", dlen);
            for (int i = 0; i < dlen; i++) {
                printf("VIC=%d ", ext[pos+i]);
            }
            printf("\n");
        } else if (tag == 0x01) { // 音频数据块
            printf("音频数据块 (长度=%d):\n", dlen);
            for (int i = 0; i + 2 < dlen; i += 3) {
                uint8_t b0 = ext[pos+i];
                uint8_t b1 = ext[pos+i+1];
                uint8_t b2 = ext[pos+i+2];
                uint8_t fmt = (b0 >> 3) & 0x0F;
                uint8_t ch = (b0 & 0x07) + 1;
                printf("  格式=%s, 声道=%d, 采样率=", audio_fmt_name(fmt), ch);
                if (b1 & 0x01) printf("32k ");
                if (b1 & 0x02) printf("44.1k ");
                if (b1 & 0x04) printf("48k ");
                if (b1 & 0x08) printf("88.2k ");
                if (b1 & 0x10) printf("96k ");
                if (b1 & 0x20) printf("176.4k ");
                if (b1 & 0x40) printf("192k ");
                if (fmt == 1) { // LPCM位深
                    uint8_t depth = b2 & 0x07;
                    printf(", 位深=");
                    if (depth == 0) printf("16bit");
                    else if (depth == 1) printf("20bit");
                    else if (depth == 2) printf("24bit");
                    else printf("reserved(%d)", depth);
                }
                printf("\n");
            }
        } else if (tag == 0x03) {
            printf("扬声器分配数据块 (长度=%d)\n", dlen);
        } else if (tag == 0x04) {
            printf("VESA视频时序块 (长度=%d)\n", dlen);
        } else if (tag == 0x60) {
            printf("扩展标签数据块 (长度=%d)\n", dlen);
        } else {
            printf("未知数据块 tag=%d (长度=%d)\n", tag, dlen);
        }
        pos += dlen;
    }
}

void parse_edid(const uint8_t* edid) {
    // 检查EDID头
    if (memcmp(edid, "\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", 8) != 0) {
        printf("无效EDID头\n");
        return;
    }
    printf("EDID制造商: %c%c%c\n", 
           'A' + ((edid[8] >> 2) & 0x1F) - 1,
           'A' + (((edid[8] & 0x03) << 3) | ((edid[9] >> 5) & 0x07)) - 1,
           'A' + (edid[9] & 0x1F) - 1);
    
    uint8_t ext_count = edid[126];
    printf("扩展块数量: %d\n", ext_count);
    
    if (ext_count == 0) {
        printf("无扩展块,EDID仅包含128字节基础块\n");
        return;
    }
    
    // 解析第一个扩展块(偏移128)
    const uint8_t* ext = edid + EDID_BASE_SIZE;
    if (ext[0] == CEA_EXT_TAG) {
        printf("发现CEA-861扩展块\n");
        parse_cea_block(ext, EDID_BASE_SIZE);
    } else {
        printf("扩展块标签=0x%02X (非CEA-861)\n", ext[0]);
    }
}

int main() {
    // 示例EDID:包含CEA扩展块的十六进制数据(前128字节基础块 + 128字节CEA扩展块)
    // 此处仅展示前256字节中的关键部分,完整数据需256字节
    uint8_t edid[256] = {0};
    
    // 基础块头
    uint8_t base_header[8] = {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00};
    memcpy(edid, base_header, 8);
    
    // 制造商ID: "ACR" -> 0x1A, 0xCD (示例)
    edid[8] = 0x1A;  // 高5位=0x0D -> 'A'+12='M'? 实际按位计算
    edid[9] = 0xCD;
    
    // 设置扩展块数量为1
    edid[126] = 0x01;
    
    // CEA扩展块(偏移128)
    uint8_t* ext = edid + 128;
    ext[0] = 0x02;          // CEA标签
    ext[1] = 0x03;          // 保留
    ext[2] = 0x04;          // DBC起始偏移=4(无详细时序描述符)
    ext[3] = 0x00;          // 特性
    
    // 数据块1:视频数据块,包含VIC 16和VIC 4
    ext[4] = (0x02 << 5) | 0x02;  // tag=2, len=2
    ext[5] = 16;  // VIC 16 (1080p60)
    ext[6] = 4;   // VIC 4 (720p60)
    
    // 数据块2:音频数据块,LPCM 2ch 48kHz 16bit + AC-3 6ch 48kHz
    ext[7] = (0x01 << 5) | 0x06;  // tag=1, len=6
    // LPCM: 格式=1, 声道=2 -> (1<<3)|(2-1)=0x09
    ext[8] = 0x09;
    ext[9] = 0x04;  // 48kHz
    ext[10] = 0x00; // 16bit
    // AC-3: 格式=2, 声道=6 -> (2<<3)|(6-1)=0x15
    ext[11] = 0x15;
    ext[12] = 0x04; // 48kHz
    ext[13] = 0x00; // 码率(AC-3中此字节含义不同,此处仅示例)
    
    // 数据块3:扬声器分配(可选,用于验证跳过逻辑)
    ext[14] = (0x03 << 5) | 0x03;  // tag=3, len=3
    ext[15] = 0x00;
    ext[16] = 0x00;
    ext[17] = 0x00;
    
    // 调用解析
    parse_edid(edid);
    
    return 0;
}

编译与运行


gcc -o edid_parser edid_parser.c
./edid_parser

预期输出


EDID制造商: M??
扩展块数量: 1
发现CEA-861扩展块
CEA扩展块: DBC起始=4, 特性=0x00
视频数据块 (长度=2): VIC=16 VIC=4 
音频数据块 (长度=6):
  格式=LPCM, 声道=2, 采样率=48k , 位深=16bit
  格式=AC-3, 声道=6, 采样率=48k 
扬声器分配数据块 (长度=3)

验证方法

运行程序后,观察输出是否与上述预期一致。验证要点:

  • EDID头校验通过,制造商ID正确解析
  • 扩展块数量识别为1,CEA标签正确识别
  • 视频数据块中VIC 16和VIC 4被正确提取
  • 音频数据块中LPCM(2声道/48kHz/16bit)和AC-3(6声道/48kHz)参数正确解析
  • 扬声器分配数据块被正确跳过并标记

判定标准:输出中所有字段与代码中构造的EDID数据完全一致,无越界或解析错误提示。

SVG:EDID扩展块解析流程

EDID基础块 128字节 偏移126 扩展块数量 > 0? 无扩展块 结束 CEA-861扩展块 偏移128, 标签=0x02 DBC数据块集合 从偏移[2]开始遍历 视频数据块 tag=0x02 提取VIC列表 音频数据块 tag=0x01 格式/声道/采样率 其他数据块 tag=0x03/0x04/0x60 跳过或标记 解析结果 结构化打印 验证输出