HardwareToPythonUdpBridge.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  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. // Hardware -> Unity receiver(端口占用时捕获异常,避免崩溃)
  225. _receiver = new UDPJpegReceiver(hardwareBindIp, hardwarePort, hardwareTimeoutSeconds, maxQueuedFrames);
  226. try
  227. {
  228. _receiver.Start();
  229. }
  230. catch (SocketException ex)
  231. {
  232. Debug.LogError($"[Bridge] 无法绑定硬件 JPEG 接收端口 {hardwareBindIp}:{hardwarePort},可能已被占用或存在重复的 LightGlueSystem。请确保场景中只通过 GameManager 创建一份预制,且未手动放置重复对象。\n{ex.Message}");
  233. try { _receiver?.Stop(); } catch { /* ignore */ }
  234. _receiver = null;
  235. }
  236. // Unity -> Python sender (根据模式选择)
  237. if (transferMode == TransferMode.Stdin)
  238. {
  239. TryConnectStdin();
  240. }
  241. else
  242. {
  243. // UDP模式(传统)
  244. var ip = IPAddress.Parse(string.IsNullOrWhiteSpace(pythonIp) ? "127.0.0.1" : pythonIp);
  245. _pythonEndpoint = new IPEndPoint(ip, pythonPort);
  246. _udpSender = new UdpClient();
  247. Debug.Log($"[Bridge] Hardware -> Unity listening on {hardwareBindIp}:{hardwarePort}");
  248. Debug.Log($"[Bridge] Unity -> Python: UDP mode sending to {_pythonEndpoint.Address}:{_pythonEndpoint.Port}");
  249. }
  250. // Unity -> 硬件:0x40 参数设置 UDP 客户端(与 JPEG 接收/转发独立)
  251. try
  252. {
  253. string ctrlIp = string.IsNullOrWhiteSpace(hardwareControlIp) ? "192.168.0.106" : hardwareControlIp;
  254. var ip = IPAddress.Parse(ctrlIp);
  255. _hwControlEndpoint = new IPEndPoint(ip, hardwareControlPort);
  256. _hwControlClient = new UdpClient();
  257. Debug.Log($"[Bridge] 硬件控制 UDP 已初始化 -> {_hwControlEndpoint.Address}:{_hwControlEndpoint.Port}(发送 0x40 参数设置到硬件)");
  258. }
  259. catch (Exception ex)
  260. {
  261. Debug.LogError($"[Bridge] 硬件控制 UDP 初始化失败: {ex.Message}");
  262. _hwControlClient = null;
  263. }
  264. // Unity -> Python 控制:简单控制指令(例如刷新参考图)
  265. if (enablePythonControl)
  266. {
  267. try
  268. {
  269. string targetIp = string.IsNullOrWhiteSpace(pythonIp) ? "127.0.0.1" : pythonIp;
  270. var ip = IPAddress.Parse(targetIp);
  271. _pyControlEndpoint = new IPEndPoint(ip, pythonControlPort);
  272. _pyControlClient = new UdpClient();
  273. Debug.Log($"[Bridge] Python 控制 UDP 已初始化 -> {_pyControlEndpoint.Address}:{_pyControlEndpoint.Port}(发送刷新参考图等控制指令)");
  274. }
  275. catch (Exception ex)
  276. {
  277. Debug.LogError($"[Bridge] Python 控制 UDP 初始化失败: {ex.Message}");
  278. _pyControlClient = null;
  279. }
  280. }
  281. // Python -> Unity result receiver
  282. if (enableResultReceiver)
  283. {
  284. try
  285. {
  286. _resultReceiver = new UDPResultReceiver(pythonResultBindIp, pythonResultPort, maxResultQueueSize);
  287. _resultReceiver.Start();
  288. Debug.Log($"[Bridge] Python -> Unity: Result receiver started on {pythonResultBindIp}:{pythonResultPort}");
  289. }
  290. catch (Exception ex)
  291. {
  292. // 端口占用或绑定失败时给出明确提示,避免与其它进程(例如 YejiDemo)抢占同一结果端口。
  293. Debug.LogError($"[Bridge] Failed to start result receiver on {pythonResultBindIp}:{pythonResultPort}. " +
  294. "The UDP port may already be in use by another process. " +
  295. $"Exception: {ex.Message}");
  296. try
  297. {
  298. _resultReceiver?.Stop();
  299. }
  300. catch
  301. {
  302. // ignore
  303. }
  304. _resultReceiver = null;
  305. }
  306. }
  307. }
  308. private void TryConnectStdin()
  309. {
  310. // Stdin模式:从Python进程控制器获取stdin写入流
  311. if (pythonProcessController != null)
  312. {
  313. if (pythonProcessController.IsRunning)
  314. {
  315. _stdinWriter = pythonProcessController.StdinWriter;
  316. if (_stdinWriter != null)
  317. {
  318. Debug.Log($"[Bridge] Hardware -> Unity listening on {hardwareBindIp}:{hardwarePort}");
  319. Debug.Log($"[Bridge] Unity -> Python: Stdin mode connected (direct process communication)");
  320. }
  321. else
  322. {
  323. Debug.LogWarning("[Bridge] Python process stdin not available. Make sure Python process is started.");
  324. }
  325. }
  326. else
  327. {
  328. Debug.LogWarning("[Bridge] Python process controller is set but Python process is not running. " +
  329. "Will retry in Update(). Make sure 'Auto Start On Enable' is enabled in PythonProcessController.");
  330. }
  331. }
  332. else
  333. {
  334. Debug.LogWarning("[Bridge] Python process controller not set. Please assign PythonProcessController component in Inspector.");
  335. }
  336. }
  337. private void OnDisable()
  338. {
  339. try { _receiver?.Stop(); } catch { /* ignore */ }
  340. _receiver = null;
  341. try { _resultReceiver?.Stop(); } catch { /* ignore */ }
  342. _resultReceiver = null;
  343. try { _hwControlClient?.Close(); } catch { /* ignore */ }
  344. _hwControlClient = null;
  345. try { _pyControlClient?.Close(); } catch { /* ignore */ }
  346. _pyControlClient = null;
  347. _stdinWriter = null;
  348. try { _udpSender?.Close(); } catch { /* ignore */ }
  349. _udpSender = null;
  350. _pythonStartedByFirstFrame = false;
  351. }
  352. /// <summary>
  353. /// 收到首帧图像时调用:若启用「首帧后再启动 Python」则启动一次,之后不再重复。
  354. /// </summary>
  355. private void TryStartPythonOnFirstFrame()
  356. {
  357. if (_pythonStartedByFirstFrame) return;
  358. if (!startPythonOnFirstImageReceived || pythonProcessController == null || pythonProcessController.IsRunning)
  359. return;
  360. if (pythonProcessController.startOnFirstImageReceived)
  361. {
  362. pythonProcessController.StartPython();
  363. _pythonStartedByFirstFrame = true;
  364. Debug.Log("[Bridge] 已收到首帧图像,启动 Python 进程。");
  365. }
  366. }
  367. // ---------- 硬件控制:下发 0x40 参数设置(供 ImageTransmissionUIController 等调用) ----------
  368. /// <summary>
  369. /// 将图像传输配置以 0x40 协议帧下发给硬件(分辨率/质量/间隔/开关)。
  370. /// 调用时机:用户点击“应用配置”等。
  371. /// </summary>
  372. public void ApplyConfig(ImageTransmissionConfig config)
  373. {
  374. if (_hwControlClient == null || _hwControlEndpoint == null || config == null)
  375. {
  376. Debug.LogWarning("[Bridge] 硬件控制未就绪或 config 为空,无法发送 0x40");
  377. return;
  378. }
  379. try
  380. {
  381. byte[] frame = BuildParameterSettingFrame(config);
  382. if (frame != null && frame.Length > 0)
  383. {
  384. _hwControlClient.Send(frame, frame.Length, _hwControlEndpoint);
  385. 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");
  386. }
  387. }
  388. catch (SocketException ex)
  389. {
  390. Debug.LogWarning($"[Bridge] 硬件控制 UDP 发送异常: {ex.SocketErrorCode} {ex.Message}");
  391. }
  392. catch (Exception ex)
  393. {
  394. Debug.LogWarning($"[Bridge] 硬件控制发送失败: {ex.Message}");
  395. }
  396. }
  397. /// <summary>
  398. /// 构建 0x40 参数设置帧(报头 0x7B + 类型 0x40 + 长度 + 总包/分包 + 标志 0xFF + 数据 20 字节 + 校验 + 报尾 0x7D)
  399. /// </summary>
  400. private static byte[] BuildParameterSettingFrame(ImageTransmissionConfig config)
  401. {
  402. if (config == null) return Array.Empty<byte>();
  403. const byte HEADER = 0x7B;
  404. const byte PROTOCOL_TYPE = 0x40;
  405. const byte TOTAL_PACKETS = 1;
  406. const byte PACKET_INDEX = 1;
  407. const byte FLAG = 0xFF;
  408. const byte FOOTER = 0x7D;
  409. const int DATA_SIZE = 20;
  410. byte[] data = new byte[DATA_SIZE];
  411. data[0] = (byte)(config.enableImageTransmission ? 1 : 0);
  412. data[1] = 0;
  413. data[2] = config.GetHardwareResolutionCode();
  414. data[3] = config.GetHardwareQuality();
  415. data[4] = (byte)Mathf.Clamp(config.reportIntervalMs, 0, 255);
  416. ushort dataLength = (ushort)DATA_SIZE;
  417. byte[] frame = new byte[29];
  418. int offset = 0;
  419. frame[offset++] = HEADER;
  420. frame[offset++] = PROTOCOL_TYPE;
  421. frame[offset++] = (byte)(dataLength & 0xFF);
  422. frame[offset++] = (byte)((dataLength >> 8) & 0xFF);
  423. frame[offset++] = TOTAL_PACKETS;
  424. frame[offset++] = PACKET_INDEX;
  425. frame[offset++] = FLAG;
  426. Buffer.BlockCopy(data, 0, frame, offset, data.Length);
  427. offset += data.Length;
  428. byte checksum = 0;
  429. for (int i = 1; i < offset; i++)
  430. checksum = (byte)(checksum + frame[i]);
  431. frame[offset++] = checksum;
  432. frame[offset++] = FOOTER;
  433. return frame;
  434. }
  435. private void Update()
  436. {
  437. if (_receiver == null) return;
  438. // 处理来自 Unity 的刷新参考图:N 键 或 按间隔发送 s/r
  439. if (enablePythonControl && _pyControlClient != null && _pyControlEndpoint != null)
  440. {
  441. if (Input.GetKeyDown(refreshReferenceKey))
  442. {
  443. SendControlCommand('n');
  444. }
  445. else if (referenceSource == ReferenceSource.ScreenCapture || referenceSource == ReferenceSource.GameView)
  446. {
  447. float now = Time.time;
  448. if (now - _lastReferenceRefreshTime >= referenceRefreshIntervalSeconds)
  449. {
  450. _lastReferenceRefreshTime = now;
  451. if (referenceSource == ReferenceSource.ScreenCapture)
  452. {
  453. SendControlCommand('s');
  454. }
  455. else
  456. {
  457. byte[] gameViewJpeg = CaptureGameViewToJpeg();
  458. if (gameViewJpeg != null && gameViewJpeg.Length > 0)
  459. {
  460. _pendingReferenceImageBytes = gameViewJpeg;
  461. SendControlCommand('r');
  462. }
  463. }
  464. }
  465. }
  466. }
  467. // 检查发送端是否可用(根据模式)
  468. if (transferMode == TransferMode.Stdin)
  469. {
  470. if (_stdinWriter == null)
  471. {
  472. // 尝试重新获取stdin写入流(如果Python进程刚启动)
  473. if (pythonProcessController != null && pythonProcessController.IsRunning)
  474. {
  475. _stdinWriter = pythonProcessController.StdinWriter;
  476. }
  477. if (_stdinWriter == null) return;
  478. }
  479. }
  480. else
  481. {
  482. if (_udpSender == null) return;
  483. }
  484. // 检查配置是否启用传输
  485. bool shouldTransmit = _configEnabled;
  486. if (transmissionConfig != null)
  487. {
  488. shouldTransmit = transmissionConfig.enableImageTransmission;
  489. }
  490. if (!shouldTransmit)
  491. {
  492. byte[] last = null;
  493. while (_receiver.TryDequeueJpeg(out var j)) { last = j; }
  494. if (last != null)
  495. {
  496. lock (_latestJpegLock) { _latestJpeg = last; }
  497. TryStartPythonOnFirstFrame();
  498. TryAutoApplyHardwareConfigOnce();
  499. }
  500. return;
  501. }
  502. float currentTime = Time.time * 1000f;
  503. bool hasPendingReference = _pendingReferenceImageBytes != null;
  504. if (!hasPendingReference && transmissionConfig != null)
  505. {
  506. float intervalMs = transmissionConfig.reportIntervalMs;
  507. if (intervalMs > 0 && (currentTime - _lastSendTime) < intervalMs)
  508. {
  509. byte[] last = null;
  510. while (_receiver.TryDequeueJpeg(out var j)) { last = j; }
  511. if (last != null)
  512. {
  513. lock (_latestJpegLock) { _latestJpeg = last; }
  514. TryStartPythonOnFirstFrame();
  515. TryAutoApplyHardwareConfigOnce();
  516. }
  517. return;
  518. }
  519. }
  520. byte[] toSend = null;
  521. if (_pendingReferenceImageBytes != null)
  522. {
  523. toSend = _pendingReferenceImageBytes;
  524. _pendingReferenceImageBytes = null;
  525. }
  526. else
  527. {
  528. byte[] latest = null;
  529. while (_receiver.TryDequeueJpeg(out var jpeg))
  530. latest = jpeg;
  531. if (latest == null) return;
  532. TryStartPythonOnFirstFrame();
  533. TryAutoApplyHardwareConfigOnce();
  534. toSend = latest;
  535. lock (_latestJpegLock)
  536. {
  537. _latestJpeg = latest;
  538. }
  539. }
  540. byte[] processedImage = ProcessImage(toSend);
  541. // 仅在 Python 已就绪接收时发送,避免 TensorRT 编译期间发图导致 UDP 缓冲堆积、后续解码损坏
  542. if (pythonProcessController != null && !pythonProcessController.IsReadyToReceiveFrames)
  543. return;
  544. try
  545. {
  546. if (transferMode == TransferMode.Stdin)
  547. {
  548. if (_stdinWriter != null)
  549. {
  550. _stdinWriter.BaseStream.Write(processedImage, 0, processedImage.Length);
  551. _stdinWriter.BaseStream.Flush();
  552. _lastSendTime = currentTime;
  553. }
  554. }
  555. else
  556. {
  557. _udpSender.Send(processedImage, processedImage.Length, _pythonEndpoint);
  558. _lastSendTime = currentTime;
  559. }
  560. }
  561. catch (SocketException ex)
  562. {
  563. Debug.LogWarning($"[Bridge] UDP send error: {ex.SocketErrorCode} {ex.Message}");
  564. }
  565. catch (IOException ex)
  566. {
  567. Debug.LogWarning($"[Bridge] Stdin write error: {ex.Message}");
  568. _stdinWriter = null;
  569. }
  570. }
  571. /// <summary>
  572. /// 标记“自动下发硬件图像配置”的前置条件已就绪(由 ImageTransmissionUIController 初始化完成后调用)。
  573. /// </summary>
  574. public void MarkHardwareAutoApplyReady()
  575. {
  576. _autoHardwareConfigReady = true;
  577. }
  578. private void TryAutoApplyHardwareConfigOnce()
  579. {
  580. if (_autoHardwareConfigApplied) return;
  581. if (!autoApplyHardwareConfigOnFirstFrame) return;
  582. if (!_autoHardwareConfigReady) return;
  583. if (transmissionConfig == null) return;
  584. if (_hwControlClient == null || _hwControlEndpoint == null) return;
  585. // 注意:ApplyConfig 内部会做一次基本校验并捕获异常;此处只要硬件控制已就绪就认为可尝试下发。
  586. ApplyConfig(transmissionConfig);
  587. _autoHardwareConfigApplied = true;
  588. Debug.Log("[Bridge] 已在检测到硬件首帧后自动下发一次 0x40 图像配置。");
  589. }
  590. /// <summary>
  591. /// 根据配置处理图像(分辨率调整和质量压缩)
  592. /// 注意:Unity的ImageConversion.EncodeToJPG需要Texture2D,这里先返回原始数据
  593. /// 如果需要真正的resize和quality控制,需要额外的图像处理库
  594. /// </summary>
  595. private byte[] ProcessImage(byte[] jpegBytes)
  596. {
  597. if (transmissionConfig == null)
  598. return jpegBytes;
  599. // TODO: 实现图像resize和quality压缩
  600. // Unity原生不支持JPEG的resize和quality调整,需要:
  601. // 1. 使用Texture2D.LoadImage解码JPEG
  602. // 2. 使用Texture2D.GetPixels/SetPixels进行resize
  603. // 3. 使用ImageConversion.EncodeToJPG进行质量压缩
  604. // 但这个过程可能较慢,建议在Python端处理
  605. // 当前实现:直接返回原始数据,配置参数可用于Python端处理
  606. // 如果需要Unity端处理,可以在这里添加图像处理逻辑
  607. return jpegBytes;
  608. }
  609. /// <summary>
  610. /// 设置传输配置(供UI控制器调用)
  611. /// </summary>
  612. public void SetTransmissionConfig(ImageTransmissionConfig config)
  613. {
  614. transmissionConfig = config;
  615. _configEnabled = config != null && config.enableImageTransmission;
  616. Debug.Log($"[Bridge] 传输配置已更新: 分辨率={config?.GetResolutionString()}, 质量={config?.quality}, 间隔={config?.reportIntervalMs}ms, 启用={_configEnabled}");
  617. // 配置一旦设置就立即下发 0x40,避免硬件用默认参数发图导致首段画面不完整、闪烁,直到用户手动点「应用」才正常
  618. if (config != null && _hwControlClient != null && _hwControlEndpoint != null)
  619. {
  620. ApplyConfig(config);
  621. }
  622. }
  623. private void SendControlCommand(char cmd)
  624. {
  625. if (_pyControlClient == null || _pyControlEndpoint == null) return;
  626. try
  627. {
  628. byte[] payload = { (byte)cmd };
  629. _pyControlClient.Send(payload, payload.Length, _pyControlEndpoint);
  630. }
  631. catch (Exception ex)
  632. {
  633. Debug.LogWarning($"[Bridge] Python 控制指令发送失败: {ex.Message}");
  634. }
  635. }
  636. private byte[] CaptureGameViewToJpeg()
  637. {
  638. Camera cam = gameViewCamera != null ? gameViewCamera : Camera.main;
  639. if (cam == null) return null;
  640. int w = Mathf.Clamp(referenceCaptureWidth, 64, 4096);
  641. int h = Mathf.Clamp(referenceCaptureHeight, 64, 4096);
  642. RenderTexture rt = RenderTexture.GetTemporary(w, h, 24, RenderTextureFormat.ARGB32);
  643. RenderTexture prevTarget = cam.targetTexture;
  644. RenderTexture prevActive = RenderTexture.active;
  645. cam.targetTexture = rt;
  646. cam.Render();
  647. RenderTexture.active = rt;
  648. Texture2D tex = new Texture2D(w, h, TextureFormat.RGB24, false);
  649. tex.ReadPixels(new Rect(0, 0, w, h), 0, 0);
  650. tex.Apply();
  651. cam.targetTexture = prevTarget;
  652. RenderTexture.active = prevActive;
  653. RenderTexture.ReleaseTemporary(rt);
  654. int quality = transmissionConfig != null ? Mathf.Clamp(transmissionConfig.quality, 1, 100) : 80;
  655. byte[] jpeg = tex.EncodeToJPG(quality);
  656. Destroy(tex);
  657. return jpeg;
  658. }
  659. }
  660. }