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