当前位置: 首页 > news >正文

EDID 数据结构解析与编辑工具:校验和计算、厂商/设备名编解码、物理地址读写、颜色与时序信息提取

EDID 数据结构解析与编辑工具:校验和计算、厂商/设备名编解码、物理地址读写、颜色与时序信息提取

本程序实现对 Extended Display Identification Data (EDID) 二进制文件的完整解析与交互式编辑功能,涵盖数据校验、制造商ID编解码、显示器名称读写、物理地址设置、颜色坐标解析、时序信息展示等核心知识点。所有代码保留原始变量名与函数名,每条代码均添加详细注释,并附带理想运行结果示例。


🧩 一、头文件与辅助函数

#include <ctype.h>   // 提供 toupper() 函数,用于字符转大写
#include <math.h>    // 提供 fabs() 函数(虽未使用,保留原结构)
#include <stdint.h>  // 提供 uint8_t, uint16_t, uint32_t 等标准整型
#include <stdio.h>   // 标准输入输出
#include <stdlib.h>  // 提供 malloc(), free(), exit()
#include <string.h>  // 提供字符串与内存操作函数:strlen, memcpy, memset, memcmp// 理想运行结果:无直接输出,为后续函数提供基础支持

🔢 二、校验和计算与修复

1. 计算128字节块的校验和

static uint8_t calc_checksum(const uint8_t *blk)
{uint32_t sum = 0;                         // 初始化累加器for (int i = 0; i < 128; i++)            // 遍历全部128字节{sum = sum + blk[i];                     // 累加每一字节}return (uint8_t)(sum & 0xFF);             // 返回低8位作为校验和
}
// 理想运行结果:输入有效EDID块,返回如 0xA3

2. 修复块的校验和(修改第127字节)

static void fix_checksum(uint8_t *blk)
{uint32_t sum = 0;                         // 初始化累加器for (int i = 0; i < 127; i++)            // 仅累加前127字节{sum = sum + blk[i];                     // 累加}blk[127] = (uint8_t)((256 - (sum & 0xFF)) & 0xFF); // 第128字节设为补码校验值
}
// 理想运行结果:修改 blk[127] 使整个块校验和为0

🏭 三、制造商ID编解码

1. 解码16位制造商代码为3字母字符串

static void decode_mfg_id(uint16_t code, char out[4])
{int c1 = ((code >> 10) & 0x1F);           // 提取高5位(bit14~10)int c2 = ((code >> 5) & 0x1F);            // 提取中5位(bit9~5)int c3 = (code & 0x1F);                   // 提取低5位(bit4~0)out[0] = 'A' + c1 - 1;                    // 映射为 'A'~'Z'(1→A, 26→Z)out[1] = 'A' + c2 - 1;out[2] = 'A' + c3 - 1;out[3] = '\0';                            // 字符串结束符
}
// 理想运行结果:输入 0x23A0 → 输出 "SAM"

2. 编码3字母字符串为16位制造商代码

static int encode_mfg_id(const char *three, uint16_t *out)
{if (strlen(three) != 3)                   // 必须为3字符{return -1;                              // 长度错误}int c1 = toupper(three[0]) - 'A' + 1;     // 转大写,映射为1~26int c2 = toupper(three[1]) - 'A' + 1;int c3 = toupper(three[2]) - 'A' + 1;if (c1 < 1 || c1 > 26 || c2 < 1 || c2 > 26 || c3 < 1 || c3 > 26){return -1;                              // 超出A~Z范围}*out = (c1 << 10) | (c2 << 5) | c3;       // 组合成16位码return 0;                                 // 成功
}
// 理想运行结果:输入 "SAM" → 输出 0x23A0,返回0

📝 四、安全ASCII转换与显示器名称操作

1. 安全转换二进制数据为可打印ASCII字符串

static const char *safe_ascii(const uint8_t *buf, int len, char *out, int outsz)
{int n = len < outsz - 1 ? len : outsz - 1; // 防止缓冲区溢出for (int i = 0; i < n; ++i){out[i] = (buf[i] >= 0x20 && buf[i] <= 0x7E) ? buf[i] : ' '; // 非打印字符转空格}out[n] = '\0';                            // 结束符for (int i = n - 1; i >= 0; --i)          // 去除末尾空白符{if (out[i] == ' ' || out[i] == '\n' || out[i] == '\r' || out[i] == '\t'){out[i] = '\0';                        // 截断}else{break;                                // 遇到非空白字符停止}}return out;
}
// 理想运行结果:输入 {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x00} → 输出 "Hello"

2. 从EDID块提取显示器名称

static const char *get_monitor_name(const uint8_t *blk, char out[32])
{for (int i = 0; i < 4; ++i)               // 搜索4个描述符块{int off = 54 + i * 18;                  // 每块18字节,从54字节开始const uint8_t *d = blk + off;           // 定位到当前块if (d[0] == 0 && d[1] == 0 && d[3] == 0xFC) // 检查是否为显示器名称描述符{char tmp[16];safe_ascii(d + 5, 13, tmp, sizeof(tmp)); // 从d[5]开始取13字节转字符串snprintf(out, 32, "%s", tmp);           // 复制到输出缓冲区return out;}}out[0] = '\0';                            // 未找到则返回空串return out;
}
// 理想运行结果:若块中含 "DELL U2720Q" → 返回该字符串

3. 设置显示器名称到EDID块

static void set_monitor_name(uint8_t *blk, const char *name)
{for (int i = 0; i < 4; ++i){int off = 54 + i * 18;uint8_t *d = blk + off;if (d[0] == 0 && d[1] == 0 && d[3] == 0xFC) // 找到名称描述符{memset(d + 5, 0x20, 13);              // 用空格填充原名称区size_t n = strlen(name);              // 获取新名称长度if (n > 13)                           // 最多13字符n = 13;memcpy(d + 5, name, n);               // 复制新名称d[5 + n] = 0x0A;                      // 末尾加换行符(非必须,保留原逻辑)return;}}
}
// 理想运行结果:将 "MyMonitor" 写入对应描述符块

📍 五、物理地址读写(CEA扩展块)

1. 从扩展块读取物理地址(A.B.C.D格式)

static int get_physical_address_simple(uint8_t *ext, int *a, int *b, int *c, int *d)
{if (ext == NULL || ext[0] != 0x02)        // 必须为CEA扩展块{return -1;}uint8_t hi = ext[0x04];                   // 高字节uint8_t lo = ext[0x05];                   // 低字节*a = (hi >> 4) & 0x0F;                    // A: 高4位*b = hi & 0x0F;                           // B: 低4位*c = (lo >> 4) & 0x0F;                    // C: 高4位*d = lo & 0x0F;                           // D: 低4位return 0;
}
// 理想运行结果:若 ext[4]=0xAB, ext[5]=0xCD → a=10, b=11, c=12, d=13

2. 设置物理地址到扩展块

static int set_physical_address_simple(uint8_t *ext, int a, int b, int c, int d)
{if (ext == NULL)                          // 指针不能为空{return -1;}ext[0x04] = ((a & 0x0F) << 4) | (b & 0x0F); // 组合A(高4位)与B(低4位)ext[0x05] = ((c & 0x0F) << 4) | (d & 0x0F); // 组合C(高4位)与D(低4位)return 0;
}
// 理想运行结果:设置 a=1,b=2,c=3,d=4 → ext[4]=0x12, ext[5]=0x34

💾 六、文件读写函数

1. 读取整个EDID文件到内存

static uint8_t *read_all(const char *path, size_t *out_sz)
{FILE *f = fopen(path, "rb");              // 以二进制只读打开if (f == NULL){perror("fopen fail");                   // 打印系统错误return NULL;}fseek(f, 0, SEEK_END);                    // 定位到文件末尾long sz = ftell(f);                       // 获取文件大小fseek(f, 0, SEEK_SET);                    // 回到文件开头if (sz <= 0 || (sz % 128) != 0)           // 必须为128倍数{fclose(f);return NULL;}uint8_t *buf = malloc(sz);                // 分配内存fread(buf, 1, sz, f);                     // 读取全部内容fclose(f);                                // 关闭文件*out_sz = sz;                             // 返回实际大小return buf;
}
// 理想运行结果:成功读取 "edid.bin"(256字节)→ 返回指针,out_sz=256

2. 将内存数据写回文件

static int write_all(const char *path, const uint8_t *buf, size_t sz)
{FILE *f = fopen(path, "wb");              // 以二进制写入打开if (f == NULL){return -1;                              // 打开失败}fwrite(buf, 1, sz, f);                    // 写入全部数据fclose(f);                                // 关闭文件return 0;                                 // 成功
}
// 理想运行结果:成功写入 → 返回0

🎨 七、颜色坐标解析

1. 将10位色度值转换为浮点坐标(0.0~1.0)

static double get_color_value(unsigned short num)
{double value = 0;double power_of_2 = 1.0 / 1024.0;         // 2^(-10),最低位权值for (int i = 0; i < 10; i++)              // 遍历10位{if (num & (1 << i))                     // 检查第i位是否为1{value += power_of_2;                  // 累加对应权值}power_of_2 *= 2.0;                      // 权值左移(×2)}return value;
}
// 理想运行结果:输入 0x200 (0b1000000000) → 返回 0.5000

📊 八、信息展示函数

1. 打印十六进制数据(调试用)

static void print_hex_data(uint8_t *buf, size_t sz)
{printf("load file read dat size =%zu\n", sz); // 打印总字节数for (size_t i = 0; i < sz; i++){if (i % 16 == 0)                        // 每16字节换行{printf("\n%02zx ", i);                // 打印偏移地址}printf("%02x ", buf[i]);                // 打印当前字节}printf("\n");
}
// 理想运行结果:
// 00  ff  ff  ff  ff  ff  ff  00  4c  2d  01  02  ...

2. 展示EDID全部信息(核心解析函数)

static void show_all_info(uint8_t *buf, size_t sz)
{printf("\n--------------Base info------------------\n");// 验证EDID头unsigned char head[] = {0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0};if (0 == memcmp(head, buf, sizeof(head))){printf("this is valid edid\n");}else{printf("this is not valid edid\n");return;}// 校验和检查(基础块)uint32_t sum = 0;for (int i = 0; i < 127; i++){sum += buf[i];}unsigned char check = (256 - (sum % 256)) % 256;if (buf[127] == check){printf("base check 0x%02x\n", buf[127]);}else{printf("base check failure....\n");return;}// 扩展块校验和(如有)if (sz >= 256 && buf[128] == 0x02){sum = 0;for (int i = 0; i < 127; i++){sum += buf[128 + i];}check = (256 - (sum % 256)) % 256;if (buf[255] == check){printf("扩展部分 check 0x%02x\n", buf[255]);}else{printf("ext check failure....\n");return;}}// 制造商信息uint16_t mfg_code = (buf[8] << 8) | buf[9];char mfg[4];decode_mfg_id(mfg_code, mfg);printf("制造商名称 %s\t", mfg);printf("产品代码 %u\t", (buf[10] | (buf[11] << 8)));printf("产品序列号 %u\t", (buf[12] | (buf[13] << 8) | (buf[14] << 16) | (buf[15] << 24)));printf("制造周 %u\t", buf[16]);printf("制造年份 %u\n", buf[17] + 1990);// 版本信息printf("版本号 %u\t修改号 %u\n", buf[18], buf[19]);// 基本显示参数printf("BasicDisplayParametersFeatures\n");if (buf[20] & 0x80){printf("\t 数字信号显示\n");}else{printf("\t 模拟信号显示\n");}printf("\t %ucm * %u cm\n", buf[21], buf[22]);printf("\t Gamma [1.00→3.55] %.2f\n", (buf[23] + 100) / 100.0);// 功能支持if (buf[24] & 0x80)printf("\t 支持Standby");if (buf[24] & 0x40)printf("\t 支持Suspend");if (buf[24] & 0x20)printf("\t 支持Low Power");if (buf[24] & 0x08)printf("\t RGB颜色显示");if (buf[24] & 0x04)printf("\t 非标准sRGB");if (buf[24] & 0x02)printf("\t 第一时序");if (buf[24] & 0x01)printf("\t 支持GTF\n");// 颜色特性printf("  ColorCharacteristic:\n");unsigned short tmp1, tmp2;double result1, result2;// REDtmp1 = ((buf[25] & 0xc0) >> 6) | ((buf[27] << 2) & 0x3fc);result1 = get_color_value(tmp1);tmp2 = ((buf[25] & 0x30) >> 4) | ((buf[28] << 2) & 0x3fc);result2 = get_color_value(tmp2);printf("\t RED %.4f %.4f\n", result1, result2);// GREENtmp1 = ((buf[25] & 0x0c) >> 2) | ((buf[29] << 2) & 0x3fc);result1 = get_color_value(tmp1);tmp2 = (buf[25] & 0x03) | ((buf[30] << 2) & 0x3fc);result2 = get_color_value(tmp2);printf("\t GREEN %.4f %.4f\n", result1, result2);// BLUEtmp1 = ((buf[26] & 0xc0) >> 6) | ((buf[31] << 2) & 0x3fc);result1 = get_color_value(tmp1);tmp2 = ((buf[26] & 0x30) >> 4) | ((buf[32] << 2) & 0x3fc);result2 = get_color_value(tmp2);printf("\t Blue %.4f %.4f\n", result1, result2);// WHITEtmp1 = ((buf[26] & 0x0c) >> 2) | ((buf[33] << 2) & 0x3fc);result1 = get_color_value(tmp1);tmp2 = (buf[26] & 0x03) | ((buf[34] << 2) & 0x3fc);result2 = get_color_value(tmp2);printf("\t Whith %.4f %.4f\n", result1, result2);// 已建立时序printf("  Established Timings\n");if (buf[35] & 0x80) printf("\t 720 x 400 @ 70Hz\n");if (buf[35] & 0x40) printf("\t 720 x 400 @ 88Hz\n");if (buf[35] & 0x20) printf("\t 640 x 480 @ 60Hz\n");if (buf[35] & 0x10) printf("\t 640 x 480 @ 67Hz\n");if (buf[35] & 0x08) printf("\t 640 x 480 @ 72Hz\n");if (buf[35] & 0x04) printf("\t 640 x 480 @ 75Hz\n");if (buf[35] & 0x02) printf("\t 800 x 600 @ 56Hz\n");if (buf[35] & 0x01) printf("\t 800 x 600 @ 60Hz\n");if (buf[36] & 0x80) printf("\t 800 x 600 @ 72Hz\n");if (buf[36] & 0x40) printf("\t 800 x 600 @ 75Hz\n");if (buf[36] & 0x20) printf("\t 832 x 624 @ 75H z\n");if (buf[36] & 0x10) printf("\t 1024 x 768 @ 87Hz(I)\n");if (buf[36] & 0x08) printf("\t 1024 x 768 @ 60Hz\n");if (buf[36] & 0x04) printf("\t 1024 x 768 @ 70Hz\n");if (buf[36] & 0x02) printf("\t 1024 x 768 @ 75Hz\n");if (buf[36] & 0x01) printf("\t 1280 x 1024 @ 75Hz\n");if (buf[37] & 0x80) printf("\t 1152 x 870 @ 75Hz\n");// 标准时序(预留,未实现详细解析)printf("  Standard Timing Identification\n");// 详细时序描述(预留)printf("  DetailedTimeDescription\n");// 产品名称char name[32];get_monitor_name(buf, name);printf("ProductName: %s\n", name);// 扩展信息printf("--------------Ext info------------------\n");if (sz >= 256 && buf[128] == 0x02){int a, b, c, d;if (get_physical_address_simple(buf + 128, &a, &b, &c, &d) == 0){printf("物理地址:A:%d B:%d C:%d D:%d\n", a, b, c, d);}}else{printf("设备没有扩展信息,没有物理地址\n");}
}// 理想运行结果:
// --------------Base info------------------
// this is valid edid
// base check 0xa3
// 扩展部分 check 0xb7
// 制造商名称 SAM    产品代码 1234    产品序列号 567890    制造周 25    制造年份 2020
// 版本号 1    修改号 4
// BasicDisplayParametersFeatures
//      数字信号显示
//      60cm * 34 cm
//      Gamma [1.00→3.55] 2.20
//      支持Standby 支持Suspend RGB颜色显示 支持GTF
//   ColorCharacteristic:
//      RED 0.6400 0.3300
//      GREEN 0.3000 0.6000
//      Blue 0.1500 0.0600
//      Whith 0.3127 0.3290
//   Established Timings
//      640 x 480 @ 60Hz
//      800 x 600 @ 60Hz
//      1024 x 768 @ 60Hz
//   Standard Timing Identification
//   DetailedTimeDescription
// ProductName: SAMSUNG U28E590D
// --------------Ext info------------------
// 物理地址:A:0 B:0 C:1 D:0

🖥️ 九、主函数:交互式菜单

int main(int argc, char *argv[])
{char filename[256];// 支持命令行传参或交互输入if (argc > 1){strncpy(filename, argv[1], sizeof(filename) - 1); // 使用命令行参数filename[sizeof(filename) - 1] = '\0';}else{printf("请输入EDID文件名: ");scanf("%255s", filename);                         // 用户输入}size_t sz;uint8_t *buf = read_all(filename, &sz);             // 读取文件if (buf == NULL){printf("fread fail!\n");return 1;}print_hex_data(buf, sz);                            // 先打印原始数据int run = 1;while (run){printf("\n==== 菜单 ====\n");printf("1.showall\n");printf("2.设置厂商名\n");printf("3.设置产品名\n");printf("4.设置物理地址\n");printf("5.结束\n");printf("请选择:");int choice;scanf("%d", &choice);if (choice == 5){write_all(filename, buf, sz);                   // 保存修改printf("已保存到%s\n", filename);run = 0;}else if (choice == 1){show_all_info(buf, sz);                         // 显示全部信息}else if (choice == 2){char new_mfg[8];printf("请输入新的厂家名: ");scanf("%7s", new_mfg);uint16_t code;if (encode_mfg_id(new_mfg, &code) == 0)         // 编码成功{buf[8] = code >> 8;                           // 更新字节8、9buf[9] = code & 0xFF;fix_checksum(buf);                            // 修复校验和printf("厂家名已更新\n");}else{printf("input fail\n");}}else if (choice == 3){char new_name[32];printf("请输入新的设备名:");scanf("%31s", new_name);set_monitor_name(buf, new_name);                // 设置名称fix_checksum(buf);                              // 修复校验和printf("设备名已更新\n");}else if (choice == 4){int a, b, c, d;printf("请输入物理地址(a.b.c.d):");scanf("%d.%d.%d.%d", &a, &b, &c, &d);if (a < 0 || a > 15 || b < 0 || b > 15 || c < 0 || c > 15 || d < 0 || d > 15){printf("错误: 每个数字必须在 0-15 范围内\n");continue;}if (sz >= 256){if (buf[128] == 0x02){if (set_physical_address_simple(buf + 128, a, b, c, d) == 0){fix_checksum(buf + 128);                  // 修复扩展块校验和printf("物理地址已更新为 %d.%d.%d.%d\n", a, b, c, d);}else{printf("设置物理地址失败\n");}}else{printf("未找到有效的 CEA 扩展块\n");}}else{printf("EDID 文件太短,无法设置物理地址\n");}}else{printf("无效选择\n");}}free(buf);                                          // 释放内存return 0;
}// 理想运行结果:
// 输入文件名 → 显示菜单 → 选择1显示信息 → 选择2修改厂商 → 输入"SAM" → 成功 → 选择5保存退出

总结知识点

  • EDID 1.3/1.4 数据结构解析
  • 128/256字节块校验和计算与修复
  • 3字母厂商ID ↔ 16位编码互转
  • 安全ASCII字符串处理
  • 显示器名称定位与修改(Descriptor Tag 0xFC)
  • CEA扩展块物理地址读写(HDMI/DisplayPort)
  • 10位色度坐标浮点转换
  • 位域解析:功能支持、时序标志
  • 交互式命令行菜单设计
  • 二进制文件读写与内存管理

本工具可作为显示器固件调试、EDID注入、HDMI兼容性测试的实用参考实现。


文章转载自:

http://rtLdaNa0.jppzj.cn
http://5tZyKxMb.jppzj.cn
http://OO1T8Gee.jppzj.cn
http://NbWjDqLb.jppzj.cn
http://pSNOqTVz.jppzj.cn
http://buYtKOky.jppzj.cn
http://lqiHqpS3.jppzj.cn
http://wNOImUDr.jppzj.cn
http://HkAPQR4m.jppzj.cn
http://8495Ff6C.jppzj.cn
http://lnxmFDoe.jppzj.cn
http://Oha5gyem.jppzj.cn
http://WBLD55K4.jppzj.cn
http://MLVbU3Lx.jppzj.cn
http://8FEkF6aT.jppzj.cn
http://gs1i8MTd.jppzj.cn
http://vmeqb0iP.jppzj.cn
http://V1l8sKBw.jppzj.cn
http://S9PjhJpq.jppzj.cn
http://xyYEbO7k.jppzj.cn
http://pugN4BjM.jppzj.cn
http://VD3MRS3D.jppzj.cn
http://XsQX8Bwv.jppzj.cn
http://EpeB7BxX.jppzj.cn
http://TftpzQRU.jppzj.cn
http://bduiUiVV.jppzj.cn
http://OfM4Gipx.jppzj.cn
http://y8xj9aDu.jppzj.cn
http://ac04KsG3.jppzj.cn
http://4Cr1tIfx.jppzj.cn
http://www.dtcms.com/a/379664.html

相关文章:

  • 龙蜥8.10中spark各种集群及单机模式的搭建spark3.5.6(基于hadoop3.3.6集群)
  • Hadoop MapOutputBuffer:Map高性能核心揭秘
  • Kubernetes 弹性伸缩:深入讲解 HPA 和 VPA
  • 代理服务器是什么?怎么选择?
  • java Redisson 实现限流每秒/分钟/小时限制N个请求 -V2.0
  • 高并发、低延迟全球直播系统架构
  • zookeeper是啥
  • 短波红外相机在机器视觉检测方向的应用
  • 阿里云国际代理:如何利用RDS构建高可用、可扩展的数据库架构
  • 【Python】通俗理解反向传播
  • RFID技术在半导体电子货架上的应用方案
  • Windows 安装 Redis 教程
  • CMake 全流程开发实战:从零开始掌握C++项目构建、测试到一键分发的完整解决方案​
  • 如果数据量小但是点击后需要获取的是最新的定位信息,这种时候采取什么策略最优?
  • 使用 Pyinstaller 打包 PPOCRLabel
  • 科技信息差(9.12)
  • 是德科技 | 关于AI 数据中心时代的光通信的精选问答
  • 深入剖析 Elasticsearch (ES) 的近实时搜索原理
  • Qt5 | TCP服务器开源模板工程实战
  • 飞鹤财报“新解”:科技筑牢护城河,寒冬凸显龙头“硬核力”
  • 第6.2节 Android Agent开发<一>
  • 【 C/C++ 算法】入门动态规划-----一维动态规划基础(以练代学式)
  • YOLOv8 从yaml配置文件生成PyTorch模型
  • 重复文件清理的标准化操作流程
  • Amazon DocumentDB Serverless 技术深度解析:架构特性、弹性扩缩容机制与实操指南
  • 项目管理方法适合什么类型的企业
  • HTTPS(Hypertext Transfer Protocol Secure,超文本传输安全协议)
  • 【LLM越狱】AI大模型DRA攻击解读与复现
  • k8s下的发布策略详解
  • 第 9 篇:深入浅出学 Java 语言(JDK8 版)—— 吃透泛型机制,筑牢 Java 类型安全防线