ohmu
·
2026-10-15
1#include "common/edidHelper.h"
2
3void ffEdidGetPhysicalResolution(const uint8_t edid[128], uint32_t* width, uint32_t* height) {
4 const int dtd = 54;
5 *width = (((uint32_t) edid[dtd + 4] >> 4) << 8) | edid[dtd + 2];
6 *height = (((uint32_t) edid[dtd + 7] >> 4) << 8) | edid[dtd + 5];
7}
8
9void ffEdidGetPreferredResolutionAndRefreshRate(const uint8_t edid[128], uint32_t* width, uint32_t* height, double* refreshRate) {
10 for (uint32_t i = 0x36; i < 0x7E; i += 0x12) { // read through descriptor blocks...
11 if (edid[i] != 0x00 && edid[i + 1] != 0x00) { // a dtd
12 uint32_t hactive = edid[i + 2] + (uint32_t) ((edid[i + 4] & 0xf0) << 4);
13 uint32_t hblank = edid[i + 3] + (uint32_t) ((edid[i + 4] & 0x0f) << 8);
14 uint32_t vactive = edid[i + 5] + (uint32_t) ((edid[i + 7] & 0xf0) << 4);
15 uint32_t vblank = edid[i + 6] + (uint32_t) ((edid[i + 7] & 0x0f) << 8);
16 uint32_t pixclk = ((uint32_t) edid[i + 1] << 8) | (edid[i]);
17 *width = hactive;
18 *height = vactive;
19 *refreshRate = (double) pixclk * 10000 / (double) (hactive + hblank) / (double) (vactive + vblank);
20 return;
21 }
22 }
23}
24
25void ffEdidGetVendorAndModel(const uint8_t edid[128], FFstrbuf* result) {
26 // https://github.com/jinksong/read_edid/blob/master/parse-edid/parse-edid.c
27 ffStrbufAppendF(result, "%c%c%c%04X", (char) (((uint32_t) edid[8] >> 2 & 0x1f) + 'A' - 1), (char) (((((uint32_t) edid[8] & 0x3) << 3) | (((uint32_t) edid[9] & 0xe0) >> 5)) + 'A' - 1), (char) (((uint32_t) edid[9] & 0x1f) + 'A' - 1), (uint32_t) (edid[10] + (uint32_t) (edid[11] << 8)));
28}
29
30bool ffEdidGetName(const uint8_t edid[128], FFstrbuf* name) {
31 // https://github.com/jinksong/read_edid/blob/master/parse-edid/parse-edid.c
32 for (uint32_t i = 0x36; i < 0x7E; i += 0x12) { // read through descriptor blocks...
33 if (edid[i] == 0x00) { // not a timing descriptor
34 if (edid[i + 3] == 0xfc) { // Model Name tag
35 for (uint32_t j = 0; j < 13; j++) {
36 if (edid[i + 5 + j] == 0x0a) {
37 ffStrbufAppendNS(name, j, (const char*) &edid[i + 5]);
38 return true;
39 }
40 }
41 }
42 }
43 }
44
45 // use manufacturer + model number as monitor name
46 ffEdidGetVendorAndModel(edid, name);
47 return false;
48}
49
50void ffEdidGetPhysicalSize(const uint8_t edid[128], uint32_t* width, uint32_t* height) {
51 // Detailed Timing Descriptors
52 uint32_t dw = (((uint32_t) edid[68] & 0xF0) << 4) + edid[66];
53 uint32_t dh = (((uint32_t) edid[68] & 0x0F) << 8) + edid[67];
54
55 // Basic Display Parameters
56 uint32_t bw = edid[21] * 10;
57 uint32_t bh = edid[22] * 10;
58
59 // Some monitors report invalid data in DTD. See #1406
60 if (abs((int) dw - (int) bw) < 10 && abs((int) dh - (int) bh) < 10) {
61 *width = dw;
62 *height = dh;
63 } else {
64 *width = bw;
65 *height = bh;
66 }
67}
68
69void ffEdidGetManufactureDate(const uint8_t edid[128], uint16_t* year, uint16_t* week) {
70 if (edid[17] > 0 && edid[17] < 0xFF) {
71 *year = (uint16_t) (edid[17] + 1990);
72 *week = (uint16_t) edid[16];
73 if (*week == 0xFF) {
74 *week = 0;
75 }
76 } else {
77 *year = *week = 0;
78 }
79}
80
81bool ffEdidGetSerial(const uint8_t edid[128], FFstrbuf* result) {
82 int descriptor_offsets[4] = {54, 72, 90, 108};
83
84 for (int i = 0; i < 4; i++) {
85 int offset = descriptor_offsets[i];
86 const uint8_t* desc = edid + offset;
87
88 if (desc[0] == 0x00 && desc[1] == 0x00) { // Monitor Descriptor
89 uint8_t tag = desc[3]; // Descriptor Type Tag
90
91 if (tag == 0xFF) { // Display Product Serial Number
92 ffStrbufClear(result);
93 for (int j = 0; j < 13; j++) {
94 uint8_t ch = desc[5 + j];
95
96 if (ch == 0x0A || ch == 0x00) {
97 break;
98 }
99
100 ffStrbufAppendC(result, (char) ch);
101 }
102
103 ffStrbufTrim(result, ' ');
104 if (result->length > 0) {
105 return true;
106 }
107 }
108 }
109 }
110
111 uint32_t int_serial = 0;
112 int_serial |= (uint32_t)edid[12];
113 int_serial |= (uint32_t)edid[13] << 8;
114 int_serial |= (uint32_t)edid[14] << 16;
115 int_serial |= (uint32_t)edid[15] << 24;
116
117 if (int_serial != 0 && int_serial != 0xFFFFFFFF) {
118 ffStrbufSetF(result, "0x%08X", int_serial);
119 return true;
120 }
121 return false;
122}
123
124bool ffEdidGetHdrCompatible(const uint8_t* edid, uint32_t length) {
125 if (length <= 128) {
126 return false;
127 }
128 for (const uint8_t* cta = &edid[128]; cta < &edid[length]; cta += 128) {
129 // https://en.wikipedia.org/wiki/Extended_Display_Identification_Data#CTA_EDID_Timing_Extension_Block
130 if (cta[0] != 0x02 /* CTA EDID */) {
131 continue;
132 }
133 if (cta[1] < 0x03 /* Version 3 */) {
134 continue;
135 }
136 const uint8_t offset = cta[2];
137 if (offset <= 4) {
138 continue;
139 }
140 for (uint8_t i = 4; i < offset;) {
141 uint8_t blkLen = cta[i] & 0x1f;
142 if (blkLen > 0) {
143 uint8_t blkTag = (cta[i] & 0xe0) >> 5;
144 if (blkTag == 0x07 /* Extended Block Type Tag */) {
145 uint8_t extendedTag = cta[i + 1];
146 if (extendedTag == 6 /* HDR SMDB */ || extendedTag == 7 /* HDR DMDB */) {
147 return true;
148 }
149 }
150 }
151 i += (uint8_t) (blkLen + 1);
152 }
153 }
154 return false;
155}
156
157bool ffEdidIsValid(const uint8_t edid[128], uint32_t length) {
158 if (length < 128 || length % 128 != 0) {
159 return false;
160 }
161
162 static const uint8_t edidHeader[] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 };
163 if (memcmp(edid, edidHeader, sizeof(edidHeader)) != 0) {
164 return false;
165 }
166
167 uint8_t sum = 0;
168 for (uint32_t i = 0; i < 128; i++) {
169 sum += edid[i];
170 }
171
172 return sum == 0;
173}