using System; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using RenderHeads.Media.AVProLiveCamera; using UnityEngine; using UnityEngine.Events; public class WebSocketCameraSender : MonoBehaviour { [SerializeField] private AVProLiveCamera _liveCamera; [Header("核心配置")] public string wsUri = "ws://127.0.0.1:8000/ai/identify"; public string modelName = "link"; public float confidence = 0.7f; public float sendInterval = 0.5f; // 每0.5秒发一帧 public float jpgQuality = 0.8f; private ClientWebSocket _ws; private CancellationTokenSource _cts; private Texture2D _captureTex; private UnityMainThreadDispatcher1 _dispatcher; // 仅声明,不初始化 public UnityEvent OnMessageReceived=new UnityEvent(); public UnityEvent OnCaptureTex=new UnityEvent(); private RenderTexture renderTexture; private async void Start() { // 1. 延迟初始化调度器(放到Start中,避免字段初始化阶段创建GameObject) _dispatcher = UnityMainThreadDispatcher1.Instance; if (_liveCamera == null) { Debug.LogError("未绑定摄像头组件"); return; } _cts = new CancellationTokenSource(); _ws = new ClientWebSocket(); try { // 连接WebSocket并开始推流 await _ws.ConnectAsync(new Uri(wsUri), _cts.Token); Debug.Log("WS连接成功"); await RunStream(); } catch (TaskCanceledException) { // 正常取消(如退出运行模式),仅打印调试日志 Debug.Log("WebSocket任务已正常取消(应用退出)"); } catch (Exception e) { Debug.LogError($"异常: {e.Message}"); Cleanup(); } } private async Task RunStream() { while (!_cts.Token.IsCancellationRequested && _ws.State == WebSocketState.Open && _liveCamera.Device != null) { // 采集摄像头画面(主线程) string base64 = await _dispatcher.DispatchAsync(() => { renderTexture = _liveCamera.OutputTexture as RenderTexture; if (renderTexture == null) return ""; // 初始化纹理 if (_captureTex == null) _captureTex = new Texture2D(renderTexture.width, renderTexture.height, TextureFormat.RGBA32, false); // 读取画面并转Base64 RenderTexture.active = renderTexture; _captureTex.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0); RenderTexture.active = null; return Convert.ToBase64String(_captureTex.EncodeToJPG((int)(jpgQuality * 100))); }); if (string.IsNullOrEmpty(base64)) { await Task.Delay((int)(sendInterval * 1000)); continue; } // 构建并发送JSON var payload = new { image_base64 = base64, model_name = modelName, confidence = confidence }; byte[] jsonBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload)); await _ws.SendAsync(new ArraySegment(jsonBytes), WebSocketMessageType.Text, true, _cts.Token); // 接收返回结果 byte[] buffer = new byte[2 * 1024 * 1024]; var result = await _ws.ReceiveAsync(new ArraySegment(buffer), _cts.Token); if (result.MessageType == WebSocketMessageType.Close) break; string resJson = Encoding.UTF8.GetString(buffer, 0, result.Count); Debug.Log($"返回结果: {resJson}"); if (_liveCamera.Device.IsRunning) { OnMessageReceived?.Invoke(resJson); OnCaptureTex?.Invoke(renderTexture); } await Task.Delay((int)(sendInterval * 1000), _cts.Token); } } private void Cleanup() { _cts?.Cancel(); _cts?.Dispose(); _ws?.Dispose(); _cts = null; _ws = null; if (_captureTex != null) Destroy(_captureTex); } private void OnDestroy() => Cleanup(); private void OnApplicationQuit() => Cleanup(); } // 修正GameObject创建时机的主线程调度器 public class UnityMainThreadDispatcher1 : MonoBehaviour { private static UnityMainThreadDispatcher1 _instance; private readonly System.Collections.Queue _actions = new(); // 静态属性:延迟创建实例(仅在首次调用时创建,且确保在合法生命周期) public static UnityMainThreadDispatcher1 Instance { get { if (_instance == null) { // 确保创建GameObject的操作在Unity合法上下文执行 GameObject dispatcherObj = new GameObject("MainThreadDispatcher"); _instance = dispatcherObj.AddComponent(); DontDestroyOnLoad(dispatcherObj); } return _instance; } } // 显式转换lambda为Action,避免类型错误 public async Task DispatchAsync(Func func) { var tcs = new TaskCompletionSource(); Action action = () => { try { tcs.SetResult(func()); } catch (Exception e) { tcs.SetException(e); } }; lock (_actions) _actions.Enqueue(action); return await tcs.Task; } private void Update() { lock (_actions) { while (_actions.Count > 0) { var action = _actions.Dequeue() as Action; action?.Invoke(); } } } }