HardwareToPythonUdpBridge.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. using System;
  2. using System.IO;
  3. using System.Net;
  4. using System.Net.Sockets;
  5. using LightGlue.Unity.Networking;
  6. using LightGlue.Unity.Config;
  7. using LightGlue.Unity.Python;
  8. using UnityEngine;
  9. namespace LightGlue.Unity.Bridge
  10. {
  11. /// <summary>
  12. /// Bridge: 统一管理硬件与 Python 的通信。
  13. /// - 硬件 -> Unity:UDP JPEG 接收(bindIp:hardwarePort)
  14. /// - Unity -> 硬件:0x40 参数设置帧(分辨率/质量/间隔/开关)
  15. /// - Unity -> Python:图像数据(stdin 或 UDP)
  16. /// - Python -> Unity:算法结果 UDP 接收(pythonResultBindIp:pythonResultPort)
  17. /// </summary>
  18. public sealed class HardwareToPythonUdpBridge : MonoBehaviour
  19. {
  20. [Header("配置管理")]
  21. [Tooltip("是否从NetworkConfigManager自动加载配置(启动时)")]
  22. public bool autoLoadNetworkConfig = true;
  23. // ---------- 硬件 -> Unity:JPEG 接收 ----------
  24. [Header("Hardware UDP (input: hardware -> Unity)")]
  25. [Tooltip("Local bind IP for hardware stream, e.g. 192.168.0.105 or 0.0.0.0")]
  26. public string hardwareBindIp = "192.168.0.105";
  27. [Tooltip("UDP port where hardware sends JPEG packets.")]
  28. public int hardwarePort = 12346;
  29. [Tooltip("Timeout to reset incomplete JPEG (seconds).")]
  30. public float hardwareTimeoutSeconds = 2.0f;
  31. // ---------- 硬件控制:Unity -> 硬件(0x40 参数设置) ----------
  32. [Header("Hardware Control UDP (Unity -> Hardware)")]
  33. [Tooltip("硬件设备 IP(如 ESP32S3),用于发送 0x40 参数设置帧")]
  34. public string hardwareControlIp = "192.168.0.106";
  35. [Tooltip("硬件控制端口(硬件接收 0x40 的端口,如 8888)")]
  36. public int hardwareControlPort = 8888;
  37. [Header("Hardware Auto Apply (one-shot)")]
  38. [Tooltip("当检测到硬件开始发送 JPEG 帧时,是否自动将当前 transmissionConfig 以 0x40 下发给硬件(仅一次)。")]
  39. public bool autoApplyHardwareConfigOnFirstFrame = true;
  40. [Header("Python Output Mode")]
  41. [Tooltip("传输模式:Stdin(直接进程通信,推荐)或 UDP(传统模式)")]
  42. public TransferMode transferMode = TransferMode.Stdin;
  43. [Header("Python UDP (legacy mode: Unity -> Python)")]
  44. [Tooltip("Python process IP (normally localhost). Only used in UDP mode.")]
  45. public string pythonIp = "127.0.0.1";
  46. [Tooltip("Python UDP port to receive JPEG from Unity. Only used in UDP mode.")]
  47. public int pythonPort = 12347;
  48. [Header("Python Control (Unity -> Python control commands)")]
  49. [Tooltip("是否启用向Python发送控制指令(例如刷新参考图),需要Python脚本开启 --control_port")]
  50. public bool enablePythonControl = true;
  51. [Tooltip("Python 控制端口(需与 demo_lightglue_camera_position_async.py 的 --control_port 一致,例如 12349)")]
  52. public int pythonControlPort = 12349;
  53. [Tooltip("用于触发“刷新参考图”指令的按键(等价于 Python 端按下 n 键)")]
  54. public KeyCode refreshReferenceKey = KeyCode.N;
  55. [Header("基准图刷新方式与频率(由 Unity 触发)")]
  56. [Tooltip("基准图来源:仅摄像头(N键) / Python截屏 / Unity游戏画面")]
  57. public ReferenceSource referenceSource = ReferenceSource.CameraOnly;
  58. [Tooltip("基准图刷新间隔(秒),仅当来源为 Python截屏 或 Unity游戏画面 时生效")]
  59. [Range(0.01f, 60f)]
  60. public float referenceRefreshIntervalSeconds = 5f;
  61. [Tooltip("Unity游戏画面作为基准图时,用于截图的相机(空则使用 Camera.main)")]
  62. public Camera gameViewCamera;
  63. [Tooltip("基准图截图宽度(与 Python --resize 一致,如 640)")]
  64. public int referenceCaptureWidth = 640;
  65. [Tooltip("基准图截图高度(与 Python --resize 一致,如 480)")]
  66. public int referenceCaptureHeight = 480;
  67. [Header("Python Process (stdin mode: Unity -> Python)")]
  68. [Tooltip("Python进程控制器(用于stdin模式,直接传递图片数据)")]
  69. public PythonProcessController pythonProcessController;
  70. [Tooltip("为 true 时:收到首帧图像后再启动 Python(需 PythonProcessController.startOnFirstImageReceived 同时为 true),避免无图时启动算法。")]
  71. public bool startPythonOnFirstImageReceived = true;
  72. [Tooltip("Max queued frames in receiver (latest N only).")]
  73. public int maxQueuedFrames = 2;
  74. [Header("Python Result Receiver (Python -> Unity)")]
  75. [Tooltip("是否启用Python结果接收(接收算法计算结果)")]
  76. public bool enableResultReceiver = true;
  77. [Tooltip("Python结果接收端口(用于接收Python发送的算法结果,默认12348)")]
  78. public int pythonResultPort = 12348;
  79. [Tooltip("Python结果接收绑定IP(默认127.0.0.1)")]
  80. public string pythonResultBindIp = "127.0.0.1";
  81. [Tooltip("结果队列最大大小(只保留最新N个结果)")]
  82. public int maxResultQueueSize = 10;
  83. [Header("Image Transmission Config")]
  84. [Tooltip("图像传输配置(运行时唯一来源:可由 LightGlueManager.defaultTransmissionConfig 或 ImageTransmissionUIController 写入)")]
  85. public ImageTransmissionConfig transmissionConfig;
  86. public enum TransferMode
  87. {
  88. Stdin, // 通过stdin直接传递(推荐,无需UDP服务器)
  89. Udp // 通过UDP传递(传统模式)
  90. }
  91. /// <summary> 基准图来源:仅 N 键摄像头帧 / Python 截屏 / Unity 游戏画面 </summary>
  92. public enum ReferenceSource
  93. {
  94. CameraOnly, // 仅通过 N 键将当前摄像头帧设为基准图
  95. ScreenCapture, // Python 端截取整屏作为基准图(按刷新频率发 s)
  96. GameView // Unity 将当前游戏画面发给 Python 作为基准图(按刷新频率发 r+图像)
  97. }
  98. private UDPJpegReceiver _receiver;
  99. private UdpClient _udpSender; // Unity->Python UDP 模式
  100. private IPEndPoint _pythonEndpoint;
  101. private StreamWriter _stdinWriter; // Stdin 模式
  102. private UDPResultReceiver _resultReceiver; // Python->Unity 结果接收
  103. // 硬件控制:Unity -> 硬件 0x40 参数设置(与 JPEG 接收/转发独立)
  104. private UdpClient _hwControlClient;
  105. private IPEndPoint _hwControlEndpoint;
  106. // Python 控制:Unity -> Python 控制指令(刷新参考图)
  107. private UdpClient _pyControlClient;
  108. private IPEndPoint _pyControlEndpoint;
  109. // 基准图刷新(仅 GameView / ScreenCapture 时按间隔触发)
  110. private float _lastReferenceRefreshTime;
  111. private byte[] _pendingReferenceImageBytes;
  112. // 传输控制
  113. private float _lastSendTime;
  114. private bool _configEnabled = true;
  115. // 最新图片缓存(供Viewer使用)
  116. private byte[] _latestJpeg;
  117. private object _latestJpegLock = new object();
  118. private bool _autoHardwareConfigApplied;
  119. private bool _autoHardwareConfigReady;
  120. private bool _pythonStartedByFirstFrame;
  121. /// <summary>
  122. /// 获取最新的JPEG图片数据(供Viewer等组件使用)
  123. /// </summary>
  124. public byte[] GetLatestJpeg()
  125. {
  126. lock (_latestJpegLock)
  127. {
  128. return _latestJpeg != null ? (byte[])_latestJpeg.Clone() : null;
  129. }
  130. }
  131. /// <summary>
  132. /// 获取接收器(供Viewer共享使用)
  133. /// </summary>
  134. public UDPJpegReceiver Receiver => _receiver;
  135. /// <summary>
  136. /// 获取结果接收器(供其他组件使用)
  137. /// </summary>
  138. public UDPResultReceiver ResultReceiver => _resultReceiver;
  139. /// <summary>
  140. /// 尝试获取最新的算法结果(非阻塞)
  141. /// 每帧会排空结果队列并只使用最后一次取到的结果,避免队列积压导致显示旧位置(延迟移动)。
  142. /// </summary>
  143. public bool TryGetLatestResult(out LightGlueResult result)
  144. {
  145. result = default(LightGlueResult);
  146. if (_resultReceiver == null) return false;
  147. bool any = false;
  148. while (_resultReceiver.TryDequeueResult(out var r))
  149. {
  150. result = r;
  151. any = true;
  152. }
  153. return any;
  154. }
  155. private void Start()
  156. {
  157. // 自动加载网络配置
  158. if (autoLoadNetworkConfig)
  159. {
  160. LoadNetworkConfig();
  161. }
  162. // 在Start中初始化,确保所有组件都已启用
  163. // 如果使用stdin模式且Python进程控制器已配置但未运行,尝试启动
  164. if (transferMode == TransferMode.Stdin && pythonProcessController != null && !pythonProcessController.IsRunning)
  165. {
  166. if (pythonProcessController.autoStartOnEnable)
  167. {
  168. // 如果配置了自动启动,但还没启动,可能是启动顺序问题,延迟一点再检查
  169. StartCoroutine(DelayedStdinCheck());
  170. }
  171. else
  172. {
  173. Debug.LogWarning("[Bridge] Python process controller is set but not running. " +
  174. "Please ensure 'Auto Start On Enable' is enabled or manually start Python process.");
  175. }
  176. }
  177. }
  178. /// <summary>
  179. /// 从NetworkConfigManager加载网络配置
  180. /// </summary>
  181. private void LoadNetworkConfig()
  182. {
  183. try
  184. {
  185. NetworkConfig config = NetworkConfigManager.LoadConfig();
  186. if (config != null && config.Validate())
  187. {
  188. hardwareBindIp = config.hardwareBindIp;
  189. hardwarePort = config.hardwarePort;
  190. hardwareTimeoutSeconds = config.hardwareTimeoutSeconds;
  191. pythonIp = config.pythonIp;
  192. pythonPort = config.pythonPort;
  193. pythonResultBindIp = config.pythonResultBindIp;
  194. pythonResultPort = config.pythonResultPort;
  195. hardwareControlIp = config.hardwareControlIp;
  196. hardwareControlPort = config.hardwareControlPort;
  197. Debug.Log($"[Bridge] 已从配置文件加载网络配置: hardwareBindIp={hardwareBindIp}, hardwarePort={hardwarePort}, pythonIp={pythonIp}, pythonPort={pythonPort}, pythonResultPort={pythonResultPort}, hardwareControlIp={hardwareControlIp}, hardwareControlPort={hardwareControlPort}");
  198. }
  199. }
  200. catch (System.Exception ex)
  201. {
  202. Debug.LogWarning($"[Bridge] 加载网络配置失败,使用Inspector中的默认值: {ex.Message}");
  203. }
  204. }
  205. private System.Collections.IEnumerator DelayedStdinCheck()
  206. {
  207. // 等待一帧,让Python进程有时间启动
  208. yield return null;
  209. TryConnectStdin();
  210. }
  211. private void OnEnable()
  212. {
  213. // 游戏全屏时保持收发 UDP/进程通信,避免因失去焦点被系统节流导致 Python 端卡顿
  214. Application.runInBackground = true;
  215. _lastReferenceRefreshTime = Time.time;
  216. _autoHardwareConfigApplied = false;
  217. _autoHardwareConfigReady = false;
  218. _autoHardwareConfigApplied = false;
  219. // 创建 receiver 前先加载网络配置,保证首次运行与“应用配置重启”使用同一绑定地址,避免预览在重启后因 IP 不一致断流
  220. if (autoLoadNetworkConfig)
  221. {
  222. LoadNetworkConfig();
  223. }
  224. // 参考图 resize:与 Python --resize 一致
  225. ReferenceImageResizeService.EnsureInitialized();
  226. referenceCaptureWidth = ReferenceImageResizeService.Width;
  227. referenceCaptureHeight = ReferenceImageResizeService.Height;
  228. ReferenceImageResizeService.OnResizeChanged -= OnResizeChanged;
  229. ReferenceImageResizeService.OnResizeChanged += OnResizeChanged;
  230. // Hardware -> Unity receiver(端口占用时捕获异常,避免崩溃)
  231. _receiver = new UDPJpegReceiver(hardwareBindIp, hardwarePort, hardwareTimeoutSeconds, maxQueuedFrames);
  232. try
  233. {
  234. _receiver.Start();
  235. }
  236. catch (SocketException ex)
  237. {
  238. Debug.LogError($"[Bridge] 无法绑定硬件 JPEG 接收端口 {hardwareBindIp}:{hardwarePort},可能已被占用或存在重复的 LightGlueSystem。请确保场景中只通过 GameManager 创建一份预制,且未手动放置重复对象。\n{ex.Message}");
  239. try { _receiver?.Stop(); } catch { /* ignore */ }
  240. _receiver = null;
  241. }
  242. // Unity -> Python sender (根据模式选择)
  243. if (transferMode == TransferMode.Stdin)
  244. {
  245. TryConnectStdin();
  246. }
  247. else
  248. {
  249. // UDP模式(传统)
  250. var ip = IPAddress.Parse(string.IsNullOrWhiteSpace(pythonIp) ? "127.0.0.1" : pythonIp);
  251. _pythonEndpoint = new IPEndPoint(ip, pythonPort);
  252. _udpSender = new UdpClient();
  253. Debug.Log($"[Bridge] Hardware -> Unity listening on {hardwareBindIp}:{hardwarePort}");
  254. Debug.Log($"[Bridge] Unity -> Python: UDP mode sending to {_pythonEndpoint.Address}:{_pythonEndpoint.Port}");
  255. }
  256. // Unity -> 硬件:0x40 参数设置 UDP 客户端(与 JPEG 接收/转发独立)
  257. try
  258. {
  259. string ctrlIp = string.IsNullOrWhiteSpace(hardwareControlIp) ? "192.168.0.106" : hardwareControlIp;
  260. var ip = IPAddress.Parse(ctrlIp);
  261. _hwControlEndpoint = new IPEndPoint(ip, hardwareControlPort);
  262. _hwControlClient = new UdpClient();
  263. Debug.Log($"[Bridge] 硬件控制 UDP 已初始化 -> {_hwControlEndpoint.Address}:{_hwControlEndpoint.Port}(发送 0x40 参数设置到硬件)");
  264. }
  265. catch (Exception ex)
  266. {
  267. Debug.LogError($"[Bridge] 硬件控制 UDP 初始化失败: {ex.Message}");
  268. _hwControlClient = null;
  269. }
  270. // Unity -> Python 控制:简单控制指令(例如刷新参考图)
  271. if (enablePythonControl)
  272. {
  273. try
  274. {
  275. string targetIp = string.IsNullOrWhiteSpace(pythonIp) ? "127.0.0.1" : pythonIp;
  276. var ip = IPAddress.Parse(targetIp);
  277. _pyControlEndpoint = new IPEndPoint(ip, pythonControlPort);
  278. _pyControlClient = new UdpClient();
  279. Debug.Log($"[Bridge] Python 控制 UDP 已初始化 -> {_pyControlEndpoint.Address}:{_pyControlEndpoint.Port}(发送刷新参考图等控制指令)");
  280. }
  281. catch (Exception ex)
  282. {
  283. Debug.LogError($"[Bridge] Python 控制 UDP 初始化失败: {ex.Message}");
  284. _pyControlClient = null;
  285. }
  286. }
  287. // Python -> Unity result receiver
  288. if (enableResultReceiver)
  289. {
  290. try
  291. {
  292. _resultReceiver = new UDPResultReceiver(pythonResultBindIp, pythonResultPort, maxResultQueueSize);
  293. _resultReceiver.Start();
  294. Debug.Log($"[Bridge] Python -> Unity: Result receiver started on {pythonResultBindIp}:{pythonResultPort}");
  295. }
  296. catch (Exception ex)
  297. {
  298. // 端口占用或绑定失败时给出明确提示,避免与其它进程(例如 YejiDemo)抢占同一结果端口。
  299. Debug.LogError($"[Bridge] Failed to start result receiver on {pythonResultBindIp}:{pythonResultPort}. " +
  300. "The UDP port may already be in use by another process. " +
  301. $"Exception: {ex.Message}");
  302. try
  303. {
  304. _resultReceiver?.Stop();
  305. }
  306. catch
  307. {
  308. // ignore
  309. }
  310. _resultReceiver = null;
  311. }
  312. }
  313. }
  314. private void OnDestroy()
  315. {
  316. ReferenceImageResizeService.OnResizeChanged -= OnResizeChanged;
  317. }
  318. private void OnResizeChanged(int w, int h)
  319. {
  320. referenceCaptureWidth = w;
  321. referenceCaptureHeight = h;
  322. }
  323. private void TryConnectStdin()
  324. {
  325. // Stdin模式:从Python进程控制器获取stdin写入流
  326. if (pythonProcessController != null)
  327. {
  328. if (pythonProcessController.IsRunning)
  329. {
  330. _stdinWriter = pythonProcessController.StdinWriter;
  331. if (_stdinWriter != null)
  332. {
  333. Debug.Log($"[Bridge] Hardware -> Unity listening on {hardwareBindIp}:{hardwarePort}");
  334. Debug.Log($"[Bridge] Unity -> Python: Stdin mode connected (direct process communication)");
  335. }
  336. else
  337. {
  338. Debug.LogWarning("[Bridge] Python process stdin not available. Make sure Python process is started.");
  339. }
  340. }
  341. else
  342. {
  343. Debug.LogWarning("[Bridge] Python process controller is set but Python process is not running. " +
  344. "Will retry in Update(). Make sure 'Auto Start On Enable' is enabled in PythonProcessController.");
  345. }
  346. }
  347. else
  348. {
  349. Debug.LogWarning("[Bridge] Python process controller not set. Please assign PythonProcessController component in Inspector.");
  350. }
  351. }
  352. private void OnDisable()
  353. {
  354. try { _receiver?.Stop(); } catch { /* ignore */ }
  355. _receiver = null;
  356. try { _resultReceiver?.Stop(); } catch { /* ignore */ }
  357. _resultReceiver = null;
  358. try { _hwControlClient?.Close(); } catch { /* ignore */ }
  359. _hwControlClient = null;
  360. try { _pyControlClient?.Close(); } catch { /* ignore */ }
  361. _pyControlClient = null;
  362. _stdinWriter = null;
  363. try { _udpSender?.Close(); } catch { /* ignore */ }
  364. _udpSender = null;
  365. _pythonStartedByFirstFrame = false;
  366. ReferenceImageResizeService.OnResizeChanged -= OnResizeChanged;
  367. }
  368. /// <summary>
  369. /// 收到首帧图像时调用:若启用「首帧后再启动 Python」则启动一次,之后不再重复。
  370. /// </summary>
  371. private void TryStartPythonOnFirstFrame()
  372. {
  373. if (_pythonStartedByFirstFrame) return;
  374. if (!startPythonOnFirstImageReceived || pythonProcessController == null || pythonProcessController.IsRunning)
  375. return;
  376. if (pythonProcessController.startOnFirstImageReceived)
  377. {
  378. pythonProcessController.StartPython();
  379. _pythonStartedByFirstFrame = true;
  380. Debug.Log("[Bridge] 已收到首帧图像,启动 Python 进程。");
  381. }
  382. }
  383. // ---------- 硬件控制:下发 0x40 参数设置(供 ImageTransmissionUIController 等调用) ----------
  384. /// <summary>
  385. /// 将图像传输配置以 0x40 协议帧下发给硬件(分辨率/质量/间隔/开关)。
  386. /// 调用时机:用户点击“应用配置”等。
  387. /// </summary>
  388. public void ApplyConfig(ImageTransmissionConfig config)
  389. {
  390. if (_hwControlClient == null || _hwControlEndpoint == null || config == null)
  391. {
  392. Debug.LogWarning("[Bridge] 硬件控制未就绪或 config 为空,无法发送 0x40");
  393. return;
  394. }
  395. try
  396. {
  397. byte[] frame = BuildParameterSettingFrame(config);
  398. if (frame != null && frame.Length > 0)
  399. {
  400. _hwControlClient.Send(frame, frame.Length, _hwControlEndpoint);
  401. Debug.Log($"[Bridge] 已发送 0x40 参数帧 -> {_hwControlEndpoint.Address}:{_hwControlEndpoint.Port}, enable={config.enableImageTransmission}, res=0x{config.GetHardwareResolutionCode():X2}, quality=0x{config.GetHardwareQuality():X2}, interval={config.reportIntervalMs}ms");
  402. }
  403. }
  404. catch (SocketException ex)
  405. {
  406. Debug.LogWarning($"[Bridge] 硬件控制 UDP 发送异常: {ex.SocketErrorCode} {ex.Message}");
  407. }
  408. catch (Exception ex)
  409. {
  410. Debug.LogWarning($"[Bridge] 硬件控制发送失败: {ex.Message}");
  411. }
  412. }
  413. /// <summary>
  414. /// 构建 0x40 参数设置帧(报头 0x7B + 类型 0x40 + 长度 + 总包/分包 + 标志 0xFF + 数据 20 字节 + 校验 + 报尾 0x7D)
  415. /// </summary>
  416. private static byte[] BuildParameterSettingFrame(ImageTransmissionConfig config)
  417. {
  418. if (config == null) return Array.Empty<byte>();
  419. const byte HEADER = 0x7B;
  420. const byte PROTOCOL_TYPE = 0x40;
  421. const byte TOTAL_PACKETS = 1;
  422. const byte PACKET_INDEX = 1;
  423. const byte FLAG = 0xFF;
  424. const byte FOOTER = 0x7D;
  425. const int DATA_SIZE = 20;
  426. byte[] data = new byte[DATA_SIZE];
  427. data[0] = (byte)(config.enableImageTransmission ? 1 : 0);
  428. data[1] = 0;
  429. data[2] = config.GetHardwareResolutionCode();
  430. data[3] = config.GetHardwareQuality();
  431. data[4] = (byte)Mathf.Clamp(config.reportIntervalMs, 0, 255);
  432. ushort dataLength = (ushort)DATA_SIZE;
  433. byte[] frame = new byte[29];
  434. int offset = 0;
  435. frame[offset++] = HEADER;
  436. frame[offset++] = PROTOCOL_TYPE;
  437. frame[offset++] = (byte)(dataLength & 0xFF);
  438. frame[offset++] = (byte)((dataLength >> 8) & 0xFF);
  439. frame[offset++] = TOTAL_PACKETS;
  440. frame[offset++] = PACKET_INDEX;
  441. frame[offset++] = FLAG;
  442. Buffer.BlockCopy(data, 0, frame, offset, data.Length);
  443. offset += data.Length;
  444. byte checksum = 0;
  445. for (int i = 1; i < offset; i++)
  446. checksum = (byte)(checksum + frame[i]);
  447. frame[offset++] = checksum;
  448. frame[offset++] = FOOTER;
  449. return frame;
  450. }
  451. private void Update()
  452. {
  453. if (_receiver == null) return;
  454. // 处理来自 Unity 的刷新参考图:N 键 或 按间隔发送 s/r
  455. if (enablePythonControl && _pyControlClient != null && _pyControlEndpoint != null)
  456. {
  457. if (Input.GetKeyDown(refreshReferenceKey))
  458. {
  459. SendControlCommand('n');
  460. }
  461. else if (referenceSource == ReferenceSource.ScreenCapture || referenceSource == ReferenceSource.GameView)
  462. {
  463. float now = Time.time;
  464. if (now - _lastReferenceRefreshTime >= referenceRefreshIntervalSeconds)
  465. {
  466. _lastReferenceRefreshTime = now;
  467. if (referenceSource == ReferenceSource.ScreenCapture)
  468. {
  469. SendControlCommand('s');
  470. }
  471. else
  472. {
  473. byte[] gameViewJpeg = CaptureGameViewToJpeg();
  474. if (gameViewJpeg != null && gameViewJpeg.Length > 0)
  475. {
  476. _pendingReferenceImageBytes = gameViewJpeg;
  477. SendControlCommand('r');
  478. }
  479. }
  480. }
  481. }
  482. }
  483. // 检查发送端是否可用(根据模式)
  484. if (transferMode == TransferMode.Stdin)
  485. {
  486. if (_stdinWriter == null)
  487. {
  488. // 尝试重新获取stdin写入流(如果Python进程刚启动)
  489. if (pythonProcessController != null && pythonProcessController.IsRunning)
  490. {
  491. _stdinWriter = pythonProcessController.StdinWriter;
  492. }
  493. if (_stdinWriter == null) return;
  494. }
  495. }
  496. else
  497. {
  498. if (_udpSender == null) return;
  499. }
  500. // 检查配置是否启用传输
  501. bool shouldTransmit = _configEnabled;
  502. if (transmissionConfig != null)
  503. {
  504. shouldTransmit = transmissionConfig.enableImageTransmission;
  505. }
  506. if (!shouldTransmit)
  507. {
  508. byte[] last = null;
  509. while (_receiver.TryDequeueJpeg(out var j)) { last = j; }
  510. if (last != null)
  511. {
  512. lock (_latestJpegLock) { _latestJpeg = last; }
  513. TryStartPythonOnFirstFrame();
  514. TryAutoApplyHardwareConfigOnce();
  515. }
  516. return;
  517. }
  518. float currentTime = Time.time * 1000f;
  519. bool hasPendingReference = _pendingReferenceImageBytes != null;
  520. if (!hasPendingReference && transmissionConfig != null)
  521. {
  522. float intervalMs = transmissionConfig.reportIntervalMs;
  523. if (intervalMs > 0 && (currentTime - _lastSendTime) < intervalMs)
  524. {
  525. byte[] last = null;
  526. while (_receiver.TryDequeueJpeg(out var j)) { last = j; }
  527. if (last != null)
  528. {
  529. lock (_latestJpegLock) { _latestJpeg = last; }
  530. TryStartPythonOnFirstFrame();
  531. TryAutoApplyHardwareConfigOnce();
  532. }
  533. return;
  534. }
  535. }
  536. byte[] toSend = null;
  537. if (_pendingReferenceImageBytes != null)
  538. {
  539. toSend = _pendingReferenceImageBytes;
  540. _pendingReferenceImageBytes = null;
  541. }
  542. else
  543. {
  544. byte[] latest = null;
  545. while (_receiver.TryDequeueJpeg(out var jpeg))
  546. latest = jpeg;
  547. if (latest == null) return;
  548. TryStartPythonOnFirstFrame();
  549. TryAutoApplyHardwareConfigOnce();
  550. toSend = latest;
  551. lock (_latestJpegLock)
  552. {
  553. _latestJpeg = latest;
  554. }
  555. }
  556. byte[] processedImage = ProcessImage(toSend);
  557. // 仅在 Python 已就绪接收时发送,避免 TensorRT 编译期间发图导致 UDP 缓冲堆积、后续解码损坏
  558. if (pythonProcessController != null && !pythonProcessController.IsReadyToReceiveFrames)
  559. return;
  560. try
  561. {
  562. if (transferMode == TransferMode.Stdin)
  563. {
  564. if (_stdinWriter != null)
  565. {
  566. _stdinWriter.BaseStream.Write(processedImage, 0, processedImage.Length);
  567. _stdinWriter.BaseStream.Flush();
  568. _lastSendTime = currentTime;
  569. }
  570. }
  571. else
  572. {
  573. _udpSender.Send(processedImage, processedImage.Length, _pythonEndpoint);
  574. _lastSendTime = currentTime;
  575. }
  576. }
  577. catch (SocketException ex)
  578. {
  579. Debug.LogWarning($"[Bridge] UDP send error: {ex.SocketErrorCode} {ex.Message}");
  580. }
  581. catch (IOException ex)
  582. {
  583. Debug.LogWarning($"[Bridge] Stdin write error: {ex.Message}");
  584. _stdinWriter = null;
  585. }
  586. }
  587. /// <summary>
  588. /// 标记“自动下发硬件图像配置”的前置条件已就绪(由 ImageTransmissionUIController 初始化完成后调用)。
  589. /// </summary>
  590. public void MarkHardwareAutoApplyReady()
  591. {
  592. _autoHardwareConfigReady = true;
  593. }
  594. private void TryAutoApplyHardwareConfigOnce()
  595. {
  596. if (_autoHardwareConfigApplied) return;
  597. if (!autoApplyHardwareConfigOnFirstFrame) return;
  598. if (!_autoHardwareConfigReady) return;
  599. if (transmissionConfig == null) return;
  600. if (_hwControlClient == null || _hwControlEndpoint == null) return;
  601. // 注意:ApplyConfig 内部会做一次基本校验并捕获异常;此处只要硬件控制已就绪就认为可尝试下发。
  602. ApplyConfig(transmissionConfig);
  603. _autoHardwareConfigApplied = true;
  604. Debug.Log("[Bridge] 已在检测到硬件首帧后自动下发一次 0x40 图像配置。");
  605. }
  606. /// <summary>
  607. /// 根据配置处理图像(分辨率调整和质量压缩)
  608. /// 注意:Unity的ImageConversion.EncodeToJPG需要Texture2D,这里先返回原始数据
  609. /// 如果需要真正的resize和quality控制,需要额外的图像处理库
  610. /// </summary>
  611. private byte[] ProcessImage(byte[] jpegBytes)
  612. {
  613. if (transmissionConfig == null)
  614. return jpegBytes;
  615. // TODO: 实现图像resize和quality压缩
  616. // Unity原生不支持JPEG的resize和quality调整,需要:
  617. // 1. 使用Texture2D.LoadImage解码JPEG
  618. // 2. 使用Texture2D.GetPixels/SetPixels进行resize
  619. // 3. 使用ImageConversion.EncodeToJPG进行质量压缩
  620. // 但这个过程可能较慢,建议在Python端处理
  621. // 当前实现:直接返回原始数据,配置参数可用于Python端处理
  622. // 如果需要Unity端处理,可以在这里添加图像处理逻辑
  623. return jpegBytes;
  624. }
  625. /// <summary>
  626. /// 设置传输配置(供UI控制器调用)
  627. /// </summary>
  628. public void SetTransmissionConfig(ImageTransmissionConfig config)
  629. {
  630. transmissionConfig = config;
  631. _configEnabled = config != null && config.enableImageTransmission;
  632. Debug.Log($"[Bridge] 传输配置已更新: 分辨率={config?.GetResolutionString()}, 质量={config?.quality}, 间隔={config?.reportIntervalMs}ms, 启用={_configEnabled}");
  633. // 配置一旦设置就立即下发 0x40,避免硬件用默认参数发图导致首段画面不完整、闪烁,直到用户手动点「应用」才正常
  634. if (config != null && _hwControlClient != null && _hwControlEndpoint != null)
  635. {
  636. ApplyConfig(config);
  637. }
  638. }
  639. private void SendControlCommand(char cmd)
  640. {
  641. if (_pyControlClient == null || _pyControlEndpoint == null) return;
  642. try
  643. {
  644. byte[] payload = { (byte)cmd };
  645. _pyControlClient.Send(payload, payload.Length, _pyControlEndpoint);
  646. }
  647. catch (Exception ex)
  648. {
  649. Debug.LogWarning($"[Bridge] Python 控制指令发送失败: {ex.Message}");
  650. }
  651. }
  652. private byte[] CaptureGameViewToJpeg()
  653. {
  654. Camera cam = gameViewCamera != null ? gameViewCamera : Camera.main;
  655. if (cam == null) return null;
  656. int w = Mathf.Clamp(referenceCaptureWidth, 64, 4096);
  657. int h = Mathf.Clamp(referenceCaptureHeight, 64, 4096);
  658. RenderTexture rt = RenderTexture.GetTemporary(w, h, 24, RenderTextureFormat.ARGB32);
  659. RenderTexture prevTarget = cam.targetTexture;
  660. RenderTexture prevActive = RenderTexture.active;
  661. cam.targetTexture = rt;
  662. cam.Render();
  663. RenderTexture.active = rt;
  664. Texture2D tex = new Texture2D(w, h, TextureFormat.RGB24, false);
  665. tex.ReadPixels(new Rect(0, 0, w, h), 0, 0);
  666. tex.Apply();
  667. cam.targetTexture = prevTarget;
  668. RenderTexture.active = prevActive;
  669. RenderTexture.ReleaseTemporary(rt);
  670. int quality = transmissionConfig != null ? Mathf.Clamp(transmissionConfig.quality, 1, 100) : 80;
  671. byte[] jpeg = tex.EncodeToJPG(quality);
  672. Destroy(tex);
  673. return jpeg;
  674. }
  675. }
  676. }