import importlib.util from pathlib import Path import re import shutil import socket import struct import subprocess import time import unittest spec = importlib.util.spec_from_file_location("harmony_stream", Path(__file__).with_name("harmony-stream.py")) stream = importlib.util.module_from_spec(spec) spec.loader.exec_module(stream) class StreamProtocolTests(unittest.TestCase): def setUp(self): self.sender, self.receiver = socket.socketpair() self.deadline = time.monotonic() + 1 def tearDown(self): self.sender.close() self.receiver.close() def test_negotiated_dimensions(self): self.sender.sendall(struct.pack(">4s5I", b"HCS1", 566, 1280, 1260, 2844, 30)) hello = stream.read_hello(self.receiver, self.deadline) self.assertEqual((hello["width"], hello["displayHeight"], hello["configuredFps"]), (566, 2844, 30)) def test_unknown_version_rejected(self): self.sender.sendall(struct.pack(">4s5I", b"HCS2", 640, 480, 1280, 960, 30)) with self.assertRaises(ValueError): stream.read_hello(self.receiver, self.deadline) def test_dimensions_bounded_before_video_allocations(self): self.sender.sendall(struct.pack(">4s5I", b"HCS1", 65536, 480, 1280, 960, 30)) with self.assertRaises(ValueError): stream.read_hello(self.receiver, self.deadline) def test_oversized_packet_rejected_without_reading_payload(self): self.sender.sendall(struct.pack(">IIq", stream.MAX_PACKET + 1, 2, 0)) with self.assertRaises(ValueError): stream.read_packet(self.receiver, self.deadline) def test_disconnect_in_packet_is_not_a_frame(self): self.sender.sendall(struct.pack(">IIq", 32, 2, 42) + b"\0\0\1\x65") self.sender.shutdown(socket.SHUT_WR) with self.assertRaises(EOFError): stream.read_packet(self.receiver, self.deadline) def test_silent_connection_has_bounded_wait(self): with self.assertRaises(TimeoutError): stream.read_packet(self.receiver, time.monotonic() + 0.02) def test_non_h264_payload_rejected(self): self.sender.sendall(struct.pack(">IIq", 8, 2, 42) + b"JPEGdata") with self.assertRaises(ValueError): stream.read_packet(self.receiver, self.deadline) @unittest.skipUnless(shutil.which("ffmpeg"), "ffmpeg required for actual decoder validation") def test_twenty_generated_frames_survive_transport_and_decode(self): encoded = subprocess.run([ "ffmpeg", "-hide_banner", "-loglevel", "error", "-f", "lavfi", "-i", "testsrc2=size=160x96:rate=30", "-frames:v", "20", "-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency", "-profile:v", "baseline", "-g", "10", "-bf", "0", "-x264-params", "aud=1:repeat-headers=1", "-f", "h264", "pipe:1"], capture_output=True, check=True, timeout=10).stdout # Access unit delimiters separate independently timestamped encoder callbacks. boundaries = [match.start() for match in re.finditer(b"\x00\x00\x00\x01\x09", encoded)] self.assertEqual(len(boundaries), 20) decoder = stream.Decoder(False) try: for index, (start, end) in enumerate(zip(boundaries, boundaries[1:] + [len(encoded)])): data = encoded[start:end] flags = 2 if index % 10 == 0 else 0 self.sender.sendall(struct.pack(">IIq", len(data), flags, index * 33333) + data) actual_flags, pts, actual_data = stream.read_packet(self.receiver, time.monotonic() + 1) self.assertEqual((actual_flags, pts), (flags, index * 33333)) decoder.write(actual_data) finally: decoder.close() self.assertEqual(decoder.process.returncode, 0, decoder.error.decode()) self.assertEqual(decoder.frames, 20, decoder.error.decode()) if __name__ == "__main__": unittest.main()