166 lines
5.7 KiB
C#
166 lines
5.7 KiB
C#
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();
|
||
}
|
||
}
|
||
}
|
||
} |