mirror of
https://gitlab.freedesktop.org/emersion/libdisplay-info.git
synced 2024-12-25 21:59:08 +01:00
5142866ee7
If the data block sizes start overlapping the DTD we cannot continue parsing this or further data blocks but the ones we parsed already should be valid. Similarily if we fail parsing a specific data block the previous ones should still be valid and the next ones might be as well. Closes: https://gitlab.freedesktop.org/emersion/libdisplay-info/-/issues/36 Closes: https://gitlab.freedesktop.org/emersion/libdisplay-info/-/issues/37 Signed-off-by: Sebastian Wick <sebastian.wick@redhat.com>
1680 lines
46 KiB
C
1680 lines
46 KiB
C
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "bits.h"
|
|
#include "cta.h"
|
|
#include "log.h"
|
|
#include "edid.h"
|
|
|
|
/**
|
|
* Number of bytes in the CTA header (tag + revision + DTD offset + flags).
|
|
*/
|
|
#define CTA_HEADER_SIZE 4
|
|
/**
|
|
* Exclusive upper bound for the detailed timing definitions in the CTA block.
|
|
*/
|
|
#define CTA_DTD_END 127
|
|
/**
|
|
* Number of bytes in a CTA short audio descriptor.
|
|
*/
|
|
#define CTA_SAD_SIZE 3
|
|
|
|
const struct di_cta_video_format *
|
|
di_cta_video_format_from_vic(uint8_t vic)
|
|
{
|
|
if (vic > _di_cta_video_formats_len ||
|
|
_di_cta_video_formats[vic].vic == 0)
|
|
return NULL;
|
|
return &_di_cta_video_formats[vic];
|
|
}
|
|
|
|
static void
|
|
add_failure(struct di_edid_cta *cta, const char fmt[], ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
_di_logger_va_add_failure(cta->logger, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
static void
|
|
add_failure_until(struct di_edid_cta *cta, int revision, const char fmt[], ...)
|
|
{
|
|
va_list args;
|
|
|
|
if (cta->revision > revision) {
|
|
return;
|
|
}
|
|
|
|
va_start(args, fmt);
|
|
_di_logger_va_add_failure(cta->logger, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
static struct di_cta_svd *
|
|
parse_svd(struct di_edid_cta *cta, uint8_t raw, const char *prefix)
|
|
{
|
|
struct di_cta_svd svd, *svd_ptr;
|
|
|
|
if (raw == 0 || raw == 128 || raw >= 254) {
|
|
/* Reserved */
|
|
add_failure_until(cta, 3,
|
|
"%s: Unknown VIC %" PRIu8 ".",
|
|
prefix,
|
|
raw);
|
|
return NULL;
|
|
} else if (raw <= 127 || raw >= 193) {
|
|
svd = (struct di_cta_svd) {
|
|
.vic = raw,
|
|
};
|
|
} else {
|
|
svd = (struct di_cta_svd) {
|
|
.vic = get_bit_range(raw, 6, 0),
|
|
.native = true,
|
|
};
|
|
}
|
|
|
|
svd_ptr = calloc(1, sizeof(*svd_ptr));
|
|
if (!svd_ptr)
|
|
return NULL;
|
|
*svd_ptr = svd;
|
|
return svd_ptr;
|
|
}
|
|
|
|
static bool
|
|
parse_video_block(struct di_edid_cta *cta, struct di_cta_video_block *video,
|
|
const uint8_t *data, size_t size)
|
|
{
|
|
size_t i;
|
|
struct di_cta_svd *svd;
|
|
|
|
if (size == 0)
|
|
add_failure(cta, "Video Data Block: Empty Data Block");
|
|
|
|
for (i = 0; i < size; i++) {
|
|
svd = parse_svd(cta, data[i], "Video Data Block");
|
|
if (!svd)
|
|
continue;
|
|
assert(video->svds_len < EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES);
|
|
video->svds[video->svds_len++] = svd;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
parse_ycbcr420_block(struct di_edid_cta *cta,
|
|
struct di_cta_video_block *ycbcr420,
|
|
const uint8_t *data, size_t size)
|
|
{
|
|
size_t i;
|
|
struct di_cta_svd *svd;
|
|
|
|
if (size == 0)
|
|
add_failure(cta, "YCbCr 4:2:0 Video Data Block: Empty Data Block");
|
|
|
|
for (i = 0; i < size; i++) {
|
|
svd = parse_svd(cta, data[i], "YCbCr 4:2:0 Video Data Block");
|
|
if (!svd)
|
|
continue;
|
|
assert(ycbcr420->svds_len < EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES);
|
|
ycbcr420->svds[ycbcr420->svds_len++] = svd;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
parse_sad_format(struct di_edid_cta *cta, const uint8_t data[static CTA_SAD_SIZE],
|
|
enum di_cta_audio_format *format)
|
|
{
|
|
uint8_t code, code_ext;
|
|
|
|
code = get_bit_range(data[0], 6, 3);
|
|
switch (code) {
|
|
case 0x0:
|
|
add_failure_until(cta, 3,
|
|
"Audio Data Block: Audio Format Code 0x00 is reserved.");
|
|
return false;
|
|
case 0x1:
|
|
*format = DI_CTA_AUDIO_FORMAT_LPCM;
|
|
break;
|
|
case 0x2:
|
|
*format = DI_CTA_AUDIO_FORMAT_AC3;
|
|
break;
|
|
case 0x3:
|
|
*format = DI_CTA_AUDIO_FORMAT_MPEG1;
|
|
break;
|
|
case 0x4:
|
|
*format = DI_CTA_AUDIO_FORMAT_MP3;
|
|
break;
|
|
case 0x5:
|
|
*format = DI_CTA_AUDIO_FORMAT_MPEG2;
|
|
break;
|
|
case 0x6:
|
|
*format = DI_CTA_AUDIO_FORMAT_AAC_LC;
|
|
break;
|
|
case 0x7:
|
|
*format = DI_CTA_AUDIO_FORMAT_DTS;
|
|
break;
|
|
case 0x8:
|
|
*format = DI_CTA_AUDIO_FORMAT_ATRAC;
|
|
break;
|
|
case 0x9:
|
|
*format = DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO;
|
|
break;
|
|
case 0xA:
|
|
*format = DI_CTA_AUDIO_FORMAT_ENHANCED_AC3;
|
|
break;
|
|
case 0xB:
|
|
*format = DI_CTA_AUDIO_FORMAT_DTS_HD;
|
|
break;
|
|
case 0xC:
|
|
*format = DI_CTA_AUDIO_FORMAT_MAT;
|
|
break;
|
|
case 0xD:
|
|
*format = DI_CTA_AUDIO_FORMAT_DST;
|
|
break;
|
|
case 0xE:
|
|
*format = DI_CTA_AUDIO_FORMAT_WMA_PRO;
|
|
break;
|
|
case 0xF:
|
|
code_ext = get_bit_range(data[2], 7, 3);
|
|
switch (code_ext) {
|
|
case 0x04:
|
|
*format = DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC;
|
|
break;
|
|
case 0x05:
|
|
*format = DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2;
|
|
break;
|
|
case 0x06:
|
|
*format = DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC;
|
|
break;
|
|
case 0x07:
|
|
*format = DI_CTA_AUDIO_FORMAT_DRA;
|
|
break;
|
|
case 0x08:
|
|
*format = DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND;
|
|
break;
|
|
case 0x0A:
|
|
*format = DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND;
|
|
break;
|
|
case 0x0B:
|
|
*format = DI_CTA_AUDIO_FORMAT_MPEGH_3D;
|
|
break;
|
|
case 0x0C:
|
|
*format = DI_CTA_AUDIO_FORMAT_AC4;
|
|
break;
|
|
case 0x0D:
|
|
*format = DI_CTA_AUDIO_FORMAT_LPCM_3D;
|
|
break;
|
|
default:
|
|
add_failure_until(cta, 3,
|
|
"Audio Data Block: Unknown Audio Ext Format 0x%02x.",
|
|
code_ext);
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
add_failure_until(cta, 3,
|
|
"Audio Data Block: Unknown Audio Format 0x%02x.",
|
|
code);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
parse_sad(struct di_edid_cta *cta, struct di_cta_audio_block *audio,
|
|
const uint8_t data[static CTA_SAD_SIZE])
|
|
{
|
|
enum di_cta_audio_format format;
|
|
struct di_cta_sad_priv *priv;
|
|
struct di_cta_sad *sad;
|
|
struct di_cta_sad_sample_rates *sample_rates;
|
|
struct di_cta_sad_lpcm *lpcm;
|
|
struct di_cta_sad_mpegh_3d *mpegh_3d;
|
|
struct di_cta_sad_mpeg_aac *mpeg_aac;
|
|
struct di_cta_sad_mpeg_surround *mpeg_surround;
|
|
struct di_cta_sad_mpeg_aac_le *mpeg_aac_le;
|
|
struct di_cta_sad_enhanced_ac3 *enhanced_ac3;
|
|
struct di_cta_sad_mat *mat;
|
|
struct di_cta_sad_wma_pro *wma_pro;
|
|
|
|
if (!parse_sad_format(cta, data, &format))
|
|
return true;
|
|
|
|
priv = calloc(1, sizeof(*priv));
|
|
if (!priv)
|
|
return false;
|
|
|
|
sad = &priv->base;
|
|
sample_rates = &priv->supported_sample_rates;
|
|
lpcm = &priv->lpcm;
|
|
mpegh_3d = &priv->mpegh_3d;
|
|
mpeg_aac = &priv->mpeg_aac;
|
|
mpeg_surround = &priv->mpeg_surround;
|
|
mpeg_aac_le = &priv->mpeg_aac_le;
|
|
enhanced_ac3 = &priv->enhanced_ac3;
|
|
mat = &priv->mat;
|
|
wma_pro = &priv->wma_pro;
|
|
|
|
sad->format = format;
|
|
|
|
/* TODO: Find DRA documentation */
|
|
|
|
switch (format) {
|
|
case DI_CTA_AUDIO_FORMAT_LPCM:
|
|
case DI_CTA_AUDIO_FORMAT_AC3:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG1:
|
|
case DI_CTA_AUDIO_FORMAT_MP3:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG2:
|
|
case DI_CTA_AUDIO_FORMAT_AAC_LC:
|
|
case DI_CTA_AUDIO_FORMAT_DTS:
|
|
case DI_CTA_AUDIO_FORMAT_ATRAC:
|
|
case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO:
|
|
case DI_CTA_AUDIO_FORMAT_ENHANCED_AC3:
|
|
case DI_CTA_AUDIO_FORMAT_DTS_HD:
|
|
case DI_CTA_AUDIO_FORMAT_MAT:
|
|
case DI_CTA_AUDIO_FORMAT_DST:
|
|
case DI_CTA_AUDIO_FORMAT_WMA_PRO:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC:
|
|
/* DRA is not documented but this is what edid-decode does */
|
|
case DI_CTA_AUDIO_FORMAT_DRA:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND:
|
|
sad->max_channels = get_bit_range(data[0], 2, 0) + 1;
|
|
break;
|
|
case DI_CTA_AUDIO_FORMAT_LPCM_3D:
|
|
sad->max_channels = (get_bit_range(data[0], 2, 0) |
|
|
(get_bit_range(data[0], 7, 7) << 3) |
|
|
(get_bit_range(data[1], 7, 7) << 4)) + 1;
|
|
break;
|
|
case DI_CTA_AUDIO_FORMAT_MPEGH_3D:
|
|
case DI_CTA_AUDIO_FORMAT_AC4:
|
|
break;
|
|
}
|
|
|
|
switch (format) {
|
|
case DI_CTA_AUDIO_FORMAT_LPCM:
|
|
case DI_CTA_AUDIO_FORMAT_AC3:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG1:
|
|
case DI_CTA_AUDIO_FORMAT_MP3:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG2:
|
|
case DI_CTA_AUDIO_FORMAT_AAC_LC:
|
|
case DI_CTA_AUDIO_FORMAT_DTS:
|
|
case DI_CTA_AUDIO_FORMAT_ATRAC:
|
|
case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO:
|
|
case DI_CTA_AUDIO_FORMAT_ENHANCED_AC3:
|
|
case DI_CTA_AUDIO_FORMAT_DTS_HD:
|
|
case DI_CTA_AUDIO_FORMAT_MAT:
|
|
case DI_CTA_AUDIO_FORMAT_DST:
|
|
case DI_CTA_AUDIO_FORMAT_WMA_PRO:
|
|
/* DRA is not documented but this is what edid-decode does */
|
|
case DI_CTA_AUDIO_FORMAT_DRA:
|
|
case DI_CTA_AUDIO_FORMAT_MPEGH_3D:
|
|
case DI_CTA_AUDIO_FORMAT_LPCM_3D:
|
|
sample_rates->has_192_khz = has_bit(data[1], 6);
|
|
sample_rates->has_176_4_khz = has_bit(data[1], 5);
|
|
/* fallthrough */
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND:
|
|
sample_rates->has_96_khz = has_bit(data[1], 4);
|
|
sample_rates->has_88_2_khz = has_bit(data[1], 3);
|
|
sample_rates->has_48_khz = has_bit(data[1], 2);
|
|
sample_rates->has_44_1_khz = has_bit(data[1], 1);
|
|
sample_rates->has_32_khz = has_bit(data[1], 0);
|
|
break;
|
|
case DI_CTA_AUDIO_FORMAT_AC4:
|
|
sample_rates->has_192_khz = has_bit(data[1], 6);
|
|
sample_rates->has_96_khz = has_bit(data[1], 4);
|
|
sample_rates->has_48_khz = has_bit(data[1], 2);
|
|
sample_rates->has_44_1_khz = has_bit(data[1], 1);
|
|
break;
|
|
}
|
|
sad->supported_sample_rates = sample_rates;
|
|
|
|
switch (format) {
|
|
case DI_CTA_AUDIO_FORMAT_AC3:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG1:
|
|
case DI_CTA_AUDIO_FORMAT_MP3:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG2:
|
|
case DI_CTA_AUDIO_FORMAT_AAC_LC:
|
|
case DI_CTA_AUDIO_FORMAT_DTS:
|
|
case DI_CTA_AUDIO_FORMAT_ATRAC:
|
|
sad->max_bitrate_kbs = data[2] * 8;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (format) {
|
|
case DI_CTA_AUDIO_FORMAT_LPCM:
|
|
case DI_CTA_AUDIO_FORMAT_LPCM_3D:
|
|
lpcm->has_sample_size_24_bits = has_bit(data[2], 2);
|
|
lpcm->has_sample_size_20_bits = has_bit(data[2], 1);
|
|
lpcm->has_sample_size_16_bits = has_bit(data[2], 0);
|
|
sad->lpcm = lpcm;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (format) {
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND:
|
|
mpeg_aac->has_frame_length_1024 = has_bit(data[2], 2);
|
|
mpeg_aac->has_frame_length_960 = has_bit(data[2], 1);
|
|
sad->mpeg_aac = mpeg_aac;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (format == DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC) {
|
|
mpeg_aac_le->supports_multichannel_sound = has_bit(data[2], 0);
|
|
sad->mpeg_aac_le = mpeg_aac_le;
|
|
}
|
|
|
|
switch (format) {
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND:
|
|
mpeg_surround->signaling = has_bit(data[2], 0);
|
|
sad->mpeg_surround = mpeg_surround;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (format == DI_CTA_AUDIO_FORMAT_MPEGH_3D) {
|
|
mpegh_3d->low_complexity_profile = has_bit(data[2], 0);
|
|
mpegh_3d->baseline_profile = has_bit(data[2], 1);
|
|
mpegh_3d->level = get_bit_range(data[0], 2, 0);
|
|
if (mpegh_3d->level > DI_CTA_SAD_MPEGH_3D_LEVEL_5) {
|
|
add_failure_until(cta, 3,
|
|
"Unknown MPEG-H 3D Audio Level 0x%02x.",
|
|
mpegh_3d->level);
|
|
mpegh_3d->level = DI_CTA_SAD_MPEGH_3D_LEVEL_UNSPECIFIED;
|
|
}
|
|
sad->mpegh_3d = mpegh_3d;
|
|
}
|
|
|
|
if (format == DI_CTA_AUDIO_FORMAT_ENHANCED_AC3) {
|
|
enhanced_ac3->supports_joint_object_coding =
|
|
has_bit(data[2], 0);
|
|
enhanced_ac3->supports_joint_object_coding_ACMOD28 =
|
|
has_bit(data[2], 1);
|
|
sad->enhanced_ac3 = enhanced_ac3;
|
|
}
|
|
|
|
if (format == DI_CTA_AUDIO_FORMAT_MAT) {
|
|
mat->supports_object_audio_and_channel_based =
|
|
has_bit(data[2], 0);
|
|
if (mat->supports_object_audio_and_channel_based)
|
|
mat->requires_hash_calculation = !has_bit(data[2], 0);
|
|
sad->mat = mat;
|
|
}
|
|
|
|
if (format == DI_CTA_AUDIO_FORMAT_WMA_PRO) {
|
|
wma_pro->profile = get_bit_range(data[2], 2, 0);
|
|
sad->wma_pro = wma_pro;
|
|
}
|
|
|
|
switch (format) {
|
|
case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO:
|
|
case DI_CTA_AUDIO_FORMAT_DTS_HD:
|
|
case DI_CTA_AUDIO_FORMAT_DST:
|
|
/* TODO data[2] 7:0 contains unknown Audio Format Code dependent value */
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (format == DI_CTA_AUDIO_FORMAT_AC4) {
|
|
/* TODO data[2] 2:0 contains unknown Audio Format Code dependent value */
|
|
}
|
|
|
|
switch (format) {
|
|
case DI_CTA_AUDIO_FORMAT_LPCM:
|
|
case DI_CTA_AUDIO_FORMAT_WMA_PRO:
|
|
if (has_bit(data[0], 7) || has_bit(data[1], 7) ||
|
|
get_bit_range(data[2], 7, 3) != 0)
|
|
add_failure_until(cta, 3,
|
|
"Bits F17, F27, F37:F33 must be 0.");
|
|
break;
|
|
case DI_CTA_AUDIO_FORMAT_AC3:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG1:
|
|
case DI_CTA_AUDIO_FORMAT_MP3:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG2:
|
|
case DI_CTA_AUDIO_FORMAT_AAC_LC:
|
|
case DI_CTA_AUDIO_FORMAT_DTS:
|
|
case DI_CTA_AUDIO_FORMAT_ATRAC:
|
|
case DI_CTA_AUDIO_FORMAT_ONE_BIT_AUDIO:
|
|
case DI_CTA_AUDIO_FORMAT_ENHANCED_AC3:
|
|
case DI_CTA_AUDIO_FORMAT_DTS_HD:
|
|
case DI_CTA_AUDIO_FORMAT_MAT:
|
|
case DI_CTA_AUDIO_FORMAT_DST:
|
|
if (has_bit(data[0], 7) || has_bit(data[1], 7))
|
|
add_failure_until(cta, 3,
|
|
"Bits F17, F27 must be 0.");
|
|
break;
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_V2:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_HE_AAC_MPEG_SURROUND:
|
|
case DI_CTA_AUDIO_FORMAT_MPEG4_AAC_LC_MPEG_SURROUND:
|
|
if (has_bit(data[0], 7) || get_bit_range(data[2], 7, 5) != 0)
|
|
add_failure_until(cta, 3,
|
|
"Bits F17, F27:F25 must be 0.");
|
|
break;
|
|
case DI_CTA_AUDIO_FORMAT_MPEGH_3D:
|
|
if (has_bit(data[0], 7) || has_bit(data[1], 7) ||
|
|
has_bit(data[2], 2))
|
|
add_failure_until(cta, 3,
|
|
"Bits F17, F27, F32 must be 0.");
|
|
break;
|
|
case DI_CTA_AUDIO_FORMAT_AC4:
|
|
if ((data[0] & 0x87) != 0 || (data[1] & 0xA9) != 0)
|
|
add_failure_until(cta, 3,
|
|
"Bits F17, F12:F10, F27, F25, F23, "
|
|
"F20 must be 0.");
|
|
break;
|
|
/* DRA documentation missing */
|
|
case DI_CTA_AUDIO_FORMAT_DRA:
|
|
case DI_CTA_AUDIO_FORMAT_LPCM_3D:
|
|
break;
|
|
}
|
|
|
|
assert(audio->sads_len < EDID_CTA_MAX_AUDIO_BLOCK_ENTRIES);
|
|
audio->sads[audio->sads_len++] = priv;
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
parse_audio_block(struct di_edid_cta *cta, struct di_cta_audio_block *audio,
|
|
const uint8_t *data, size_t size)
|
|
{
|
|
size_t i;
|
|
|
|
if (size % 3 != 0)
|
|
add_failure(cta, "Broken CTA-861 audio block length %d.", size);
|
|
|
|
for (i = 0; i + 3 <= size; i += 3) {
|
|
if (!parse_sad(cta, audio, &data[i]))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
parse_speaker_alloc_block(struct di_edid_cta *cta,
|
|
struct di_cta_speaker_alloc_block *speaker_alloc,
|
|
const uint8_t *data, size_t size)
|
|
{
|
|
bool rlc_rrc;
|
|
|
|
if (size < 3) {
|
|
add_failure(cta,
|
|
"Speaker Allocation Data Block: Empty Data Block with length %zu.",
|
|
size);
|
|
return false;
|
|
}
|
|
|
|
speaker_alloc->flw_frw = has_bit(data[0], 7);
|
|
rlc_rrc = has_bit(data[0], 6);
|
|
speaker_alloc->flc_frc = has_bit(data[0], 5);
|
|
speaker_alloc->bc = has_bit(data[0], 4);
|
|
speaker_alloc->bl_br = has_bit(data[0], 3);
|
|
speaker_alloc->fc = has_bit(data[0], 2);
|
|
speaker_alloc->lfe1 = has_bit(data[0], 1);
|
|
speaker_alloc->fl_fr = has_bit(data[0], 0);
|
|
if (rlc_rrc) {
|
|
if (cta->revision >= 3)
|
|
add_failure(cta, "Speaker Allocation Data Block: Deprecated bit F16 must be 0.");
|
|
else
|
|
speaker_alloc->bl_br = true;
|
|
}
|
|
|
|
speaker_alloc->tpsil_tpsir = has_bit(data[1], 7);
|
|
speaker_alloc->sil_sir = has_bit(data[1], 6);
|
|
speaker_alloc->tpbc = has_bit(data[1], 5);
|
|
speaker_alloc->lfe2 = has_bit(data[1], 4);
|
|
speaker_alloc->ls_rs = has_bit(data[1], 3);
|
|
speaker_alloc->tpfc = has_bit(data[1], 2);
|
|
speaker_alloc->tpc = has_bit(data[1], 1);
|
|
speaker_alloc->tpfl_tpfr = has_bit(data[1], 0);
|
|
|
|
if (get_bit_range(data[2], 7, 4) != 0)
|
|
add_failure(cta, "Speaker Allocation Data Block: Bits F37, F36, F34 must be 0.");
|
|
if (cta->revision >= 3 && has_bit(data[2], 3))
|
|
add_failure(cta, "Speaker Allocation Data Block: Deprecated bit F33 must be 0.");
|
|
speaker_alloc->btfl_btfr = has_bit(data[2], 2);
|
|
speaker_alloc->btfc = has_bit(data[2], 1);
|
|
speaker_alloc->tpbl_tpbr = has_bit(data[2], 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
parse_video_cap_block(struct di_edid_cta *cta,
|
|
struct di_cta_video_cap_block *video_cap,
|
|
const uint8_t *data, size_t size)
|
|
{
|
|
if (size < 1) {
|
|
add_failure(cta,
|
|
"Video Capability Data Block: Empty Data Block with length %u.",
|
|
size);
|
|
return false;
|
|
}
|
|
|
|
video_cap->selectable_ycc_quantization_range = has_bit(data[0], 7);
|
|
video_cap->selectable_rgb_quantization_range = has_bit(data[0], 6);
|
|
video_cap->pt_over_underscan = get_bit_range(data[0], 5, 4);
|
|
video_cap->it_over_underscan = get_bit_range(data[0], 3, 2);
|
|
video_cap->ce_over_underscan = get_bit_range(data[0], 1, 0);
|
|
|
|
if (!video_cap->selectable_rgb_quantization_range && cta->revision >= 3)
|
|
add_failure(cta,
|
|
"Video Capability Data Block: Set Selectable RGB Quantization to avoid interop issues.");
|
|
/* TODO: add failure if selectable_ycc_quantization_range is unset,
|
|
* the sink supports YCbCr formats and the revision is 3+ */
|
|
|
|
switch (video_cap->it_over_underscan) {
|
|
case DI_CTA_VIDEO_CAP_ALWAYS_OVERSCAN:
|
|
if (cta->flags.it_underscan)
|
|
add_failure(cta, "Video Capability Data Block: IT video formats are always overscanned, but bit 7 of Byte 3 of the CTA-861 Extension header is set to underscanned.");
|
|
break;
|
|
case DI_CTA_VIDEO_CAP_ALWAYS_UNDERSCAN:
|
|
if (!cta->flags.it_underscan)
|
|
add_failure(cta, "Video Capability Data Block: IT video formats are always underscanned, but bit 7 of Byte 3 of the CTA-861 Extension header is set to overscanned.");
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
check_vesa_dddb_num_channels(enum di_cta_vesa_dddb_interface_type interface,
|
|
uint8_t num_channels)
|
|
{
|
|
switch (interface) {
|
|
case DI_CTA_VESA_DDDB_INTERFACE_VGA:
|
|
case DI_CTA_VESA_DDDB_INTERFACE_NAVI_V:
|
|
case DI_CTA_VESA_DDDB_INTERFACE_NAVI_D:
|
|
return num_channels == 0;
|
|
case DI_CTA_VESA_DDDB_INTERFACE_LVDS:
|
|
case DI_CTA_VESA_DDDB_INTERFACE_RSDS:
|
|
return true;
|
|
case DI_CTA_VESA_DDDB_INTERFACE_DVI_D:
|
|
return num_channels == 1 || num_channels == 2;
|
|
case DI_CTA_VESA_DDDB_INTERFACE_DVI_I_ANALOG:
|
|
return num_channels == 0;
|
|
case DI_CTA_VESA_DDDB_INTERFACE_DVI_I_DIGITAL:
|
|
return num_channels == 1 || num_channels == 2;
|
|
case DI_CTA_VESA_DDDB_INTERFACE_HDMI_A:
|
|
return num_channels == 1;
|
|
case DI_CTA_VESA_DDDB_INTERFACE_HDMI_B:
|
|
return num_channels == 2;
|
|
case DI_CTA_VESA_DDDB_INTERFACE_MDDI:
|
|
return num_channels == 1 || num_channels == 2;
|
|
case DI_CTA_VESA_DDDB_INTERFACE_DISPLAYPORT:
|
|
return num_channels == 1 || num_channels == 2 || num_channels == 4;
|
|
case DI_CTA_VESA_DDDB_INTERFACE_IEEE_1394:
|
|
case DI_CTA_VESA_DDDB_INTERFACE_M1_ANALOG:
|
|
return num_channels == 0;
|
|
case DI_CTA_VESA_DDDB_INTERFACE_M1_DIGITAL:
|
|
return num_channels == 1 || num_channels == 2;
|
|
}
|
|
abort(); /* unreachable */
|
|
}
|
|
|
|
static void
|
|
parse_vesa_dddb_additional_primary_chromaticity(struct di_cta_vesa_dddb_additional_primary_chromaticity *coords,
|
|
uint8_t low,
|
|
const uint8_t high[static 2])
|
|
{
|
|
uint16_t raw_x, raw_y; /* only 10 bits are used */
|
|
|
|
raw_x = (uint16_t) ((high[0] << 2) | get_bit_range(low, 3, 2));
|
|
raw_y = (uint16_t) ((high[1] << 2) | get_bit_range(low, 1, 0));
|
|
|
|
*coords = (struct di_cta_vesa_dddb_additional_primary_chromaticity) {
|
|
.x = (float) raw_x / 1024,
|
|
.y = (float) raw_y / 1024,
|
|
};
|
|
}
|
|
|
|
static bool
|
|
parse_vesa_dddb(struct di_edid_cta *cta, struct di_cta_vesa_dddb *dddb,
|
|
const uint8_t *data, size_t size)
|
|
{
|
|
const size_t offset = 2; /* CTA block header */
|
|
uint8_t interface_type, num_channels, content_protection, scan_direction,
|
|
subpixel_layout;
|
|
|
|
if (size + offset != 32) {
|
|
add_failure(cta, "VESA Video Display Device Data Block: Invalid length %u.", size);
|
|
return false;
|
|
}
|
|
|
|
interface_type = get_bit_range(data[0x02 - offset], 7, 4);
|
|
num_channels = get_bit_range(data[0x02 - offset], 3, 0);
|
|
switch (interface_type) {
|
|
case 0x0: /* Analog */
|
|
/* Special case: num_channels contains the detailed interface
|
|
* type. */
|
|
switch (num_channels) {
|
|
case 0x0:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_VGA;
|
|
break;
|
|
case 0x1:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_NAVI_V;
|
|
break;
|
|
case 0x2:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_NAVI_D;
|
|
break;
|
|
default:
|
|
add_failure(cta,
|
|
"VESA Video Display Device Data Block: Unknown analog interface type 0x%x.",
|
|
num_channels);
|
|
return false;
|
|
}
|
|
num_channels = 0;
|
|
break;
|
|
case 0x1:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_LVDS;
|
|
break;
|
|
case 0x2:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_RSDS;
|
|
break;
|
|
case 0x3:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_DVI_D;
|
|
break;
|
|
case 0x4:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_DVI_I_ANALOG;
|
|
break;
|
|
case 0x5:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_DVI_I_DIGITAL;
|
|
break;
|
|
case 0x6:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_HDMI_A;
|
|
break;
|
|
case 0x7:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_HDMI_B;
|
|
break;
|
|
case 0x8:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_MDDI;
|
|
break;
|
|
case 0x9:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_DISPLAYPORT;
|
|
break;
|
|
case 0xA:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_IEEE_1394;
|
|
break;
|
|
case 0xB:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_M1_ANALOG;
|
|
break;
|
|
case 0xC:
|
|
dddb->interface_type = DI_CTA_VESA_DDDB_INTERFACE_M1_DIGITAL;
|
|
break;
|
|
default:
|
|
add_failure(cta,
|
|
"VESA Video Display Device Data Block: Unknown interface type 0x%x.",
|
|
interface_type);
|
|
return false;
|
|
}
|
|
|
|
if (check_vesa_dddb_num_channels(dddb->interface_type, num_channels))
|
|
dddb->num_channels = num_channels;
|
|
else
|
|
add_failure(cta,
|
|
"VESA Video Display Device Data Block: Invalid number of lanes/channels %u.",
|
|
num_channels);
|
|
|
|
dddb->interface_version = get_bit_range(data[0x03 - offset], 7, 4);
|
|
dddb->interface_release = get_bit_range(data[0x03 - offset], 3, 0);
|
|
|
|
content_protection = data[0x04 - offset];
|
|
switch (content_protection) {
|
|
case DI_CTA_VESA_DDDB_CONTENT_PROTECTION_NONE:
|
|
case DI_CTA_VESA_DDDB_CONTENT_PROTECTION_HDCP:
|
|
case DI_CTA_VESA_DDDB_CONTENT_PROTECTION_DTCP:
|
|
case DI_CTA_VESA_DDDB_CONTENT_PROTECTION_DPCP:
|
|
dddb->content_protection = content_protection;
|
|
break;
|
|
default:
|
|
add_failure(cta,
|
|
"VESA Video Display Device Data Block: Invalid content protection 0x%x.",
|
|
content_protection);
|
|
}
|
|
|
|
dddb->min_clock_freq_mhz = get_bit_range(data[0x05 - offset], 7, 2);
|
|
dddb->max_clock_freq_mhz =
|
|
(get_bit_range(data[0x05 - offset], 1, 0) << 8) | data[0x06 - offset];
|
|
if (dddb->min_clock_freq_mhz > dddb->max_clock_freq_mhz) {
|
|
add_failure(cta,
|
|
"VESA Video Display Device Data Block: Minimum clock frequency (%d MHz) greater than maximum (%d MHz).",
|
|
dddb->min_clock_freq_mhz, dddb->max_clock_freq_mhz);
|
|
dddb->min_clock_freq_mhz = dddb->max_clock_freq_mhz = 0;
|
|
}
|
|
|
|
dddb->native_horiz_pixels = data[0x07 - offset] | (data[0x08 - offset] << 8);
|
|
dddb->native_vert_pixels = data[0x09 - offset] | (data[0x0A - offset] << 8);
|
|
|
|
dddb->aspect_ratio = (float)data[0x0B - offset] / 100 + 1;
|
|
dddb->default_orientation = get_bit_range(data[0x0C - offset], 7, 6);
|
|
dddb->rotation_cap = get_bit_range(data[0x0C - offset], 5, 4);
|
|
dddb->zero_pixel_location = get_bit_range(data[0x0C - offset], 3, 2);
|
|
scan_direction = get_bit_range(data[0x0C - offset], 1, 0);
|
|
if (scan_direction != 3)
|
|
dddb->scan_direction = scan_direction;
|
|
else
|
|
add_failure(cta,
|
|
"VESA Video Display Device Data Block: Invalid scan direction 0x%x.",
|
|
scan_direction);
|
|
|
|
subpixel_layout = data[0x0D - offset];
|
|
switch (subpixel_layout) {
|
|
case DI_CTA_VESA_DDDB_SUBPIXEL_UNDEFINED:
|
|
case DI_CTA_VESA_DDDB_SUBPIXEL_RGB_VERT:
|
|
case DI_CTA_VESA_DDDB_SUBPIXEL_RGB_HORIZ:
|
|
case DI_CTA_VESA_DDDB_SUBPIXEL_EDID_CHROM_VERT:
|
|
case DI_CTA_VESA_DDDB_SUBPIXEL_EDID_CHROM_HORIZ:
|
|
case DI_CTA_VESA_DDDB_SUBPIXEL_QUAD_RGGB:
|
|
case DI_CTA_VESA_DDDB_SUBPIXEL_QUAD_GBRG:
|
|
case DI_CTA_VESA_DDDB_SUBPIXEL_DELTA_RGB:
|
|
case DI_CTA_VESA_DDDB_SUBPIXEL_MOSAIC:
|
|
case DI_CTA_VESA_DDDB_SUBPIXEL_QUAD_ANY:
|
|
case DI_CTA_VESA_DDDB_SUBPIXEL_FIVE:
|
|
case DI_CTA_VESA_DDDB_SUBPIXEL_SIX:
|
|
case DI_CTA_VESA_DDDB_SUBPIXEL_CLAIRVOYANTE_PENTILE:
|
|
dddb->subpixel_layout = subpixel_layout;
|
|
break;
|
|
default:
|
|
add_failure(cta,
|
|
"VESA Video Display Device Data Block: Invalid subpixel layout 0x%x.",
|
|
subpixel_layout);
|
|
}
|
|
|
|
dddb->horiz_pitch_mm = (float)data[0x0E - offset] * 0.01f;
|
|
dddb->vert_pitch_mm = (float)data[0x0F - offset] * 0.01f;
|
|
|
|
dddb->dithering_type = get_bit_range(data[0x10 - offset], 7, 6);
|
|
dddb->direct_drive = has_bit(data[0x10 - offset], 5);
|
|
dddb->overdrive_not_recommended = has_bit(data[0x10 - offset], 4);
|
|
dddb->deinterlacing = has_bit(data[0x10 - offset], 3);
|
|
if (get_bit_range(data[0x10 - offset], 2, 0) != 0)
|
|
add_failure(cta, "VESA Video Display Device Data Block: Reserved miscellaneous display capabilities bits 2-0 must be 0.");
|
|
|
|
dddb->audio_support = has_bit(data[0x11 - offset], 7);
|
|
dddb->separate_audio_inputs = has_bit(data[0x11 - offset], 6);
|
|
dddb->audio_input_override = has_bit(data[0x11 - offset], 5);
|
|
if (get_bit_range(data[0x11 - offset], 4, 0) != 0)
|
|
add_failure(cta, "VESA Video Display Device Data Block: Reserved audio bits 4-0 must be 0.");
|
|
|
|
dddb->audio_delay_provided = data[0x12 - offset] != 0;
|
|
dddb->audio_delay_ms = 2 * get_bit_range(data[0x12 - offset], 6, 0);
|
|
if (!has_bit(data[0x12 - offset], 7))
|
|
dddb->audio_delay_ms = -dddb->audio_delay_ms;
|
|
|
|
dddb->frame_rate_conversion = get_bit_range(data[0x13 - offset], 7, 6);
|
|
dddb->frame_rate_range_hz = get_bit_range(data[0x13 - offset], 5, 0);
|
|
dddb->frame_rate_native_hz = data[0x14 - offset];
|
|
|
|
dddb->bit_depth_interface = get_bit_range(data[0x15 - offset], 7, 4) + 1;
|
|
dddb->bit_depth_display = get_bit_range(data[0x15 - offset], 3, 0) + 1;
|
|
|
|
dddb->additional_primary_chromaticities_len = get_bit_range(data[0x17 - offset], 1, 0);
|
|
parse_vesa_dddb_additional_primary_chromaticity(&dddb->additional_primary_chromaticities[0],
|
|
get_bit_range(data[0x16 - offset], 7, 4),
|
|
&data[0x18 - offset]);
|
|
parse_vesa_dddb_additional_primary_chromaticity(&dddb->additional_primary_chromaticities[1],
|
|
get_bit_range(data[0x16 - offset], 3, 0),
|
|
&data[0x1A - offset]);
|
|
parse_vesa_dddb_additional_primary_chromaticity(&dddb->additional_primary_chromaticities[2],
|
|
get_bit_range(data[0x17 - offset], 7, 4),
|
|
&data[0x1C - offset]);
|
|
if (get_bit_range(data[0x17 - offset], 3, 2) != 0)
|
|
add_failure(cta, "VESA Video Display Device Data Block: Reserved additional primary chromaticities bits 3-2 of byte 0x17 must be 0.");
|
|
|
|
dddb->resp_time_transition = has_bit(data[0x1E - offset], 7);
|
|
dddb->resp_time_ms = get_bit_range(data[0x1E - offset], 6, 0);
|
|
|
|
dddb->overscan_horiz_pct = get_bit_range(data[0x1F - offset], 7, 4);
|
|
dddb->overscan_vert_pct = get_bit_range(data[0x1F - offset], 3, 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
parse_colorimetry_block(struct di_edid_cta *cta,
|
|
struct di_cta_colorimetry_block *colorimetry,
|
|
const uint8_t *data, size_t size)
|
|
{
|
|
if (size < 2) {
|
|
add_failure(cta, "Colorimetry Data Block: Empty Data Block with length %u.",
|
|
size);
|
|
return false;
|
|
}
|
|
|
|
colorimetry->bt2020_rgb = has_bit(data[0], 7);
|
|
colorimetry->bt2020_ycc = has_bit(data[0], 6);
|
|
colorimetry->bt2020_cycc = has_bit(data[0], 5);
|
|
colorimetry->oprgb = has_bit(data[0], 4);
|
|
colorimetry->opycc_601 = has_bit(data[0], 3);
|
|
colorimetry->sycc_601 = has_bit(data[0], 2);
|
|
colorimetry->xvycc_709 = has_bit(data[0], 1);
|
|
colorimetry->xvycc_601 = has_bit(data[0], 0);
|
|
|
|
colorimetry->st2113_rgb = has_bit(data[1], 7);
|
|
colorimetry->ictcp = has_bit(data[1], 6);
|
|
|
|
if (get_bit_range(data[1], 5, 0) != 0)
|
|
add_failure_until(cta, 3,
|
|
"Colorimetry Data Block: Reserved bits MD0-MD3 must be 0.");
|
|
|
|
return true;
|
|
}
|
|
|
|
static float
|
|
parse_max_luminance(uint8_t raw)
|
|
{
|
|
if (raw == 0)
|
|
return 0;
|
|
return 50 * powf(2, (float) raw / 32);
|
|
}
|
|
|
|
static float
|
|
parse_min_luminance(uint8_t raw, float max)
|
|
{
|
|
if (raw == 0)
|
|
return 0;
|
|
return max * powf((float) raw / 255, 2) / 100;
|
|
}
|
|
|
|
static bool
|
|
parse_hdr_static_metadata_block(struct di_edid_cta *cta,
|
|
struct di_cta_hdr_static_metadata_block_priv *metadata,
|
|
const uint8_t *data, size_t size)
|
|
{
|
|
uint8_t eotfs, descriptors;
|
|
|
|
if (size < 2) {
|
|
add_failure(cta, "HDR Static Metadata Data Block: Empty Data Block with length %u.",
|
|
size);
|
|
return false;
|
|
}
|
|
|
|
metadata->base.eotfs = &metadata->eotfs;
|
|
metadata->base.descriptors = &metadata->descriptors;
|
|
|
|
eotfs = data[0];
|
|
metadata->eotfs.traditional_sdr = has_bit(eotfs, 0);
|
|
metadata->eotfs.traditional_hdr = has_bit(eotfs, 1);
|
|
metadata->eotfs.pq = has_bit(eotfs, 2);
|
|
metadata->eotfs.hlg = has_bit(eotfs, 3);
|
|
if (get_bit_range(eotfs, 7, 4))
|
|
add_failure_until(cta, 3, "HDR Static Metadata Data Block: Unknown EOTF.");
|
|
|
|
descriptors = data[1];
|
|
metadata->descriptors.type1 = has_bit(descriptors, 0);
|
|
if (get_bit_range(descriptors, 7, 1))
|
|
add_failure_until(cta, 3, "HDR Static Metadata Data Block: Unknown descriptor type.");
|
|
|
|
if (size > 2)
|
|
metadata->base.desired_content_max_luminance = parse_max_luminance(data[2]);
|
|
if (size > 3)
|
|
metadata->base.desired_content_max_frame_avg_luminance = parse_max_luminance(data[3]);
|
|
if (size > 4) {
|
|
if (metadata->base.desired_content_max_luminance == 0)
|
|
add_failure(cta, "HDR Static Metadata Data Block: Desired content min luminance is set, but max luminance is unset.");
|
|
else
|
|
metadata->base.desired_content_min_luminance =
|
|
parse_min_luminance(data[4], metadata->base.desired_content_max_luminance);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
static bool
|
|
parse_hdr_dynamic_metadata_block(struct di_edid_cta *cta,
|
|
struct di_cta_hdr_dynamic_metadata_block_priv *priv,
|
|
const uint8_t *data, size_t size)
|
|
{
|
|
struct di_cta_hdr_dynamic_metadata_block *base;
|
|
struct di_cta_hdr_dynamic_metadata_block_type1 *type1;
|
|
struct di_cta_hdr_dynamic_metadata_block_type2 *type2;
|
|
struct di_cta_hdr_dynamic_metadata_block_type3 *type3;
|
|
struct di_cta_hdr_dynamic_metadata_block_type4 *type4;
|
|
struct di_cta_hdr_dynamic_metadata_block_type256 *type256;
|
|
size_t length;
|
|
int type;
|
|
|
|
base = &priv->base;
|
|
type1 = &priv->type1;
|
|
type2 = &priv->type2;
|
|
type3 = &priv->type3;
|
|
type4 = &priv->type4;
|
|
type256 = &priv->type256;
|
|
|
|
if (size < 3) {
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Empty Data Block with length %u.",
|
|
size);
|
|
return false;
|
|
}
|
|
|
|
while (size >= 3) {
|
|
length = data[0];
|
|
|
|
if (size < length + 1) {
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Length of type bigger than block size.");
|
|
return false;
|
|
}
|
|
|
|
if (length < 2) {
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type has wrong length.");
|
|
return false;
|
|
}
|
|
|
|
type = (data[2] << 8) | data[1];
|
|
switch (type) {
|
|
case 0x0001:
|
|
if (length < 3) {
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 missing Support Flags.");
|
|
break;
|
|
}
|
|
if (length != 3)
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 length must be 3.");
|
|
type1->type_1_hdr_metadata_version = get_bit_range(data[3], 3, 0);
|
|
base->type1 = type1;
|
|
if (get_bit_range(data[3], 7, 4) != 0)
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 support flags bits 7-4 must be 0.");
|
|
break;
|
|
case 0x0002:
|
|
if (length < 3) {
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type 2 missing Support Flags.");
|
|
break;
|
|
}
|
|
if (length != 3)
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type 2 length must be 3.");
|
|
type2->ts_103_433_spec_version = get_bit_range(data[3], 3, 0);
|
|
if (type2->ts_103_433_spec_version == 0) {
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type 2 spec version of 0 is not allowed.");
|
|
break;
|
|
}
|
|
type2->ts_103_433_1_capable = has_bit(data[3], 4);
|
|
type2->ts_103_433_2_capable = has_bit(data[3], 5);
|
|
type2->ts_103_433_3_capable = has_bit(data[3], 6);
|
|
base->type2 = type2;
|
|
if (has_bit(data[3], 7) != 0)
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type 1 support flags bit 7 must be 0.");
|
|
break;
|
|
case 0x0003:
|
|
if (length != 2)
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type 3 length must be 2.");
|
|
base->type3 = type3;
|
|
break;
|
|
case 0x0004:
|
|
if (length < 3) {
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type 4 missing Support Flags.");
|
|
break;
|
|
}
|
|
if (length != 3)
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type 4 length must be 3.");
|
|
type4->type_4_hdr_metadata_version = get_bit_range(data[3], 3, 0);
|
|
base->type4 = type4;
|
|
if (get_bit_range(data[3], 7, 4) != 0)
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type 4 support flags bits 7-4 must be 0.");
|
|
break;
|
|
case 0x0100:
|
|
if (length < 3) {
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type 256 missing Support Flags.");
|
|
break;
|
|
}
|
|
if (length != 3)
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type 256 length must be 3.");
|
|
type256->graphics_overlay_flag_version = get_bit_range(data[3], 3, 0);
|
|
base->type256 = type256;
|
|
if (get_bit_range(data[3], 7, 4) != 0)
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Type 256 support flags bits 7-4 must be 0.");
|
|
break;
|
|
default:
|
|
add_failure(cta, "HDR Dynamic Metadata Data Block: Unknown Type 0x%04x.", type);
|
|
break;
|
|
}
|
|
|
|
size -= length + 1;
|
|
data += length + 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
parse_vesa_transfer_characteristics_block(struct di_edid_cta *cta,
|
|
struct di_cta_vesa_transfer_characteristics *tf,
|
|
const uint8_t *data, size_t size)
|
|
{
|
|
size_t i;
|
|
|
|
if (size != 7 && size != 15 && size != 31) {
|
|
add_failure(cta, "Invalid length %u.", size);
|
|
return false;
|
|
}
|
|
|
|
tf->points_len = (uint8_t) size + 1;
|
|
tf->usage = get_bit_range(data[0], 7, 6);
|
|
|
|
tf->points[0] = get_bit_range(data[0], 5, 0) / 1023.0f;
|
|
for (i = 1; i < size; i++)
|
|
tf->points[i] = tf->points[i - 1] + data[i] / 1023.0f;
|
|
tf->points[i] = 1.0f;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
parse_ycbcr420_cap_map(struct di_edid_cta *cta,
|
|
struct di_cta_ycbcr420_cap_map *ycbcr420_cap_map,
|
|
const uint8_t *data, size_t size)
|
|
{
|
|
if (size == 0) {
|
|
ycbcr420_cap_map->all = true;
|
|
return;
|
|
}
|
|
|
|
assert(size <= sizeof(ycbcr420_cap_map->svd_bitmap));
|
|
memcpy(ycbcr420_cap_map->svd_bitmap, data, size);
|
|
}
|
|
|
|
static struct di_cta_infoframe_descriptor *
|
|
parse_infoframe(struct di_edid_cta *cta, uint8_t type,
|
|
const uint8_t *data, size_t size)
|
|
{
|
|
struct di_cta_infoframe_descriptor infoframe = {0};
|
|
struct di_cta_infoframe_descriptor *ifp;
|
|
|
|
if (type >= 8 && type <= 0x1f) {
|
|
add_failure(cta, "InfoFrame Data Block: Type code %u is reserved.",
|
|
type);
|
|
return NULL;
|
|
}
|
|
|
|
if (type >= 0x20) {
|
|
add_failure(cta, "InfoFrame Data Block: Type code %u is forbidden.",
|
|
type);
|
|
return NULL;
|
|
}
|
|
|
|
if (type == 1) {
|
|
/* No known vendor specific InfoFrames, yet */
|
|
return NULL;
|
|
} else {
|
|
switch (type) {
|
|
case 0x02:
|
|
infoframe.type = DI_CTA_INFOFRAME_TYPE_AUXILIARY_VIDEO_INFORMATION;
|
|
break;
|
|
case 0x03:
|
|
infoframe.type = DI_CTA_INFOFRAME_TYPE_SOURCE_PRODUCT_DESCRIPTION;
|
|
break;
|
|
case 0x04:
|
|
infoframe.type = DI_CTA_INFOFRAME_TYPE_AUDIO;
|
|
break;
|
|
case 0x05:
|
|
infoframe.type = DI_CTA_INFOFRAME_TYPE_MPEG_SOURCE;
|
|
break;
|
|
case 0x06:
|
|
infoframe.type = DI_CTA_INFOFRAME_TYPE_NTSC_VBI;
|
|
break;
|
|
case 0x07:
|
|
infoframe.type = DI_CTA_INFOFRAME_TYPE_DYNAMIC_RANGE_AND_MASTERING;
|
|
break;
|
|
default:
|
|
abort(); /* unreachable */
|
|
}
|
|
}
|
|
|
|
ifp = calloc(1, sizeof(*ifp));
|
|
if (!ifp)
|
|
return NULL;
|
|
|
|
*ifp = infoframe;
|
|
return ifp;
|
|
}
|
|
|
|
static bool
|
|
parse_infoframe_block(struct di_edid_cta *cta,
|
|
struct di_cta_infoframe_block_priv *ifb,
|
|
const uint8_t *data, size_t size)
|
|
{
|
|
size_t index = 0, length;
|
|
uint8_t type;
|
|
struct di_cta_infoframe_descriptor *infoframe;
|
|
|
|
if (size < 2) {
|
|
add_failure(cta, "InfoFrame Data Block: Empty Data Block with length %u.",
|
|
size);
|
|
return false;
|
|
}
|
|
|
|
ifb->block.num_simultaneous_vsifs = data[1] + 1;
|
|
ifb->block.infoframes = (const struct di_cta_infoframe_descriptor *const *)ifb->infoframes;
|
|
|
|
index = get_bit_range(data[0], 7, 5) + 2;
|
|
if (get_bit_range(data[0], 4, 0) != 0)
|
|
add_failure(cta, "InfoFrame Data Block: InfoFrame Processing "
|
|
"Descriptor Header bits F14-F10 shall be 0.");
|
|
|
|
while (true) {
|
|
if (index == size)
|
|
break;
|
|
if (index > size) {
|
|
add_failure(cta, "InfoFrame Data Block: Payload length exceeds block size.");
|
|
return false;
|
|
}
|
|
|
|
length = get_bit_range(data[index], 7, 5);
|
|
type = get_bit_range(data[index], 4, 0);
|
|
|
|
if (type == 0) {
|
|
add_failure(cta, "InfoFrame Data Block: Short InfoFrame Descriptor with type 0 is forbidden.");
|
|
return false;
|
|
} else if (type == 1) {
|
|
length += 4;
|
|
} else {
|
|
length += 1;
|
|
}
|
|
|
|
if (index + length > size) {
|
|
add_failure(cta, "InfoFrame Data Block: Payload length exceeds block size.");
|
|
return false;
|
|
}
|
|
|
|
infoframe = parse_infoframe(cta, type, &data[index], length);
|
|
if (infoframe) {
|
|
assert(ifb->infoframes_len < EDID_CTA_INFOFRAME_BLOCK_ENTRIES);
|
|
ifb->infoframes[ifb->infoframes_len++] = infoframe;
|
|
}
|
|
|
|
index += length;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
destroy_data_block(struct di_cta_data_block *data_block)
|
|
{
|
|
size_t i;
|
|
struct di_cta_video_block *video;
|
|
struct di_cta_audio_block *audio;
|
|
struct di_cta_infoframe_block_priv *infoframe;
|
|
|
|
switch (data_block->tag) {
|
|
case DI_CTA_DATA_BLOCK_VIDEO:
|
|
video = &data_block->video;
|
|
for (i = 0; i < video->svds_len; i++)
|
|
free(video->svds[i]);
|
|
break;
|
|
case DI_CTA_DATA_BLOCK_YCBCR420:
|
|
video = &data_block->ycbcr420;
|
|
for (i = 0; i < video->svds_len; i++)
|
|
free(video->svds[i]);
|
|
break;
|
|
case DI_CTA_DATA_BLOCK_AUDIO:
|
|
audio = &data_block->audio;
|
|
for (i = 0; i < audio->sads_len; i++)
|
|
free(audio->sads[i]);
|
|
break;
|
|
case DI_CTA_DATA_BLOCK_INFOFRAME:
|
|
infoframe = &data_block->infoframe;
|
|
for (i = 0; i < infoframe->infoframes_len; i++)
|
|
free(infoframe->infoframes[i]);
|
|
break;
|
|
default:
|
|
break; /* Nothing to do */
|
|
}
|
|
|
|
free(data_block);
|
|
}
|
|
|
|
static bool
|
|
parse_data_block(struct di_edid_cta *cta, uint8_t raw_tag, const uint8_t *data, size_t size)
|
|
{
|
|
enum di_cta_data_block_tag tag;
|
|
uint8_t extended_tag;
|
|
struct di_cta_data_block *data_block;
|
|
|
|
data_block = calloc(1, sizeof(*data_block));
|
|
if (!data_block) {
|
|
return false;
|
|
}
|
|
|
|
switch (raw_tag) {
|
|
case 1:
|
|
tag = DI_CTA_DATA_BLOCK_AUDIO;
|
|
if (!parse_audio_block(cta, &data_block->audio, data, size))
|
|
goto error;
|
|
break;
|
|
case 2:
|
|
tag = DI_CTA_DATA_BLOCK_VIDEO;
|
|
if (!parse_video_block(cta, &data_block->video, data, size))
|
|
goto error;
|
|
break;
|
|
case 3:
|
|
/* Vendor-Specific Data Block */
|
|
goto skip;
|
|
case 4:
|
|
tag = DI_CTA_DATA_BLOCK_SPEAKER_ALLOC;
|
|
if (!parse_speaker_alloc_block(cta, &data_block->speaker_alloc,
|
|
data, size))
|
|
goto error;
|
|
break;
|
|
case 5:
|
|
tag = DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC;
|
|
if (!parse_vesa_transfer_characteristics_block(cta,
|
|
&data_block->vesa_transfer_characteristics,
|
|
data, size))
|
|
goto error;
|
|
break;
|
|
case 6:
|
|
tag = DI_CTA_DATA_BLOCK_VIDEO_FORMAT;
|
|
break;
|
|
case 7:
|
|
/* Use Extended Tag */
|
|
if (size < 1) {
|
|
add_failure(cta, "Empty block with extended tag.");
|
|
goto skip;
|
|
}
|
|
|
|
extended_tag = data[0];
|
|
data = &data[1];
|
|
size--;
|
|
|
|
switch (extended_tag) {
|
|
case 0:
|
|
tag = DI_CTA_DATA_BLOCK_VIDEO_CAP;
|
|
if (!parse_video_cap_block(cta, &data_block->video_cap,
|
|
data, size))
|
|
goto skip;
|
|
break;
|
|
case 2:
|
|
tag = DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE;
|
|
if (!parse_vesa_dddb(cta, &data_block->vesa_dddb,
|
|
data, size))
|
|
goto skip;
|
|
break;
|
|
case 5:
|
|
tag = DI_CTA_DATA_BLOCK_COLORIMETRY;
|
|
if (!parse_colorimetry_block(cta,
|
|
&data_block->colorimetry,
|
|
data, size))
|
|
goto skip;
|
|
break;
|
|
case 6:
|
|
tag = DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA;
|
|
if (!parse_hdr_static_metadata_block(cta,
|
|
&data_block->hdr_static_metadata,
|
|
data, size))
|
|
goto skip;
|
|
break;
|
|
case 7:
|
|
tag = DI_CTA_DATA_BLOCK_HDR_DYNAMIC_METADATA;
|
|
if (!parse_hdr_dynamic_metadata_block(cta,
|
|
&data_block->hdr_dynamic_metadata,
|
|
data, size))
|
|
goto skip;
|
|
break;
|
|
case 8:
|
|
tag = DI_CTA_DATA_BLOCK_NATIVE_VIDEO_RESOLUTION;
|
|
break;
|
|
case 13:
|
|
tag = DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF;
|
|
break;
|
|
case 14:
|
|
tag = DI_CTA_DATA_BLOCK_YCBCR420;
|
|
if (!parse_ycbcr420_block(cta,
|
|
&data_block->ycbcr420,
|
|
data, size))
|
|
goto skip;
|
|
break;
|
|
case 15:
|
|
tag = DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP;
|
|
parse_ycbcr420_cap_map(cta,
|
|
&data_block->ycbcr420_cap_map,
|
|
data, size);
|
|
break;
|
|
case 18:
|
|
tag = DI_CTA_DATA_BLOCK_HDMI_AUDIO;
|
|
break;
|
|
case 19:
|
|
tag = DI_CTA_DATA_BLOCK_ROOM_CONFIG;
|
|
break;
|
|
case 20:
|
|
tag = DI_CTA_DATA_BLOCK_SPEAKER_LOCATION;
|
|
break;
|
|
case 32:
|
|
tag = DI_CTA_DATA_BLOCK_INFOFRAME;
|
|
if (!parse_infoframe_block(cta,
|
|
&data_block->infoframe,
|
|
data, size))
|
|
goto skip;
|
|
break;
|
|
case 34:
|
|
tag = DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VII;
|
|
break;
|
|
case 35:
|
|
tag = DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_VIII;
|
|
break;
|
|
case 42:
|
|
tag = DI_CTA_DATA_BLOCK_DISPLAYID_VIDEO_TIMING_X;
|
|
break;
|
|
case 120:
|
|
tag = DI_CTA_DATA_BLOCK_HDMI_EDID_EXT_OVERRIDE;
|
|
break;
|
|
case 121:
|
|
tag = DI_CTA_DATA_BLOCK_HDMI_SINK_CAP;
|
|
break;
|
|
case 1: /* Vendor-Specific Video Data Block */
|
|
case 17: /* Vendor-Specific Audio Data Block */
|
|
goto skip;
|
|
default:
|
|
/* Reserved */
|
|
add_failure_until(cta, 3,
|
|
"Unknown CTA-861 Data Block (extended tag 0x"PRIx8", length %zu).",
|
|
extended_tag, size);
|
|
goto skip;
|
|
}
|
|
break;
|
|
default:
|
|
/* Reserved */
|
|
add_failure_until(cta, 3, "Unknown CTA-861 Data Block (tag 0x"PRIx8", length %zu).",
|
|
raw_tag, size);
|
|
goto skip;
|
|
}
|
|
|
|
data_block->tag = tag;
|
|
assert(cta->data_blocks_len < EDID_CTA_MAX_DATA_BLOCKS);
|
|
cta->data_blocks[cta->data_blocks_len++] = data_block;
|
|
return true;
|
|
|
|
skip:
|
|
free(data_block);
|
|
return true;
|
|
|
|
error:
|
|
destroy_data_block(data_block);
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
_di_edid_cta_parse(struct di_edid_cta *cta, const uint8_t *data, size_t size,
|
|
struct di_logger *logger)
|
|
{
|
|
uint8_t flags, dtd_start;
|
|
uint8_t data_block_header, data_block_tag, data_block_size;
|
|
size_t i;
|
|
struct di_edid_detailed_timing_def_priv *detailed_timing_def;
|
|
|
|
assert(size == 128);
|
|
assert(data[0] == 0x02);
|
|
|
|
cta->logger = logger;
|
|
|
|
cta->revision = data[1];
|
|
dtd_start = data[2];
|
|
|
|
flags = data[3];
|
|
if (cta->revision >= 2) {
|
|
cta->flags.it_underscan = has_bit(flags, 7);
|
|
cta->flags.basic_audio = has_bit(flags, 6);
|
|
cta->flags.ycc444 = has_bit(flags, 5);
|
|
cta->flags.ycc422 = has_bit(flags, 4);
|
|
cta->flags.native_dtds = get_bit_range(flags, 3, 0);
|
|
} else if (flags != 0) {
|
|
/* Reserved */
|
|
add_failure(cta, "Non-zero byte 3.");
|
|
}
|
|
|
|
if (dtd_start == 0) {
|
|
return true;
|
|
} else if (dtd_start < CTA_HEADER_SIZE || dtd_start >= size) {
|
|
errno = EINVAL;
|
|
return false;
|
|
}
|
|
|
|
i = CTA_HEADER_SIZE;
|
|
while (i < dtd_start) {
|
|
data_block_header = data[i];
|
|
data_block_tag = get_bit_range(data_block_header, 7, 5);
|
|
data_block_size = get_bit_range(data_block_header, 4, 0);
|
|
|
|
if (i + 1 + data_block_size > dtd_start) {
|
|
add_failure(cta, "Data Block at offset %zu overlaps Detailed Timing "
|
|
"Definitions. Skipping all further Data Blocks.", i);
|
|
break;
|
|
}
|
|
|
|
if (!parse_data_block(cta, data_block_tag,
|
|
&data[i + 1], data_block_size)) {
|
|
_di_edid_cta_finish(cta);
|
|
return false;
|
|
}
|
|
|
|
i += 1 + data_block_size;
|
|
}
|
|
|
|
if (i != dtd_start)
|
|
add_failure(cta, "Offset is %"PRIu8", but should be %zu.",
|
|
dtd_start, i);
|
|
|
|
for (i = dtd_start; i + EDID_BYTE_DESCRIPTOR_SIZE <= CTA_DTD_END;
|
|
i += EDID_BYTE_DESCRIPTOR_SIZE) {
|
|
if (data[i] == 0)
|
|
break;
|
|
|
|
detailed_timing_def = _di_edid_parse_detailed_timing_def(&data[i]);
|
|
if (!detailed_timing_def) {
|
|
_di_edid_cta_finish(cta);
|
|
return false;
|
|
}
|
|
assert(cta->detailed_timing_defs_len < EDID_CTA_MAX_DETAILED_TIMING_DEFS);
|
|
cta->detailed_timing_defs[cta->detailed_timing_defs_len++] = detailed_timing_def;
|
|
}
|
|
|
|
/* All padding bytes after the last DTD must be zero */
|
|
while (i < CTA_DTD_END) {
|
|
if (data[i] != 0) {
|
|
add_failure(cta, "Padding: Contains non-zero bytes.");
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
cta->logger = NULL;
|
|
return true;
|
|
}
|
|
|
|
void
|
|
_di_edid_cta_finish(struct di_edid_cta *cta)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < cta->data_blocks_len; i++) {
|
|
destroy_data_block(cta->data_blocks[i]);
|
|
}
|
|
|
|
for (i = 0; i < cta->detailed_timing_defs_len; i++) {
|
|
free(cta->detailed_timing_defs[i]);
|
|
}
|
|
}
|
|
|
|
int
|
|
di_edid_cta_get_revision(const struct di_edid_cta *cta)
|
|
{
|
|
return cta->revision;
|
|
}
|
|
|
|
const struct di_edid_cta_flags *
|
|
di_edid_cta_get_flags(const struct di_edid_cta *cta)
|
|
{
|
|
return &cta->flags;
|
|
}
|
|
|
|
const struct di_cta_data_block *const *
|
|
di_edid_cta_get_data_blocks(const struct di_edid_cta *cta)
|
|
{
|
|
return (const struct di_cta_data_block *const *) cta->data_blocks;
|
|
}
|
|
|
|
enum di_cta_data_block_tag
|
|
di_cta_data_block_get_tag(const struct di_cta_data_block *block)
|
|
{
|
|
return block->tag;
|
|
}
|
|
|
|
const struct di_cta_svd *const *
|
|
di_cta_data_block_get_svds(const struct di_cta_data_block *block)
|
|
{
|
|
if (block->tag != DI_CTA_DATA_BLOCK_VIDEO) {
|
|
return NULL;
|
|
}
|
|
return (const struct di_cta_svd *const *) block->video.svds;
|
|
}
|
|
|
|
const struct di_cta_svd *const *
|
|
di_cta_data_block_get_ycbcr420_svds(const struct di_cta_data_block *block)
|
|
{
|
|
if (block->tag != DI_CTA_DATA_BLOCK_YCBCR420) {
|
|
return NULL;
|
|
}
|
|
return (const struct di_cta_svd *const *) block->ycbcr420.svds;
|
|
}
|
|
|
|
const struct di_cta_sad *const *
|
|
di_cta_data_block_get_sads(const struct di_cta_data_block *block)
|
|
{
|
|
if (block->tag != DI_CTA_DATA_BLOCK_AUDIO) {
|
|
return NULL;
|
|
}
|
|
return (const struct di_cta_sad *const *) block->audio.sads;
|
|
}
|
|
|
|
const struct di_cta_speaker_alloc_block *
|
|
di_cta_data_block_get_speaker_alloc(const struct di_cta_data_block *block)
|
|
{
|
|
if (block->tag != DI_CTA_DATA_BLOCK_SPEAKER_ALLOC) {
|
|
return NULL;
|
|
}
|
|
return &block->speaker_alloc;
|
|
}
|
|
|
|
const struct di_cta_colorimetry_block *
|
|
di_cta_data_block_get_colorimetry(const struct di_cta_data_block *block)
|
|
{
|
|
if (block->tag != DI_CTA_DATA_BLOCK_COLORIMETRY) {
|
|
return NULL;
|
|
}
|
|
return &block->colorimetry;
|
|
}
|
|
|
|
const struct di_cta_hdr_static_metadata_block *
|
|
di_cta_data_block_get_hdr_static_metadata(const struct di_cta_data_block *block)
|
|
{
|
|
if (block->tag != DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA) {
|
|
return NULL;
|
|
}
|
|
return &block->hdr_static_metadata.base;
|
|
}
|
|
|
|
const struct di_cta_hdr_dynamic_metadata_block *
|
|
di_cta_data_block_get_hdr_dynamic_metadata(const struct di_cta_data_block *block)
|
|
{
|
|
if (block->tag != DI_CTA_DATA_BLOCK_HDR_DYNAMIC_METADATA) {
|
|
return NULL;
|
|
}
|
|
return &block->hdr_dynamic_metadata.base;
|
|
}
|
|
|
|
const struct di_cta_video_cap_block *
|
|
di_cta_data_block_get_video_cap(const struct di_cta_data_block *block)
|
|
{
|
|
if (block->tag != DI_CTA_DATA_BLOCK_VIDEO_CAP) {
|
|
return NULL;
|
|
}
|
|
return &block->video_cap;
|
|
}
|
|
|
|
const struct di_cta_vesa_dddb *
|
|
di_cta_data_block_get_vesa_dddb(const struct di_cta_data_block *block)
|
|
{
|
|
if (block->tag != DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE) {
|
|
return NULL;
|
|
}
|
|
return &block->vesa_dddb;
|
|
}
|
|
|
|
bool
|
|
di_cta_ycbcr420_cap_map_supported(const struct di_cta_ycbcr420_cap_map *cap_map,
|
|
size_t svd_index)
|
|
{
|
|
size_t byte, bit;
|
|
|
|
if (cap_map->all)
|
|
return true;
|
|
|
|
byte = svd_index / 8;
|
|
bit = svd_index % 8;
|
|
|
|
if (byte >= EDID_CTA_MAX_YCBCR420_CAP_MAP_BLOCK_ENTRIES)
|
|
return false;
|
|
|
|
return cap_map->svd_bitmap[byte] & (1 << bit);
|
|
}
|
|
|
|
const struct di_cta_ycbcr420_cap_map *
|
|
di_cta_data_block_get_ycbcr420_cap_map(const struct di_cta_data_block *block)
|
|
{
|
|
if (block->tag != DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP) {
|
|
return NULL;
|
|
}
|
|
return &block->ycbcr420_cap_map;
|
|
}
|
|
|
|
const struct di_cta_infoframe_block *
|
|
di_cta_data_block_get_infoframe(const struct di_cta_data_block *block)
|
|
{
|
|
if (block->tag != DI_CTA_DATA_BLOCK_INFOFRAME) {
|
|
return NULL;
|
|
}
|
|
return &block->infoframe.block;
|
|
}
|
|
|
|
const struct di_edid_detailed_timing_def *const *
|
|
di_edid_cta_get_detailed_timing_defs(const struct di_edid_cta *cta)
|
|
{
|
|
return (const struct di_edid_detailed_timing_def *const *) cta->detailed_timing_defs;
|
|
}
|
|
|
|
const struct di_cta_vesa_transfer_characteristics *
|
|
di_cta_data_block_get_vesa_transfer_characteristics(const struct di_cta_data_block *block)
|
|
{
|
|
if (block->tag != DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC) {
|
|
return NULL;
|
|
}
|
|
return &block->vesa_transfer_characteristics;
|
|
}
|