| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779 |
- using System;
- using System.IO;
- using System.Net;
- using System.Net.Sockets;
- using LightGlue.Unity.Networking;
- using LightGlue.Unity.Config;
- using LightGlue.Unity.Python;
- using UnityEngine;
- namespace LightGlue.Unity.Bridge
- {
- /// <summary>
- /// Bridge: 统一管理硬件与 Python 的通信。
- /// - 硬件 -> Unity:UDP JPEG 接收(bindIp:hardwarePort)
- /// - Unity -> 硬件:0x40 参数设置帧(分辨率/质量/间隔/开关)
- /// - Unity -> Python:图像数据(stdin 或 UDP)
- /// - Python -> Unity:算法结果 UDP 接收(pythonResultBindIp:pythonResultPort)
- /// </summary>
- public sealed class HardwareToPythonUdpBridge : MonoBehaviour
- {
- [Header("配置管理")]
- [Tooltip("是否从NetworkConfigManager自动加载配置(启动时)")]
- public bool autoLoadNetworkConfig = true;
- // ---------- 硬件 -> Unity:JPEG 接收 ----------
- [Header("Hardware UDP (input: hardware -> Unity)")]
- [Tooltip("Local bind IP for hardware stream, e.g. 192.168.0.105 or 0.0.0.0")]
- public string hardwareBindIp = "192.168.0.105";
- [Tooltip("UDP port where hardware sends JPEG packets.")]
- public int hardwarePort = 12346;
- [Tooltip("Timeout to reset incomplete JPEG (seconds).")]
- public float hardwareTimeoutSeconds = 2.0f;
- // ---------- 硬件控制:Unity -> 硬件(0x40 参数设置) ----------
- [Header("Hardware Control UDP (Unity -> Hardware)")]
- [Tooltip("硬件设备 IP(如 ESP32S3),用于发送 0x40 参数设置帧")]
- public string hardwareControlIp = "192.168.0.106";
- [Tooltip("硬件控制端口(硬件接收 0x40 的端口,如 8888)")]
- public int hardwareControlPort = 8888;
- [Header("Hardware Auto Apply (one-shot)")]
- [Tooltip("当检测到硬件开始发送 JPEG 帧时,是否自动将当前 transmissionConfig 以 0x40 下发给硬件(仅一次)。")]
- public bool autoApplyHardwareConfigOnFirstFrame = true;
- [Header("Python Output Mode")]
- [Tooltip("传输模式:Stdin(直接进程通信,推荐)或 UDP(传统模式)")]
- public TransferMode transferMode = TransferMode.Stdin;
- [Header("Python UDP (legacy mode: Unity -> Python)")]
- [Tooltip("Python process IP (normally localhost). Only used in UDP mode.")]
- public string pythonIp = "127.0.0.1";
- [Tooltip("Python UDP port to receive JPEG from Unity. Only used in UDP mode.")]
- public int pythonPort = 12347;
- [Header("Python Control (Unity -> Python control commands)")]
- [Tooltip("是否启用向Python发送控制指令(例如刷新参考图),需要Python脚本开启 --control_port")]
- public bool enablePythonControl = true;
- [Tooltip("Python 控制端口(需与 demo_lightglue_camera_position_async.py 的 --control_port 一致,例如 12349)")]
- public int pythonControlPort = 12349;
- [Tooltip("用于触发“刷新参考图”指令的按键(等价于 Python 端按下 n 键)")]
- public KeyCode refreshReferenceKey = KeyCode.N;
- [Header("基准图刷新方式与频率(由 Unity 触发)")]
- [Tooltip("基准图来源:仅摄像头(N键) / Python截屏 / Unity游戏画面")]
- public ReferenceSource referenceSource = ReferenceSource.CameraOnly;
- [Tooltip("基准图刷新间隔(秒),仅当来源为 Python截屏 或 Unity游戏画面 时生效")]
- [Range(0.01f, 60f)]
- public float referenceRefreshIntervalSeconds = 5f;
- [Tooltip("Unity游戏画面作为基准图时,用于截图的相机(空则使用 Camera.main)")]
- public Camera gameViewCamera;
- [Tooltip("基准图截图宽度(与 Python --resize 一致,如 640)")]
- public int referenceCaptureWidth = 640;
- [Tooltip("基准图截图高度(与 Python --resize 一致,如 480)")]
- public int referenceCaptureHeight = 480;
- [Header("Python Process (stdin mode: Unity -> Python)")]
- [Tooltip("Python进程控制器(用于stdin模式,直接传递图片数据)")]
- public PythonProcessController pythonProcessController;
- [Tooltip("为 true 时:收到首帧图像后再启动 Python(需 PythonProcessController.startOnFirstImageReceived 同时为 true),避免无图时启动算法。")]
- public bool startPythonOnFirstImageReceived = true;
- [Tooltip("Max queued frames in receiver (latest N only).")]
- public int maxQueuedFrames = 2;
- [Header("Python Result Receiver (Python -> Unity)")]
- [Tooltip("是否启用Python结果接收(接收算法计算结果)")]
- public bool enableResultReceiver = true;
- [Tooltip("Python结果接收端口(用于接收Python发送的算法结果,默认12348)")]
- public int pythonResultPort = 12348;
- [Tooltip("Python结果接收绑定IP(默认127.0.0.1)")]
- public string pythonResultBindIp = "127.0.0.1";
- [Tooltip("结果队列最大大小(只保留最新N个结果)")]
- public int maxResultQueueSize = 10;
- [Header("Image Transmission Config")]
- [Tooltip("图像传输配置(运行时唯一来源:可由 LightGlueManager.defaultTransmissionConfig 或 ImageTransmissionUIController 写入)")]
- public ImageTransmissionConfig transmissionConfig;
- public enum TransferMode
- {
- Stdin, // 通过stdin直接传递(推荐,无需UDP服务器)
- Udp // 通过UDP传递(传统模式)
- }
- /// <summary> 基准图来源:仅 N 键摄像头帧 / Python 截屏 / Unity 游戏画面 </summary>
- public enum ReferenceSource
- {
- CameraOnly, // 仅通过 N 键将当前摄像头帧设为基准图
- ScreenCapture, // Python 端截取整屏作为基准图(按刷新频率发 s)
- GameView // Unity 将当前游戏画面发给 Python 作为基准图(按刷新频率发 r+图像)
- }
- private UDPJpegReceiver _receiver;
- private UdpClient _udpSender; // Unity->Python UDP 模式
- private IPEndPoint _pythonEndpoint;
- private StreamWriter _stdinWriter; // Stdin 模式
- private UDPResultReceiver _resultReceiver; // Python->Unity 结果接收
- // 硬件控制:Unity -> 硬件 0x40 参数设置(与 JPEG 接收/转发独立)
- private UdpClient _hwControlClient;
- private IPEndPoint _hwControlEndpoint;
- // Python 控制:Unity -> Python 控制指令(刷新参考图)
- private UdpClient _pyControlClient;
- private IPEndPoint _pyControlEndpoint;
- // 基准图刷新(仅 GameView / ScreenCapture 时按间隔触发)
- private float _lastReferenceRefreshTime;
- private byte[] _pendingReferenceImageBytes;
- // 传输控制
- private float _lastSendTime;
- private bool _configEnabled = true;
-
- // 最新图片缓存(供Viewer使用)
- private byte[] _latestJpeg;
- private object _latestJpegLock = new object();
- private bool _autoHardwareConfigApplied;
- private bool _autoHardwareConfigReady;
- private bool _pythonStartedByFirstFrame;
-
- /// <summary>
- /// 获取最新的JPEG图片数据(供Viewer等组件使用)
- /// </summary>
- public byte[] GetLatestJpeg()
- {
- lock (_latestJpegLock)
- {
- return _latestJpeg != null ? (byte[])_latestJpeg.Clone() : null;
- }
- }
-
- /// <summary>
- /// 获取接收器(供Viewer共享使用)
- /// </summary>
- public UDPJpegReceiver Receiver => _receiver;
- /// <summary>
- /// 获取结果接收器(供其他组件使用)
- /// </summary>
- public UDPResultReceiver ResultReceiver => _resultReceiver;
- /// <summary>
- /// 尝试获取最新的算法结果(非阻塞)
- /// 每帧会排空结果队列并只使用最后一次取到的结果,避免队列积压导致显示旧位置(延迟移动)。
- /// </summary>
- public bool TryGetLatestResult(out LightGlueResult result)
- {
- result = default(LightGlueResult);
- if (_resultReceiver == null) return false;
- bool any = false;
- while (_resultReceiver.TryDequeueResult(out var r))
- {
- result = r;
- any = true;
- }
- return any;
- }
- private void Start()
- {
- // 自动加载网络配置
- if (autoLoadNetworkConfig)
- {
- LoadNetworkConfig();
- }
- // 在Start中初始化,确保所有组件都已启用
- // 如果使用stdin模式且Python进程控制器已配置但未运行,尝试启动
- if (transferMode == TransferMode.Stdin && pythonProcessController != null && !pythonProcessController.IsRunning)
- {
- if (pythonProcessController.autoStartOnEnable)
- {
- // 如果配置了自动启动,但还没启动,可能是启动顺序问题,延迟一点再检查
- StartCoroutine(DelayedStdinCheck());
- }
- else
- {
- Debug.LogWarning("[Bridge] Python process controller is set but not running. " +
- "Please ensure 'Auto Start On Enable' is enabled or manually start Python process.");
- }
- }
- }
- /// <summary>
- /// 从NetworkConfigManager加载网络配置
- /// </summary>
- private void LoadNetworkConfig()
- {
- try
- {
- NetworkConfig config = NetworkConfigManager.LoadConfig();
- if (config != null && config.Validate())
- {
- hardwareBindIp = config.hardwareBindIp;
- hardwarePort = config.hardwarePort;
- hardwareTimeoutSeconds = config.hardwareTimeoutSeconds;
- pythonIp = config.pythonIp;
- pythonPort = config.pythonPort;
- pythonResultBindIp = config.pythonResultBindIp;
- pythonResultPort = config.pythonResultPort;
- hardwareControlIp = config.hardwareControlIp;
- hardwareControlPort = config.hardwareControlPort;
- Debug.Log($"[Bridge] 已从配置文件加载网络配置: hardwareBindIp={hardwareBindIp}, hardwarePort={hardwarePort}, pythonIp={pythonIp}, pythonPort={pythonPort}, pythonResultPort={pythonResultPort}, hardwareControlIp={hardwareControlIp}, hardwareControlPort={hardwareControlPort}");
- }
- }
- catch (System.Exception ex)
- {
- Debug.LogWarning($"[Bridge] 加载网络配置失败,使用Inspector中的默认值: {ex.Message}");
- }
- }
- private System.Collections.IEnumerator DelayedStdinCheck()
- {
- // 等待一帧,让Python进程有时间启动
- yield return null;
- TryConnectStdin();
- }
- private void OnEnable()
- {
- // 游戏全屏时保持收发 UDP/进程通信,避免因失去焦点被系统节流导致 Python 端卡顿
- Application.runInBackground = true;
- _lastReferenceRefreshTime = Time.time;
- _autoHardwareConfigApplied = false;
- _autoHardwareConfigReady = false;
- _autoHardwareConfigApplied = false;
- // 创建 receiver 前先加载网络配置,保证首次运行与“应用配置重启”使用同一绑定地址,避免预览在重启后因 IP 不一致断流
- if (autoLoadNetworkConfig)
- {
- LoadNetworkConfig();
- }
- // 参考图 resize:与 Python --resize 一致
- ReferenceImageResizeService.EnsureInitialized();
- referenceCaptureWidth = ReferenceImageResizeService.Width;
- referenceCaptureHeight = ReferenceImageResizeService.Height;
- ReferenceImageResizeService.OnResizeChanged -= OnResizeChanged;
- ReferenceImageResizeService.OnResizeChanged += OnResizeChanged;
- // Hardware -> Unity receiver(端口占用时捕获异常,避免崩溃)
- _receiver = new UDPJpegReceiver(hardwareBindIp, hardwarePort, hardwareTimeoutSeconds, maxQueuedFrames);
- try
- {
- _receiver.Start();
- }
- catch (SocketException ex)
- {
- Debug.LogError($"[Bridge] 无法绑定硬件 JPEG 接收端口 {hardwareBindIp}:{hardwarePort},可能已被占用或存在重复的 LightGlueSystem。请确保场景中只通过 GameManager 创建一份预制,且未手动放置重复对象。\n{ex.Message}");
- try { _receiver?.Stop(); } catch { /* ignore */ }
- _receiver = null;
- }
- // Unity -> Python sender (根据模式选择)
- if (transferMode == TransferMode.Stdin)
- {
- TryConnectStdin();
- }
- else
- {
- // UDP模式(传统)
- var ip = IPAddress.Parse(string.IsNullOrWhiteSpace(pythonIp) ? "127.0.0.1" : pythonIp);
- _pythonEndpoint = new IPEndPoint(ip, pythonPort);
- _udpSender = new UdpClient();
-
- Debug.Log($"[Bridge] Hardware -> Unity listening on {hardwareBindIp}:{hardwarePort}");
- Debug.Log($"[Bridge] Unity -> Python: UDP mode sending to {_pythonEndpoint.Address}:{_pythonEndpoint.Port}");
- }
- // Unity -> 硬件:0x40 参数设置 UDP 客户端(与 JPEG 接收/转发独立)
- try
- {
- string ctrlIp = string.IsNullOrWhiteSpace(hardwareControlIp) ? "192.168.0.106" : hardwareControlIp;
- var ip = IPAddress.Parse(ctrlIp);
- _hwControlEndpoint = new IPEndPoint(ip, hardwareControlPort);
- _hwControlClient = new UdpClient();
- Debug.Log($"[Bridge] 硬件控制 UDP 已初始化 -> {_hwControlEndpoint.Address}:{_hwControlEndpoint.Port}(发送 0x40 参数设置到硬件)");
- }
- catch (Exception ex)
- {
- Debug.LogError($"[Bridge] 硬件控制 UDP 初始化失败: {ex.Message}");
- _hwControlClient = null;
- }
- // Unity -> Python 控制:简单控制指令(例如刷新参考图)
- if (enablePythonControl)
- {
- try
- {
- string targetIp = string.IsNullOrWhiteSpace(pythonIp) ? "127.0.0.1" : pythonIp;
- var ip = IPAddress.Parse(targetIp);
- _pyControlEndpoint = new IPEndPoint(ip, pythonControlPort);
- _pyControlClient = new UdpClient();
- Debug.Log($"[Bridge] Python 控制 UDP 已初始化 -> {_pyControlEndpoint.Address}:{_pyControlEndpoint.Port}(发送刷新参考图等控制指令)");
- }
- catch (Exception ex)
- {
- Debug.LogError($"[Bridge] Python 控制 UDP 初始化失败: {ex.Message}");
- _pyControlClient = null;
- }
- }
- // Python -> Unity result receiver
- if (enableResultReceiver)
- {
- try
- {
- _resultReceiver = new UDPResultReceiver(pythonResultBindIp, pythonResultPort, maxResultQueueSize);
- _resultReceiver.Start();
- Debug.Log($"[Bridge] Python -> Unity: Result receiver started on {pythonResultBindIp}:{pythonResultPort}");
- }
- catch (Exception ex)
- {
- // 端口占用或绑定失败时给出明确提示,避免与其它进程(例如 YejiDemo)抢占同一结果端口。
- Debug.LogError($"[Bridge] Failed to start result receiver on {pythonResultBindIp}:{pythonResultPort}. " +
- "The UDP port may already be in use by another process. " +
- $"Exception: {ex.Message}");
- try
- {
- _resultReceiver?.Stop();
- }
- catch
- {
- // ignore
- }
- _resultReceiver = null;
- }
- }
- }
- private void OnDestroy()
- {
- ReferenceImageResizeService.OnResizeChanged -= OnResizeChanged;
- }
- private void OnResizeChanged(int w, int h)
- {
- referenceCaptureWidth = w;
- referenceCaptureHeight = h;
- }
- private void TryConnectStdin()
- {
- // Stdin模式:从Python进程控制器获取stdin写入流
- if (pythonProcessController != null)
- {
- if (pythonProcessController.IsRunning)
- {
- _stdinWriter = pythonProcessController.StdinWriter;
- if (_stdinWriter != null)
- {
- Debug.Log($"[Bridge] Hardware -> Unity listening on {hardwareBindIp}:{hardwarePort}");
- Debug.Log($"[Bridge] Unity -> Python: Stdin mode connected (direct process communication)");
- }
- else
- {
- Debug.LogWarning("[Bridge] Python process stdin not available. Make sure Python process is started.");
- }
- }
- else
- {
- Debug.LogWarning("[Bridge] Python process controller is set but Python process is not running. " +
- "Will retry in Update(). Make sure 'Auto Start On Enable' is enabled in PythonProcessController.");
- }
- }
- else
- {
- Debug.LogWarning("[Bridge] Python process controller not set. Please assign PythonProcessController component in Inspector.");
- }
- }
- private void OnDisable()
- {
- try { _receiver?.Stop(); } catch { /* ignore */ }
- _receiver = null;
- try { _resultReceiver?.Stop(); } catch { /* ignore */ }
- _resultReceiver = null;
- try { _hwControlClient?.Close(); } catch { /* ignore */ }
- _hwControlClient = null;
- try { _pyControlClient?.Close(); } catch { /* ignore */ }
- _pyControlClient = null;
- _stdinWriter = null;
- try { _udpSender?.Close(); } catch { /* ignore */ }
- _udpSender = null;
- _pythonStartedByFirstFrame = false;
- ReferenceImageResizeService.OnResizeChanged -= OnResizeChanged;
- }
- /// <summary>
- /// 收到首帧图像时调用:若启用「首帧后再启动 Python」则启动一次,之后不再重复。
- /// </summary>
- private void TryStartPythonOnFirstFrame()
- {
- if (_pythonStartedByFirstFrame) return;
- if (!startPythonOnFirstImageReceived || pythonProcessController == null || pythonProcessController.IsRunning)
- return;
- if (pythonProcessController.startOnFirstImageReceived)
- {
- pythonProcessController.StartPython();
- _pythonStartedByFirstFrame = true;
- Debug.Log("[Bridge] 已收到首帧图像,启动 Python 进程。");
- }
- }
- // ---------- 硬件控制:下发 0x40 参数设置(供 ImageTransmissionUIController 等调用) ----------
- /// <summary>
- /// 将图像传输配置以 0x40 协议帧下发给硬件(分辨率/质量/间隔/开关)。
- /// 调用时机:用户点击“应用配置”等。
- /// </summary>
- public void ApplyConfig(ImageTransmissionConfig config)
- {
- if (_hwControlClient == null || _hwControlEndpoint == null || config == null)
- {
- Debug.LogWarning("[Bridge] 硬件控制未就绪或 config 为空,无法发送 0x40");
- return;
- }
- try
- {
- byte[] frame = BuildParameterSettingFrame(config);
- if (frame != null && frame.Length > 0)
- {
- _hwControlClient.Send(frame, frame.Length, _hwControlEndpoint);
- 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");
- }
- }
- catch (SocketException ex)
- {
- Debug.LogWarning($"[Bridge] 硬件控制 UDP 发送异常: {ex.SocketErrorCode} {ex.Message}");
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"[Bridge] 硬件控制发送失败: {ex.Message}");
- }
- }
- /// <summary>
- /// 构建 0x40 参数设置帧(报头 0x7B + 类型 0x40 + 长度 + 总包/分包 + 标志 0xFF + 数据 20 字节 + 校验 + 报尾 0x7D)
- /// </summary>
- private static byte[] BuildParameterSettingFrame(ImageTransmissionConfig config)
- {
- if (config == null) return Array.Empty<byte>();
- const byte HEADER = 0x7B;
- const byte PROTOCOL_TYPE = 0x40;
- const byte TOTAL_PACKETS = 1;
- const byte PACKET_INDEX = 1;
- const byte FLAG = 0xFF;
- const byte FOOTER = 0x7D;
- const int DATA_SIZE = 20;
- byte[] data = new byte[DATA_SIZE];
- data[0] = (byte)(config.enableImageTransmission ? 1 : 0);
- data[1] = 0;
- data[2] = config.GetHardwareResolutionCode();
- data[3] = config.GetHardwareQuality();
- data[4] = (byte)Mathf.Clamp(config.reportIntervalMs, 0, 255);
- ushort dataLength = (ushort)DATA_SIZE;
- byte[] frame = new byte[29];
- int offset = 0;
- frame[offset++] = HEADER;
- frame[offset++] = PROTOCOL_TYPE;
- frame[offset++] = (byte)(dataLength & 0xFF);
- frame[offset++] = (byte)((dataLength >> 8) & 0xFF);
- frame[offset++] = TOTAL_PACKETS;
- frame[offset++] = PACKET_INDEX;
- frame[offset++] = FLAG;
- Buffer.BlockCopy(data, 0, frame, offset, data.Length);
- offset += data.Length;
- byte checksum = 0;
- for (int i = 1; i < offset; i++)
- checksum = (byte)(checksum + frame[i]);
- frame[offset++] = checksum;
- frame[offset++] = FOOTER;
- return frame;
- }
- private void Update()
- {
- if (_receiver == null) return;
- // 处理来自 Unity 的刷新参考图:N 键 或 按间隔发送 s/r
- if (enablePythonControl && _pyControlClient != null && _pyControlEndpoint != null)
- {
- if (Input.GetKeyDown(refreshReferenceKey))
- {
- SendControlCommand('n');
- }
- else if (referenceSource == ReferenceSource.ScreenCapture || referenceSource == ReferenceSource.GameView)
- {
- float now = Time.time;
- if (now - _lastReferenceRefreshTime >= referenceRefreshIntervalSeconds)
- {
- _lastReferenceRefreshTime = now;
- if (referenceSource == ReferenceSource.ScreenCapture)
- {
- SendControlCommand('s');
- }
- else
- {
- byte[] gameViewJpeg = CaptureGameViewToJpeg();
- if (gameViewJpeg != null && gameViewJpeg.Length > 0)
- {
- _pendingReferenceImageBytes = gameViewJpeg;
- SendControlCommand('r');
- }
- }
- }
- }
- }
- // 检查发送端是否可用(根据模式)
- if (transferMode == TransferMode.Stdin)
- {
- if (_stdinWriter == null)
- {
- // 尝试重新获取stdin写入流(如果Python进程刚启动)
- if (pythonProcessController != null && pythonProcessController.IsRunning)
- {
- _stdinWriter = pythonProcessController.StdinWriter;
- }
- if (_stdinWriter == null) return;
- }
- }
- else
- {
- if (_udpSender == null) return;
- }
- // 检查配置是否启用传输
- bool shouldTransmit = _configEnabled;
- if (transmissionConfig != null)
- {
- shouldTransmit = transmissionConfig.enableImageTransmission;
- }
- if (!shouldTransmit)
- {
- byte[] last = null;
- while (_receiver.TryDequeueJpeg(out var j)) { last = j; }
- if (last != null)
- {
- lock (_latestJpegLock) { _latestJpeg = last; }
- TryStartPythonOnFirstFrame();
- TryAutoApplyHardwareConfigOnce();
- }
- return;
- }
- float currentTime = Time.time * 1000f;
- bool hasPendingReference = _pendingReferenceImageBytes != null;
- if (!hasPendingReference && transmissionConfig != null)
- {
- float intervalMs = transmissionConfig.reportIntervalMs;
- if (intervalMs > 0 && (currentTime - _lastSendTime) < intervalMs)
- {
- byte[] last = null;
- while (_receiver.TryDequeueJpeg(out var j)) { last = j; }
- if (last != null)
- {
- lock (_latestJpegLock) { _latestJpeg = last; }
- TryStartPythonOnFirstFrame();
- TryAutoApplyHardwareConfigOnce();
- }
- return;
- }
- }
- byte[] toSend = null;
- if (_pendingReferenceImageBytes != null)
- {
- toSend = _pendingReferenceImageBytes;
- _pendingReferenceImageBytes = null;
- }
- else
- {
- byte[] latest = null;
- while (_receiver.TryDequeueJpeg(out var jpeg))
- latest = jpeg;
- if (latest == null) return;
- TryStartPythonOnFirstFrame();
- TryAutoApplyHardwareConfigOnce();
- toSend = latest;
- lock (_latestJpegLock)
- {
- _latestJpeg = latest;
- }
- }
- byte[] processedImage = ProcessImage(toSend);
- // 仅在 Python 已就绪接收时发送,避免 TensorRT 编译期间发图导致 UDP 缓冲堆积、后续解码损坏
- if (pythonProcessController != null && !pythonProcessController.IsReadyToReceiveFrames)
- return;
- try
- {
- if (transferMode == TransferMode.Stdin)
- {
- if (_stdinWriter != null)
- {
- _stdinWriter.BaseStream.Write(processedImage, 0, processedImage.Length);
- _stdinWriter.BaseStream.Flush();
- _lastSendTime = currentTime;
- }
- }
- else
- {
- _udpSender.Send(processedImage, processedImage.Length, _pythonEndpoint);
- _lastSendTime = currentTime;
- }
- }
- catch (SocketException ex)
- {
- Debug.LogWarning($"[Bridge] UDP send error: {ex.SocketErrorCode} {ex.Message}");
- }
- catch (IOException ex)
- {
- Debug.LogWarning($"[Bridge] Stdin write error: {ex.Message}");
- _stdinWriter = null;
- }
- }
- /// <summary>
- /// 标记“自动下发硬件图像配置”的前置条件已就绪(由 ImageTransmissionUIController 初始化完成后调用)。
- /// </summary>
- public void MarkHardwareAutoApplyReady()
- {
- _autoHardwareConfigReady = true;
- }
- private void TryAutoApplyHardwareConfigOnce()
- {
- if (_autoHardwareConfigApplied) return;
- if (!autoApplyHardwareConfigOnFirstFrame) return;
- if (!_autoHardwareConfigReady) return;
- if (transmissionConfig == null) return;
- if (_hwControlClient == null || _hwControlEndpoint == null) return;
- // 注意:ApplyConfig 内部会做一次基本校验并捕获异常;此处只要硬件控制已就绪就认为可尝试下发。
- ApplyConfig(transmissionConfig);
- _autoHardwareConfigApplied = true;
- Debug.Log("[Bridge] 已在检测到硬件首帧后自动下发一次 0x40 图像配置。");
- }
- /// <summary>
- /// 根据配置处理图像(分辨率调整和质量压缩)
- /// 注意:Unity的ImageConversion.EncodeToJPG需要Texture2D,这里先返回原始数据
- /// 如果需要真正的resize和quality控制,需要额外的图像处理库
- /// </summary>
- private byte[] ProcessImage(byte[] jpegBytes)
- {
- if (transmissionConfig == null)
- return jpegBytes;
- // TODO: 实现图像resize和quality压缩
- // Unity原生不支持JPEG的resize和quality调整,需要:
- // 1. 使用Texture2D.LoadImage解码JPEG
- // 2. 使用Texture2D.GetPixels/SetPixels进行resize
- // 3. 使用ImageConversion.EncodeToJPG进行质量压缩
- // 但这个过程可能较慢,建议在Python端处理
-
- // 当前实现:直接返回原始数据,配置参数可用于Python端处理
- // 如果需要Unity端处理,可以在这里添加图像处理逻辑
-
- return jpegBytes;
- }
- /// <summary>
- /// 设置传输配置(供UI控制器调用)
- /// </summary>
- public void SetTransmissionConfig(ImageTransmissionConfig config)
- {
- transmissionConfig = config;
- _configEnabled = config != null && config.enableImageTransmission;
- Debug.Log($"[Bridge] 传输配置已更新: 分辨率={config?.GetResolutionString()}, 质量={config?.quality}, 间隔={config?.reportIntervalMs}ms, 启用={_configEnabled}");
- // 配置一旦设置就立即下发 0x40,避免硬件用默认参数发图导致首段画面不完整、闪烁,直到用户手动点「应用」才正常
- if (config != null && _hwControlClient != null && _hwControlEndpoint != null)
- {
- ApplyConfig(config);
- }
- }
- private void SendControlCommand(char cmd)
- {
- if (_pyControlClient == null || _pyControlEndpoint == null) return;
- try
- {
- byte[] payload = { (byte)cmd };
- _pyControlClient.Send(payload, payload.Length, _pyControlEndpoint);
- }
- catch (Exception ex)
- {
- Debug.LogWarning($"[Bridge] Python 控制指令发送失败: {ex.Message}");
- }
- }
- private byte[] CaptureGameViewToJpeg()
- {
- Camera cam = gameViewCamera != null ? gameViewCamera : Camera.main;
- if (cam == null) return null;
- int w = Mathf.Clamp(referenceCaptureWidth, 64, 4096);
- int h = Mathf.Clamp(referenceCaptureHeight, 64, 4096);
- RenderTexture rt = RenderTexture.GetTemporary(w, h, 24, RenderTextureFormat.ARGB32);
- RenderTexture prevTarget = cam.targetTexture;
- RenderTexture prevActive = RenderTexture.active;
- cam.targetTexture = rt;
- cam.Render();
- RenderTexture.active = rt;
- Texture2D tex = new Texture2D(w, h, TextureFormat.RGB24, false);
- tex.ReadPixels(new Rect(0, 0, w, h), 0, 0);
- tex.Apply();
- cam.targetTexture = prevTarget;
- RenderTexture.active = prevActive;
- RenderTexture.ReleaseTemporary(rt);
- int quality = transmissionConfig != null ? Mathf.Clamp(transmissionConfig.quality, 1, 100) : 80;
- byte[] jpeg = tex.EncodeToJPG(quality);
- Destroy(tex);
- return jpeg;
- }
- }
- }
|