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
{
///
/// Bridge: 统一管理硬件与 Python 的通信。
/// - 硬件 -> Unity:UDP JPEG 接收(bindIp:hardwarePort)
/// - Unity -> 硬件:0x40 参数设置帧(分辨率/质量/间隔/开关)
/// - Unity -> Python:图像数据(stdin 或 UDP)
/// - Python -> Unity:算法结果 UDP 接收(pythonResultBindIp:pythonResultPort)
///
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传递(传统模式)
}
/// 基准图来源:仅 N 键摄像头帧 / Python 截屏 / Unity 游戏画面
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;
///
/// 获取最新的JPEG图片数据(供Viewer等组件使用)
///
public byte[] GetLatestJpeg()
{
lock (_latestJpegLock)
{
return _latestJpeg != null ? (byte[])_latestJpeg.Clone() : null;
}
}
///
/// 获取接收器(供Viewer共享使用)
///
public UDPJpegReceiver Receiver => _receiver;
///
/// 获取结果接收器(供其他组件使用)
///
public UDPResultReceiver ResultReceiver => _resultReceiver;
///
/// 尝试获取最新的算法结果(非阻塞)
/// 每帧会排空结果队列并只使用最后一次取到的结果,避免队列积压导致显示旧位置(延迟移动)。
///
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.");
}
}
}
///
/// 从NetworkConfigManager加载网络配置
///
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();
}
// 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 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;
}
///
/// 收到首帧图像时调用:若启用「首帧后再启动 Python」则启动一次,之后不再重复。
///
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 等调用) ----------
///
/// 将图像传输配置以 0x40 协议帧下发给硬件(分辨率/质量/间隔/开关)。
/// 调用时机:用户点击“应用配置”等。
///
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}");
}
}
///
/// 构建 0x40 参数设置帧(报头 0x7B + 类型 0x40 + 长度 + 总包/分包 + 标志 0xFF + 数据 20 字节 + 校验 + 报尾 0x7D)
///
private static byte[] BuildParameterSettingFrame(ImageTransmissionConfig config)
{
if (config == null) return Array.Empty();
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;
}
}
///
/// 标记“自动下发硬件图像配置”的前置条件已就绪(由 ImageTransmissionUIController 初始化完成后调用)。
///
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 图像配置。");
}
///
/// 根据配置处理图像(分辨率调整和质量压缩)
/// 注意:Unity的ImageConversion.EncodeToJPG需要Texture2D,这里先返回原始数据
/// 如果需要真正的resize和quality控制,需要额外的图像处理库
///
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;
}
///
/// 设置传输配置(供UI控制器调用)
///
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;
}
}
}