This is an automatic generated email to let you know that the following patch 
were queued:

Subject: edid-decode: add initial support for parsing InfoFrames
Author:  Hans Verkuil <hverkuil-ci...@xs4all.nl>
Date:    Wed Jul 31 16:20:45 2024 +0200

This adds initial support for parsing InfoFrames.

Starting with kernel v6.11-rc1 InfoFrames are exposed in debugfs
by some HDMI output drivers (Raspberry Pi among them).

These can be specified with the new --infoframe (or -I) option.
This option can be used multiple times to specify multiple
InfoFrames.

Each InfoFrame is parsed by edid-decode, and it is also checked
for conformity. Note that the conformity checks are still
work-in-progress for the AVI and HDMI InfoFrames.

It can also be combined with specifying the EDID of the display
the InfoFrames are sent to, and that will allow for more strict
conformity checks against that EDID.

This is also still work-in-progress.

Signed-off-by: Hans Verkuil <hverkuil-ci...@xs4all.nl>

 edid-decode.cpp     |  283 +++++++++++++-
 edid-decode.h       |   24 ++
 meson.build         |    1 +
 parse-cta-block.cpp |   26 +-
 parse-if.cpp        | 1075 +++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1402 insertions(+), 7 deletions(-)

---

diff --git a/edid-decode.cpp b/edid-decode.cpp
index 2e38ddc7aecf..863364415ff2 100644
--- a/edid-decode.cpp
+++ b/edid-decode.cpp
@@ -45,6 +45,7 @@ enum Option {
        OptFBModeTimings = 'F',
        OptHelp = 'h',
        OptOnlyHexDump = 'H',
+       OptInfoFrame = 'I',
        OptLongTimings = 'L',
        OptNativeResolution = 'n',
        OptNTSC = 'N',
@@ -113,6 +114,7 @@ static struct option long_options[] = {
        { "list-hdmi-vics", no_argument, 0, OptListHDMIVICs },
        { "list-rid-timings", required_argument, 0, OptListRIDTimings },
        { "list-rids", no_argument, 0, OptListRIDs },
+       { "infoframe", required_argument, 0, OptInfoFrame },
        { 0, 0, 0, 0 }
 };
 
@@ -120,7 +122,7 @@ static void usage(void)
 {
        printf("Usage: edid-decode <options> [in [out]]\n"
               "  [in]                  EDID file to parse. Read from standard 
input if none given\n"
-              "                        or if the input filename is '-'.\n"
+              "                        and --infoframe was not used, or if the 
input filename is '-'.\n"
               "  [out]                 Output the read EDID to this file. 
Write to standard output\n"
               "                        if the output filename is '-'.\n"
               "\nOptions:\n"
@@ -194,6 +196,8 @@ static void usage(void)
               "  --list-hdmi-vics      List all known HDMI VICs.\n"
               "  --list-rids           List all known RIDs.\n"
               "  --list-rid-timings <rid> List all timings for RID <rid> or 
all known RIDs if <rid> is 0.\n"
+              "  -I, --infoframe <file> Parse the InfoFrame from <file> that 
was sent to this display.\n"
+              "                        This option can be specified multiple 
times for different InfoFrame files.\n"
               "  -h, --help            Display this help message.\n");
 }
 #endif
@@ -301,6 +305,26 @@ void calc_ratio(struct timings *t)
        }
 }
 
+unsigned calc_fps(const struct timings *t)
+{
+       unsigned vact = t->vact;
+       unsigned vbl = t->vfp + t->vsync + t->vbp + 2 * t->vborder;
+       unsigned hbl = t->hfp + t->hsync + t->hbp + 2 * t->hborder;
+       unsigned htotal = t->hact + hbl;
+
+       if (t->interlaced)
+               vact /= 2;
+
+       double vtotal = vact + vbl;
+
+       if (t->even_vtotal)
+               vtotal = vact + t->vfp + t->vsync + t->vbp;
+       else if (t->interlaced)
+               vtotal = vact + t->vfp + t->vsync + t->vbp + 0.5;
+
+       return t->pixclk_khz * 1000.0 / (htotal * vtotal);
+}
+
 std::string edid_state::dtd_type(unsigned cnt)
 {
        unsigned len = std::to_string(cta.preparsed_total_dtds).length();
@@ -1511,6 +1535,7 @@ int edid_state::parse_edid()
        printf("\n----------------\n");
 
        if (!options[OptSkipSHA] && strlen(STRING(SHA))) {
+               options[OptSkipSHA] = 1;
                printf("\nedid-decode SHA: %s %s\n", STRING(SHA), STRING(DATE));
        }
 
@@ -1524,6 +1549,226 @@ int edid_state::parse_edid()
        return failures ? -2 : 0;
 }
 
+/* InfoFrame parsing */
+
+static unsigned char infoframe[32];
+static unsigned if_size;
+
+static bool if_add_byte(const char *s)
+{
+       char buf[3];
+
+       if (if_size == sizeof(infoframe))
+               return false;
+       buf[0] = s[0];
+       buf[1] = s[1];
+       buf[2] = 0;
+       infoframe[if_size++] = strtoul(buf, NULL, 16);
+       return true;
+}
+
+static bool extract_if_hex(const char *s)
+{
+       for (; *s; s++) {
+               if (isspace(*s))
+                       continue;
+
+               /* Read one or two hex digits from the log */
+               if (!isxdigit(s[0]))
+                       break;
+
+               if (!isxdigit(s[1])) {
+                       odd_hex_digits = true;
+                       return false;
+               }
+               if (!if_add_byte(s))
+                       return false;
+               s++;
+       }
+       return if_size;
+}
+
+static bool extract_if(int fd)
+{
+       std::vector<char> if_data;
+       char buf[128];
+
+       for (;;) {
+               ssize_t i = read(fd, buf, sizeof(buf));
+
+               if (i < 0)
+                       return false;
+               if (i == 0)
+                       break;
+               if_data.insert(if_data.end(), buf, buf + i);
+       }
+
+       if (if_data.empty()) {
+               if_size = 0;
+               return false;
+       }
+       // Ensure it is safely terminated by a 0 char
+       if_data.push_back('\0');
+
+       const char *data = &if_data[0];
+       const char *start;
+
+       /* Look for edid-decode output */
+       start = strstr(data, "edid-decode InfoFrame (hex):");
+       if (start)
+               return extract_if_hex(strchr(start, ':') + 1);
+
+       // Drop the extra '\0' byte since we now assume binary data
+       if_data.pop_back();
+
+       if_size = if_data.size();
+
+       /* Assume binary */
+       if (if_size > sizeof(infoframe)) {
+               fprintf(stderr, "Binary InfoFrame length %u is greater than 
%zu.\n",
+                       if_size, sizeof(infoframe));
+               return false;
+       }
+       memcpy(infoframe, data, if_size);
+       return true;
+}
+
+static int if_from_file(const char *from_file)
+{
+#ifdef O_BINARY
+       // Windows compatibility
+       int flags = O_RDONLY | O_BINARY;
+#else
+       int flags = O_RDONLY;
+#endif
+       int fd;
+
+       memset(infoframe, 0, sizeof(infoframe));
+       if_size = 0;
+
+       if ((fd = open(from_file, flags)) == -1) {
+               perror(from_file);
+               return -1;
+       }
+
+       odd_hex_digits = false;
+       if (!extract_if(fd)) {
+               if (!if_size) {
+                       fprintf(stderr, "InfoFrame of '%s' was empty.\n", 
from_file);
+                       return -1;
+               }
+               fprintf(stderr, "InfoFrame extraction of '%s' failed: ", 
from_file);
+               if (odd_hex_digits)
+                       fprintf(stderr, "odd number of hexadecimal digits.\n");
+               else
+                       fprintf(stderr, "unknown format.\n");
+               return -1;
+       }
+       close(fd);
+
+       return 0;
+}
+
+static void show_if_msgs(bool is_warn)
+{
+       printf("\n%s:\n\n", is_warn ? "Warnings" : "Failures");
+       if (s_msgs[0][is_warn].empty())
+               return;
+       printf("InfoFrame:\n%s",
+              s_msgs[0][is_warn].c_str());
+}
+
+int edid_state::parse_if(const std::string &fname)
+{
+       int ret = if_from_file(fname.c_str());
+       unsigned min_size = 4;
+       bool is_hdmi = false;
+
+       if (ret)
+               return ret;
+
+       state.block_nr = 0;
+       state.data_block.clear();
+
+       if (!options[OptSkipHexDump]) {
+               printf("edid-decode InfoFrame (hex):\n\n");
+               hex_block("", infoframe, if_size, false);
+               if (options[OptOnlyHexDump])
+                       return 0;
+               printf("\n----------------\n\n");
+       }
+
+       if (infoframe[0] >= 0x80) {
+               is_hdmi = true;
+               min_size++;
+       }
+
+       if (if_size < min_size) {
+               fail("InfoFrame is too small to parse.\n");
+               return -1;
+       }
+
+       if (is_hdmi) {
+               do_checksum("HDMI InfoFrame ", infoframe, if_size, 3);
+               printf("\n");
+               memcpy(infoframe + 3, infoframe + 4, if_size - 4);
+               infoframe[0] &= 0x7f;
+               if_size--;
+       }
+
+       switch (infoframe[0]) {
+       case 0x01:
+               parse_if_vendor(infoframe, if_size);
+               break;
+       case 0x02:
+               parse_if_avi(infoframe, if_size);
+               break;
+       case 0x03:
+               parse_if_spd(infoframe, if_size);
+               break;
+       case 0x04:
+               parse_if_audio(infoframe, if_size);
+               break;
+       case 0x05:
+               parse_if_mpeg_source(infoframe, if_size);
+               break;
+       case 0x06:
+               parse_if_ntsc_vbi(infoframe, if_size);
+               break;
+       case 0x07:
+               parse_if_drm(infoframe, if_size);
+               break;
+       default:
+               if (infoframe[0] <= 0x1f)
+                       fail("Reserved InfoFrame type %hhx.\n", infoframe[0]);
+               else
+                       fail("Forbidden InfoFrame type %hhx.\n", infoframe[0]);
+               break;
+       }
+
+       if (!options[OptCheck] && !options[OptCheckInline])
+               return 0;
+
+       printf("\n----------------\n");
+
+       if (!options[OptSkipSHA] && strlen(STRING(SHA))) {
+               options[OptSkipSHA] = 1;
+               printf("\nedid-decode SHA: %s %s\n", STRING(SHA), STRING(DATE));
+       }
+
+       if (options[OptCheck]) {
+               if (warnings)
+                       show_if_msgs(true);
+               if (failures)
+                       show_if_msgs(false);
+       }
+
+       printf("\n%s conformity: %s\n",
+              state.data_block.empty() ? "InfoFrame" : 
state.data_block.c_str(),
+              failures ? "FAIL" : "PASS");
+       return failures ? -2 : 0;
+}
+
 #ifndef __EMSCRIPTEN__
 
 static unsigned char crc_calc(const unsigned char *b)
@@ -2020,6 +2265,7 @@ int main(int argc, char **argv)
        enum output_format out_fmt = OUT_FMT_DEFAULT;
        gtf_parsed_data gtf_data;
        unsigned list_rid = 0;
+       std::vector<std::string> if_names;
        int ret;
 
        while (1) {
@@ -2116,6 +2362,9 @@ int main(int argc, char **argv)
                case OptListRIDTimings:
                        list_rid = strtoul(optarg, NULL, 0);
                        break;
+               case OptInfoFrame:
+                       if_names.push_back(optarg);
+                       break;
                case ':':
                        fprintf(stderr, "Option '%s' requires a value.\n",
                                argv[optind]);
@@ -2163,10 +2412,14 @@ int main(int argc, char **argv)
                return 0;
        }
 
-       if (optind == argc)
-               ret = edid_from_file("-", stdout);
-       else
+       if (optind == argc) {
+               if (options[OptInfoFrame] && !options[OptGTF])
+                       ret = 0;
+               else
+                       ret = edid_from_file("-", stdout);
+       } else {
                ret = edid_from_file(argv[optind], argv[optind + 1] ? stderr : 
stdout);
+       }
 
        if (ret && options[OptPhysicalAddress]) {
                printf("f.f.f.f\n");
@@ -2203,7 +2456,26 @@ int main(int argc, char **argv)
                return 0;
        }
 
-       return ret ? ret : state.parse_edid();
+       if (!ret && state.edid_size)
+               ret = state.parse_edid();
+
+       bool show_line = state.edid_size;
+
+       for (const auto &n : if_names) {
+               if (show_line)
+                       printf("\n================\n\n");
+               show_line = true;
+
+               state.warnings = state.failures = 0;
+               for (unsigned i = 0; i < EDID_MAX_BLOCKS + 1; i++) {
+                       s_msgs[i][0].clear();
+                       s_msgs[i][1].clear();
+               }
+               int r = state.parse_if(n);
+               if (r && !ret)
+                       ret = r;
+       }
+       return ret;
 }
 
 #else
@@ -2222,6 +2494,7 @@ extern "C" int parse_edid(const char *input)
        options[OptCheck] = 1;
        options[OptPreferredTimings] = 1;
        options[OptNativeResolution] = 1;
+       options[OptSkipSHA] = 0;
        state = edid_state();
        int ret = edid_from_file(input, stderr);
        return ret ? ret : state.parse_edid();
diff --git a/edid-decode.h b/edid-decode.h
index b480b6ccad41..ca6867db1289 100644
--- a/edid-decode.h
+++ b/edid-decode.h
@@ -202,6 +202,11 @@ struct edid_state {
                cta.preparsed_t8vtdb_dmt = 0;
                cta.preparsed_max_vic_pixclk_khz = 0;
                cta.warn_about_hdmi_2x_dtd = false;
+               cta.avi_version = 2;
+               cta.avi_v4_length = 14;
+               cta.has_ycbcr444 = false;
+               cta.has_ycbcr422 = false;
+               cta.has_ycbcr420 = false;
 
                // DisplayID block state
                dispid.version = 0;
@@ -333,6 +338,11 @@ struct edid_state {
                std::vector<unsigned char> preparsed_svds[2];
                unsigned preparsed_max_vic_pixclk_khz;
                bool warn_about_hdmi_2x_dtd;
+               unsigned avi_version;
+               unsigned avi_v4_length;
+               bool has_ycbcr444;
+               bool has_ycbcr422;
+               bool has_ycbcr420;
        } cta;
 
        // DisplayID block state
@@ -510,6 +520,18 @@ struct edid_state {
        void print_preferred_timings();
        void print_native_res();
        int parse_edid();
+
+       int parse_if(const std::string &fname);
+       int parse_if_hdr(const unsigned char *x, unsigned size, unsigned char 
mask = 0xff);
+       void parse_if_hdmi(const unsigned char *x, unsigned len);
+       void parse_if_hdmi_forum(const unsigned char *x, unsigned len);
+       void parse_if_vendor(const unsigned char *x, unsigned size);
+       void parse_if_avi(const unsigned char *x, unsigned size);
+       void parse_if_spd(const unsigned char *x, unsigned size);
+       void parse_if_audio(const unsigned char *x, unsigned size);
+       void parse_if_mpeg_source(const unsigned char *x, unsigned size);
+       void parse_if_ntsc_vbi(const unsigned char *x, unsigned size);
+       void parse_if_drm(const unsigned char *x, unsigned size);
 };
 
 static inline void add_str(std::string &s, const std::string &add)
@@ -571,6 +593,7 @@ void do_checksum(const char *prefix, const unsigned char 
*x, size_t len, size_t
                 unsigned unused_bytes = 0);
 void replace_checksum(unsigned char *x, size_t len);
 void calc_ratio(struct timings *t);
+unsigned calc_fps(const struct timings *t);
 const char *oui_name(unsigned oui, unsigned *ouinum = NULL);
 unsigned gcd(unsigned a, unsigned b);
 
@@ -580,6 +603,7 @@ const struct timings *find_dmt_id(unsigned char dmt_id);
 const struct timings *close_match_to_dmt(const timings &t, unsigned &dmt);
 const struct timings *find_vic_id(unsigned char vic);
 const struct cta_rid *find_rid(unsigned char rid);
+unsigned char rid_fps_to_vic(unsigned char rid, unsigned fps);
 const struct timings *find_hdmi_vic_id(unsigned char hdmi_vic);
 const struct timings *cta_close_match_to_vic(const timings &t, unsigned &vic);
 bool cta_matches_vic(const timings &t, unsigned &vic);
diff --git a/meson.build b/meson.build
index a3f45128ffdb..e18a7f29cc02 100644
--- a/meson.build
+++ b/meson.build
@@ -57,6 +57,7 @@ edid_decode_sources = [
        'parse-displayid-block.cpp',
        'parse-ls-ext-block.cpp',
        'parse-vtb-ext-block.cpp',
+       'parse-if.cpp',
 ]
 
 edid_decode = executable(
diff --git a/parse-cta-block.cpp b/parse-cta-block.cpp
index 9e7a80edee6a..f193b568a6d0 100644
--- a/parse-cta-block.cpp
+++ b/parse-cta-block.cpp
@@ -305,6 +305,15 @@ static unsigned char rid_to_vic(unsigned char rid, 
unsigned char rate_index)
        return rid2vic[rid][rate_index - 1];
 }
 
+unsigned char rid_fps_to_vic(unsigned char rid, unsigned fps)
+{
+       for (unsigned i = 1; i < ARRAY_SIZE(vf_rate_values); i++) {
+               if (vf_rate_values[i] == fps)
+                       return rid2vic[rid][i - 1];
+       }
+       return 0;
+}
+
 const struct timings *cta_close_match_to_vic(const timings &t, unsigned &vic)
 {
        for (vic = 1; vic <= ARRAY_SIZE(edid_cta_modes1); vic++) {
@@ -584,6 +593,8 @@ void edid_state::cta_svd(const unsigned char *x, unsigned 
n, bool for_ycbcr420)
                if ((svd - 1) & 0x40) {
                        vic = svd;
                        native = 0;
+                       if (cta.avi_version == 2)
+                               cta.avi_version = 3;
                } else {
                        vic = svd & 0x7f;
                        native = svd & 0x80;
@@ -623,6 +634,7 @@ void edid_state::cta_svd(const unsigned char *x, unsigned 
n, bool for_ycbcr420)
                                struct timings tmp = *t;
                                tmp.ycbcr420 = true;
                                print_timings("    ", &tmp, type, flags);
+                               cta.has_ycbcr420 = true;
                        } else {
                                print_timings("    ", t, type, flags);
                        }
@@ -665,6 +677,9 @@ cta_vfd edid_state::cta_parse_vfd(const unsigned char *x, 
unsigned lvfd)
 {
        cta_vfd vfd = {};
 
+       cta.avi_version = 4;
+       if (cta.avi_v4_length < 15)
+               cta.avi_v4_length = 15;
        vfd.rid = x[0] & 0x3f;
        if (vfd.rid >= ARRAY_SIZE(rids)) {
                vfd.rid = 0;
@@ -819,6 +834,7 @@ void edid_state::cta_y420cmdb(const unsigned char *x, 
unsigned length)
                                if (cta.preparsed_has_vic[1][vic])
                                        fail("VIC %u is also a YCbCr 4:2:0-only 
VIC.\n", vic);
                        }
+                       cta.has_ycbcr420 = true;
                }
        }
        if (max_idx >= cta.preparsed_svds[0].size())
@@ -2294,6 +2310,8 @@ void edid_state::cta_colorimetry_block(const unsigned 
char *x, unsigned length)
        // desirable.
        if (!base.uses_srgb && !(x[1] & 0x20))
                warn("Set the sRGB colorimetry bit to avoid interop issues.\n");
+       if (x[1] & 0xf0)
+               cta.avi_version = 4;
 }
 
 static const char *eotf_map[] = {
@@ -2917,10 +2935,14 @@ void edid_state::parse_cta_block(const unsigned char *x)
                                warn("IT Video Formats are overscanned by 
default, but normally this should be underscanned.\n");
                        if (x[3] & 0x40)
                                printf("  Basic audio support\n");
-                       if (x[3] & 0x20)
+                       if (x[3] & 0x20) {
                                printf("  Supports YCbCr 4:4:4\n");
-                       if (x[3] & 0x10)
+                               cta.has_ycbcr444 = true;
+                       }
+                       if (x[3] & 0x10) {
                                printf("  Supports YCbCr 4:2:2\n");
+                               cta.has_ycbcr422 = true;
+                       }
                        // Disable this test: this fails a lot of EDIDs, and 
there are
                        // also some corner cases where you only want to 
receive 4:4:4
                        // and refuse a fallback to 4:2:2.
diff --git a/parse-if.cpp b/parse-if.cpp
new file mode 100644
index 000000000000..589594a516e4
--- /dev/null
+++ b/parse-if.cpp
@@ -0,0 +1,1075 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright 2024 Cisco Systems, Inc. and/or its affiliates. All rights 
reserved.
+ *
+ * Author: Hans Verkuil <hverkuil-ci...@xs4all.nl>
+ */
+
+#include <stdio.h>
+
+#include "edid-decode.h"
+
+int edid_state::parse_if_hdr(const unsigned char *x, unsigned size, unsigned 
char mask)
+{
+       unsigned char length = x[2];
+
+       printf("%s\n", data_block.c_str());
+       printf("  Version: %u\n", x[1] & mask);
+       printf("  Length: %u\n", length);
+
+       if (length + 3U > size) {
+               fail("Expected InfoFrame total length of %d, but have only %d 
bytes.\n",
+                    length + 3, size);
+               return -1;
+       }
+       if (length + 3U < size) {
+               warn("There are %d dummy bytes after the payload.\n", length + 
3 - size);
+               if (!memchk(x + length + 3, length + 3 - size))
+                       warn("There are non-zero dummy bytes after the 
payload.\n");
+       }
+       return 0;
+}
+
+static const char *Structure_map[] = {
+       "Frame packing",
+       "Field alternative",
+       "Line alternative",
+       "Side-by-Side (Full)",
+       "L + depth",
+       "L + depth + graphics + graphics-depth",
+       "Top-and-Bottom",
+       "Reserved (7)",
+       "Side-by-Side (Half)",
+       "Reserved (9)",
+       "Reserved (10)",
+       "Reserved (11)",
+       "Reserved (12)",
+       "Reserved (13)",
+       "Reserved (14)",
+       "Not in use"
+};
+
+static const char *ExtData_map[] = {
+       "Horizontal sub-sampling (0)",
+       "Horizontal sub-sampling (1)",
+       "Horizontal sub-sampling (2)",
+       "Horizontal sub-sampling (3)",
+       "Quincunx matrix: Odd/Left picture, Odd/Right picture",
+       "Quincunx matrix: Odd/Left picture, Even/Right picture",
+       "Quincunx matrix: Even/Left picture, Odd/Right picture",
+       "Quincunx matrix: Even/Left picture, Even/Right picture"
+};
+
+void edid_state::parse_if_hdmi(const unsigned char *x, unsigned len)
+{
+       if (len < 4) {
+               fail("Expected InfoFrame length of at least 4, got %d.\n", len);
+               return;
+       }
+       if (x[4] & 0x1f)
+               fail("Bits 4-0 of PB4 are not 0.\n");
+
+       printf("  HDMI Video Format: ");
+
+       char buf[32];
+
+       switch (x[4] >> 5) {
+       case 0:
+               printf("No additional data\n");
+               if (!memchk(x + 5, len - 4))
+                       fail("Trailing non-0 bytes.\n");
+               return;
+       case 1: {
+               printf("HDMI_VIC is present\n");
+
+               if (len < 5) {
+                       fail("Expected InfoFrame length of at least 5, got 
%d.\n", len);
+                       return;
+               }
+               sprintf(buf, "HDMI VIC %u", x[5]);
+               const struct timings *t = find_hdmi_vic_id(x[5]);
+               if (!t) {
+                       printf("  HDMI VIC: %d (0x%02x)\n", x[5], x[5]);
+                       fail("Unknown HDMI VIC code.\n");
+               } else {
+                       print_timings("  ", t, buf);
+               }
+               if (!memchk(x + 6, len - 5))
+                       fail("Trailing non-0 bytes.\n");
+               return;
+       }
+       case 2:
+               printf("3D format indication present\n");
+
+               if (len < 5) {
+                       fail("Expected InfoFrame length of at least 5, got 
%d.\n", len);
+                       return;
+               }
+               break;
+       default:
+               printf("Reserved (%d)\n", x[4] >> 5);
+               fail("Invalid HDMI Video Format (%d).\n", x[4] >> 5);
+               return;
+       }
+
+       // Parsing of 3D extension continues here
+       unsigned char v = x[5] >> 4;
+
+       printf("  3D Structure: %s\n", Structure_map[v]);
+       if (x[5] & 7)
+               fail("Bits 2-0 of PB5 are not 0.\n");
+       printf("3D Metadata Present: %s\n", (x[5] & 8) ? "Yes" : "No");
+
+       if (v < 8) {
+               if (!memchk(x + 6, len - 5))
+                       fail("Trailing non-0 bytes.\n");
+               return;
+       }
+
+       if (len < 6) {
+               fail("Expected InfoFrame length of at least 6, got %d.\n", len);
+               return;
+       }
+
+       if (x[6] & 0xf)
+               fail("Bits 3-0 of PB6 are not 0.\n");
+
+       if ((x[6] >> 4) >= 8)
+               printf("  3D Extended Data: Reserved (%d)\n", (x[6] >> 4));
+       else
+               printf("  3D Extended Data: %s\n", ExtData_map[x[6] >> 4]);
+
+       if (!(x[5] & 8)) {
+               if (!memchk(x + 7, len - 6))
+                       fail("Trailing non-0 bytes.\n");
+               return;
+       }
+
+       if (len < 7) {
+               fail("Expected InfoFrame length of at least 7, got %d.\n", len);
+               return;
+       }
+       unsigned mlen = x[7] & 0x1f;
+       if (len < mlen + 7) {
+               fail("Expected InfoFrame length of at least %d, got %d.\n", 
mlen + 7, len);
+               return;
+       }
+       if (!memchk(x + mlen + 7, len - mlen - 6))
+               fail("Trailing non-0 bytes.\n");
+       if (x[7] >> 5) {
+               printf("  3D Metadata Type: Reserved (%d)\n", x[7] >> 5);
+               fail("Invalid 3D Metadata Type.\n");
+               return;
+       } else {
+               printf("  3D Metadata Type: Parallax Information\n");
+       }
+       printf("  Metadata: Parallax Zero: %u\n", (x[8] << 8) | x[9]);
+       printf("  Metadata: Parallax Scale: %u\n", (x[10] << 8) | x[11]);
+       printf("  Metadata: DRef: %u\n", (x[12] << 8) | x[13]);
+       printf("  Metadata: WRef: %u\n", (x[14] << 8) | x[15]);
+}
+
+void edid_state::parse_if_hdmi_forum(const unsigned char *x, unsigned len)
+{
+       if (len < 5) {
+               fail("Expected InfoFrame length of at least 5, got %d.\n", len);
+               return;
+       }
+       if (x[5] & 0x0c)
+               fail("Bits 3-2 of PB5 are not 0.\n");
+       unsigned char v = x[5] >> 4;
+       printf("  Color Content Bits Per Component: ");
+       if (!v)
+               printf("No Indication\n");
+       else if (v > 9)
+               printf("Reserved (%d)\n", v);
+       else
+               printf("%d bits\n", v + 7);
+       if (x[5] & 1)
+               printf("  3D Valid\n");
+       printf("  Auto Low-Latency Mode: %s\n", (x[5] & 2) ? "Yes" : "No");
+       if (!(x[5] & 1)) {
+               if (!memchk(x + 6, len - 5))
+                       fail("Trailing non-0 bytes.\n");
+               return;
+       }
+
+       // Parsing of 3D extension continues here
+       unsigned offset = 6;
+       v = x[offset] >> 4;
+
+       if (len < offset)
+               goto err_len;
+
+       printf("  3D Structure: %s\n", Structure_map[v]);
+       if (x[offset] & 1)
+               fail("Bit 0 of PB6 is not 0.\n");
+       printf("3D Additional Info Present: %s\n", (x[offset] & 8) ? "Yes" : 
"No");
+       printf("3D Disparity Data Present: %s\n", (x[offset] & 4) ? "Yes" : 
"No");
+       printf("3D Metadata Present: %s\n", (x[offset] & 2) ? "Yes" : "No");
+       offset++;
+
+       if (v >= 8) {
+               if (len < offset)
+                       goto err_len;
+
+               if (x[offset] & 0xf)
+                       fail("Bits 3-0 of PB7 are not 0.\n");
+
+               if ((x[offset] >> 4) >= 8)
+                       printf("  3D Extended Data: Reserved (%d)\n", 
(x[offset] >> 4));
+               else
+                       printf("  3D Extended Data: %s\n", 
ExtData_map[x[offset] >> 4]);
+               offset++;
+       }
+
+       if (x[6] & 8) {
+               if (len < offset)
+                       goto err_len;
+
+               if (x[offset] & 0xe0)
+                       fail("Bits 7-5 if PB%d are not 0.\n", offset);
+               printf("  3D Dual View: %s\n", (x[offset] & 0x10) ? "Yes" : 
"No");
+
+               static const char *ViewDep_map[] = {
+                       "No Indication",
+                       "Independent Right View",
+                       "Independent Left View",
+                       "Independent Right and Left Views"
+               };
+               printf("  3D View Dependency: %s\n", ViewDep_map[(x[offset] & 
0x0c) >> 2]);
+
+               static const char *Pref2DView_map[] = {
+                       "No Indication",
+                       "Right View",
+                       "Left View",
+                       "Either View"
+               };
+               printf("  3D Preferred 2D View: %s\n", Pref2DView_map[x[offset] 
& 3]);
+               offset++;
+       }
+
+       if (x[6] & 4) {
+               if (len < offset)
+                       goto err_len;
+
+               printf("  Disparity Data Version: %d\n", x[offset] >> 5);
+               unsigned dlen = x[offset] & 0x1f;
+               printf("  Disparity Data Length: %d\n", dlen);
+               if (len < offset + dlen)
+                       goto err_len;
+               offset++;
+
+               hex_block("  Disparity Data Payload: ", x + offset, dlen, 
false, dlen);
+
+               offset += dlen;
+       }
+
+       if (x[6] & 2) {
+               if (len < offset)
+                       goto err_len;
+
+               if (x[offset] >> 5) {
+                       printf("  3D Metadata Type: Reserved (%d)\n", x[offset] 
>> 5);
+                       fail("Invalid 3D Metadata Type.\n");
+                       return;
+               }
+               printf("  3D Metadata Type: Parallax Information\n");
+               printf("  3D Metadata Length: %d\n", x[offset] & 0x1f);
+               if (len < offset + (x[offset] & 0x1f))
+                       goto err_len;
+               offset++;
+
+               printf("  Metadata: Parallax Zero: %u\n", (x[offset] << 8) | 
x[offset+1]);
+               printf("  Metadata: Parallax Scale: %u\n", (x[offset+2] << 8) | 
x[offset+3]);
+               printf("  Metadata: DRef: %u\n", (x[offset+4] << 8) | 
x[offset+5]);
+               printf("  Metadata: WRef: %u\n", (x[offset+6] << 8) | 
x[offset+7]);
+
+               offset += x[offset - 1] & 0x1f;
+       }
+
+       if (!memchk(x + offset + 1, len - offset))
+               fail("Trailing non-0 bytes.\n");
+       return;
+
+err_len:
+       fail("Expected InfoFrame length of at least %d, got %d.\n", offset, 
len);
+       return;
+}
+
+void edid_state::parse_if_vendor(const unsigned char *x, unsigned size)
+{
+       data_block = "Vendor-Specific InfoFrame";
+
+       unsigned oui;
+       unsigned char len = x[2];
+
+       data_block_oui(data_block, x + 3, 3, &oui, false, false, false, true);
+
+       if (parse_if_hdr(x, size, (x[1] & 0x7f) == 2 ? 0x7f : 0xff))
+               return;
+
+       if (x[1] != 1 && (x[1] & 0x7f) != 2) {
+               fail("Invalid version %d\n", x[1] & 0x7f);
+               return;
+       }
+       if (len < 3) {
+               fail("Expected InfoFrame length of at least 3, got %d.\n", len);
+               return;
+       }
+
+       if (x[1] != 1)
+               printf("  VSIF Change: %s\n", (x[1] & 0x80) ? "Yes" : "No");
+
+       // After this x[1] will refer to Data Byte 1
+       x += 2;
+
+       switch (oui) {
+       case kOUI_HDMI:
+               parse_if_hdmi(x, len);
+               break;
+       case kOUI_HDMIForum:
+               parse_if_hdmi_forum(x, len);
+               break;
+       default:
+               hex_block("  Payload: ", x + 4, len - 3, false, len - 3);
+               break;
+       }
+}
+
+void edid_state::parse_if_avi(const unsigned char *x, unsigned size)
+{
+       unsigned char version = x[1];
+       unsigned char length = x[2];
+
+       data_block = "AVI InfoFrame";
+
+       if (parse_if_hdr(x, size))
+               return;
+
+       if (version == 0 || version > 4) {
+               fail("Invalid version %u\n", version);
+               return;
+       }
+       if (version == 1)
+               fail("Sources shall not use version 1.\n");
+       if (length < 13) {
+               fail("Expected InfoFrame length of 13, got %u.\n", length);
+               return;
+       }
+
+       // After this x[1] will refer to Data Byte 1
+       x += 2;
+
+       if (version == 1) {
+               if (x[3] & 0xfc)
+                       fail("Bits F37-F32 are not 0.\n");
+               if (x[4])
+                       fail("Bits F47-F40 are not 0.\n");
+               if (x[5])
+                       fail("Bits F57-F50 are not 0.\n");
+       }
+       if (version == 2) {
+               if (x[1] & 0x80)
+                       fail("Bit Y2 is not 0.\n");
+               if (x[4] & 0x80)
+                       fail("Bit VIC7 is not 0.\n");
+       }
+       if (version == 4 && length == 15) {
+               if (x[15] & 0x80)
+                       fail("Bit F157 is not 0.\n");
+       }
+       if (version == 4 && length < 14) {
+               fail("Version 4 expects a length of >= 14, got %u\n", length);
+               return;
+       }
+
+       if (edid_size) {
+               if (version > cta.avi_version)
+                       warn("AVI version is %u, but EDID indicates support for 
%u only.\n",
+                            version, cta.avi_version);
+               if (version == 4 && length > cta.avi_v4_length)
+                       warn("AVI version 4 length is %u, but EDID indicates 
support for %u only.\n",
+                            length, cta.avi_v4_length);
+       }
+
+       char buf[32];
+       unsigned vic_fps = 0;
+       unsigned char vic = x[4];
+       unsigned char rid = length >= 14 ? x[15] & 0x3f : 0;
+
+       sprintf(buf, "VIC %3u", vic);
+       const struct timings *t = find_vic_id(vic);
+       if (t) {
+               print_timings("  ", t, buf);
+               vic_fps = calc_fps(t);
+       } else if (vic) {
+               printf("  VIC: %d (0x%02x)\n", vic, vic);
+               fail("Unknown VIC code.\n");
+       }
+
+       static const char *Y_map[] = {
+               "RGB",
+               "YCbCr 4:2:2",
+               "YCbCr 4:4:4",
+               "YCbCr 4:2:0",
+               "Reserved (4)",
+               "Reserved (5)",
+               "Reserved (6)",
+               "IDO-Defined",
+       };
+       unsigned char y = x[1] >> 5;
+       unsigned char v;
+
+       printf("  Y: Color Component Sample Format: %s\n", Y_map[y]);
+       if (y == 7 && version == 2)
+               warn("Y == 7 but AVI Version == 2.\n");
+       if (edid_size) {
+               if ((y == 1 && !cta.has_ycbcr422) ||
+                   (y == 2 && !cta.has_ycbcr444) ||
+                   (y == 3 && !cta.has_ycbcr420))
+                       fail("Y == %s, but this capability is not enabled in 
the EDID.\n", Y_map[y]);
+       }
+
+       printf("  A: Active Format Information Present: %s\n",
+              ((x[1] >> 4) & 1) ? "Yes" : "No");
+
+       static const char *B_map[] = {
+               "Bar Data not present",
+               "Vertical Bar Info present",
+               "Horizontal Bar Info present",
+               "Vertical and Horizontal Bar Info present"
+       };
+       printf("  B: Bar Data Present: %s\n", B_map[(x[1] >> 2) & 3]);
+
+       static const char *S_map[] = {
+               "No Data",
+               "Composed for an overscanned display",
+               "Composed for an underscanned display",
+               "Reserved"
+       };
+       printf("  S: Scan Information: %s\n", S_map[x[1] & 3]);
+
+       static const char *C_map[] = {
+               "No Data",
+               "SMPTE ST 170",
+               "Rec. ITU-R BT.709",
+               "Extended Colorimetry Information Valid"
+       };
+       printf("  C: Colorimetry: %s\n", C_map[x[2] >> 6]);
+
+       static const char *M_map[] = {
+               "No Data",
+               "4:3",
+               "16:9",
+               "Reserved"
+       };
+       v = (x[2] >> 4) & 3;
+       printf("  M: Picture Aspect Ratio: %s\n", M_map[v]);
+       if ((vic || rid) && v)
+               warn("If a VID or RID is specified, then set M to 0.\n");
+       printf("  R: Active Portion Aspect Ratio: %d\n", x[2] & 0xf);
+
+       static const char *ITC_map[] = {
+               "No Data",
+               "IT Content (CN is valid)"
+       };
+       printf("  ITC: IT Content: %s\n", ITC_map[x[3] >> 7]);
+
+       static const char *EC_map[] = {
+               "xvYCC601",
+               "xvYCC709",
+               "sYCC601",
+               "opYCC601",
+               "opRGB",
+               "Rec. ITU-R BR.2020 YcCbcCrc",
+               "Rec. ITU-R BR.2020 RGB or YCbCr",
+               "Additional Colorimetry Extension Information Valid"
+       };
+       printf("  EC: Extended Colorimetry: %s\n", EC_map[(x[3] >> 4) & 7]);
+
+       static const char *Q_map[] = {
+               "Default",
+               "Limited Range",
+               "Full Range",
+               "Reserved"
+       };
+       printf("  Q: RGB Quantization Range: %s\n", Q_map[(x[3] >> 2) & 3]);
+
+       static const char *SC_map[] = {
+               "No Known non-uniform scaling",
+               "Picture has been scaled horizontally",
+               "Picture has been scaled vertically",
+               "Picture has been scaled horizontally and vertically"
+       };
+       printf("  SC: Non-Uniform Picture Scaling: %s\n", SC_map[x[3] & 3]);
+
+       static const char *YQ_map[] = {
+               "Limited Range",
+               "Full Range",
+               "Reserved",
+               "Reserved"
+       };
+       printf("  YQ: YCC Quantization Range: %s\n", YQ_map[x[5] >> 6]);
+
+       static const char *CN_map[] = {
+               "Graphics",
+               "Photo",
+               "Cinema",
+               "Game"
+       };
+       printf("  CN: IT Content Type: %s\n", CN_map[(x[5] >> 4) & 3]);
+       unsigned char pr = x[5] & 0xf;
+       printf("  PR: Pixel Data Repetition Count: %d\n", pr);
+
+       const unsigned short pr_2 = 2;
+       const unsigned short pr_1_10 = 0x3ff;
+       const unsigned short pr_1_2 = 3;
+       const unsigned short pr_1_2_4 = 0xb;
+       static const unsigned short vic_valid_pr[] = {
+               // VIC 0-7
+               0, 0, 0, 0, 0, 0, pr_2, pr_2, 
+               // VIC 8-15
+               pr_2, pr_2, pr_1_10, pr_1_10, pr_1_10, pr_1_10, pr_1_2, pr_1_2,
+               // VIC 16-23
+               0, 0, 0, 0, 0, pr_2, pr_2, pr_2,
+               // VIC 24-31
+               pr_2, pr_1_10, pr_1_10, pr_1_10, pr_1_10, pr_1_2, pr_1_2, 0,
+               // VIC 32-39
+               0, 0, 0, pr_1_2_4, pr_1_2_4, pr_1_2_4, pr_1_2_4, 0,
+               // VIC 40-47
+               0, 0, 0, 0, pr_2, pr_2, 0, 0,
+               // VIC 48-55
+               0, 0, pr_2, pr_2, 0, 0, pr_2, pr_2,
+               // VIC 56-59
+               0, 0, pr_2, pr_2
+       };
+
+       if (pr >= 10)
+               fail("PR >= 10 is a reserved value.\n");
+       else if (pr && (vic >= ARRAY_SIZE(vic_valid_pr) ||
+                       !(vic_valid_pr[vic] & (1 << pr))))
+               fail("PR %u is not supported by VIC %u.\n", pr, vic);
+
+       printf("  Line Number of End of Top Bar: %d\n", (x[7] << 8) | x[6]);
+       printf("  Line Number of Start of Bottom Bar: %d\n", (x[9] << 8) | 
x[8]);
+       printf("  Pixel Number of End of Left Bar: %d\n", (x[11] << 8) | x[10]);
+       printf("  Pixel Number of Start of Right Bar: %d\n", (x[13] << 8) | 
x[12]);
+       if (length <= 13)
+               return;
+
+       static const char *ACE_map[] = {
+               "SMPTE ST 2113 P3D65 RGB",
+               "SMPTE ST 2113 P3DCI RGB",
+               "Rec. ITU-R BT.2100 ICtCp",
+               "sRGB",
+               "defaultRGB",
+               "Reserved (5)",
+               "Reserved (6)",
+               "Reserved (7)",
+               "Reserved (8)",
+               "Reserved (9)",
+               "Reserved (10)",
+               "Reserved (11)",
+               "Reserved (12)",
+               "Reserved (13)",
+               "Reserved (14)",
+               "Reserved (15)"
+       };
+       printf("  ACE: Additional Colorimetry Extension: %s\n", ACE_map[x[14] 
>> 4]);
+       if (length <= 14) {
+               if (x[14] & 0xf) {
+                       printf("  FR: %d\n", x[14] & 0xf);
+                       fail("InfoFrame length is 14, but FR != 0.\n");
+               }
+               return;
+       }
+
+       unsigned fr = ((x[15] & 0x40) >> 2) | (x[14] >> 4);
+
+       printf("  RID/FR: %u/%u\n", rid, fr);
+       if (vic && rid)
+               fail("Both a RID and a VIC were specified.\n");
+
+       if (!rid && !fr)
+               return;
+
+       if (rid && !fr) {
+               fail("RID is set, but FR is 0.\n");
+               return;
+       }
+
+       static const unsigned fr_rate_values[] = {
+               /* FR 0-7 */
+                 0,  24,  24,  25,  30,  30,  48,  48,
+               /* FR 8-15 */
+                50,  60,  60, 100, 120, 120, 144, 144,
+               /* FR 16-23 */
+               200, 240, 240, 300, 360, 360, 400, 480,
+               /* FR 24 */
+               480
+       };
+       static const bool fr_ntsc[] = {
+               /* FR 0-7 */
+               0, 1, 0, 0, 1, 0, 1, 0,
+               /* FR 8-15 */
+               0, 1, 0, 0, 1, 0, 1, 0,
+               /* FR 16-23 */
+               0, 1, 0, 0, 1, 0, 0, 1,
+               /* FR 24 */
+               0
+       };
+
+       if (fr >= ARRAY_SIZE(fr_rate_values)) {
+               fail("Unknown FR %u.\n", fr);
+               return;
+       }
+       unsigned fps = fr_rate_values[fr];
+       bool ntsc = fr_ntsc[fr];
+
+       printf("  Frame Rate: %u%s\n", fps, ntsc ? "/1.001" : "");
+
+       if (vic) {
+               if (vic_fps != fps)
+                       warn("VIC %u is %u fps, while FR indicates %u fps.\n",
+                            x[4], vic_fps, fps);
+               return;
+       }
+
+       if (!rid)
+               return;
+
+       const struct cta_rid *crid = find_rid(rid);
+
+       if (!crid) {
+               fail("Unknown RID %u.\n", rid);
+               return;
+       }
+
+       unsigned char rid_vic = rid_fps_to_vic(rid, fps);
+       if (rid_vic) {
+               sprintf(buf, "VIC %3u", rid_vic);
+               const struct timings *t = find_vic_id(rid_vic);
+               print_timings("  ", t, buf);
+               warn("RID/FR %u/%u maps to VIC %d.\n", rid, fr, rid_vic);
+       } else {
+               sprintf(buf, "RID/FR %u/%u", rid, fr);
+               timings t = calc_ovt_mode(crid->hact, crid->vact,
+                                         crid->hratio, crid->vratio, fps);
+               print_timings("", &t, buf, "", false, false, ntsc);
+       }
+}
+
+void edid_state::parse_if_spd(const unsigned char *x, unsigned size)
+{
+       data_block = "Source Product Description InfoFrame";
+
+       if (parse_if_hdr(x, size))
+               return;
+
+       if (x[1] != 1) {
+               fail("Invalid version %d\n", x[1]);
+               return;
+       }
+       if (x[2] < 25) {
+               fail("Expected InfoFrame length of 25, got %d.\n", x[2]);
+               return;
+       }
+
+       // After this x[1] will refer to Data Byte 1
+       x += 2;
+
+       for (unsigned i = 1; i <= 24; i++) {
+               if (x[i] & 0x80) {
+                       fail("SPD contains ASCII character with bit 7 set.\n");
+               }
+       }
+       char vendor[9] = {};
+       memcpy(vendor, x + 1, 8);
+       printf("  Vendor Name: '%s'\n", vendor);
+       unsigned len = strlen(vendor);
+       if (!memchk(x + 1 + len, 8 - len))
+               fail("Vendor name has trailing non-zero characters.\n");
+
+       char product[17] = {};
+       memcpy(product, x + 9, 16);
+       printf("  Product Description: '%s'\n", product);
+       len = strlen(product);
+       if (!memchk(x + 9 + len, 16 - len))
+               fail("Product name has trailing non-zero characters.\n");
+
+       if (x[25] >= 0x0e) {
+               printf("  Source Information: %d (Reserved)\n", x[25]);
+               fail("Source Information value %d is reserved.\n", x[25]);
+       } else {
+               static const char *SI_map[] = {
+                       "Unknown",
+                       "Digital STB",
+                       "DVD player",
+                       "D-VHS",
+                       "HDD Videorecorder",
+                       "DVC",
+                       "DSC",
+                       "Video CD",
+                       "Game",
+                       "PC general",
+                       "Blu-Ray Disck (DB)",
+                       "Super Audio CD",
+                       "HD DVD",
+                       "PMP"
+               };
+               printf("  Source Information: %s\n", SI_map[x[25]]);
+       }
+}
+
+void edid_state::parse_if_audio(const unsigned char *x, unsigned size)
+{
+       data_block = "Audio InfoFrame";
+
+       if (parse_if_hdr(x, size))
+               return;
+
+       if (x[1] != 1) {
+               fail("Invalid version %d\n", x[1]);
+               return;
+       }
+       if (x[2] < 10) {
+               fail("Expected InfoFrame length of 10, got %d.\n", x[2]);
+               return;
+       }
+
+       // After this x[1] will refer to Data Byte 1
+       x += 2;
+
+       if (x[1] & 0x08)
+               fail("Bit F13 is not 0.\n");
+       if (x[2] & 0xe0)
+               fail("Bits F27-F25 are not 0.\n");
+       if (x[3] & 0xe0)
+               fail("Bits F37-F35 are not 0.\n");
+       if (x[5] & 0x04)
+               fail("Bit F52 is not 0.\n");
+       if (x[4] <= 0x31 && !memchk(x + 6, 5))
+               fail("Bits F107-F60 are not 0.\n");
+       else if (x[4] == 0xfe && (x[10] || x[9] || (x[8] & 0xf8)))
+               fail("Bits F107-F90 and/or F87-F83 are not 0.\n");
+       else if (x[4] == 0xff && x[10])
+               fail("Bits F107-F100 are not 0.\n");
+
+       static const char *CT_map[] = {
+               "Refer to Stream Header",
+               "L-PCM",
+               "AC-3",
+               "MPEG-1",
+               "MP3",
+               "MPEG2",
+               "AAC LC",
+               "DTS",
+               "ATRAC",
+               "DSD",
+               "Enhanced AC-3",
+               "DTS-(U)HD",
+               "MAT",
+               "DST",
+               "WMA Pro",
+               "Refer to Audio Coding Extension Type (CXT) Field"
+       };
+       printf("  CT: Audio Coding Type: %s\n", CT_map[x[1] >> 4]);
+       if (x[1] & 7)
+               printf("  CC: Audio Channel Count: %d\n", (x[1] & 7) + 1);
+       else
+               printf("  CC: Audio Channel Count: Refer to Stream Header\n");
+
+       static const char *SF_map[] = {
+               "Refer to Stream Header",
+               "32 kHz",
+               "44.1 kHz (CD)",
+               "48 kHz",
+               "88.2 kHz",
+               "96 kHz",
+               "176.4 kHz",
+               "192 kHz"
+       };
+       printf("  SF: Sampling Frequency: %s\n", SF_map[(x[2] >> 2) & 7]);
+
+       static const char *SS_map[] = {
+               "Refer to Stream Header",
+               "16 bit",
+               "20 bit",
+               "24 bit"
+       };
+       printf("  SS: Bits/Sample: %s\n", SS_map[x[2] & 3]);
+
+       static const char *CXT_map[] = {
+               "Refer to Audio Coding Type (CT) Field",
+               "Not in Use (1)",
+               "Not in Use (2)",
+               "Not in Use (3)",
+               "MPEG-4 HE AAC",
+               "MPEG-4 HE AAC v2",
+               "MPEG-4 AAC LC",
+               "DRA",
+               "MPEG-4 HE AAC + MPEG Surround",
+               "Reserved (9)",
+               "MPEG-4 AAC LC + MPEG Surround",
+               "MPEG-H 3D Audio",
+               "AC-4",
+               "L-PCM 3D Audio",
+               "Auro-Cx",
+               "MPEG-D USAC"
+       };
+       if ((x[3] & 0x1f) < ARRAY_SIZE(CXT_map))
+               printf("  CXT: Audio Coding Extension Type: %s\n", CXT_map[x[3] 
& 0x1f]);
+       else
+               printf("  CXT: Audio Coding Extension Type: Reserved (%d)\n", 
x[3] & 0x1f);
+       if ((x[3] & 0x1f) == 9 || (x[3] & 0x1f) >= ARRAY_SIZE(CXT_map))
+               fail("CXT: Reserved value.\n");
+
+       static const char *CA_map[] = {
+               "FR/FL",
+               "LFE1, FR/FL",
+               "FC, FR/FL",
+               "FC, LFE1, FR/FL",
+               "BC, FR/FL",
+               "BC, LFE1, FR/FL",
+               "BC, FC, FR/FL",
+               "BC, FC, LFE1, FR/FL",
+               "RS/LS, FR/FL",
+               "RS/LS, LFE1, FR/FL",
+               "RS/LS, FC, FR/FL",
+               "RS/LS, FC, LFE1, FR/FL",
+               "BC, RS/LS, FR/FL",
+               "BC, RS/LS, LFE1, FR/FL",
+               "BC, RS/LS, FC, FR/FL",
+               "BC, RS/LS, FC, LFE1, FR/FL",
+               "BR/BL, RS/LS, FR/FL",
+               "BR/BL, RS/LS, LFE1, FR/FL",
+               "BR/BL, RS/LS, FC, FR/FL",
+               "BR/BL, RS/LS, FC, LFE1, FR/FL",
+               "FRc/FLc, FR/FL",
+               "FRc/FLc, LFE1, FR/FL",
+               "FRc/FLc, FC, FR/FL",
+               "FRc/FLc, FC, LFE1, FR/FL",
+               "FRc/FLc, BC, FR/FL",
+               "FRc/FLc, BC, LFE1, FR/FL",
+               "FRc/FLc, BC, FC, FR/FL",
+               "FRc/FLc, BC, FC, LFE1, FR/FL",
+               "FRc/FLc, RS/LS, FR/FL",
+               "FRc/FLc, RS/LS, LFE1, FR/FL",
+               "FRc/FLc, RS/LS, FC, FR/FL",
+               "FRc/FLc, RS/LS, FC, LFE1, FR/FL",
+               "TpFC, RS/LS, FC, FR/FL",
+               "TpFC, RS/LS, FC, LFE1, FR/FL",
+               "TpC, RS/LS, FC, FR/FL",
+               "TpC, RS/LS, FC, LFE1, FR/FL",
+               "TpFR/TpFL, RS/LS, FR/FL",
+               "TpFR/TpFL, RS/LS, LFE1, FR/FL",
+               "FRw/FLw, RS/LS, FR/FL",
+               "FRw/FLw, RS/LS, LFE1, FR/FL",
+               "TpC, BC, RS/LS, FC, FR/FL",
+               "TpC, BC, RS/LS, FC, LFE1, FR/FL",
+               "TpFC, BC, RS/LS, FC, FR/FL",
+               "TpFC, BC, RS/LS, FC, FR/FL",
+               "TpC, TpFC, RS/LS, FC, FR/FL",
+               "TpC, TpFC, RS/LS, FC, FR/FL",
+               "TpFR/TpFL, RS/LS, FC, FR/FL",
+               "TpFR/TpFL, RS/LS, FC, FR/FL",
+               "FRw/FLw, RS/LS, FC, FR/FL",
+               "FRw/FLw, RS/LS, FC, FR/FL"
+       };
+       if (x[4] < ARRAY_SIZE(CA_map))
+               printf("  CA: Channel Allocation: %s\n", CA_map[x[4]]);
+       else if (x[4] < 0xfe) {
+               printf("  CA: Channel Allocation: Reserved (%d, 0x%02x)\n", 
x[4], x[4]);
+               fail("CA: Reserved value.\n");
+       }
+       else if (x[4] == 0xfe)
+               printf("  CA: Channel Allocation: According to the Speaker 
Mask\n");
+       else
+               printf("  CA: Channel Allocation: According to Channel 
Index\n");
+       printf("  LSV: Level Shift Value: %d dB\n", (x[5] >> 3) & 0xf);
+       printf("  DM_INH: Allow the Down Mixed Stereo Output: %s\n",
+              (x[5] & 0x80) ? "Prohibited" : "Yes");
+
+       static const char *LFEPBL_map[] = {
+               "Unknown or refer to other information",
+               "0 dB",
+               "+10 dB",
+               "Reserved"
+       };
+       printf("  LFEPBL: LFE Playback Level compared to other channels: %s\n", 
LFEPBL_map[x[5] & 3]);
+       if ((x[5] & 3) == 3)
+               fail("LFEPBL: Reserved value.\n");
+
+       if (x[4] < 0xfe)
+               return;
+
+       if (x[4] == 0xfe) {
+               if (x[6] & 0x40)
+                       warn("F66 is not 0, the use of this bit is 
deprecated.\n");
+               if (x[8] & 0x08)
+                       warn("F83 is not 0, the use of this bit is 
deprecated.\n");
+               printf("  SPM: Speaker Mask:\n");
+
+               unsigned spm = (x[8] << 16) | (x[7] << 8) | x[6];
+
+               for (unsigned i = 0; cta_speaker_map[i]; i++) {
+                       if ((spm >> i) & 1)
+                               printf("    %s\n", cta_speaker_map[i]);
+               }
+               return;
+       }
+
+       // CA == 0xff
+       printf("  CID: Channel Index: ");
+
+       unsigned cid = (x[9] << 24) | (x[8] << 16) | (x[7] << 8) | x[6];
+       bool first = true;
+
+       for (unsigned i = 0; i < 32; i++) {
+               if ((cid >> i) & 1) {
+                       if (!first)
+                               printf(" ,");
+                       first = false;
+                       printf("%u", i);
+               }
+       }
+       printf("\n");
+}
+
+void edid_state::parse_if_mpeg_source(const unsigned char *x, unsigned size)
+{
+       data_block = "MPEG Source InfoFrame";
+
+       warn("The use of the %s is not recommended.\n", data_block.c_str());
+
+       if (parse_if_hdr(x, size))
+               return;
+
+       if (x[1] != 1) {
+               fail("Invalid version %d\n", x[1]);
+               return;
+       }
+       if (x[2] < 10) {
+               fail("Expected InfoFrame length of 10, got %d.\n", x[2]);
+               return;
+       }
+
+       // After this x[1] will refer to Data Byte 1
+       x += 2;
+
+       unsigned mb = (x[4] << 24) | (x[3] << 16) | (x[2] << 8) | x[1];
+
+       if (mb)
+               printf("  MB: MPEG Bit Rate: %u Hz\n", mb);
+       else
+               printf("  MB: MPEG Bit Rate: Unknown/Does Not Apply\n");
+
+       static const char *MF_map[] = {
+               "Unknown (No Data)",
+               "I Picture",
+               "B Picture",
+               "P Picture"
+       };
+       printf("  MF: MPEG Frame: %s\n", MF_map[x[5] & 3]);
+       printf("  FR: Field Repeat: %s\n", (x[5] & 0x10) ? "Repeated Field" : 
"New Field (Picture)");
+       if (x[5] & 0xec)
+               fail("Bits F57-F55 and/or F53-F52 are not 0.\n");
+       if (x[6] || x[7] || x[8] || x[9] || x[10])
+               fail("Bits F100-F60 are not 0.\n");
+}
+
+void edid_state::parse_if_ntsc_vbi(const unsigned char *x, unsigned size)
+{
+       data_block = "NTSC VBI InfoFrame";
+
+       if (parse_if_hdr(x, size))
+               return;
+
+       int len = x[2];
+
+       if (x[1] != 1) {
+               fail("Invalid version %d\n", x[1]);
+               return;
+       }
+
+       // After this x[1] will refer to Data Byte 1
+       x += 2;
+
+       // See SCTE 127, Table 2 for more details
+       hex_block("  PES_data_field: ", x + 1, len, false, len);
+}
+
+void edid_state::parse_if_drm(const unsigned char *x, unsigned size)
+{
+       unsigned length = x[2];
+
+       data_block = "Dynamic Range and Mastering InfoFrame";
+
+       if (parse_if_hdr(x, size))
+               return;
+
+       if (x[1] != 1) {
+               fail("Invalid version %d\n", x[1]);
+               return;
+       }
+
+       // After this x[1] will refer to Data Byte 1
+       x += 2;
+
+       if (x[1] & 0xf8)
+               fail("Bits F17-F13 are not 0.\n");
+
+       static const char *TF_map[] = {
+               "Traditional Gamma - SDR Luminance Range",
+               "Traditional Gamma - HDR Luminance Range",
+               "Perceptual Quantization (PQ) based on SMPTE ST 2084",
+               "Hybrid Log-Gamma (HLG) based on Rec. ITU-R BT.2100",
+               "Reserved (4)",
+               "Reserved (5)",
+               "Reserved (6)",
+               "Reserved (7)"
+       };
+       printf("Transfer Function: %s\n",
+              TF_map[x[1] & 7]);
+       if (length < 2)
+               return;
+
+       if (x[2] & 0xf8)
+               fail("Bits F27-F23 are not 0.\n");
+       if (x[2] & 7) {
+               printf("Static Metadata Descriptor ID: Reserved (%d)\n", x[2] & 
7);
+               if (!memchk(x + 3, length - 2))
+                       fail("Trailing non-zero bytes.\n");
+               return;
+       }
+       printf("Static Metadata Descriptor ID: Type 1\n");
+       if (length < 26) {
+               fail("Expected a length of 26, got %d.\n", length);
+               return;
+       }
+       if (!memchk(x + 3, 12)) {
+               printf("    Display Primary 0: (%.5f, %.5f)\n", chrom2d(x + 3), 
chrom2d(x + 5));
+               printf("    Display Primary 1: (%.5f, %.5f)\n", chrom2d(x + 7), 
chrom2d(x + 7));
+               printf("    Display Primary 2: (%.5f, %.5f)\n", chrom2d(x + 
11), chrom2d(x + 13));
+       }
+       if (!memchk(x + 15, 4)) {
+               printf("    White Point: (%.5f, %.5f)\n", chrom2d(x + 15), 
chrom2d(x + 17));
+       }
+       if (!memchk(x + 19, 2)) {
+               printf("    Max Display Mastering Luminance: %u cd/m^2\n",
+                      x[19] + (x[20] << 8));
+       }
+       if (!memchk(x + 21, 2)) {
+               printf("    Min Display Mastering Luminance: %f cd/m^2\n",
+                      (x[21] + (x[22] << 8)) * 0.0001);
+       }
+       if (!memchk(x + 23, 2)) {
+               printf("    Maximum Content Light Level (MaxCLL): %u cd/m^2\n",
+                      x[23] + (x[24] << 8));
+       }
+       if (!memchk(x + 25, 2)) {
+               printf("    Maximum Frame-Average Light Level (MaxFALL): %u 
cd/m^2\n",
+                      x[25] + (x[26] << 8));
+       }
+}

Reply via email to