MythTV  master
mythedid.cpp
Go to the documentation of this file.
1 // MythTV
2 #include "mythlogging.h"
3 #include "mythedid.h"
4 
5 #define DESCRIPTOR_ALPHANUMERIC_STRING 0xFE
6 #define DESCRIPTOR_PRODUCT_NAME 0xFC
7 #define DESCRIPTOR_SERIAL_NUMBER 0xFF
8 #define DATA_BLOCK_OFFSET 0x36
9 #define SERIAL_OFFSET 0x0C
10 #define VERSION_OFFSET 0x12
11 #define DISPLAY_OFFSET 0x14
12 #define WIDTH_OFFSET 0x15
13 #define HEIGHT_OFFSET 0x16
14 #define GAMMA_OFFSET 0x17
15 #define FEATURES_OFFSET 0x18
16 #define EXTENSIONS_OFFSET 0x7E
17 
18 #define LOC QString("EDID: ")
19 
20 MythEDID::MythEDID(QByteArray &Data)
21  : m_data(Data)
22 {
23  Parse();
24 }
25 
26 MythEDID::MythEDID(const char* Data, int Length)
27  : m_data(Data, Length)
28 {
29  Parse();
30 }
31 
32 bool MythEDID::Valid(void) const
33 {
34  return m_valid;
35 }
36 
37 QStringList MythEDID::SerialNumbers(void) const
38 {
39  return m_serialNumbers;
40 }
41 
42 QSize MythEDID::DisplaySize(void) const
43 {
44  return m_displaySize;
45 }
46 
47 double MythEDID::DisplayAspect(void) const
48 {
49  return m_displayAspect;
50 }
51 
53 {
54  return m_physicalAddress;
55 }
56 
57 float MythEDID::Gamma(void) const
58 {
59  return m_gamma;
60 }
61 
62 bool MythEDID::IsHDMI(void) const
63 {
64  return m_isHDMI;
65 }
66 
67 bool MythEDID::IsSRGB(void) const
68 {
69  return m_sRGB;
70 }
71 
72 bool MythEDID::IsLikeSRGB(void) const
73 {
74  return m_likeSRGB;
75 }
76 
78 {
79  return m_primaries;
80 }
81 
82 int MythEDID::AudioLatency(bool Interlaced) const
83 {
84  return m_audioLatency[Interlaced ? 1 : 0];
85 }
86 
87 int MythEDID::VideoLatency(bool Interlaced) const
88 {
89  return m_videoLatency[Interlaced ? 1 : 0];
90 }
91 
92 // from QEdidParser
93 static QString ParseEdidString(const quint8 *data, bool Replace)
94 {
95  QByteArray buffer(reinterpret_cast<const char *>(data), 13);
96 
97  // Erase carriage return and line feed
98  buffer = buffer.replace('\r', '\0').replace('\n', '\0');
99 
100  // Replace non-printable characters with dash
101  // Earlier versions of QEdidParser got this wrong - so we potentially get
102  // different values for serialNumber from QScreen
103  if (Replace)
104  {
105  for (int i = 0; i < buffer.count(); ++i) {
106  if (buffer[i] < '\040' || buffer[i] > '\176')
107  buffer[i] = '-';
108  }
109  }
110  return QString::fromLatin1(buffer.trimmed());
111 }
112 
113 void MythEDID::Parse(void)
114 {
115  // This is adapted from various sources including QEdidParser, edid-decode and xrandr
116  if (!m_data.constData() || m_data.isEmpty())
117  {
118  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Invalid EDID");
119  return;
120  }
121 
122  m_size = static_cast<uint>(m_data.size());
123  const auto *data = reinterpret_cast<const quint8 *>(m_data.constData());
124 
125  // EDID data should be in 128 byte blocks
126  if ((m_size % 0x80) || data[0] != 0x00 || data[1] != 0xff)
127  {
128  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Invalid EDID");
129  return;
130  }
131 
132  // checksum
133  qint8 sum = 0;
134  for (char i : qAsConst(m_data))
135  sum += i;
136  if (sum != 0)
137  {
138  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Checksum error");
139  return;
140  }
141 
142  if (!ParseBaseBlock(data))
143  return;
144 
145  // Look at extension blocks
146  uint extensions = data[EXTENSIONS_OFFSET];
147  uint available = (m_size / 128) - 1;
148  if (extensions != available)
149  {
150  LOG(VB_GENERAL, LOG_WARNING, LOC + "Extension count error");
151  return;
152  }
153 
154  if (extensions < 1)
155  return;
156 
157  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("EDID has %1 extension blocks").arg(extensions));
158  for (uint i = 1; i <= available; ++i)
159  {
160  uint offset = i * 128;
161  switch (data[offset])
162  {
163  case 0x02: m_valid &= ParseCTA861(data, offset); break;
164  default: continue;
165  }
166  }
167 }
168 
169 bool MythEDID::ParseBaseBlock(const quint8 *Data)
170 {
171  // retrieve version
172  if (Data[VERSION_OFFSET] != 1)
173  {
174  LOG(VB_GENERAL, LOG_DEBUG, LOC + "Unknown major version number");
175  return false;
176  }
177  m_minorVersion = Data[VERSION_OFFSET + 1];
178 
179  // retrieve serial number. This may be subsequently overridden.
180  // N.B. 0 is a valid serial number.
181  qint32 serial = Data[SERIAL_OFFSET] +
182  (Data[SERIAL_OFFSET + 1] << 8) +
183  (Data[SERIAL_OFFSET + 2] << 16) +
184  (Data[SERIAL_OFFSET + 3] << 24);
185  m_serialNumbers << QString::number(serial);
186 
187  // digital or analog
188  //bool digital = Data[DISPLAY_OFFSET] & 0x80;
189 
190  // Display size
191  if (Data[WIDTH_OFFSET] && Data[HEIGHT_OFFSET])
192  {
193  m_displaySize = QSize(Data[WIDTH_OFFSET] * 10, Data[HEIGHT_OFFSET] * 10);
194  }
195  else if (m_minorVersion >= 4 && (Data[WIDTH_OFFSET] || Data[HEIGHT_OFFSET]))
196  {
197  if (Data[WIDTH_OFFSET])
198  m_displayAspect = 100.0 / (Data[HEIGHT_OFFSET] + 99); // Landscape
199  else
200  m_displayAspect = 100.0 / (Data[WIDTH_OFFSET] + 99); // Portrait
201  }
202 
203  // retrieve gamma
204  quint8 gamma = Data[GAMMA_OFFSET];
205  if (gamma == 0xFF)
206  {
207  // for 1.4 - this means gamma is defined in an extension block
208  if (m_minorVersion < 4)
209  m_gamma = 1.0F;
210  }
211  else
212  {
213  m_gamma = (gamma + 100.0F) / 100.0F;
214  }
215 
216  // Chromaticity
217  // Note - the EDID format introduces slight rounding errors when converting
218  // to sRGB specs. If sRGB is set, the client should use the spec values - not
219  // the computed values
220  m_sRGB = ((Data[FEATURES_OFFSET] & 0x04) != 0);
221  static constexpr std::array<const uint8_t,10> s_sRGB =
222  { 0xEE, 0x91, 0xA3, 0x54, 0x4C, 0x99, 0x26, 0x0F, 0x50, 0x54 };
223  bool srgb = std::equal(s_sRGB.cbegin(), s_sRGB.cend(), Data + 0x19);
224 
225  if (!m_sRGB && srgb)
226  m_sRGB = true;
227  else if (m_sRGB && !srgb)
228  LOG(VB_GENERAL, LOG_WARNING, LOC + "Chromaticity mismatch!");
229 
230  // Red
231  m_primaries.primaries[0][0] = ((Data[0x1B] << 2) | (Data[0x19] >> 6)) / 1024.0F;
232  m_primaries.primaries[0][1] = ((Data[0x1C] << 2) | ((Data[0x19] >> 4) & 3)) / 1024.0F;
233  // Green
234  m_primaries.primaries[1][0] = ((Data[0x1D] << 2) | ((Data[0x19] >> 2) & 3)) / 1024.0F;
235  m_primaries.primaries[1][1] = ((Data[0x1E] << 2) | (Data[0x19] & 3)) / 1024.0F;
236  // Blue
237  m_primaries.primaries[2][0] = ((Data[0x1F] << 2) | (Data[0x1A] >> 6)) / 1024.0F;
238  m_primaries.primaries[2][1] = ((Data[0x20] << 2) | ((Data[0x1A] >> 4) & 3)) / 1024.0F;
239  // White
240  m_primaries.whitepoint[0] = ((Data[0x21] << 2) | ((Data[0x1A] >> 2) & 3)) / 1024.0F;
241  m_primaries.whitepoint[1] = ((Data[0x22] << 2) | (Data[0x1A] & 3)) / 1024.0F;
242 
243  // Check whether this is very similar to sRGB and hence if non-exact colourspace
244  // handling is preferred, then just use sRGB.
245  // TODO Move to new MythColourSpace class.
246 
247  // As per VideoColourspace.
248  static const Primaries s_sRGBPrim =
249  {{{{0.640F, 0.330F}, {0.300F, 0.600F}, {0.150F, 0.060F}}}, {0.3127F, 0.3290F}};
250 
251  auto like = [](const Primaries &First, const Primaries &Second, float Fuzz)
252  {
253  auto cmp = [=](float One, float Two) { return (qAbs(One - Two) < Fuzz); };
254  return cmp(First.primaries[0][0], Second.primaries[0][0]) &&
255  cmp(First.primaries[0][1], Second.primaries[0][1]) &&
256  cmp(First.primaries[1][0], Second.primaries[1][0]) &&
257  cmp(First.primaries[1][1], Second.primaries[1][1]) &&
258  cmp(First.primaries[2][0], Second.primaries[2][0]) &&
259  cmp(First.primaries[2][1], Second.primaries[2][1]) &&
260  cmp(First.whitepoint[0], Second.whitepoint[0]) &&
261  cmp(First.whitepoint[1], Second.whitepoint[1]);
262  };
263 
264  m_likeSRGB = like(m_primaries, s_sRGBPrim, 0.025F) && qFuzzyCompare(m_gamma + 1.0F, 2.20F + 1.0F);
265 
266  // Parse blocks
267  for (int i = 0; i < 5; ++i)
268  {
269  const int offset = DATA_BLOCK_OFFSET + i * 18;
270  if (Data[offset] != 0 || Data[offset + 1] != 0 || Data[offset + 2] != 0)
271  continue;
272  if (Data[offset + 3] == DESCRIPTOR_SERIAL_NUMBER)
273  {
274  m_serialNumbers << ParseEdidString(&Data[offset + 5], false);
275  m_serialNumbers << ParseEdidString(&Data[offset + 5], true);
276  }
277  }
278 
279  // Set status
280  for (const auto & sn : qAsConst(m_serialNumbers))
281  if (!sn.isEmpty())
282  m_valid = true;
283  if (!m_valid)
284  LOG(VB_GENERAL, LOG_WARNING, LOC + "No serial number(s) in EDID");
285  return m_valid;
286 }
287 
288 bool MythEDID::ParseCTA861(const quint8 *Data, uint Offset)
289 {
290  if (Offset >= m_size)
291  return false;
292 
293  bool result = true;
294  quint8 version = Data[Offset + 1];
295  quint8 offset = Data[Offset + 2];
296 
297  if (offset < 4 || version != 3)
298  return result;
299 
300  for (uint i = Offset + 4; (i < (Offset + offset)) && (i < m_size); i += (Data[i] & 0x1F) + 1)
301  result &= ParseCTABlock(Data, i);
302  return result;
303 }
304 
305 bool MythEDID::ParseCTABlock(const quint8 *Data, uint Offset)
306 {
307  uint length = Data[Offset] & 0x1F;
308  uint type = (Data[Offset] & 0xE0) >> 5;
309  switch (type)
310  {
311  case 0x01: break; // Audio data block // NOLINT(bugprone-branch-clone)
312  case 0x02: break; // Video data block
313  case 0x03: ParseVSDB(Data, Offset + 1, length); break; // Vendor Specific Data Block
314  case 0x04: break; // Speaker Allocation data block // NOLINT(bugprone-branch-clone)
315  case 0x05: break; // VESA DTC data block
316  case 0x07: break; // Extended tag. HDR metadata here
317  default: break;
318  }
319  return true;
320 }
321 
322 bool MythEDID::ParseVSDB(const quint8 *Data, uint Offset, uint Length)
323 {
324  if (Offset + 3 >= m_size)
325  return false;
326 
327  int registration = Data[Offset] + (Data[Offset + 1] << 8) + (Data[Offset + 2] << 16);
328 
329  // HDMI
330  while (registration == 0x000C03)
331  {
332  m_isHDMI = true;
333 
334  if (Length < 5 || (Offset + 5 >= m_size))
335  break;
336 
337  // CEC physical address
338  m_physicalAddress = static_cast<uint16_t>((Data[Offset + 3] << 8) + Data[Offset + 4]);
339  if (Length < 8 || (Offset + 8 >= m_size))
340  break;
341 
342  // Audio and video latencies
343  m_latencies = ((Data[Offset + 7] & 0x80) != 0);
344  m_interLatencies = ((Data[Offset + 7] & 0x40) != 0) && m_latencies;
345 
346  if (Length < 10 || (Offset + 10 >= m_size))
347  break;
348  if (m_latencies)
349  {
350  quint8 video = Data[Offset + 8];
351  if (video > 0 && video <= 251)
352  m_videoLatency[0] = (video - 1) << 1;
353  quint8 audio = Data[Offset + 9];
354  if (audio > 0 && audio <= 251)
355  m_audioLatency[0] = (audio - 1) << 1;
356  }
357 
358  if (Length < 12 || (Offset + 12 >= m_size))
359  break;
360 
361  if (m_interLatencies)
362  {
363  quint8 video = Data[Offset + 10];
364  if (video > 0 && video <= 251)
365  m_videoLatency[1] = (video - 1) << 1;
366  quint8 audio = Data[Offset + 11];
367  if (audio > 0 && audio <= 251)
368  m_audioLatency[1] = (audio - 1) << 1;
369  }
370 
371  break;
372  }
373  return true;
374 }
375 
376 void MythEDID::Debug(void) const
377 {
378  if (!m_valid)
379  {
380  LOG(VB_GENERAL, LOG_INFO, LOC + "Invalid EDID");
381  return;
382  }
383 
384  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Version:1.%1 Size:%2 Exensions:%3")
385  .arg(m_minorVersion).arg(m_size).arg(m_size / 128 -1));
386  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Gamma:%1 sRGB:%2")
387  .arg(static_cast<double>(m_gamma)).arg(m_sRGB));
388  LOG(VB_GENERAL, LOG_INFO, LOC + "Display chromaticity:-");
389  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Red:\t%1,\t%2")
390  .arg(static_cast<double>(m_primaries.primaries[0][0]), 0, 'f', 4)
391  .arg(static_cast<double>(m_primaries.primaries[0][1]), 0, 'f', 4));
392  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Green:\t%1,\t%2")
393  .arg(static_cast<double>(m_primaries.primaries[1][0]), 0, 'f', 4)
394  .arg(static_cast<double>(m_primaries.primaries[1][1]), 0, 'f', 4));
395  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Blue:\t%1,\t%2")
396  .arg(static_cast<double>(m_primaries.primaries[2][0]), 0, 'f', 4)
397  .arg(static_cast<double>(m_primaries.primaries[2][1]), 0, 'f', 4));
398  LOG(VB_GENERAL, LOG_INFO, LOC + QString("White:\t%1,\t%2")
399  .arg(static_cast<double>(m_primaries.whitepoint[0]), 0, 'f', 4)
400  .arg(static_cast<double>(m_primaries.whitepoint[1]), 0, 'f', 4));
401  QString address = !m_physicalAddress ? QString("N/A") :
402  QString("%1.%2.%3.%4").arg((m_physicalAddress >> 12) & 0xF)
403  .arg((m_physicalAddress >> 8) & 0xF)
404  .arg((m_physicalAddress >> 4) & 0xF)
405  .arg(m_physicalAddress & 0xF);
406  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Physical address: %1").arg(address));
407  if (m_latencies)
408  {
409  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Latencies: Audio:%1 Video:%2")
411  if (m_interLatencies)
412  {
413  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Latencies: Audio:%1 Video:%2 (Interlaced)")
415  }
416  }
417 }
HEIGHT_OFFSET
#define HEIGHT_OFFSET
Definition: mythedid.cpp:13
MythEDID::Primaries::primaries
PrimarySpace primaries
Definition: mythedid.h:24
WIDTH_OFFSET
#define WIDTH_OFFSET
Definition: mythedid.cpp:12
MythEDID::m_physicalAddress
uint16_t m_physicalAddress
Definition: mythedid.h:65
MythEDID::PhysicalAddress
uint16_t PhysicalAddress(void) const
Definition: mythedid.cpp:52
MythEDID::Gamma
float Gamma(void) const
Definition: mythedid.cpp:57
MythEDID::IsLikeSRGB
bool IsLikeSRGB(void) const
Definition: mythedid.cpp:72
arg
arg(title).arg(filename).arg(doDelete))
MythEDID::m_minorVersion
quint8 m_minorVersion
Definition: mythedid.h:56
GAMMA_OFFSET
#define GAMMA_OFFSET
Definition: mythedid.cpp:14
MythEDID::m_primaries
Primaries m_primaries
Definition: mythedid.h:63
MythEDID::ParseCTABlock
bool ParseCTABlock(const quint8 *Data, uint Offset)
Definition: mythedid.cpp:305
ParseEdidString
static QString ParseEdidString(const quint8 *data, bool Replace)
Definition: mythedid.cpp:93
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
EXTENSIONS_OFFSET
#define EXTENSIONS_OFFSET
Definition: mythedid.cpp:16
MythEDID::m_displayAspect
double m_displayAspect
Definition: mythedid.h:58
MythEDID::m_size
uint m_size
Definition: mythedid.h:55
MythEDID::Valid
bool Valid(void) const
Definition: mythedid.cpp:32
MythEDID::ParseVSDB
bool ParseVSDB(const quint8 *Data, uint Offset, uint Length)
Definition: mythedid.cpp:322
MythEDID::Primaries::whitepoint
WhiteSpace whitepoint
Definition: mythedid.h:25
MythEDID::m_audioLatency
std::array< int, 2 > m_audioLatency
Definition: mythedid.h:68
MythEDID::ParseBaseBlock
bool ParseBaseBlock(const quint8 *Data)
Definition: mythedid.cpp:169
MythEDID::m_serialNumbers
QStringList m_serialNumbers
Definition: mythedid.h:59
SERIAL_OFFSET
#define SERIAL_OFFSET
Definition: mythedid.cpp:9
video
QDomElement video
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:661
MythEDID::m_isHDMI
bool m_isHDMI
Definition: mythedid.h:64
MythEDID::m_data
QByteArray m_data
Definition: mythedid.h:54
mythlogging.h
MythEDID::m_sRGB
bool m_sRGB
Definition: mythedid.h:61
FEATURES_OFFSET
#define FEATURES_OFFSET
Definition: mythedid.cpp:15
MythEDID::IsHDMI
bool IsHDMI(void) const
Definition: mythedid.cpp:62
MythEDID::m_videoLatency
std::array< int, 2 > m_videoLatency
Definition: mythedid.h:69
MythEDID::MythEDID
MythEDID(void)=default
VERSION_OFFSET
#define VERSION_OFFSET
Definition: mythedid.cpp:10
uint
unsigned int uint
Definition: compat.h:141
MythEDID::m_interLatencies
bool m_interLatencies
Definition: mythedid.h:67
MythEDID::VideoLatency
int VideoLatency(bool Interlaced) const
Definition: mythedid.cpp:87
MythEDID::m_gamma
float m_gamma
Definition: mythedid.h:60
MythEDID::Primaries
Definition: mythedid.h:23
MythEDID::Debug
void Debug(void) const
Definition: mythedid.cpp:376
mythedid.h
MythEDID::m_latencies
bool m_latencies
Definition: mythedid.h:66
LOC
#define LOC
Definition: mythedid.cpp:18
MythEDID::m_likeSRGB
bool m_likeSRGB
Definition: mythedid.h:62
MythEDID::m_valid
bool m_valid
Definition: mythedid.h:53
MythEDID::DisplaySize
QSize DisplaySize(void) const
Definition: mythedid.cpp:42
MythEDID::IsSRGB
bool IsSRGB(void) const
Definition: mythedid.cpp:67
MythEDID::Parse
void Parse(void)
Definition: mythedid.cpp:113
uint16_t
unsigned short uint16_t
Definition: iso6937tables.h:3
DESCRIPTOR_SERIAL_NUMBER
#define DESCRIPTOR_SERIAL_NUMBER
Definition: mythedid.cpp:7
DATA_BLOCK_OFFSET
#define DATA_BLOCK_OFFSET
Definition: mythedid.cpp:8
MythEDID::AudioLatency
int AudioLatency(bool Interlaced) const
Definition: mythedid.cpp:82
MythEDID::DisplayAspect
double DisplayAspect(void) const
Definition: mythedid.cpp:47
MythEDID::ParseCTA861
bool ParseCTA861(const quint8 *Data, uint Offset)
Definition: mythedid.cpp:288
MythEDID::m_displaySize
QSize m_displaySize
Definition: mythedid.h:57
nv_python_libs.bbciplayer.bbciplayer_api.version
string version
Definition: bbciplayer_api.py:81
MythEDID::ColourPrimaries
Primaries ColourPrimaries(void) const
Definition: mythedid.cpp:77
MythEDID::SerialNumbers
QStringList SerialNumbers(void) const
Definition: mythedid.cpp:37