mirror of
https://gitlab.freedesktop.org/emersion/libdisplay-info.git
synced 2024-12-26 21:59:15 +01:00
4698004913
Signed-off-by: Sebastian Wick <sebastian.wick@redhat.com>
1546 lines
43 KiB
C
1546 lines
43 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 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;
|
|
|
|
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;
|
|
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;
|
|
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) {
|
|
_di_edid_cta_finish(cta);
|
|
errno = EINVAL;
|
|
return false;
|
|
}
|
|
|
|
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_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;
|
|
}
|