Skip to content

Commit 5e17b76

Browse files
committed
Merge remote-tracking branch 'origin/main' into netsniff-autom
2 parents eeeee84 + 668fa0a commit 5e17b76

File tree

19 files changed

+1169
-104
lines changed

19 files changed

+1169
-104
lines changed

.github/workflows/smoke-tests.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,21 @@ jobs:
2121
timeout-minutes: 60
2222
outputs:
2323
pipenv-activate: ${{ steps.pipenv-install.outputs.VIRTUAL_ENV }}
24+
runner-id: ${{ steps.get-runner-id.outputs.runner_id }}
2425
steps:
2526
- name: 'preparation: Harden Runner'
2627
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
2728
with:
2829
egress-policy: audit
30+
- name: 'preparation: Get Runner ID'
31+
id: get-runner-id
32+
run: |
33+
# Extract runner number from RUNNER_NAME (e.g., "cicd-1" -> "1")
34+
RUNNER_NUMBER=$(echo "$RUNNER_NAME" | grep -o '[0-9]\+$' || echo "1")
35+
# Format as runner-X
36+
RUNNER_ID="runner-${RUNNER_NUMBER}"
37+
echo "runner_id=$RUNNER_ID" >> "$GITHUB_OUTPUT"
38+
echo "Using runner ID: $RUNNER_ID (from runner name: $RUNNER_NAME)"
2939
- name: 'preparation: Restore valid repository owner and print env'
3040
if: always()
3141
run: |
@@ -126,14 +136,14 @@ jobs:
126136
echo "VIRTUAL_ENV=$PWD/venv/bin/activate" >> "$GITHUB_ENV"
127137
validation-run-tests:
128138
needs: [validation-build-mtl]
129-
runs-on: [Linux, self-hosted, DPDK]
139+
runs-on: [self-hosted, "${{ needs.validation-build-mtl.outputs.runner-id }}"]
130140
timeout-minutes: 720
131141
env:
132142
PYTEST_RETRIES: '3'
133143
steps:
134144
- name: Replace secrets in example config files
135145
run: |
136-
sed -i "s+MTL_PATH_PLACEHOLDER+${{ secrets.BARE_METAL_MTL_PATH }}+" tests/validation/configs/test_config.yaml
146+
sed -i "s+MTL_PATH_PLACEHOLDER+${{ github.workspace }}+" tests/validation/configs/test_config.yaml
137147
sed -i "s/IP_ADDRESS_PLACEHOLDER/${{ secrets.BARE_METAL_IP_ADDRESS }}/" tests/validation/configs/topology_config.yaml
138148
sed -i "s/SSH_PORT_PLACEHOLDER/${{ secrets.BARE_METAL_SSH_PORT }}/" tests/validation/configs/topology_config.yaml
139149
sed -i "s/USERNAME_PLACEHOLDER/${{ secrets.BARE_METAL_USERNAME }}/" tests/validation/configs/topology_config.yaml
@@ -167,6 +177,7 @@ jobs:
167177
run: |
168178
sudo tests/validation/venv/bin/python3 -m pytest --topology_config=tests/validation/configs/topology_config.yaml --test_config=tests/validation/configs/test_config.yaml -m smoke --template=html/index.html --report=report.html
169179
- name: "upload report"
180+
if: always()
170181
id: upload-report
171182
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
172183
with:
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Media Transport Library - Integrity Testing
2+
3+
This directory contains tools for validating the integrity of video and audio data in the Media Transport Library.
4+
5+
## Overview
6+
7+
The integrity tools provide functionality to:
8+
9+
- Validate video frames using MD5 checksums and text recognition
10+
- Validate audio frames using MD5 checksums of PCM data
11+
- Support both file-based and stream-based (segmented files) testing
12+
13+
## Prerequisites
14+
15+
Install the required dependencies:
16+
17+
```bash
18+
pip install -r requirements.txt
19+
```
20+
21+
## Usage
22+
23+
### Audio Integrity
24+
25+
#### Audio File Mode
26+
27+
Compares a single audio file against a reference source file:
28+
29+
```bash
30+
python audio_integrity.py file <source_file> <output_file> \
31+
--sample_size 2 --sample_num 480 --channel_num 2 \
32+
--output_path /path/to/output/dir
33+
```
34+
35+
#### Audio Stream Mode
36+
37+
Checks the integrity of segmented audio files from a stream:
38+
39+
```bash
40+
python audio_integrity.py stream <source_file> <segment_prefix> \
41+
--sample_size 2 --sample_num 480 --channel_num 2 \
42+
--output_path /path/to/segments/dir
43+
```
44+
45+
### Video Integrity
46+
47+
#### Video File Mode
48+
49+
Compares a single video file against a reference source file:
50+
51+
```bash
52+
python video_integrity.py file <source_file> <output_file> <resolution> <format> \
53+
--output_path /path/to/output/dir
54+
```
55+
56+
#### Video Stream Mode
57+
58+
Checks the integrity of segmented video files from a stream:
59+
60+
```bash
61+
python video_integrity.py stream <source_file> <segment_prefix> <resolution> <format> \
62+
--output_path /path/to/segments/dir \
63+
--segment_duration 3 --workers 5
64+
```
65+
66+
## Integration with Test Framework
67+
68+
The `integrity_runner.py` provides Python classes for integrating integrity validation into test scripts:
69+
70+
- `FileVideoIntegrityRunner`: For single video file validation
71+
- `StreamVideoIntegrityRunner`: For video stream validation
72+
- `FileAudioIntegrityRunner`: For single audio file validation
73+
- `StreamAudioIntegrityRunner`: For audio stream validation
74+
75+
Example usage in a test script:
76+
77+
```python
78+
from common.integrity.integrity_runner import FileAudioIntegrityRunner
79+
80+
# Create a runner instance
81+
runner = FileAudioIntegrityRunner(
82+
host=host,
83+
test_repo_path=repo_path,
84+
src_url="/path/to/source.pcm",
85+
out_name="output.pcm",
86+
sample_size=2,
87+
sample_num=480,
88+
channel_num=2,
89+
out_path="/mnt/ramdisk",
90+
)
91+
92+
# Run the integrity check
93+
runner.setup()
94+
result = runner.run()
95+
assert result, "Audio integrity check failed"
96+
```
97+
98+
See the test scripts in the repository for more detailed usage examples.
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
# Copyright(c) 2024-2025 Intel Corporation
3+
# Media Communications Mesh
4+
5+
import argparse
6+
import logging
7+
import sys
8+
from pathlib import Path
9+
10+
from video_integrity import calculate_chunk_hashes
11+
12+
13+
def get_pcm_frame_size(sample_size: int, sample_num: int, channel_num: int) -> int:
14+
return sample_size * sample_num * channel_num
15+
16+
17+
class AudioIntegritor:
18+
def __init__(
19+
self,
20+
logger: logging.Logger,
21+
src_url: str,
22+
out_name: str,
23+
sample_size: int = 2,
24+
sample_num: int = 480,
25+
channel_num: int = 2,
26+
out_path: str = "/mnt/ramdisk",
27+
delete_file: bool = True,
28+
):
29+
self.logger = logger
30+
self.src_url = src_url
31+
self.out_name = out_name
32+
self.sample_size = sample_size
33+
self.sample_num = sample_num
34+
self.channel_num = channel_num
35+
self.frame_size = get_pcm_frame_size(sample_size, sample_num, channel_num)
36+
self.out_path = out_path
37+
self.delete_file = delete_file
38+
self.src_chunk_sums = calculate_chunk_hashes(src_url, self.frame_size)
39+
40+
41+
class AudioFileIntegritor(AudioIntegritor):
42+
def check_integrity_file(self, out_url) -> bool:
43+
self.logger.info(
44+
f"Checking integrity for src {self.src_url} and out {out_url} "
45+
f"with frame size {self.frame_size}"
46+
)
47+
src_chunk_sums = self.src_chunk_sums
48+
out_chunk_sums = calculate_chunk_hashes(out_url, self.frame_size)
49+
bad_frames = 0
50+
for idx, chunk_sum in enumerate(out_chunk_sums):
51+
if idx >= len(src_chunk_sums) or chunk_sum != src_chunk_sums[idx]:
52+
self.logger.error(f"Bad audio frame at index {idx} in {out_url}")
53+
bad_frames += 1
54+
if bad_frames:
55+
self.logger.error(
56+
f"Received {bad_frames} bad frames out of {len(out_chunk_sums)} checked."
57+
)
58+
return False
59+
self.logger.info(f"All {len(out_chunk_sums)} frames in {out_url} are correct.")
60+
return True
61+
62+
63+
class AudioStreamIntegritor(AudioIntegritor):
64+
def get_out_files(self):
65+
return sorted(Path(self.out_path).glob(f"{self.out_name}*"))
66+
67+
def check_stream_integrity(self) -> bool:
68+
bad_frames_total = 0
69+
out_files = self.get_out_files()
70+
if not out_files:
71+
self.logger.error(
72+
f"No output files found for stream in {self.out_path} with prefix {self.out_name}"
73+
)
74+
return False
75+
for out_file in out_files:
76+
self.logger.info(f"Checking integrity for segment file: {out_file}")
77+
out_chunk_sums = calculate_chunk_hashes(str(out_file), self.frame_size)
78+
for idx, chunk_sum in enumerate(out_chunk_sums):
79+
if (
80+
idx >= len(self.src_chunk_sums)
81+
or chunk_sum != self.src_chunk_sums[idx]
82+
):
83+
self.logger.error(f"Bad audio frame at index {idx} in {out_file}")
84+
bad_frames_total += 1
85+
if self.delete_file:
86+
out_file.unlink()
87+
if bad_frames_total:
88+
self.logger.error(
89+
f"Received {bad_frames_total} bad frames in stream segments."
90+
)
91+
return False
92+
self.logger.info("All frames in stream segments are correct.")
93+
return True
94+
95+
96+
def main():
97+
# Set up logging
98+
logging.basicConfig(
99+
level=logging.INFO,
100+
format="%(asctime)s - %(levelname)s - %(message)s",
101+
)
102+
logger = logging.getLogger(__name__)
103+
104+
# Create the argument parser
105+
parser = argparse.ArgumentParser(
106+
description="Audio Integrity Checker",
107+
formatter_class=argparse.RawDescriptionHelpFormatter,
108+
)
109+
subparsers = parser.add_subparsers(
110+
dest="mode", help="Operation mode", required=True
111+
)
112+
113+
# Common arguments for both file and stream modes
114+
def add_common_arguments(parser):
115+
parser.add_argument("src", help="Source audio file path")
116+
parser.add_argument("out", help="Output audio file name (without extension)")
117+
parser.add_argument(
118+
"--sample_size",
119+
type=int,
120+
default=2,
121+
help="Audio sample size in bytes (default: 2)",
122+
)
123+
parser.add_argument(
124+
"--sample_num",
125+
type=int,
126+
default=480,
127+
help="Number of samples per frame (default: 480)",
128+
)
129+
parser.add_argument(
130+
"--channel_num",
131+
type=int,
132+
default=2,
133+
help="Number of audio channels (default: 2)",
134+
)
135+
parser.add_argument(
136+
"--output_path",
137+
type=str,
138+
default="/mnt/ramdisk",
139+
help="Output path (default: /mnt/ramdisk)",
140+
)
141+
parser.add_argument(
142+
"--delete_file",
143+
action="store_true",
144+
default=True,
145+
help="Delete output files after processing (default: True)",
146+
)
147+
parser.add_argument(
148+
"--no_delete_file",
149+
action="store_false",
150+
dest="delete_file",
151+
help="Do NOT delete output files after processing",
152+
)
153+
154+
# Stream mode parser
155+
stream_help = """Check integrity for audio stream (stream saved into files segmented by time)
156+
157+
It assumes that there is X digit segment number in the file name like `out_name_001.pcm` or `out_name_02.pcm`.
158+
It can be achieved by using ffmpeg with `-f segment` option.
159+
160+
Example: ffmpeg -i input.wav -f segment -segment_time 3 out_name_%03d.pcm"""
161+
stream_parser = subparsers.add_parser(
162+
"stream",
163+
help="Check integrity for audio stream (segmented files)",
164+
description=stream_help,
165+
formatter_class=argparse.RawDescriptionHelpFormatter,
166+
)
167+
add_common_arguments(stream_parser)
168+
stream_parser.add_argument(
169+
"--segment_duration",
170+
type=int,
171+
default=3,
172+
help="Segment duration in seconds (default: 3)",
173+
)
174+
175+
# File mode parser
176+
file_help = """Check integrity for single audio file.
177+
178+
This mode compares a single output audio file against a source reference file.
179+
It performs frame-by-frame integrity checking using MD5 checksums."""
180+
file_parser = subparsers.add_parser(
181+
"file",
182+
help="Check integrity for single audio file",
183+
description=file_help,
184+
formatter_class=argparse.RawDescriptionHelpFormatter,
185+
)
186+
add_common_arguments(file_parser)
187+
188+
# Parse the arguments
189+
args = parser.parse_args()
190+
191+
# Execute based on mode
192+
if args.mode == "stream":
193+
integrator = AudioStreamIntegritor(
194+
logger,
195+
args.src,
196+
args.out,
197+
args.sample_size,
198+
args.sample_num,
199+
args.channel_num,
200+
args.output_path,
201+
args.delete_file,
202+
)
203+
result = integrator.check_stream_integrity()
204+
elif args.mode == "file":
205+
# For file mode, construct the full output file path
206+
out_file = Path(args.output_path) / args.out
207+
integrator = AudioFileIntegritor(
208+
logger,
209+
args.src,
210+
args.out,
211+
args.sample_size,
212+
args.sample_num,
213+
args.channel_num,
214+
args.output_path,
215+
args.delete_file,
216+
)
217+
result = integrator.check_integrity_file(str(out_file))
218+
else:
219+
parser.print_help()
220+
return
221+
222+
if result:
223+
logging.info("Audio integrity check passed")
224+
else:
225+
logging.error("Audio integrity check failed")
226+
sys.exit(1)
227+
228+
229+
if __name__ == "__main__":
230+
main()

0 commit comments

Comments
 (0)