feat(harmony): add H.264 companion development pipeline
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user