diff --git a/tests/validation/tests/Engine/RxTxApp.py b/tests/validation/tests/Engine/RxTxApp.py index 1c1ec93e0..8334e60c1 100644 --- a/tests/validation/tests/Engine/RxTxApp.py +++ b/tests/validation/tests/Engine/RxTxApp.py @@ -195,6 +195,7 @@ def add_st20p_sessions( packing: str = "BPM", enable_rtcp: bool = False, measure_latency: bool = False, + out_url: str = "", ) -> dict: config = add_interfaces( config=config, nic_port_list=nic_port_list, test_mode=test_mode @@ -224,6 +225,7 @@ def add_st20p_sessions( config["rx_sessions"][0]["st20p"][0]["enable_rtcp"] = enable_rtcp config["rx_sessions"][0]["st20p"][0]["measure_latency"] = measure_latency config["tx_sessions"][0]["st20p"][0]["st20p_url"] = st20p_url + config["rx_sessions"][0]["st20p"][0]["st20p_url"] = out_url return config @@ -286,6 +288,7 @@ def add_st30p_sessions( audio_channel: list = ["U02"], audio_sampling: str = "96kHz", audio_ptime: str = "1", + out_url: str = "", ) -> dict: config = add_interfaces( config=config, nic_port_list=nic_port_list, test_mode=test_mode @@ -304,7 +307,8 @@ def add_st30p_sessions( config["tx_sessions"][0]["st30p"][0]["audio_ptime"] = audio_ptime config["rx_sessions"][0]["st30p"][0]["audio_ptime"] = audio_ptime config["tx_sessions"][0]["st30p"][0]["audio_url"] = filename - config["rx_sessions"][0]["st30p"][0]["audio_url"] = filename + config["rx_sessions"][0]["st30p"][0]["audio_url"] = out_url + return config diff --git a/tests/validation/tests/Engine/integrity.py b/tests/validation/tests/Engine/integrity.py new file mode 100644 index 000000000..e3eb00d40 --- /dev/null +++ b/tests/validation/tests/Engine/integrity.py @@ -0,0 +1,156 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024-2025 Intel Corporation + +import hashlib +import re +from math import floor + +from tests.Engine.execute import log_fail + + +def check_st20p_integrity(src_url: str, out_url: str, frame_size: int): + src_chunk_sums = [] + + with open(src_url, "rb") as f: + while chunk := f.read(frame_size): + chunk_sum = hashlib.md5(chunk).hexdigest() + src_chunk_sums.append(chunk_sum) + + out_chunk_sums = [] + + with open(out_url, "rb") as f: + while chunk := f.read(frame_size): + chunk_sum = hashlib.md5(chunk).hexdigest() + out_chunk_sums.append(chunk_sum) + + if len(src_chunk_sums) < len(out_chunk_sums): + for i in range(len(src_chunk_sums)): + if src_chunk_sums[i] != out_chunk_sums[i]: + log_fail(f"Received frame {i} is invalid") + return False + else: + for i in range(len(out_chunk_sums)): + if out_chunk_sums[i] != src_chunk_sums[i]: + log_fail(f"Received frame {i} is invalid") + return False + + return True + + +def calculate_yuv_frame_size(width: int, height: int, file_format: str): + match file_format: + case "YUV422RFC4175PG2BE10": + pixel_size = 2.5 + case "YUV422PLANAR10LE": + pixel_size = 4 + case _: + log_fail(f"Size of {file_format} pixel is not known") + + return int(width * height * pixel_size) + + +def check_st30p_integrity(src_url: str, out_url: str, size: int): + src_chunks = [] + + with open(src_url, "rb") as f: + while chunk := f.read(size): + src_chunks.append(chunk) + + out_chunks = [] + + with open(out_url, "rb") as f: + while chunk := f.read(size): + out_chunks.append(chunk) + + if len(src_chunks) < len(out_chunks): + for i in range(len(src_chunks)): + if src_chunks[i] != out_chunks[i]: + log_fail(f"Received frame {i} is invalid") + return False + else: + for i in range(len(out_chunks)): + if out_chunks[i] != src_chunks[i]: + log_fail(f"Received frame {i} is invalid") + return False + + return True + + +def calculate_st30p_framebuff_size( + format: str, ptime: str, sampling: str, channel: str +): + match format: + case "PCM8": + sample_size = 1 + case "PCM16": + sample_size = 2 + case "PCM24": + sample_size = 3 + + match sampling: + case "48kHz": + match ptime: + case "1": + sample_num = 48 + case "0.12": + sample_num = 6 + case "0.25": + sample_num = 12 + case "0.33": + sample_num = 16 + case "4": + sample_num = 192 + case "96kHz": + match ptime: + case "1": + sample_num = 96 + case "0.12": + sample_num = 12 + case "0.25": + sample_num = 24 + case "0.33": + sample_num = 32 + case "4": + sample_num = 384 + + match channel: + case "M": + channel_num = 1 + case "DM" | "ST" | "LtRt" | "AES3": + channel_num = 2 + case "51": + channel_num = 6 + case "71": + channel_num = 8 + case "222": + channel_num = 24 + case "SGRP": + channel_num = 4 + case _: + match = re.match(r"^U(\d{2})$", channel) + + if match: + channel_num = int(match.group(1)) + + packet_size = sample_size * sample_num * channel_num + + match ptime: + case "1": + packet_time = 1_000_000 * 1 + case "0.12": + packet_time = 1_000_000 * 0.125 + case "0.25": + packet_time = 1_000_000 * 0.25 + case "0.33": + packet_time = 1_000_000 * 1 / 3 + case "4": + packet_time = 1_000_000 * 4 + + desired_frame_time = 10_000_000 + + packet_per_frame = 1 + + if desired_frame_time > packet_time: + packet_per_frame = floor(desired_frame_time / packet_time) + + return packet_per_frame * packet_size diff --git a/tests/validation/tests/Engine/media_files.py b/tests/validation/tests/Engine/media_files.py index 5327edc9b..62dab4a79 100644 --- a/tests/validation/tests/Engine/media_files.py +++ b/tests/validation/tests/Engine/media_files.py @@ -313,6 +313,7 @@ "format": "YUV_422_10bit", "width": 1280, "height": 720, + "fps": 25, }, Penguin_1080p={ "filename": "HDR_BBC_v4_008_Penguin1_1920x1080_10bit_25Hz_P422_180frames.yuv", @@ -320,6 +321,7 @@ "format": "YUV_422_10bit", "width": 1920, "height": 1080, + "fps": 25, }, ) @@ -355,12 +357,21 @@ ) yuv_files_422rfc10 = dict( + Penguin_720p={ + "filename": "HDR_BBC_v4_008_Penguin1_1280x720_10bit_25Hz_P422_180frames.yuv", + "file_format": "YUV422RFC4175PG2BE10", + "format": "YUV_422_10bit", + "width": 1280, + "height": 720, + "fps": 25, + }, Penguin_1080p={ "filename": "HDR_BBC_v4_008_Penguin1_1920x1080_10bit_25Hz_180frames_yuv422p10be_To_yuv422rfc4175be10.yuv", "file_format": "YUV422RFC4175PG2BE10", "format": "YUV_422_10bit", "width": 1920, "height": 1080, + "fps": 25, }, Penguin_4K={ "filename": "HDR_BBC_v4_008_Penguin1_3840x2160_10bit_25Hz_P422_180frames_yuv422rfc4175be10.yuv", @@ -368,6 +379,7 @@ "format": "YUV_422_10bit", "width": 3840, "height": 2160, + "fps": 25, }, Penguin_8K={ "filename": "HDR_BBC_v4_008_Penguin1_7680x4320_10bit_25Hz_P422_To_yuv422rfc4175be10_180frames.yuv", @@ -375,6 +387,7 @@ "format": "YUV_422_10bit", "width": 7680, "height": 4320, + "fps": 25, }, Crosswalk_720p={ "filename": "Netflix_Crosswalk_1280x720_10bit_60Hz_P422_yuv422p10be_To_yuv422YCBCR10be.yuv", @@ -382,6 +395,7 @@ "format": "YUV_422_10bit", "width": 1280, "height": 720, + "fps": 60, }, Crosswalk_1080p={ "filename": "Netflix_Crosswalk_1920x1080_10bit_60Hz_P422_yuv422p10be_To_yuv422YCBCR10be.yuv", @@ -389,6 +403,7 @@ "format": "YUV_422_10bit", "width": 1920, "height": 1080, + "fps": 60, }, Crosswalk_4K={ "filename": "Netflix_Crosswalk_3840x2160_10bit_60Hz_P422_To_yuv422p10be_To_yuv422YCBCR10be.yuv", @@ -396,6 +411,7 @@ "format": "YUV_422_10bit", "width": 3840, "height": 2160, + "fps": 60, }, ParkJoy_720p={ "filename": "ParkJoy_1280x720_10bit_50Hz_P422_To_yuv422p10be_To_yuv422YCBCR10be.yuv", @@ -403,6 +419,7 @@ "format": "YUV_422_10bit", "width": 1280, "height": 720, + "fps": 50, }, ParkJoy_1080p={ "filename": "ParkJoy_1920x1080_10bit_50Hz_P422_yuv422p10be_To_yuv422YCBCR10be.yuv", @@ -410,6 +427,7 @@ "format": "YUV_422_10bit", "width": 1920, "height": 1080, + "fps": 50, }, ParkJoy_4K={ "filename": "ParkJoy_3840x2160_10bit_50Hz_P422_To_yuv422p10be_To_yuv422YCBCR10be.yuv", @@ -417,6 +435,7 @@ "format": "YUV_422_10bit", "width": 3840, "height": 2160, + "fps": 50, }, Pedestrian_720p={ "filename": "Plalaedit_Pedestrian_10bit_1280x720_30Hz_P420_To_yuv422p10be_To_yuv422YCBCR10be.yuv", @@ -424,6 +443,7 @@ "format": "YUV_422_10bit", "width": 1280, "height": 720, + "fps": 30, }, Pedestrian_1080p={ "filename": "Plalaedit_Pedestrian_10bit_1920x1080_30Hz_P420_To_yuv422p10be_To_yuv422YCBCR10be.yuv", @@ -431,6 +451,7 @@ "format": "YUV_422_10bit", "width": 1920, "height": 1080, + "fps": 30, }, Pedestrian_4K={ "filename": "Plalaedit_Pedestrian_10bit_3840x2160_30Hz_P420_To_yuv422p10be_To_yuv422YCBCR10be.yuv", @@ -438,6 +459,7 @@ "format": "YUV_422_10bit", "width": 3840, "height": 2160, + "fps": 30, }, test_4K={ "filename": "test_3840x2160_for_25fps.yuv", @@ -445,6 +467,7 @@ "format": "YUV_422_10bit", "width": 3840, "height": 2160, + "fps": 25, }, test_8K={ "filename": "test_8k.yuv", diff --git a/tests/validation/tests/Engine/rxtxapp_config.py b/tests/validation/tests/Engine/rxtxapp_config.py index 047e46b32..5f0ef765c 100644 --- a/tests/validation/tests/Engine/rxtxapp_config.py +++ b/tests/validation/tests/Engine/rxtxapp_config.py @@ -189,6 +189,7 @@ "measure_latency": False, "display": False, "enable_rtcp": False, + "st20p_url": "", } # st22p diff --git a/tests/validation/tests/single/st20p/integrity/__init__.py b/tests/validation/tests/single/st20p/integrity/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/validation/tests/single/st20p/integrity/test_integrity.py b/tests/validation/tests/single/st20p/integrity/test_integrity.py new file mode 100644 index 000000000..21972fa50 --- /dev/null +++ b/tests/validation/tests/single/st20p/integrity/test_integrity.py @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024-2025 Intel Corporation + +import os + +import pytest +import tests.Engine.RxTxApp as rxtxapp +from tests.Engine.execute import log_info +from tests.Engine.integrity import calculate_yuv_frame_size, check_st20p_integrity +from tests.Engine.logging import LOG_FOLDER +from tests.Engine.media_files import yuv_files_422p10le, yuv_files_422rfc10 + + +@pytest.mark.parametrize( + "st20p_file, fps", + [ + (yuv_files_422rfc10["Penguin_720p"], "p25"), + (yuv_files_422rfc10["Penguin_1080p"], "p25"), + (yuv_files_422p10le["Penguin_720p"], "p25"), + (yuv_files_422p10le["Penguin_1080p"], "p25"), + ], +) +def test_integrity(build, media, nic_port_list, test_time, st20p_file, fps): + st20p_file_url = os.path.join(media, st20p_file["filename"]) + + out_file_url = os.path.join(os.getcwd(), LOG_FOLDER, "latest", "out.yuv") + + config = rxtxapp.create_empty_config() + config = rxtxapp.add_st20p_sessions( + config=config, + nic_port_list=nic_port_list, + test_mode="unicast", + height=st20p_file["height"], + width=st20p_file["width"], + fps=fps, + input_format=st20p_file["file_format"], + transport_format=st20p_file["format"], + output_format=st20p_file["file_format"], + st20p_url=st20p_file_url, + out_url=out_file_url, + ) + + rxtxapp.execute_test(config=config, build=build, test_time=test_time) + + frame_size = calculate_yuv_frame_size( + st20p_file["width"], st20p_file["height"], st20p_file["file_format"] + ) + result = check_st20p_integrity( + src_url=st20p_file_url, out_url=out_file_url, frame_size=frame_size + ) + + if result: + log_info("INTEGRITY PASS") + else: + log_info("INTEGRITY FAIL") diff --git a/tests/validation/tests/single/st30p/integrity/__init__.py b/tests/validation/tests/single/st30p/integrity/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/validation/tests/single/st30p/integrity/test_integrity.py b/tests/validation/tests/single/st30p/integrity/test_integrity.py new file mode 100644 index 000000000..5dac44cae --- /dev/null +++ b/tests/validation/tests/single/st30p/integrity/test_integrity.py @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024-2025 Intel Corporation + +import os + +import pytest +import tests.Engine.RxTxApp as rxtxapp +from tests.Engine.execute import LOG_FOLDER, log_info +from tests.Engine.integrity import calculate_st30p_framebuff_size, check_st30p_integrity +from tests.Engine.media_files import audio_files + + +@pytest.mark.parametrize("audio_format", ["PCM8", "PCM24"]) +def test_integrity(build, media, nic_port_list, test_time, audio_format): + st30p_file = audio_files[audio_format] + st30p_file_url = os.path.join(media, st30p_file["filename"]) + + out_file_url = os.path.join(os.getcwd(), LOG_FOLDER, "latest", "out.wav") + + config = rxtxapp.create_empty_config() + config = rxtxapp.add_st30p_sessions( + config=config, + nic_port_list=nic_port_list, + test_mode="unicast", + audio_format=audio_format, + audio_channel=["U02"], + audio_sampling="48kHz", + audio_ptime="1", + filename=st30p_file_url, + out_url=out_file_url, + ) + + rxtxapp.execute_test(config=config, build=build, test_time=test_time) + + size = calculate_st30p_framebuff_size( + format=audio_format, ptime="1", sampling="48kHz", channel="U02" + ) + result = check_st30p_integrity( + src_url=st30p_file_url, out_url=out_file_url, size=size + ) + + if result: + log_info("INTEGRITY PASS") + else: + log_info("INTEGRITY FAIL")