增加了通过网络主动上报图像的支持
增加了指令维护通道的支持
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
using SHH.CameraDashboard.Services; // 引用服务命名空间
|
||||
using SHH.Contracts; // ★★★ 引用契约库 (VideoPayload) ★★★
|
||||
|
||||
namespace SHH.CameraDashboard;
|
||||
|
||||
@@ -8,7 +8,7 @@ public class VideoTileViewModel : ViewModelBase
|
||||
{
|
||||
private readonly string _boundCameraId;
|
||||
|
||||
// --- 属性定义 ---
|
||||
// --- 属性定义 (保持不变) ---
|
||||
private string _cameraName;
|
||||
public string CameraName
|
||||
{
|
||||
@@ -37,38 +37,59 @@ public class VideoTileViewModel : ViewModelBase
|
||||
CameraName = name;
|
||||
StatusInfo = "等待信号...";
|
||||
|
||||
// 【修正 1】直接订阅单例服务
|
||||
// 不需要判断 null,因为 Instance 是静态初始化的,永远存在
|
||||
StreamReceiverService.Instance.OnFrameReceived += OnGlobalFrameReceived;
|
||||
// ★★★ 变更 1: 订阅新的 OnPayloadReceived 事件 ★★★
|
||||
// 旧的 OnFrameReceived(string, byte[]) 已经无法满足需求
|
||||
StreamReceiverService.Instance.OnPayloadReceived += OnPayloadReceived;
|
||||
}
|
||||
|
||||
// --- 事件回调 (后台线程) ---
|
||||
private void OnGlobalFrameReceived(string cameraId, byte[] jpgData)
|
||||
// ★★★ 变更 2: 参数变为 VideoPayload 实体对象 ★★★
|
||||
private void OnPayloadReceived(VideoPayload payload)
|
||||
{
|
||||
// 1. 过滤:不是我的画面,直接忽略
|
||||
if (cameraId != _boundCameraId) return;
|
||||
// 1. 过滤:校验 Payload 中的 CameraId
|
||||
if (payload.CameraId != _boundCameraId) return;
|
||||
|
||||
// 2. 解码:耗时操作在后台完成
|
||||
var bitmap = BitmapHelper.ToBitmapImage(jpgData);
|
||||
// 2. ★★★ 智能选图策略 ★★★
|
||||
// 优先显示 AI 处理后的图 (TargetImageBytes)
|
||||
// 如果没有处理图,则降级显示原始图 (OriginalImageBytes)
|
||||
byte[] dataToShow = null;
|
||||
|
||||
if (payload.HasTargetImage && payload.TargetImageBytes != null)
|
||||
{
|
||||
dataToShow = payload.TargetImageBytes;
|
||||
}
|
||||
else if (payload.HasOriginalImage && payload.OriginalImageBytes != null)
|
||||
{
|
||||
dataToShow = payload.OriginalImageBytes;
|
||||
}
|
||||
|
||||
// 如果两张图都没有,直接返回
|
||||
if (dataToShow == null || dataToShow.Length == 0) return;
|
||||
|
||||
// 3. 解码图片 (耗时操作在后台完成)
|
||||
var bitmap = BitmapHelper.ToBitmapImage(dataToShow);
|
||||
if (bitmap == null) return;
|
||||
|
||||
// 3. 【修正 2】恢复 UI 更新逻辑
|
||||
// 必须使用 Dispatcher,因为 VideoSource 绑定在界面上,只能在主线程修改
|
||||
// 4. ★★★ 计算端到端延迟 ★★★
|
||||
// 当前时间(接收端) - 采集时间(发送端) = 真实的网络+处理延迟
|
||||
long latency = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - payload.CaptureTimestamp;
|
||||
|
||||
// 5. UI 更新
|
||||
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
VideoSource = bitmap;
|
||||
|
||||
// 更新状态信息 (例如显示当前时间和数据大小)
|
||||
StatusInfo = $"{DateTime.Now:HH:mm:ss} | {jpgData.Length / 1024} KB";
|
||||
// 显示更丰富的信息:延迟毫秒数、数据量、当前时间
|
||||
// 工业监控中,"延迟(ms)" 是比 "当前时间" 更重要的指标
|
||||
StatusInfo = $"延迟: {latency}ms | {dataToShow.Length / 1024} KB | {DateTime.Now:HH:mm:ss}";
|
||||
});
|
||||
}
|
||||
|
||||
// --- 资源清理 ---
|
||||
public void Unload()
|
||||
{
|
||||
// 【修正 3】从单例服务取消订阅
|
||||
// 这一步至关重要,否则切换页面时会内存泄漏
|
||||
StreamReceiverService.Instance.OnFrameReceived -= OnGlobalFrameReceived;
|
||||
// ★★★ 变更 3: 取消订阅新的事件 ★★★
|
||||
StreamReceiverService.Instance.OnPayloadReceived -= OnPayloadReceived;
|
||||
|
||||
// 清空图片引用,帮助 GC 回收内存
|
||||
VideoSource = null;
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
using SHH.Contracts;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace SHH.CameraDashboard
|
||||
{
|
||||
public class VideoWallViewModel : ViewModelBase
|
||||
{
|
||||
// 引用推流接收服务
|
||||
private readonly VideoPushServer _pushServer;
|
||||
|
||||
// 视频列表
|
||||
public ObservableCollection<VideoTileViewModel> VideoTiles { get; } = new ObservableCollection<VideoTileViewModel>();
|
||||
|
||||
@@ -27,35 +23,11 @@ namespace SHH.CameraDashboard
|
||||
{
|
||||
SetLayoutCommand = new RelayCommand<string>(ExecuteSetLayout);
|
||||
|
||||
// 1. 初始化并启动接收服务
|
||||
_pushServer = new VideoPushServer();
|
||||
_pushServer.OnFrameReceived += OnGlobalFrameReceived;
|
||||
|
||||
// 2. 启动监听端口 (比如 6000)
|
||||
// 之后你的采集端 ForwarderClient 需要 Connect("tcp://你的IP:6000")
|
||||
_pushServer.Start(6000);
|
||||
|
||||
// 3. 初始化格子 (不再需要传入 IP/Port 去主动连接了)
|
||||
// 我们用 CameraId 或 Name 来作为匹配标识
|
||||
InitVideoTiles();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局接收回调:收到任何一路视频都会进这里
|
||||
/// </summary>
|
||||
private void OnGlobalFrameReceived(VideoPayload payload)
|
||||
{
|
||||
// 1. 在 VideoTiles 集合中找到对应的格子
|
||||
// 假设 payload.CameraId 与我们 VideoTileViewModel 中的 ID 对应
|
||||
//var targetTile = VideoTiles.FirstOrDefault(t => t.id == payload.CameraId);
|
||||
|
||||
//if (targetTile != null)
|
||||
//{
|
||||
// // 2. 将数据交给格子去渲染
|
||||
// targetTile.UpdateFrame(payload);
|
||||
//}
|
||||
}
|
||||
|
||||
private void InitVideoTiles()
|
||||
{
|
||||
// 假设我们预设 4 个格子,分别对应不同的摄像头 ID
|
||||
|
||||
Reference in New Issue
Block a user