doubly interlace video and make server asynchronous

This commit is contained in:
Camryn Thomas 2025-01-31 21:58:09 -06:00
parent 34b15e9543
commit 3e7ba62d3a
Signed by: cptlobster
GPG Key ID: 33D607425C830B4C
3 changed files with 104 additions and 46 deletions

View File

@ -5,7 +5,7 @@ import uuid
uuid = uuid.uuid4() uuid = uuid.uuid4()
from common import StdPacket, InterlacedPacket from common import StdPacket, InterlacedPacket, DoublyInterlacedPacket
UDP_IP = "127.0.0.1" UDP_IP = "127.0.0.1"
UDP_PORT = 5005 UDP_PORT = 5005
@ -48,6 +48,19 @@ def breakdown_image_interlaced(frame):
pkt = InterlacedPacket(uuid, j, i, True, frame[i + 1:i + 32:2, j:j + 16]) pkt = InterlacedPacket(uuid, j, i, True, frame[i + 1:i + 32:2, j:j + 16])
send_packet(sock, pkt.to_bytestr()) send_packet(sock, pkt.to_bytestr())
def breakdown_image_dint(frame):
(cols, rows, colors) = frame.shape
# break the array into 16x32 chunks. we'll split those further into odd and even rows
# and send each as UDP packets. this should make packet loss less obvious
for l in range(0, 4):
for i in range(0, cols, 32):
for j in range(0, rows, 32):
# print("Sending frame segment (%d, %d)", i, j)
i_even = l % 2 == 0
j_even = l >= 2
pkt = DoublyInterlacedPacket(uuid, j, i, j_even, i_even, frame[i + i_even:i + 32:2, j + j_even:j + 32:2])
send_packet(sock, pkt.to_bytestr())
while True: while True:
# Capture frame-by-frame # Capture frame-by-frame
ret, frame = cap.read() ret, frame = cap.read()
@ -57,7 +70,7 @@ while True:
print("Can't receive frame (stream end?). Exiting ...") print("Can't receive frame (stream end?). Exiting ...")
break break
breakdown_image_interlaced(frame) breakdown_image_dint(frame)
# Release the capture and close all windows # Release the capture and close all windows
cap.release() cap.release()

View File

@ -52,4 +52,35 @@ def from_bytes_int(b: bytes) -> InterlacedPacket:
even = bool.from_bytes(b[24:28]) even = bool.from_bytes(b[24:28])
array = np.frombuffer(b[28:], np.uint8).reshape(16, 16, 3) array = np.frombuffer(b[28:], np.uint8).reshape(16, 16, 3)
return InterlacedPacket(uuid, x, y, even, array) return InterlacedPacket(uuid, x, y, even, array)
class DoublyInterlacedPacket:
size = 16 + 4 + 4 + 4 + 768
def __init__(self, uuid: UUID, x: int, y: int, even_x: bool, even_y: bool, array: np.ndarray):
self.uuid = uuid
self.x = x
self.y = y
self.even_x = even_x
self.even_y = even_y
self.array = array
def to_bytestr(self) -> bytes:
bytestr = b""
bytestr += self.uuid.bytes
bytestr += self.x.to_bytes(length=4, signed=False)
bytestr += self.y.to_bytes(length=4, signed=False)
bytestr += self.even_x.to_bytes(length=2)
bytestr += self.even_y.to_bytes(length=2)
bytestr += self.array.tobytes()
return bytestr
def from_bytes_dint(b: bytes) -> DoublyInterlacedPacket:
uuid = UUID(bytes=b[0:16])
x = int.from_bytes(b[16:20], signed=False)
y = int.from_bytes(b[20:24], signed=False)
even_x = bool.from_bytes(b[24:26])
even_y = bool.from_bytes(b[26:28])
array = np.frombuffer(b[28:], np.uint8).reshape(16, 16, 3)
return DoublyInterlacedPacket(uuid, x, y, even_x, even_y, array)

100
server.py
View File

@ -1,21 +1,17 @@
from datetime import datetime
from typing import Dict from typing import Dict
import cv2 import cv2
import socket import socket
import numpy as np import numpy as np
from datetime import datetime
import asyncio
from common import InterlacedPacket, from_bytes_int from common import DoublyInterlacedPacket, from_bytes_dint
# bind any IP address # bind any IP address
UDP_IP = "" UDP_IP = ""
UDP_PORT = 5005 UDP_PORT = 5005
sock = socket.socket(socket.AF_INET, # Internet
socket.SOCK_DGRAM) # UDP
sock.setblocking(False)
sock.bind((UDP_IP, UDP_PORT))
HEIGHT = 480 HEIGHT = 480
WIDTH = 640 WIDTH = 640
@ -24,11 +20,12 @@ class Client:
self.last_updated = datetime.now() self.last_updated = datetime.now()
self.frame = np.ndarray((HEIGHT, WIDTH, 3), dtype=np.uint8) self.frame = np.ndarray((HEIGHT, WIDTH, 3), dtype=np.uint8)
def update(self, pkt: InterlacedPacket): def update(self, pkt: DoublyInterlacedPacket):
if pkt.even: x = pkt.x
self.frame[y + 1:y + 32:2, x:x + 16] = arr y = pkt.y
else: arr = pkt.array
self.frame[y:y + 32:2, x:x + 16] = arr self.frame[y + pkt.even_y:y + 32:2, x + pkt.even_x:x + 32:2] = arr
self.last_updated = datetime.now() self.last_updated = datetime.now()
def latency(self) -> float: def latency(self) -> float:
@ -40,41 +37,58 @@ class Client:
frames: Dict[str, Client] = {} frames: Dict[str, Client] = {}
while True: async def read_packet():
# break the array down into 16-bit chunks, then transmit them as UDP packets while True:
for repeats in range(10000): for i in range(0, 1200):
try: # break the array down into 16-bit chunks, then transmit them as UDP packets
data, addr = sock.recvfrom(InterlacedPacket.size) # buffer size is 768 bytes try:
# print("received packet from", addr) data, addr = sock.recvfrom(DoublyInterlacedPacket.size) # buffer size is 768 bytes
if data: # print("received packet from", addr)
pkt = from_bytes_int(data) if data:
pkt = from_bytes_dint(data)
uuid = str(pkt.uuid) uuid = str(pkt.uuid)
x = pkt.x
y = pkt.y
arr = pkt.array
if uuid not in frames.keys(): if uuid not in frames.keys():
print("New client acquired, naming %s", uuid) print("New client acquired, naming %s", uuid)
frames[uuid] = Client() frames[uuid] = Client()
frames[uuid].update(pkt) frames[uuid].update(pkt)
except BlockingIOError: except BlockingIOError:
pass pass
# Display the resulting frame await asyncio.sleep(0.001)
for id in list(frames.keys()):
if frames[id].latency() >= 5:
print("Client likely lost connection, dropping %s", id)
cv2.destroyWindow(id)
frames.pop(id)
else:
cv2.imshow(id, frames[id].read())
# Break the loop if 'q' key is pressed async def show_frames():
if cv2.waitKey(1) == ord('q'): while True:
break # Display the resulting frame
for id in list(frames.keys()):
if frames[id].latency() >= 5:
print("Client likely lost connection, dropping %s", id)
cv2.destroyWindow(id)
frames.pop(id)
else:
cv2.imshow(id, frames[id].read())
# Release the capture and close all windows cv2.waitKey(1)
cv2.destroyAllWindows() await asyncio.sleep(0.01)
if __name__ == "__main__":
sock = socket.socket(socket.AF_INET, # Internet
socket.SOCK_DGRAM) # UDP
sock.setblocking(False)
sock.bind((UDP_IP, UDP_PORT))
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.create_task(read_packet())
loop.create_task(show_frames())
try:
loop.run_forever()
finally:
loop.run_until_complete(loop.shutdown_asyncgens())
loop.close()
# Release the capture and close all windows
cv2.destroyAllWindows()