From e9f5975a79f70a45af37f6069b8fe44323153289 Mon Sep 17 00:00:00 2001
From: twice109 <3518499@qq.com>
Date: Fri, 26 Dec 2025 06:14:55 +0800
Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E8=A7=A3=E5=86=B3=E4=BD=BF=E7=94=A8?=
=?UTF-8?q?=20Cv2.ImShow=20=E6=92=AD=E6=94=BE=E7=94=BB=E9=9D=A2=EF=BC=8C?=
=?UTF-8?q?=E9=97=AA=E7=83=81=E4=B8=80=E4=B8=8B=E7=AA=97=E5=8F=A3=E4=B8=8D?=
=?UTF-8?q?=E8=A7=81=E7=9A=84=E9=97=AE=E9=A2=98=202=E3=80=81=E4=B9=8B?=
=?UTF-8?q?=E5=89=8D=E6=B3=A8=E5=86=8C=E6=92=AD=E6=94=BE=E3=80=81=E5=88=86?=
=?UTF-8?q?=E6=9E=90=E5=B8=A7=EF=BC=8C=E5=9B=9E=E8=B0=83=E6=97=B6=E5=BF=85?=
=?UTF-8?q?=E9=A1=BB=E5=88=A4=E5=AE=9A=E6=98=AF=E5=90=A6=E5=BD=93=E5=89=8D?=
=?UTF-8?q?=E6=B3=A8=E5=86=8C=E6=BA=90=EF=BC=8C=E7=8E=B0=E5=A2=9E=E5=8A=A0?=
=?UTF-8?q?=E6=96=B0=E6=96=B9=E6=B3=95=E5=8F=AF=E4=BB=A5=E4=B8=8D=E7=94=A8?=
=?UTF-8?q?=E5=88=A4=E5=AE=9A=203=E3=80=81=E5=B0=86=E4=B9=8B=E5=89=8D?=
=?UTF-8?q?=E7=A8=8B=E5=BA=8F=E4=B8=80=E8=BF=90=E8=A1=8C=E5=B0=B1=E6=92=AD?=
=?UTF-8?q?=E6=94=BE=EF=BC=8C=E8=B0=83=E6=95=B4=E4=B8=BA=E6=89=8B=E5=8A=A8?=
=?UTF-8?q?=E6=8C=87=E5=AE=9A=20IsRunning=20=E5=80=BC=E6=9D=A5=E6=8E=A7?=
=?UTF-8?q?=E5=88=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 10 +
Ayay.Solution.sln | 25 ++
SHH.CameraSdk/Core/Manager/CameraManager.cs | 10 +-
.../Core/Pipeline/GlobalStreamDispatcher.cs | 23 ++
SHH.CameraSdk/Program.cs | 295 ++++++++++--------
5 files changed, 226 insertions(+), 137 deletions(-)
create mode 100644 .gitignore
create mode 100644 Ayay.Solution.sln
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1f8cb6d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+[Bb]in/
+[Oo]bj/
+.vs/
+*.user
+*.suo
+*.rar
+*.zip
+ExportCode_Dump.txt
+Publish/
+Bat/
diff --git a/Ayay.Solution.sln b/Ayay.Solution.sln
new file mode 100644
index 0000000..1583076
--- /dev/null
+++ b/Ayay.Solution.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 18
+VisualStudioVersion = 18.1.11304.174
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SHH.CameraSdk", "SHH.CameraSdk\SHH.CameraSdk.csproj", "{21B70A94-43FC-4D17-AB83-9E4B5178397E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {21B70A94-43FC-4D17-AB83-9E4B5178397E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {21B70A94-43FC-4D17-AB83-9E4B5178397E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {21B70A94-43FC-4D17-AB83-9E4B5178397E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {21B70A94-43FC-4D17-AB83-9E4B5178397E}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {64321063-16F8-41E4-9595-E85C32FE4FDC}
+ EndGlobalSection
+EndGlobal
diff --git a/SHH.CameraSdk/Core/Manager/CameraManager.cs b/SHH.CameraSdk/Core/Manager/CameraManager.cs
index 3e03513..c2cfd3d 100644
--- a/SHH.CameraSdk/Core/Manager/CameraManager.cs
+++ b/SHH.CameraSdk/Core/Manager/CameraManager.cs
@@ -89,11 +89,11 @@ public class CameraManager : IDisposable, IAsyncDisposable
// 1. 全局驱动环境预初始化:初始化厂商 SDK 运行环境
HikSdkManager.Initialize();
- // 2. 激活现有设备池中所有设备的“运行意图”,触发设备连接流程
- foreach (var source in _cameraPool.Values)
- {
- source.IsRunning = true;
- }
+ //// 2. 激活现有设备池中所有设备的“运行意图”,触发设备连接流程
+ //foreach (var source in _cameraPool.Values)
+ //{
+ // source.IsRunning = true;
+ //}
// 标记引擎启动状态,后续新增设备自动激活
_isEngineStarted = true;
diff --git a/SHH.CameraSdk/Core/Pipeline/GlobalStreamDispatcher.cs b/SHH.CameraSdk/Core/Pipeline/GlobalStreamDispatcher.cs
index abcab37..285c98d 100644
--- a/SHH.CameraSdk/Core/Pipeline/GlobalStreamDispatcher.cs
+++ b/SHH.CameraSdk/Core/Pipeline/GlobalStreamDispatcher.cs
@@ -66,6 +66,29 @@ public static class GlobalStreamDispatcher
);
}
+ ///
+ /// [新增] 精准订阅:仅监听指定设备的特定 AppId 帧
+ /// 优势:内部自动过滤 DeviceId,回调函数无需再写 if 判断
+ ///
+ /// 需求标识
+ /// 只接收此设备的帧
+ /// 处理回调(注意:此处签名不含 deviceId,因为已隐式确定)
+ public static void Subscribe(string appId, long specificDeviceId, Action handler)
+ {
+ // 创建一个“过滤器”闭包
+ Action wrapper = (id, frame) =>
+ {
+ // 只有当来源 ID 与订阅 ID 一致时,才触发用户的业务回调
+ if (id == specificDeviceId)
+ {
+ handler(frame);
+ }
+ };
+
+ // 将过滤器注册到基础路由表中
+ Subscribe(appId, wrapper);
+ }
+
///
/// 取消订阅:移除指定 AppId 的帧处理回调
/// 线程安全:支持多线程并发调用,无订阅时静默处理
diff --git a/SHH.CameraSdk/Program.cs b/SHH.CameraSdk/Program.cs
index c44915a..5b74353 100644
--- a/SHH.CameraSdk/Program.cs
+++ b/SHH.CameraSdk/Program.cs
@@ -1,147 +1,178 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
+using OpenCvSharp;
using SHH.CameraSdk;
using System.Diagnostics;
-// ==============================================================================
-// 1. 基础设施初始化
-// ==============================================================================
-InitHardwareEnv();
-using var cameraManager = new CameraManager();
-
-// ==============================================================================
-// 2. 启动 Web 监控与诊断服务
-// ==============================================================================
-var app = await StartWebMonitoring(cameraManager);
-
-// ==============================================================================
-// 3. 业务编排:配置设备与流控策略 (8+2 演示)
-// ==============================================================================
-await ConfigureBusinessLogic(cameraManager);
-
-// ==============================================================================
-// 4. 启动引擎与交互
-// ==============================================================================
-Console.WriteLine("\n[系统] 正在启动全局管理引擎...");
-await cameraManager.StartAsync();
-
-Console.WriteLine(">> 系统就绪。访问 http://localhost:5000/swagger 查看诊断信息。");
-Console.WriteLine(">> 按 'S' 键退出...");
-
-while (Console.ReadKey(true).Key != ConsoleKey.S) { Thread.Sleep(100); }
-
-Console.WriteLine("[系统] 正在停机...");
-await app.StopAsync();
-
-
-// ==============================================================================
-// Local Functions (方法拆分)
-// ==============================================================================
-
-static void InitHardwareEnv()
+namespace SHH.CameraSdk
{
- Console.WriteLine("=== 工业级视频 SDK 架构测试 (V3.3 分层版) ===");
- Console.WriteLine("[硬件] 海康驱动预热中...");
- HikNativeMethods.NET_DVR_Init();
- HikSdkManager.ForceWarmUp(); // 强制加载 PlayCtrl.dll
- Console.WriteLine("[硬件] 预热完成。");
-}
-
-static async Task StartWebMonitoring(CameraManager manager)
-{
- var builder = WebApplication.CreateBuilder();
-
- // 注入服务
- builder.Services.AddControllers();
- builder.Services.AddEndpointsApiExplorer();
- builder.Services.AddSwaggerGen(c =>
+ ///
+ /// A 方案:标准控制台结构 (显式 Main 方法 + STAThread)
+ ///
+ public class Program
{
- c.SwaggerDoc("v1", new OpenApiInfo { Title = "SHH Camera Diagnostics", Version = "v1" });
- });
- builder.Services.AddCors(o => o.AddPolicy("AllowAll", p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));
-
- // 关键:注入单例 Manager
- builder.Services.AddSingleton(manager);
-
- 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;
-}
-
-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);
-
- if (manager.GetDevice(101) is HikVideoSource hikCamera)
- {
- // 2. 注册需求 (告诉控制器我要什么)
- // ----------------------------------------------------
- hikCamera.Controller.Register("WPF_Display_Main", 8); // UI 要 8 帧
- hikCamera.Controller.Register("AI_Behavior_Engine", 2); // AI 要 2 帧
-
- // 1. 注册差异化需求 (给每个消费者唯一的 AppId)
- // ----------------------------------------------------
- // 模拟:A 进程(如远程预览)带宽有限,只要 3fps
- hikCamera.Controller.Register("Process_A_Remote", 3);
-
- // 模拟:B 进程(如本地大屏)性能强劲,要 8fps
- hikCamera.Controller.Register("Process_B_Local", 8);
-
- // 模拟:AI 引擎
- hikCamera.Controller.Register("AI_Engine_Core", 2);
-
- // 2. 精准订阅 (Subscribe 替代了 +=)
- // ----------------------------------------------------
-
- // [消费者 A] - 绝对只会收到 3fps
- GlobalStreamDispatcher.Subscribe("Process_A_Remote", (deviceId, frame) =>
+ // [关键点 1] 显式声明 STA 线程模式,确保 OpenCV/GUI 窗口消息循环正常
+ [STAThread]
+ public static async Task Main(string[] args)
{
- // 这里不需要判断 deviceId,也不需要判断 frame 类型
- // 能进这个回调,说明这帧就是专为 Process_A_Remote 准备的
- if (deviceId == 101)
- {
- Console.WriteLine($"[Process A] 远程推流一帧 (3fps节奏)");
- }
- });
+ // ==============================================================================
+ // 1. 基础设施初始化
+ // ==============================================================================
+ InitHardwareEnv();
+ using var cameraManager = new CameraManager();
- // [消费者 B] - 绝对只会收到 8fps
- GlobalStreamDispatcher.Subscribe("Process_B_Local", (deviceId, frame) =>
- {
- if (deviceId == 101)
- {
- Console.WriteLine($"[Process B] 本地渲染一帧 (8fps节奏)");
- }
- });
+ // [关键点 2] 提升变量作用域
+ // 将渲染器(消费者)在 Main 中声明,确保它与主程序同寿命,不会被中途回收
+ using var remoteRenderer = new FrameConsumer("Process A Remote Preview");
+ remoteRenderer.Start();
- // [消费者 AI]
- GlobalStreamDispatcher.Subscribe("AI_Engine_Core", (deviceId, frame) =>
- {
- if (deviceId == 101)
+ // ==============================================================================
+ // 2. 启动 Web 监控与诊断服务
+ // ==============================================================================
+ var app = await StartWebMonitoring(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)
{
- Console.WriteLine($" >>> [AI] 分析一帧...");
+ Thread.Sleep(100);
}
- });
+
+ Console.WriteLine("[系统] 正在停机...");
+ await app.StopAsync();
+ }
+
+ // ==============================================================================
+ // Static Methods (原 Local Functions 转换为类的静态方法)
+ // ==============================================================================
+
+ static void InitHardwareEnv()
+ {
+ Console.WriteLine("=== 工业级视频 SDK 架构测试 (V3.3 分层版 - STA模式) ===");
+ Console.WriteLine("[硬件] 海康驱动预热中...");
+ HikNativeMethods.NET_DVR_Init();
+ HikSdkManager.ForceWarmUp(); // 强制加载 PlayCtrl.dll
+ Console.WriteLine("[硬件] 预热完成。");
+ }
+
+ static async Task StartWebMonitoring(CameraManager manager)
+ {
+ var builder = WebApplication.CreateBuilder();
+
+ // 注入服务
+ builder.Services.AddControllers();
+ builder.Services.AddEndpointsApiExplorer();
+ builder.Services.AddSwaggerGen(c =>
+ {
+ c.SwaggerDoc("v1", new OpenApiInfo { Title = "SHH Camera Diagnostics", Version = "v1" });
+ });
+ builder.Services.AddCors(o => o.AddPolicy("AllowAll", p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));
+
+ // 关键:注入单例 Manager
+ builder.Services.AddSingleton(manager);
+
+ 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)
+ {
+ // 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 = "RRYFOA",
+ StreamType = 0 // 主码流
+ };
+ manager.AddDevice(config);
+
+ if (manager.GetDevice(101) is HikVideoSource hikCamera)
+ {
+ // 2. 注册需求 (告诉控制器我要什么)
+ // ----------------------------------------------------
+ hikCamera.Controller.Register("WPF_Display_Main", 8); // UI 要 8 帧
+ hikCamera.Controller.Register("AI_Behavior_Engine", 2); // AI 要 2 帧
+
+ // 1. 注册差异化需求 (给每个消费者唯一的 AppId)
+ // ----------------------------------------------------
+ // 模拟:A 进程(如远程预览)带宽有限,只要 3fps
+ hikCamera.Controller.Register("Process_A_Remote", 20);
+
+ // 模拟:B 进程(如本地大屏)性能强劲,要 8fps
+ hikCamera.Controller.Register("Process_B_Local", 8);
+
+ // 模拟:AI 引擎
+ hikCamera.Controller.Register("AI_Engine_Core", 2);
+
+ // [已移除] 这里的 using var remoteRenderer = ... 已被移除
+ // 改为使用传入的 renderer 参数,确保其生命周期受控于 Main
+
+ // 2. 精准订阅 (Subscribe 替代了 +=)
+ // ----------------------------------------------------
+
+ // [消费者 A] - 绝对只会收到 3fps
+ GlobalStreamDispatcher.Subscribe("Process_A_Remote", 101, frame =>
+ {
+ // 关键:增加引用计数,防止在投递过程中被 Pipeline 回收
+ frame.AddRef();
+
+ // 投递到渲染线程 (FrameConsumer)
+ renderer.Enqueue(frame);
+
+ Console.WriteLine("Frame Enqueued");
+ });
+
+ // [消费者 B] - 绝对只会收到 8fps
+ GlobalStreamDispatcher.Subscribe("Process_B_Local", (deviceId, frame) =>
+ {
+ if (deviceId == 101)
+ {
+ Console.WriteLine($"[Process B] 本地渲染一帧 (8fps节奏)");
+ }
+ });
+
+ // [消费者 AI]
+ GlobalStreamDispatcher.Subscribe("AI_Engine_Core", (deviceId, frame) =>
+ {
+ if (deviceId == 101)
+ {
+ Console.WriteLine($" >>> [AI] 分析一帧...");
+ }
+ });
+ }
+ }
}
}
\ No newline at end of file