2022-06-11 11:38:49 +02:00
# include <assert.h>
2022-06-11 12:19:27 +02:00
# include <errno.h>
2022-08-02 12:48:33 +02:00
# include <inttypes.h>
2022-08-03 10:37:07 +02:00
# include <math.h>
2022-06-11 14:06:23 +02:00
# include <stdlib.h>
2022-08-02 18:56:55 +02:00
# include <string.h>
2022-06-11 11:38:49 +02:00
2022-06-11 12:19:27 +02:00
# include "bits.h"
2022-06-11 11:38:49 +02:00
# include "cta.h"
2022-08-02 12:48:33 +02:00
# include "log.h"
2022-08-02 18:56:55 +02:00
# include "edid.h"
2022-06-11 11:38:49 +02:00
2022-06-11 14:06:23 +02:00
/**
* Number of bytes in the CTA header ( tag + revision + DTD offset + flags ) .
*/
# define CTA_HEADER_SIZE 4
2022-08-02 18:56:55 +02:00
/**
* Exclusive upper bound for the detailed timing definitions in the CTA block .
*/
# define CTA_DTD_END 127
2022-09-06 19:21:50 +02:00
/**
* Number of bytes in a CTA short audio descriptor .
*/
# define CTA_SAD_SIZE 3
2022-06-11 14:06:23 +02:00
2022-08-02 12:48:33 +02:00
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 ) ;
}
2022-08-29 15:17:11 +02:00
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 ;
uint8_t raw ;
struct di_cta_svd svd , * svd_ptr ;
if ( size = = 0 )
add_failure ( cta , " Video Data Block: Empty Data Block " ) ;
for ( i = 0 ; i < size ; i + + ) {
raw = data [ i ] ;
if ( raw = = 0 | | raw = = 128 | | raw > = 254 ) {
/* Reserved */
add_failure_until ( cta , 3 ,
" Video Data Block: Unknown VIC % " PRIu8 " . " ,
raw ) ;
continue ;
} 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 false ;
* svd_ptr = svd ;
2022-09-06 11:19:48 +02:00
assert ( video - > svds_len < EDID_CTA_MAX_VIDEO_BLOCK_ENTRIES ) ;
2022-08-29 15:17:11 +02:00
video - > svds [ video - > svds_len + + ] = svd_ptr ;
}
return true ;
}
2022-09-06 19:21:50 +02:00
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 ;
}
2022-09-01 13:43:22 +02:00
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 ;
}
2022-07-27 17:51:53 +02:00
static bool
2022-08-02 12:48:33 +02:00
parse_colorimetry_block ( struct di_edid_cta * cta ,
struct di_cta_colorimetry_block * colorimetry ,
2022-07-27 17:51:53 +02:00
const uint8_t * data , size_t size )
{
if ( size < 2 ) {
2022-08-02 12:48:33 +02:00
add_failure ( cta , " Colorimetry Data Block: Empty Data Block with length %u. " ,
size ) ;
2022-07-27 17:51:53 +02:00
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 ) ;
2022-08-02 12:48:33 +02:00
if ( get_bit_range ( data [ 1 ] , 5 , 0 ) ! = 0 )
add_failure_until ( cta , 3 ,
" Colorimetry Data Block: Reserved bits MD0-MD3 must be 0. " ) ;
2022-07-27 17:51:53 +02:00
return true ;
}
2022-08-03 10:37:07 +02:00
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 ;
}
2022-08-30 15:10:40 +02:00
metadata - > base . eotfs = & metadata - > eotfs ;
metadata - > base . descriptors = & metadata - > descriptors ;
2022-08-03 10:37:07 +02:00
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 ;
}
2022-09-27 02:26:22 +02:00
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 ;
}
2022-08-29 15:17:11 +02:00
static void
destroy_data_block ( struct di_cta_data_block * data_block )
{
size_t i ;
struct di_cta_video_block * video ;
2022-09-06 19:21:50 +02:00
struct di_cta_audio_block * audio ;
2022-08-29 15:17:11 +02:00
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 ;
2022-09-06 19:21:50 +02:00
case DI_CTA_DATA_BLOCK_AUDIO :
audio = & data_block - > audio ;
for ( i = 0 ; i < audio - > sads_len ; i + + )
free ( audio - > sads [ i ] ) ;
break ;
2022-08-29 15:17:11 +02:00
default :
break ; /* Nothing to do */
}
free ( data_block ) ;
}
2022-06-11 14:06:23 +02:00
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 ;
2022-07-27 17:51:53 +02:00
data_block = calloc ( 1 , sizeof ( * data_block ) ) ;
if ( ! data_block ) {
return false ;
}
2022-06-11 14:06:23 +02:00
switch ( raw_tag ) {
case 1 :
tag = DI_CTA_DATA_BLOCK_AUDIO ;
2022-09-06 19:21:50 +02:00
if ( ! parse_audio_block ( cta , & data_block - > audio , data , size ) )
goto error ;
2022-06-11 14:06:23 +02:00
break ;
case 2 :
tag = DI_CTA_DATA_BLOCK_VIDEO ;
2022-08-29 15:17:11 +02:00
if ( ! parse_video_block ( cta , & data_block - > video , data , size ) )
goto error ;
2022-06-11 14:06:23 +02:00
break ;
case 3 :
/* Vendor-Specific Data Block */
2022-08-02 12:48:33 +02:00
goto skip ;
2022-06-11 14:06:23 +02:00
case 4 :
tag = DI_CTA_DATA_BLOCK_SPEAKER_ALLOC ;
break ;
case 5 :
tag = DI_CTA_DATA_BLOCK_VESA_DISPLAY_TRANSFER_CHARACTERISTIC ;
2022-09-27 02:26:22 +02:00
if ( ! parse_vesa_transfer_characteristics_block ( cta ,
& data_block - > vesa_transfer_characteristics ,
data , size ) )
goto error ;
2022-06-11 14:06:23 +02:00
break ;
case 7 :
/* Use Extended Tag */
if ( size < 1 ) {
2022-08-02 12:48:33 +02:00
add_failure ( cta , " Empty block with extended tag. " ) ;
goto skip ;
2022-06-11 14:06:23 +02:00
}
extended_tag = data [ 0 ] ;
data = & data [ 1 ] ;
size - - ;
switch ( extended_tag ) {
case 0 :
tag = DI_CTA_DATA_BLOCK_VIDEO_CAP ;
2022-09-01 13:43:22 +02:00
if ( ! parse_video_cap_block ( cta , & data_block - > video_cap ,
data , size ) )
goto skip ;
2022-06-11 14:06:23 +02:00
break ;
case 2 :
tag = DI_CTA_DATA_BLOCK_VESA_DISPLAY_DEVICE ;
break ;
case 5 :
tag = DI_CTA_DATA_BLOCK_COLORIMETRY ;
2022-08-02 12:48:33 +02:00
if ( ! parse_colorimetry_block ( cta ,
& data_block - > colorimetry ,
2022-07-27 17:51:53 +02:00
data , size ) )
2022-08-02 12:48:33 +02:00
goto skip ;
2022-06-11 14:06:23 +02:00
break ;
case 6 :
tag = DI_CTA_DATA_BLOCK_HDR_STATIC_METADATA ;
2022-08-03 10:37:07 +02:00
if ( ! parse_hdr_static_metadata_block ( cta ,
& data_block - > hdr_static_metadata ,
data , size ) )
goto skip ;
2022-06-11 14:06:23 +02:00
break ;
case 7 :
tag = DI_CTA_DATA_BLOCK_HDR_DYNAMIC_METADATA ;
break ;
case 13 :
tag = DI_CTA_DATA_BLOCK_VIDEO_FORMAT_PREF ;
break ;
case 14 :
tag = DI_CTA_DATA_BLOCK_YCBCR420 ;
break ;
case 15 :
tag = DI_CTA_DATA_BLOCK_YCBCR420_CAP_MAP ;
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 */
2022-08-02 12:48:33 +02:00
goto skip ;
2022-06-11 14:06:23 +02:00
default :
/* Reserved */
2022-08-02 12:48:33 +02:00
add_failure_until ( cta , 3 ,
" Unknown CTA-861 Data Block (extended tag 0x " PRIx8 " , length %zu). " ,
extended_tag , size ) ;
goto skip ;
2022-06-11 14:06:23 +02:00
}
break ;
default :
/* Reserved */
2022-08-02 12:48:33 +02:00
add_failure_until ( cta , 3 , " Unknown CTA-861 Data Block (tag 0x " PRIx8 " , length %zu). " ,
raw_tag , size ) ;
goto skip ;
2022-06-11 14:06:23 +02:00
}
data_block - > tag = tag ;
2022-09-06 11:19:48 +02:00
assert ( cta - > data_blocks_len < EDID_CTA_MAX_DATA_BLOCKS ) ;
2022-06-11 14:06:23 +02:00
cta - > data_blocks [ cta - > data_blocks_len + + ] = data_block ;
return true ;
2022-07-27 17:51:53 +02:00
2022-08-02 12:48:33 +02:00
skip :
2022-07-27 17:51:53 +02:00
free ( data_block ) ;
2022-08-02 12:48:33 +02:00
return true ;
2022-08-29 15:17:11 +02:00
error :
destroy_data_block ( data_block ) ;
return false ;
2022-06-11 14:06:23 +02:00
}
2022-06-11 11:38:49 +02:00
bool
2022-08-02 12:48:33 +02:00
_di_edid_cta_parse ( struct di_edid_cta * cta , const uint8_t * data , size_t size ,
struct di_logger * logger )
2022-06-11 11:38:49 +02:00
{
2022-06-11 14:06:23 +02:00
uint8_t flags , dtd_start ;
uint8_t data_block_header , data_block_tag , data_block_size ;
size_t i ;
2022-08-17 00:47:08 +02:00
struct di_edid_detailed_timing_def_priv * detailed_timing_def ;
2022-06-11 12:19:27 +02:00
2022-06-11 11:38:49 +02:00
assert ( size = = 128 ) ;
assert ( data [ 0 ] = = 0x02 ) ;
2022-08-02 12:48:33 +02:00
cta - > logger = logger ;
2022-06-11 11:38:49 +02:00
cta - > revision = data [ 1 ] ;
2022-06-11 14:06:23 +02:00
dtd_start = data [ 2 ] ;
2022-06-11 11:38:49 +02:00
2022-06-11 12:19:27 +02:00
flags = data [ 3 ] ;
if ( cta - > revision > = 2 ) {
2022-09-01 12:32:37 +02:00
cta - > flags . it_underscan = has_bit ( flags , 7 ) ;
2022-06-11 12:19:27 +02:00
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 */
2022-08-02 12:48:33 +02:00
add_failure ( cta , " Non-zero byte 3. " ) ;
2022-06-11 12:19:27 +02:00
}
2022-06-11 14:06:23 +02:00
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 ,
2022-08-02 12:48:33 +02:00
& data [ i + 1 ] , data_block_size ) ) {
2022-06-11 14:06:23 +02:00
_di_edid_cta_finish ( cta ) ;
return false ;
}
i + = 1 + data_block_size ;
}
2022-08-02 12:48:33 +02:00
if ( i ! = dtd_start )
add_failure ( cta , " Offset is % " PRIu8 " , but should be %zu. " ,
dtd_start , i ) ;
2022-06-11 14:06:23 +02:00
2022-08-02 18:56:55 +02:00
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 ;
}
2022-09-06 11:19:48 +02:00
assert ( cta - > detailed_timing_defs_len < EDID_CTA_MAX_DETAILED_TIMING_DEFS ) ;
2022-08-02 18:56:55 +02:00
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 + + ;
}
2022-08-02 12:48:33 +02:00
cta - > logger = NULL ;
2022-06-11 11:38:49 +02:00
return true ;
}
2022-06-11 14:06:23 +02:00
void
_di_edid_cta_finish ( struct di_edid_cta * cta )
{
size_t i ;
for ( i = 0 ; i < cta - > data_blocks_len ; i + + ) {
2022-08-29 15:17:11 +02:00
destroy_data_block ( cta - > data_blocks [ i ] ) ;
2022-06-11 14:06:23 +02:00
}
2022-08-02 18:56:55 +02:00
for ( i = 0 ; i < cta - > detailed_timing_defs_len ; i + + ) {
free ( cta - > detailed_timing_defs [ i ] ) ;
}
2022-06-11 14:06:23 +02:00
}
2022-06-11 11:38:49 +02:00
int
di_edid_cta_get_revision ( const struct di_edid_cta * cta )
{
return cta - > revision ;
}
2022-06-11 12:19:27 +02:00
const struct di_edid_cta_flags *
di_edid_cta_get_flags ( const struct di_edid_cta * cta )
{
return & cta - > flags ;
}
2022-06-11 14:06:23 +02:00
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 ;
}
2022-07-27 17:51:53 +02:00
2022-08-29 15:17:11 +02:00
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 ;
}
2022-09-06 19:21:50 +02:00
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 ;
}
2022-07-27 17:51:53 +02:00
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 ;
}
2022-08-02 18:56:55 +02:00
2022-08-03 10:37:07 +02:00
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 ;
}
2022-09-01 13:43:22 +02:00
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 ;
}
2022-08-02 18:56:55 +02:00
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 ;
}
2022-09-27 02:26:22 +02:00
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 ;
}