DaoNiaoShu_Dog/Assets/Yolo/Scripts/WebSocketCameraSender.cs
2026-04-21 10:53:58 +08:00

166 lines
5.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string> OnMessageReceived=new UnityEvent<string>();
public UnityEvent<RenderTexture> OnCaptureTex=new UnityEvent<RenderTexture>();
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<byte>(jsonBytes), WebSocketMessageType.Text, true, _cts.Token);
// 接收返回结果
byte[] buffer = new byte[2 * 1024 * 1024];
var result = await _ws.ReceiveAsync(new ArraySegment<byte>(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<UnityMainThreadDispatcher1>();
DontDestroyOnLoad(dispatcherObj);
}
return _instance;
}
}
// 显式转换lambda为Action避免类型错误
public async Task<T> DispatchAsync<T>(Func<T> func)
{
var tcs = new TaskCompletionSource<T>();
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();
}
}
}
}