增加本地Cv2.ImShow播放的动态增加、移除的支持

This commit is contained in:
2025-12-26 17:28:07 +08:00
parent 83ad6221a4
commit e98059fd30
5 changed files with 305 additions and 185 deletions

View File

@@ -2,165 +2,149 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using OpenCvSharp;
using SHH.CameraSdk;
using System.Diagnostics;
namespace SHH.CameraSdk
namespace SHH.CameraSdk;
/// <summary>
/// A 方案:标准控制台结构 (动态窗口版)
/// </summary>
public class Program
{
/// <summary>
/// A 方案:标准控制台结构 (显式 Main 方法 + STAThread)
/// </summary>
public class Program
[STAThread]
public static async Task Main(string[] args)
{
// [关键点 1] 显式声明 STA 线程模式,确保 OpenCV/GUI 窗口消息循环正常
[STAThread]
public static async Task Main(string[] args)
{
// ==============================================================================
// 1. 基础设施初始化
// ==============================================================================
InitHardwareEnv();
using var cameraManager = new CameraManager();
// ==============================================================================
// 1. 基础设施初始化
// ==============================================================================
InitHardwareEnv();
// [关键点 2] 提升变量作用域
// 将渲染器(消费者)在 Main 中声明,确保它与主程序同寿命,不会被中途回收
using var remoteRenderer = new FrameConsumer("Process A Remote Preview");
remoteRenderer.Start();
// 核心设备管理器
using var cameraManager = new CameraManager();
// ==============================================================================
// 2. 启动 Web 监控与诊断服务
// ==============================================================================
var app = await StartWebMonitoring(cameraManager);
// [新增] 启动网络哨兵 (它会自动在后台跑)
// 就像保安一样你不需要管它它每3秒会把所有摄像头的 IsOnline 状态刷一遍
var sentinel = new ConnectivitySentinel(cameraManager);
// ==============================================================================
// 3. 业务编排:配置设备与流控策略 (8+2 演示)
// ==============================================================================
// [关键点 3] 将渲染器作为参数传递进去
await ConfigureBusinessLogic(cameraManager, remoteRenderer);
// ==============================================================================
// 4. 启动引擎与交互
// ==============================================================================
Console.WriteLine("\n[系统] 正在启动全局管理引擎...");
await cameraManager.StartAsync();
Console.WriteLine(">> 系统就绪。访问 http://localhost:5000/swagger 查看诊断信息。");
Console.WriteLine(">> 按 'S' 键退出...");
// [关键点 4] 阻塞主线程
// 只要这个循环在跑remoteRenderer 就不会被 Dispose窗口就会一直存在
while (Console.ReadKey(true).Key != ConsoleKey.S)
{
Thread.Sleep(100);
}
Console.WriteLine("[系统] 正在停机...");
await app.StopAsync();
}
// [新增] 动态窗口管理器 (不再直接 new FrameConsumer)
// 这是一个单例服务,负责在运行期间管理所有弹出的窗口
var displayManager = new DisplayWindowManager();
// ==============================================================================
// Static Methods (原 Local Functions 转换为类的静态方法)
// 2. 启动 Web 监控与诊断服务 (注入两个管理器)
// ==============================================================================
var app = await StartWebMonitoring(cameraManager, displayManager);
static void InitHardwareEnv()
// 启动网络哨兵 (后台 Ping)
var sentinel = new ConnectivitySentinel(cameraManager);
// ==============================================================================
// 3. 业务编排:仅配置设备,不配置窗口
// ==============================================================================
await ConfigureBusinessLogic(cameraManager);
// ==============================================================================
// 4. 启动引擎与交互
// ==============================================================================
Console.WriteLine("\n[系统] 正在启动全局管理引擎...");
await cameraManager.StartAsync();
Console.WriteLine(">> 系统就绪。");
Console.WriteLine(">>当前无播放窗口。请通过 Web 界面 '新增订阅' -> 模式选 'UI_Preview' 来动态打开。");
Console.WriteLine(">> 按 'S' 键退出...");
// 阻塞主线程,保持程序运行
while (Console.ReadKey(true).Key != ConsoleKey.S)
{
Console.WriteLine("=== 工业级视频 SDK 架构测试 (V3.3 分层版 - STA模式) ===");
Console.WriteLine("[硬件] 海康驱动预热中...");
HikNativeMethods.NET_DVR_Init();
HikSdkManager.ForceWarmUp(); // 强制加载 PlayCtrl.dll
Console.WriteLine("[硬件] 预热完成。");
Thread.Sleep(100);
}
static async Task<WebApplication> StartWebMonitoring(CameraManager manager)
Console.WriteLine("[系统] 正在停机...");
await app.StopAsync();
}
// ==============================================================================
// Static Methods
// ==============================================================================
static void InitHardwareEnv()
{
Console.WriteLine("=== 工业级视频 SDK 架构测试 (V3.3 动态窗口版) ===");
Console.WriteLine("[硬件] 海康驱动预热中...");
HikNativeMethods.NET_DVR_Init();
HikSdkManager.ForceWarmUp();
Console.WriteLine("[硬件] 预热完成。");
}
// [修改] 签名增加 DisplayWindowManager 参数
static async Task<WebApplication> StartWebMonitoring(CameraManager manager, DisplayWindowManager displayMgr)
{
var builder = WebApplication.CreateBuilder();
builder.Services.AddCors(options =>
{
var builder = WebApplication.CreateBuilder();
// [新增] 屏蔽日志配置
builder.Logging.AddFilter("Microsoft", Microsoft.Extensions.Logging.LogLevel.Warning);
builder.Logging.AddFilter("System", Microsoft.Extensions.Logging.LogLevel.Warning);
builder.Logging.AddFilter("Microsoft.AspNetCore.Hosting.Diagnostics", Microsoft.Extensions.Logging.LogLevel.Warning);
// 注入服务
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
options.AddPolicy("AllowAll", policy =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "SHH Camera Diagnostics", Version = "v1" });
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
builder.Services.AddCors(o => o.AddPolicy("AllowAll", p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));
});
// 关键:注入单例 Manager
builder.Services.AddSingleton(manager);
// 日志屏蔽
builder.Logging.AddFilter("Microsoft", LogLevel.Warning);
builder.Logging.AddFilter("System", LogLevel.Warning);
builder.Logging.AddFilter("Microsoft.AspNetCore.Hosting.Diagnostics", LogLevel.Warning);
var webApp = builder.Build();
// 配置管道
webApp.UseSwagger();
webApp.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Diagnostics V1"));
webApp.UseCors("AllowAll");
webApp.MapControllers();
// 异步启动,不阻塞主线程
_ = webApp.RunAsync("http://0.0.0.0:5000");
Console.WriteLine("[Web] 监控API已启动: http://localhost:5000");
return webApp;
}
// [关键点 5] 方法签名修改:接收 FrameConsumer 参数
static async Task ConfigureBusinessLogic(CameraManager manager, FrameConsumer renderer)
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
// 1. 配置设备
var config = new VideoSourceConfig
{
Id = 101,
Brand = DeviceBrand.HikVision,
IpAddress = "172.16.41.206",
//IpAddress = "192.168.5.9",
Port = 8000,
Username = "admin",
Password = "abcd1234",
//Password = "RRYFOA",
StreamType = 0 // 主码流
};
manager.AddDevice(config);
c.SwaggerDoc("v1", new OpenApiInfo { Title = "SHH Camera Diagnostics", Version = "v1" });
});
builder.Services.AddCors(o => o.AddPolicy("AllowAll", p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));
if (manager.GetDevice(101) is HikVideoSource hikCamera)
{
// 1. 注册差异化需求 (给每个消费者唯一的 AppId)
// ----------------------------------------------------
// 1. 注册需求时,手动加上 _Display 后缀
hikCamera.Controller.Register("Process_A_Remote_Display", 20);
// [关键] 注入两个单例服务,让 Controller 能调用它们
builder.Services.AddSingleton(manager);
builder.Services.AddSingleton(displayMgr);
// 2. 订阅时,也改用带后缀的名称
GlobalStreamDispatcher.Subscribe("Process_A_Remote_Display", 101, frame =>
{
frame.AddRef();
renderer.Enqueue(frame);
});
}
var webApp = builder.Build();
var config2 = new VideoSourceConfig
{
Id = 102,
Brand = DeviceBrand.HikVision,
IpAddress = "172.16.41.20",
Port = 8000,
Username = "admin",
Password = "abcd1234",
StreamType = 0 // 主码流
};
manager.AddDevice(config2);
webApp.UseSwagger();
webApp.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Diagnostics V1"));
webApp.UseCors("AllowAll");
webApp.MapControllers();
//if (manager.GetDevice(102) is HikVideoSource hikCamera2)
//{
_ = webApp.RunAsync("http://0.0.0.0:5000");
Console.WriteLine("[Web] 监控API已启动: http://localhost:5000");
//}
}
return webApp;
}
// [修改] 移除 FrameConsumer 参数,不再进行硬编码订阅
static async Task ConfigureBusinessLogic(CameraManager manager)
{
// 1. 仅添加设备配置
var config = new VideoSourceConfig
{
Id = 101,
Brand = DeviceBrand.HikVision,
IpAddress = "172.16.41.206",
Port = 8000,
Username = "admin",
Password = "abcd1234",
StreamType = 0
};
manager.AddDevice(config);
var config2 = new VideoSourceConfig
{
Id = 102,
Brand = DeviceBrand.HikVision,
IpAddress = "172.16.41.20",
Port = 8000,
Username = "admin",
Password = "abcd1234",
StreamType = 0
};
manager.AddDevice(config2);
// 注意:此处不再调用 Register 或 Subscribe
// 所有的播放请求都将由 WebAPI 收到前端指令后,调用 DisplayWindowManager 来动态发起
}
}