udp_frame_forwarder.py 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. """
  2. 将 JPEG 帧通过 OrangePi 的 FrameHeader(16B) 分片协议转发出去(UDP)。
  3. 用于 Python -> Unity 图像显示链路,便于 Unity 端复用同一套分片重组逻辑。
  4. Header(小端):
  5. uint32 magic = 0x57494649 ("WIFI")
  6. uint32 frame_id
  7. uint16 seq
  8. uint16 total
  9. uint16 data_len
  10. uint16 reserved
  11. """
  12. from __future__ import annotations
  13. import socket
  14. import struct
  15. import threading
  16. from dataclasses import dataclass
  17. MAGIC = 0x57494649
  18. HEADER_FMT = "<I I H H H H"
  19. HEADER_SIZE = struct.calcsize(HEADER_FMT)
  20. MAX_UDP_SIZE = 1400
  21. MAX_PAYLOAD = MAX_UDP_SIZE - HEADER_SIZE # 1384
  22. @dataclass
  23. class ForwardConfig:
  24. target_ip: str = "127.0.0.1"
  25. target_port: int = 12366
  26. class UDPFrameForwarder:
  27. def __init__(self, target_ip: str = "127.0.0.1", target_port: int = 12366) -> None:
  28. self.cfg = ForwardConfig(target_ip=target_ip, target_port=target_port)
  29. self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  30. self._frame_id = 0
  31. self._lock = threading.Lock()
  32. def set_target(self, ip: str, port: int) -> None:
  33. with self._lock:
  34. self.cfg.target_ip = ip
  35. self.cfg.target_port = int(port)
  36. def send_jpeg(self, jpeg_bytes: bytes) -> None:
  37. if not jpeg_bytes:
  38. return
  39. with self._lock:
  40. target = (self.cfg.target_ip, self.cfg.target_port)
  41. frame_id = self._frame_id
  42. self._frame_id = (self._frame_id + 1) & 0xFFFFFFFF
  43. total = (len(jpeg_bytes) + MAX_PAYLOAD - 1) // MAX_PAYLOAD
  44. if total <= 0:
  45. return
  46. if total > 65535:
  47. # 单帧过大,不发送
  48. return
  49. for seq in range(total):
  50. start = seq * MAX_PAYLOAD
  51. end = min(start + MAX_PAYLOAD, len(jpeg_bytes))
  52. payload = jpeg_bytes[start:end]
  53. header = struct.pack(HEADER_FMT, MAGIC, frame_id, seq, total, len(payload), 0)
  54. self.sock.sendto(header + payload, target)
  55. def close(self) -> None:
  56. try:
  57. self.sock.close()
  58. except Exception:
  59. pass