Files
Ayay/SHH.CameraDashboard/Invokes/StreamReceiverService.cs
wilson 3351ae739e 在 AiVideo 中能看到图像
增加了在线状态同步逻辑
2026-01-09 12:30:36 +08:00

122 lines
4.1 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 MessagePack;
using NetMQ;
using NetMQ.Sockets;
using Newtonsoft.Json;
using SHH.Contracts; // ★★★ 必须引用契约库 ★★★
using System.Diagnostics;
namespace SHH.CameraDashboard;
public class StreamReceiverService : IDisposable
{
// 单例模式
public static StreamReceiverService Instance { get; } = new StreamReceiverService();
// ★★★ 核心变更:使用强类型契约载体 ★★★
public event Action<VideoPayload>? OnPayloadReceived;
private SubscriberSocket? _subSocket;
private Task? _receiveTask;
private CancellationTokenSource? _cts;
public int ListenPort { get; private set; }
// 运行状态检查
public bool IsRunning => _receiveTask != null && !_receiveTask.IsCompleted;
private StreamReceiverService() { }
public void Start(int port = 6000)
{
if (IsRunning) return;
ListenPort = port;
_cts = new CancellationTokenSource();
try
{
_subSocket = new SubscriberSocket();
// 设置高水位,防止 UI 卡顿时内存溢出
_subSocket.Options.ReceiveHighWatermark = 1000;
_subSocket.Bind($"tcp://*:{ListenPort}");
_subSocket.Subscribe(""); // 订阅所有内容(这是 Dealer-Router/Pub-Sub 的基础)
Console.WriteLine($"[Dashboard] 视频流接收服务启动: tcp://*:{ListenPort}");
}
catch (Exception ex)
{
// 明确抛出异常,让 App.xaml.cs 知道启动失败了
_subSocket?.Dispose();
_subSocket = null;
throw new Exception($"端口 {port} 绑定失败,可能被占用。", ex);
}
_receiveTask = Task.Run(ReceiveLoop, _cts.Token);
}
private void ReceiveLoop()
{
var token = _cts?.Token ?? CancellationToken.None;
while (!token.IsCancellationRequested)
{
try
{
if (_subSocket == null) break;
// =========================================================
// 核心解析逻辑:适配 Service 端的 4 帧复合协议
// =========================================================
NetMQMessage msg = new NetMQMessage();
// 1. 非阻塞接收多帧消息
if (!_subSocket.TryReceiveMultipartMessage(TimeSpan.FromMilliseconds(500), ref msg))
continue;
// 2. 协议完整性检查
if (msg.FrameCount < 4) continue;
// 3. 协议头校验 (Frame 0)
if (msg[0].ConvertToString() != "SHH_V1") continue;
//// 4. 反序列化元数据 (Frame 1)
//string json = msg[1].ConvertToString();
//var payload = JsonConvert.DeserializeObject<VideoPayload>(json);
// 4. 反序列化元数据 (Frame 1)
// 直接获取二进制数据,不需要转 String (省去了 UTF8 解码开销)
byte[] metaBytes = msg[1].ToByteArray();
// 极速反序列化
var payload = MessagePackSerializer.Deserialize<VideoPayload>(metaBytes);
if (payload == null) continue;
// 5. 填充二进制图像数据 (Frame 2 & 3)
// 注意NetMQ 的 msg 数据是非托管内存,转为 byte[] 实现了拷贝,安全供 UI 使用
if (payload.HasOriginalImage)
payload.OriginalImageBytes = msg[2].ToByteArray();
if (payload.HasTargetImage)
payload.TargetImageBytes = msg[3].ToByteArray();
// 6. 触发事件
OnPayloadReceived?.Invoke(payload);
}
catch (Exception ex)
{
Debug.WriteLine($"[Receiver Error] {ex.Message}");
}
}
Console.WriteLine("[Dashboard] 接收循环已停止");
}
public void Stop()
{
_cts?.Cancel();
try { _subSocket?.Dispose(); } catch { }
_subSocket = null;
_receiveTask = null;
}
public void Dispose() => Stop();
}