更新UCD-API库及文档

This commit is contained in:
xinzhu.yin
2026-07-02 17:16:18 +08:00
parent a500751d85
commit 9fa811a9eb
290 changed files with 9558 additions and 2306 deletions

View File

@@ -1,4 +1,5 @@
import copy
import math
import re
import threading
import time
@@ -17,22 +18,19 @@ from UniTAP.utils.function_wrapper import function_scheduler
from UniTAP.libs.lib_dscl.dscl import DSC_TOOLS_FOLDER, DSCL_ExtractPPSFromData, DSCL_Encode, DSCL_Decode
from UniTAP.libs.lib_dscl.dscl_utils import calculate_slice_size, dscl_image_to_dsc_vf
from UniTAP.libs.lib_pdl.pdl import generate_pattern_as_vf, PatternType
from UniTAP.libs.lib_tsi.tsi_types import TSI_MEMORY_WRITE_W, TSI_MEMORY_BLOCK_INDEX, TSI_MEMORY_LAYOUT, \
TSI_DP20_SINKCTS_SUPPORT_444CRC, TSI_DP14_SINKCTS_SUPPORT_444CRC
from UniTAP.libs.lib_tsi.tsi_private_types import TSI_DSC_MEMORY_BLOCK, TSI_DSC_DATA_SIZE, \
TSI_DSC_TX_CRC, TSI_DPRX_DSC_TEST_CRC, TSI_SELECT_SUITE
from UniTAP.libs.lib_tsi.tsi import TSIX_TS_SetConfigItem, TSIX_TS_GetConfigItem
import UniTAP.libs.lib_tsi.tsi as tsi
from ctypes import c_uint64, c_uint32
from UniTAP.libs.lib_tsi import TSI_OPF_RETURN_CODE_ABORT, TSI_OPF_RETURN_CODE_PASS, \
TSI_OPF_RETURN_CODE_PROCEED, TSI_OPF_RETURN_CODE_FAIL, TSI_OPF_RETURN_CODE_AUTO_CLOSED
import UniTAP.libs.lib_tsi.tsi_private_types as p_ci
import UniTAP.libs.lib_tsi.tsi_types as ci
class OPFDialogAnswer(IntEnum):
ABORT = TSI_OPF_RETURN_CODE_ABORT
PASS = TSI_OPF_RETURN_CODE_PASS
PROCEED = TSI_OPF_RETURN_CODE_PROCEED
FAIL = TSI_OPF_RETURN_CODE_FAIL
NOTHING = TSI_OPF_RETURN_CODE_AUTO_CLOSED
ABORT = ci.TSI_OPF_RETURN_CODE_ABORT
PASS = ci.TSI_OPF_RETURN_CODE_PASS
PROCEED = ci.TSI_OPF_RETURN_CODE_PROCEED
FAIL = ci.TSI_OPF_RETURN_CODE_FAIL
NOTHING = ci.TSI_OPF_RETURN_CODE_AUTO_CLOSED
class OPFFunctions:
@@ -113,7 +111,7 @@ class OPFFunctions:
def check_video(dev_rx) -> bool:
def is_video_available(_dev_rx):
return _dev_rx.link.status.stream(0).crc != [0, 0, 0] and \
_dev_rx.link.status.stream(0).video_mode.timing.hactive != 0
_dev_rx.link.status.stream(0).video_mode.timing.hactive != 0
return function_scheduler(is_video_available, dev_rx, interval=2, timeout=10)
@@ -380,7 +378,16 @@ class OPFFunctions:
for i in range(iterations):
vf = capture_frame(dprx)
vf = video_frame_to_ci(vf, vf.color_info, OPFFunctions.g_vf.data_info)
if OPFFunctions.g_vf is None:
data_info = DataInfo()
data_info.packing = DataInfo.Packing.P_PACKED
data_info.component_order = DataInfo.ComponentOrder.CO_RGB
data_info.alignment = DataInfo.Alignment.A_LSB
else:
data_info = OPFFunctions.g_vf.data_info
vf = video_frame_to_ci(vf, vf.color_info, data_info)
if vf is not None and len(vf.data) > 0:
if OPFFunctions.g_vf is not None:
@@ -390,11 +397,88 @@ class OPFFunctions:
else:
if OPFFunctions.g_vf.data in vf.data:
return OPFDialogAnswer.PASS
else:
return OPFDialogAnswer.PASS
return OPFDialogAnswer.FAIL
@staticmethod
def opf_13_handler(dptx, dprx, message: str) -> OPFDialogAnswer:
return OPFFunctions.opf_pass_handler(dptx, dprx, message)
audio_mode = dptx.ag.audio_mode
dptx.ag.setup(audio_mode, UniTAP.AudioPattern.SignalPing, 2000, 100)
if not dptx.ag.apply():
return OPFDialogAnswer.FAIL
dprx.audio_capturer.start(m_sec=10000)
dprx.audio_capturer.stop()
result = dprx.audio_capturer.capture_result
sample_size_bytes = int(math.ceil(result.audio_mode.bits / 8))
samples = {}
for channel in range(result.audio_mode.channel_count):
samples[channel] = []
for frame in result.buffer:
frame: UniTAP.AudioFrameData
sample_per_channel = int(len(frame.data) / sample_size_bytes / result.audio_mode.channel_count)
for sample_index in range(sample_per_channel):
for channel in range(result.audio_mode.channel_count):
sample_offset = (sample_index * sample_size_bytes * result.audio_mode.channel_count) + (
channel * sample_size_bytes)
sample_value = int.from_bytes(frame.data[sample_offset:sample_offset + sample_size_bytes], "little",
signed=True)
samples[channel].append(sample_value)
logging.info(f"Total samples per channel 0: {len(samples[0])}")
def find_next_transition(start_pos: int, sample_list: list):
for pos in range(start_pos + 1, len(sample_list) - 1):
previous_value = sample_list[pos - 1]
current_value = sample_list[pos]
next_value = sample_list[pos + 1]
if previous_value == 0 and current_value == 0 and next_value != 0:
return "burst", pos + 1
elif previous_value != 0 and current_value == 0 and next_value == 0:
return "silence", pos + 1
return None, -1
transition_type, transition_pos = find_next_transition(0, samples[0])
if transition_type is None:
logging.warn("OPF 13: No transitions were found. Audio may be invalid.")
return OPFDialogAnswer.FAIL
#
# Cut beginning of captured data to start exactly with transition.
#
samples[0] = samples[0][transition_pos:]
chunk_info_list = []
transition_pos = 0
while (t_info := find_next_transition(transition_pos, samples[0])) != (None, -1):
chuck_size = t_info[1] - transition_pos + 1
chunk_length_ms = int(chuck_size / result.audio_mode.sample_rate * 1000)
logging.info(f"Chuck size: {chuck_size}")
logging.info(f"Chuck length: {chunk_length_ms}ms")
chunk_info_list.append((t_info[0], chuck_size, chunk_length_ms))
transition_pos = t_info[1]
valid = True
for chunk in chunk_info_list:
if chunk[2] not in range(245, 255):
valid = False
break
logging.info(f"Chunk validation: {'PASS' if valid else 'FAIL'}")
logging.info(f"Chunks: {chunk_info_list}")
return OPFDialogAnswer.PASS if valid else OPFDialogAnswer.FAIL
@staticmethod
def opf_14_handler(dptx, dprx, message: str) -> OPFDialogAnswer:
@@ -553,10 +637,10 @@ class OPFFunctions:
try:
if not OPFFunctions.check_video(dprx):
print('Video is not available.')
logging.error('Video is not available.')
return OPFDialogAnswer.ABORT
except BaseException as e:
print(e)
logging.error(e)
return OPFDialogAnswer.ABORT
return OPFDialogAnswer.PROCEED
@@ -582,16 +666,10 @@ class OPFFunctions:
def opf_19_handler(dptx, dprx, use_3tap) -> OPFDialogAnswer:
if not OPFFunctions.check_dprx(dprx):
return OPFDialogAnswer.ABORT
return OPFDialogAnswer.FAIL
# check DSC state
if not dprx.link.capabilities.link_caps_status().dsc:
return OPFDialogAnswer.ABORT
incorrect_crc = [0xFFFF, 0xFFFF, 0xFFFF, 0x1111, 0x1111, 0x1111]
def load_dsc(handle: int, values: list) -> OPFDialogAnswer:
TSIX_TS_SetConfigItem(handle, TSI_DPRX_DSC_TEST_CRC, values, data_count=6)
return OPFDialogAnswer.FAIL
# TODO: Need to change on public interface. Temporary solution.
@@ -599,140 +677,153 @@ class OPFFunctions:
__getattribute__('_MemoryManager__io'). \
__getattribute__('device_handle')
iterations = 5
for i in range(iterations):
# capture frame
try:
dprx.video_capturer.start(frames_count=1)
vf = dprx.video_capturer.capture_result.buffer[-1]
dprx.video_capturer.stop()
except BaseException:
dprx.video_capturer.stop()
return load_dsc(device_handle, incorrect_crc)
def create_reference_image(video_frame, crc_values: list):
try:
pps_info = DSCL_ExtractPPSFromData(video_frame.data)
except BaseException:
return
sampling = UICL_Sampling.Sampling_444
if pps_info.is_yuv():
if pps_info.is_420():
sampling = UICL_Sampling.Sampling_420
elif pps_info.is_422():
sampling = UICL_Sampling.Sampling_422
elif pps_info.is_simple_422():
sampling = UICL_Sampling.Sampling_422
image = UICL_Image()
parameters = UICL_ImageParameters()
parameters.Width = pps_info.width()
parameters.Height = pps_info.height()
parameters.BitsPerColor = pps_info.bpc()
parameters.Alignment = UICL_Alignment.Alignment_LSB
parameters.IsFullRange = False
if pps_info.is_yuv():
parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr
parameters.Colorimetry = UICL_Colorimetry.Colorimetry_ITU_R_BT709
parameters.Sampling = sampling
parameters.ComponentOrder = UICL_ComponentOrder.Order_YCbCr
parameters.Packing = UICL_Packing.Packing_Planar
image.Parameters = parameters
try:
image.DataSize = UICL_GetRequiredBufferSize(image)
image.DataPtr = (c_uint8 * image.DataSize)()
if use_3tap:
res = UICL_GeneratePattern_3Tap(image, 1)
else:
res = UICL_GeneratePattern(image, 1)
if res < UICL_SUCCESS:
return
except BaseException:
return
else:
parameters.Colorspace = UICL_Colorspace.Colorspace_RGB
parameters.Colorimetry = UICL_Colorimetry.Colorimetry_Unknown
parameters.Sampling = sampling
parameters.ComponentOrder = UICL_ComponentOrder.Order_RGB
parameters.Packing = UICL_Packing.Packing_Packed
image.Parameters = parameters
try:
image.DataSize = UICL_GetRequiredBufferSize(image)
image.DataPtr = (c_uint8 * image.DataSize)()
if use_3tap:
res = UICL_GeneratePattern_3Tap(image, 0)
else:
res = UICL_GeneratePattern(image, 0)
if res < UICL_SUCCESS:
return
except BaseException:
return
dsc_encoded_image = DSCL_Encode(image, pps_info)
OPFFunctions.g_vf = dscl_image_to_dsc_vf(dsc_encoded_image)
dsc_decoded_uicl_image = DSCL_Decode(dsc_encoded_image)
reference_image_crc = UICL_CRC16()
try:
res = UICL_CalculateCRC16(dsc_decoded_uicl_image, reference_image_crc)
if res < UICL_SUCCESS:
crc_values.extend([0xFFFF, 0xFFFF, 0xFFFF])
else:
crc_values.extend([reference_image_crc.R, reference_image_crc.G, reference_image_crc.B])
except BaseException:
return
def decompress_dsc(video_frame, crc_values: list):
decoded_captured_vf = decode_video_frame(video_frame)
uicl_decoded_image_2 = image_from_vf(decoded_captured_vf)
decompressed_image_crc = UICL_CRC16()
try:
res = UICL_CalculateCRC16(uicl_decoded_image_2, decompressed_image_crc)
if res < UICL_SUCCESS:
crc_values.extend([0x1111, 0x1111, 0x1111])
else:
crc_values.extend([decompressed_image_crc.R, decompressed_image_crc.G,
decompressed_image_crc.B])
except BaseException:
return
reference_image_crc_values = []
decompressed_image_crc_values = []
thread_create_reference_image = threading.Thread(target=create_reference_image,
args=(vf, reference_image_crc_values))
thread_decompressed_image = threading.Thread(target=decompress_dsc,
args=(vf, decompressed_image_crc_values))
thread_create_reference_image.start()
thread_decompressed_image.start()
thread_create_reference_image.join()
thread_decompressed_image.join()
if len(reference_image_crc_values) > 0 and len(decompressed_image_crc_values) > 0 and \
((reference_image_crc_values == decompressed_image_crc_values) or
(reference_image_crc_values != decompressed_image_crc_values and (i == iterations - 1))):
# Write crc to register
load_dsc(device_handle, [decompressed_image_crc_values[0],
decompressed_image_crc_values[1],
decompressed_image_crc_values[2],
reference_image_crc_values[0],
reference_image_crc_values[1],
reference_image_crc_values[2]])
def upload_dut_and_te_crc(handle: int, values: list) -> OPFDialogAnswer:
if tsi.TSIX_TS_SetConfigItem(handle, p_ci.TSI_DPRX_DSC_TEST_CRC, values, data_count=6) > ci.TSI_SUCCESS:
return OPFDialogAnswer.PROCEED
else:
return OPFDialogAnswer.FAIL
return load_dsc(device_handle, incorrect_crc)
def create_reference_image(video_frame, crc_value: list):
try:
pps_info = DSCL_ExtractPPSFromData(video_frame.data)
except BaseException:
return
sampling = UICL_Sampling.Sampling_444
if pps_info.is_yuv():
if pps_info.is_420():
sampling = UICL_Sampling.Sampling_420
elif pps_info.is_422():
sampling = UICL_Sampling.Sampling_422
elif pps_info.is_simple_422():
sampling = UICL_Sampling.Sampling_422
image = UICL_Image()
parameters = UICL_ImageParameters()
parameters.Width = pps_info.width()
parameters.Height = pps_info.height()
parameters.BitsPerColor = pps_info.bpc()
parameters.Alignment = UICL_Alignment.Alignment_LSB
parameters.IsFullRange = False
if pps_info.is_yuv():
parameters.Colorspace = UICL_Colorspace.Colorspace_YCbCr
parameters.Colorimetry = UICL_Colorimetry.Colorimetry_ITU_R_BT709
parameters.Sampling = sampling
parameters.ComponentOrder = UICL_ComponentOrder.Order_YCbCr
parameters.Packing = UICL_Packing.Packing_Planar
image.Parameters = parameters
try:
image.DataSize = UICL_GetRequiredBufferSize(image)
image.DataPtr = (c_uint8 * image.DataSize)()
if use_3tap:
res = UICL_GeneratePattern_3Tap(image, 1)
else:
res = UICL_GeneratePattern(image, 1)
if res < UICL_SUCCESS:
return
except BaseException:
return
else:
parameters.Colorspace = UICL_Colorspace.Colorspace_RGB
parameters.Colorimetry = UICL_Colorimetry.Colorimetry_Unknown
parameters.Sampling = sampling
parameters.ComponentOrder = UICL_ComponentOrder.Order_RGB
parameters.Packing = UICL_Packing.Packing_Packed
image.Parameters = parameters
try:
image.DataSize = UICL_GetRequiredBufferSize(image)
image.DataPtr = (c_uint8 * image.DataSize)()
if use_3tap:
res = UICL_GeneratePattern_3Tap(image, 0)
else:
res = UICL_GeneratePattern(image, 0)
if res < UICL_SUCCESS:
return
except BaseException:
return
dsc_encoded_image = DSCL_Encode(image, pps_info)
OPFFunctions.g_vf = dscl_image_to_dsc_vf(dsc_encoded_image)
dsc_decoded_uicl_image = DSCL_Decode(dsc_encoded_image)
reference_image_crc = UICL_CRC16()
try:
res = UICL_CalculateCRC16(dsc_decoded_uicl_image, reference_image_crc)
if res >= UICL_SUCCESS:
crc = [reference_image_crc.R, reference_image_crc.G, reference_image_crc.B]
logging.info(f"TE successfully generated image from DUT's PPS and calculated CRC: {crc}")
crc_value[:] = crc
else:
logging.error(f"TE failed to generate image from DUT's PPS without exception with error: {res}")
except BaseException as exc:
logging.error(f"TE failed to generated image fro DUT's PPS due an exception: {exc}")
def decompress_dsc(video_frame, crc_value: list):
decoded_captured_vf = decode_video_frame(video_frame)
uicl_decoded_image_2 = image_from_vf(decoded_captured_vf)
decompressed_image_crc = UICL_CRC16()
try:
res = UICL_CalculateCRC16(uicl_decoded_image_2, decompressed_image_crc)
if res >= UICL_SUCCESS:
crc = [decompressed_image_crc.R, decompressed_image_crc.G, decompressed_image_crc.B]
logging.info(f"DUT's DSC CRC successfully calculated: {crc}")
crc_value[:] = crc
else:
logging.error(f"DUT's DSC CRC calculation failed, without exception: {res}")
except BaseException as exc:
logging.error(f"Failed to decompress DUT's DSC image due an exception: {exc}")
reference_image_crc_values = [0x0BAD, 0x0BAD, 0x0BAD]
decompressed_image_crc_values = [0xBAD0, 0xBAD0, 0xBAD0]
max_frames_to_validate = 5
try:
dprx.video_capturer.start()
for frame_idx in range(1, max_frames_to_validate + 1):
try:
logging.info(f"Iteration {frame_idx}. Start frame capturing.")
frame = dprx.video_capturer.pop_element()
thread_create_reference_image = threading.Thread(target=create_reference_image,
args=(frame, reference_image_crc_values))
thread_decompressed_image = threading.Thread(target=decompress_dsc,
args=(frame, decompressed_image_crc_values))
thread_create_reference_image.start()
thread_decompressed_image.start()
thread_create_reference_image.join()
thread_decompressed_image.join()
if reference_image_crc_values == decompressed_image_crc_values:
logging.info(f"Frame {frame_idx} is OK.")
break
else:
te_crc_str = [f"0x{value:04X}" for value in reference_image_crc_values]
dut_crc_str = [f"0x{value:04X}" for value in decompressed_image_crc_values]
logging.info(f"Frame {frame_idx} is NOT OK. "
f"TE CRC: {te_crc_str}. "
f"DUT CRC: {dut_crc_str}.")
except BaseException as exception:
logging.error(f"Frame {frame_idx} is NOT OK. Exception: {exception}")
except BaseException as exc:
logging.error(f"Failed to process OPF 19 due an exception: {exc}")
finally:
dprx.video_capturer.stop()
logging.info(f"Capturing has been stopped.")
combined_crc_list = decompressed_image_crc_values + reference_image_crc_values
logging.info("OPF 19 finished.")
return upload_dut_and_te_crc(device_handle, combined_crc_list)
@staticmethod
def opf_20_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, col_format, col_range, col_yc, tim_std,
@@ -742,6 +833,14 @@ class OPFFunctions:
logging.info((res_x, res_y, res_frate, res_bpc, col_format, col_range, col_yc, tim_std, pattern_name))
if res_x == 0 and res_y == 0:
logging.info("Received request to disable PG.")
dptx.pg.set_pattern(pattern=UniTAP.VideoPattern.Disabled)
res = dptx.pg.apply()
logging.info(f"PG perform apply. Success: {res}.")
return UniTAP.OPFDialogAnswer.PASS if res else UniTAP.OPFDialogAnswer.FAIL
if pattern_name is not None:
if pattern_name == 'No Video':
pattern_name = VideoPattern.Disabled
@@ -812,7 +911,7 @@ class OPFFunctions:
audio_mode.bits = sample_size
audio_mode.sample_rate = sample_freq
audio_pattern_value = UniTAP.AudioPattern.SignalSawtooth if audio_pattern.find("Sawtooth") != -1 else (
UniTAP.AudioPattern.SignalSine)
UniTAP.AudioPattern.SignalSine)
dptx.ag.setup(audio_mode=audio_mode,
audio_pattern=audio_pattern_value,
@@ -823,6 +922,18 @@ class OPFFunctions:
return OPFDialogAnswer.ABORT
return OPFDialogAnswer.PROCEED
@staticmethod
def opf_22_handler(dptx, dprx, message: str) -> OPFDialogAnswer:
return OPFDialogAnswer.PROCEED
@staticmethod
def opf_23_handler(dptx, dprx, message: str) -> OPFDialogAnswer:
return OPFDialogAnswer.PROCEED
@staticmethod
def opf_24_handler(dptx, dprx, message: str) -> OPFDialogAnswer:
return OPFDialogAnswer.PROCEED
@staticmethod
def opf_101_handler(dptx, dprx, message: str) -> OPFDialogAnswer:
if not OPFFunctions.check_dprx(dprx):
@@ -834,7 +945,7 @@ class OPFFunctions:
return UniTAP.OPFDialogAnswer.PASS if len(
dprx.audio_capturer.capture_result.buffer) > 0 else UniTAP.OPFDialogAnswer.FAIL
except BaseException as e:
print(f"Cannot capture audio. Error: {e}")
logging.error(f"Cannot capture audio. Error: {e}")
return UniTAP.OPFDialogAnswer.FAIL
@staticmethod
@@ -894,7 +1005,7 @@ class OPFFunctions:
if len(vf.data) > 0:
return OPFDialogAnswer.PASS
return OPFDialogAnswer.FAIL
return OPFDialogAnswer.FAIL
@staticmethod
def opf_103_handler(dptx, dprx, width, height, rate, color_format, is_block_prediction_enabled, bpc,
@@ -926,7 +1037,7 @@ class OPFFunctions:
OPFFunctions.g_h_slice_size = h_slice_size
OPFFunctions.g_v_slice_size = v_slice_size
res, suite_id = TSIX_TS_GetConfigItem(device_handle, TSI_SELECT_SUITE, c_uint32)
res, suite_id = tsi.TSIX_TS_GetConfigItem(device_handle, p_ci.TSI_SELECT_SUITE, c_uint32)
if res > 0:
suite_id = TestGroupId(suite_id)
else:
@@ -936,10 +1047,12 @@ class OPFFunctions:
try:
if suite_id in [TestGroupId.DP_TX_LL_CTS, TestGroupId.DP_TX_LL_CTS_DSC,
TestGroupId.DP_TX_DISPLAYID, TestGroupId.DP_TX_ADAPTIVESYNC]:
res, is_simple_as_444 = TSIX_TS_GetConfigItem(device_handle, TSI_DP14_SINKCTS_SUPPORT_444CRC, c_uint32)
res, is_simple_as_444 = tsi.TSIX_TS_GetConfigItem(device_handle, ci.TSI_DP14_SINKCTS_SUPPORT_444CRC,
c_uint32)
elif suite_id in [TestGroupId.DP_2_1_TX_LL_CTS, TestGroupId.DP_2_1_TX_DSC_CTS,
TestGroupId.DP_2_1_TX_DISPAYID, TestGroupId.DP_2_1_TX_ADAPTIVESYNC]:
res, is_simple_as_444 = TSIX_TS_GetConfigItem(device_handle, TSI_DP20_SINKCTS_SUPPORT_444CRC, c_uint32)
res, is_simple_as_444 = tsi.TSIX_TS_GetConfigItem(device_handle, ci.TSI_DP20_SINKCTS_SUPPORT_444CRC,
c_uint32)
except BaseException as e:
pass
@@ -964,18 +1077,18 @@ class OPFFunctions:
OPFFunctions.g_vm = copy.deepcopy(encoded_video_frame)
TSIX_TS_SetConfigItem(device_handle, TSI_MEMORY_LAYOUT, len(encoded_video_frame.data), c_uint64)
TSIX_TS_SetConfigItem(device_handle, TSI_MEMORY_BLOCK_INDEX, 0, data_size=4)
TSIX_TS_SetConfigItem(device_handle, TSI_MEMORY_WRITE_W, bytearray(encoded_video_frame.data),
data_type=c_uint8,
data_count=len(encoded_video_frame.data))
TSIX_TS_SetConfigItem(device_handle, TSI_DSC_MEMORY_BLOCK, 0)
TSIX_TS_SetConfigItem(device_handle, TSI_DSC_DATA_SIZE, len(encoded_video_frame.data))
tsi.TSIX_TS_SetConfigItem(device_handle, ci.TSI_MEMORY_LAYOUT, len(encoded_video_frame.data), c_uint64)
tsi.TSIX_TS_SetConfigItem(device_handle, ci.TSI_MEMORY_BLOCK_INDEX, 0, data_size=4)
tsi.TSIX_TS_SetConfigItem(device_handle, ci.TSI_MEMORY_WRITE_W, bytearray(encoded_video_frame.data),
data_type=c_uint8,
data_count=len(encoded_video_frame.data))
tsi.TSIX_TS_SetConfigItem(device_handle, p_ci.TSI_DSC_MEMORY_BLOCK, 0)
tsi.TSIX_TS_SetConfigItem(device_handle, p_ci.TSI_DSC_DATA_SIZE, len(encoded_video_frame.data))
decoded_vf = decode_video_frame(encoded_video_frame)
crc0, crc1, crc2 = calculate_crc(decoded_vf)
TSIX_TS_SetConfigItem(device_handle, TSI_DSC_TX_CRC, [crc0, crc1, crc2], data_count=3)
tsi.TSIX_TS_SetConfigItem(device_handle, p_ci.TSI_DSC_TX_CRC, [crc0, crc1, crc2], data_count=3)
return OPFDialogAnswer.PROCEED
@@ -1020,14 +1133,17 @@ class OPFFunctions:
vf.data[pps_size + slice_size:slice_size * 2]:
return OPFDialogAnswer.PASS
else:
print(f"Frame {i + 1}: Image looks good and distortion free for slice #2. It is wrong.")
logging.info(
f"Frame {i + 1}: Image looks good and distortion free for slice #2. It is wrong.")
else:
print(
logging.info(
f"Frame {i + 1}: Image does not look good and distortion free for slice #4. It is wrong.")
else:
print(f"Frame {i + 1}: Image does not look good and distortion free for slice #3. It is wrong.")
logging.info(
f"Frame {i + 1}: Image does not look good and distortion free for slice #3. It is wrong.")
else:
print(f"Frame {i + 1}: Image does not look good and distortion free for slice #1. It is wrong.")
logging.info(
f"Frame {i + 1}: Image does not look good and distortion free for slice #1. It is wrong.")
return OPFDialogAnswer.FAIL
@@ -1043,6 +1159,66 @@ class OPFFunctions:
def opf_107_handler(dptx, dprx, message: str) -> OPFDialogAnswer:
return OPFFunctions.opf_pass_handler(dptx, dprx, message)
@staticmethod
def opf_108_handler(dptx, dprx, *args) -> OPFDialogAnswer:
if not OPFFunctions.check_dprx(dprx):
return OPFDialogAnswer.ABORT
if len(args) == 4:
audio_pattern, sample_rate, sample_size, ch_count = args
else:
audio_pattern, sample_rate, sample_size, ch_count = None, None, None, None
logging.info(audio_pattern, sample_rate, sample_size, ch_count)
try:
dprx.audio_capturer.start(10)
dprx.audio_capturer.stop()
if len(dprx.audio_capturer.capture_result.buffer) <= 0:
logging.info("Audio buffer is empty")
return UniTAP.OPFDialogAnswer.FAIL
if audio_pattern is None:
logging.info("Default 108 OPF, check audio availability")
return UniTAP.OPFDialogAnswer.PASS if len(dprx.audio_capturer.capture_result.buffer) > 0 else \
UniTAP.OPFDialogAnswer.FAIL
def check_audio_frame(index, ch_count, sample_freq, sample_size) -> bool:
frame = dprx.audio_capturer.capture_result.buffer[index]
_data = list(frame.data)
length = len(_data)
zero_count = _data.count(0)
is_only_zero = zero_count == length
active_channel_count = OPFFunctions.get_active_channel_count(raw_data=frame.data,
bit_depths=[frame.sample_size],
channel_range=[frame.channel_count],
expected_channels=ch_count
)
logging.info(f"Frame index[{index}]: Len - {length}, Zero count - {zero_count}, "
f"Reference channel count - {ch_count}, Captured active channel count - {active_channel_count}, "
f"Reference sample rate - {sample_freq}, Captured sample rate - {frame.sample_rate}\n")
return not is_only_zero and \
active_channel_count == ch_count and \
frame.sample_rate == sample_freq and \
frame.sample_size == sample_size
buffer_len = len(dprx.audio_capturer.capture_result.buffer)
state = buffer_len > 0 and \
(check_audio_frame(0, ch_count, sample_rate, sample_size) or
check_audio_frame(int(buffer_len / 2), ch_count, sample_rate, sample_size) or
check_audio_frame(buffer_len - 1, ch_count, sample_rate, sample_size))
return UniTAP.OPFDialogAnswer.PASS if state else UniTAP.OPFDialogAnswer.FAIL
except BaseException as e:
logging.error(f"Cannot capture audio. Error: {e}")
return UniTAP.OPFDialogAnswer.FAIL
@staticmethod
def opf_120_handler(dptx, dprx, res_x, res_y, res_frate, res_bpc, color_format) -> OPFDialogAnswer:
if not OPFFunctions.check_dprx(dprx):
@@ -1256,6 +1432,38 @@ class OPFFunctions:
else:
return OPFDialogAnswer.ABORT
@staticmethod
def opf_146_handler(dptx, dprx, res_x, res_y, rate):
if not OPFFunctions.check_dptx(dptx):
logging.error("DPTX is not initialized")
return OPFDialogAnswer.ABORT
logging.info(res_x, res_y, rate)
return OPFDialogAnswer.PROCEED
@staticmethod
def opf_147_handler(dptx, dprx, rate):
if not OPFFunctions.check_dptx(dptx):
logging.error("DPTX is not initialized")
return OPFDialogAnswer.ABORT
logging.info(f"Refresh rate passed from OPF: {rate}")
dptx.pg.set_as_config(as_config=FixedASParams(refresh_rate=rate,
divide_by_1_001=False,
increase_lines=100,
decrease_lines=100))
res_pg = dptx.pg.apply()
if not res_pg:
logging.info("DPTX pattern is not applied")
return OPFDialogAnswer.ABORT
time.sleep(1)
return OPFDialogAnswer.PROCEED
@staticmethod
def opf_150_handler(dptx, dprx, message: str) -> OPFDialogAnswer:
return OPFFunctions.opf_pass_handler(dptx, dprx, message)
@@ -1342,7 +1550,7 @@ class OPFFunctions:
@staticmethod
def check_dsc_content_library(*args):
width, height, color_mode, is_block_prediction_enabled, bpc, bpp, h_slice_number, v_slice_number, \
compression_info, dsc_v_major, dsc_v_minor, data_info = args
compression_info, dsc_v_major, dsc_v_minor, data_info = args
if OPFFunctions.dsc_content_library_path != "":
if not os.path.exists(OPFFunctions.dsc_content_library_path):
os.makedirs(OPFFunctions.dsc_content_library_path)
@@ -1375,7 +1583,7 @@ class OPFFunctions:
@staticmethod
def get_compression_info(*args):
v_slice_size, h_slice_size, bpp, color_mode, buffer_bit_depth, is_block_prediction_enabled, dsc_v_major, \
dsc_v_minor, is_simple_as_444 = args
dsc_v_minor, is_simple_as_444 = args
compression_info = CompressionInfo()
compression_info.v_slice_size = v_slice_size
@@ -1388,3 +1596,94 @@ class OPFFunctions:
compression_info.is_simple_as_444 = is_simple_as_444
return compression_info
@staticmethod
def read_pcm(raw_data, sample_size):
"""
Read raw PCM data from file into a list of int32 values.
Handles 16, 20, and 24-bit samples (little-endian).
"""
if sample_size == 16:
if len(raw_data) % 2 != 0:
raise ValueError("File length not divisible by 2 for 16-bit audio")
result = []
for i in range(0, len(raw_data), 2):
sample = int.from_bytes(raw_data[i:i + 2], byteorder='little', signed=True)
result.append(sample)
return result, 32767
elif sample_size == 20 or sample_size == 24:
if len(raw_data) % 3 != 0:
raise ValueError("File length not divisible by 3 for 24/20-bit audio")
result = []
for i in range(0, len(raw_data), 3):
val = (raw_data[i] |
(raw_data[i + 1] << 8) |
(raw_data[i + 2] << 16))
sign_mask = 1 << 23
val = (val & (sign_mask - 1)) - (val & sign_mask)
result.append(val)
return result, (1 << 23) - 1
else:
raise ValueError(f"Unsupported bit depth: {sample_size}")
@staticmethod
def get_active_channel_count(raw_data, bit_depths: list, channel_range: list, expected_channels: int,
rms_threshold=1e-3, nonzero_frac_threshold=1e-4):
"""
Try multiple bit depths and channel counts, report active channels.
"""
active_count = 0
for bd in bit_depths:
try:
data, dtype_max = OPFFunctions.read_pcm(raw_data, bd)
except Exception as e:
logging.info(f"[{bd}-bit] Error: {e}")
continue
logging.info(f"\n=== Testing bit depth {bd} ===")
for nch in [expected_channels] + [ch for ch in channel_range if ch != expected_channels]:
if len(data) % nch != 0:
logging.info(f"Skipping {nch} channels: data length {len(data)} not divisible by {nch}")
continue
frames = len(data) // nch
arr = []
for f in range(frames):
frame = data[f * nch:(f + 1) * nch]
arr.append(frame)
rms = []
peak = []
nonzero_frac = []
for ch in range(nch):
channel_data = [arr[f][ch] for f in range(frames)]
mean_square = sum(x * x for x in channel_data) / frames
rms.append(mean_square ** 0.5)
peak.append(max(abs(x) for x in channel_data))
nonzero_count = sum(1 for x in channel_data if x != 0)
nonzero_frac.append(nonzero_count / frames)
norm_rms = [r / dtype_max for r in rms]
norm_peak = [p / dtype_max for p in peak]
logging.info(f"\nAssuming {nch} channel(s):")
channel_active_count = 0
for ch in range(nch):
active = (norm_rms[ch] > rms_threshold or
nonzero_frac[ch] > nonzero_frac_threshold)
if active:
channel_active_count += 1
logging.info(f" Ch{ch}: normRMS={norm_rms[ch]:.6f}, "
f"normPeak={norm_peak[ch]:.6f}, "
f"nonzero={nonzero_frac[ch]:.6f}, ACTIVE={active}")
logging.info(f" => Detected {channel_active_count}/{nch} active channels")
if channel_active_count == expected_channels:
active_count = channel_active_count
break
return active_count