1、解决使用 Cv2.ImShow 播放画面,闪烁一下窗口不见的问题

2、之前注册播放、分析帧,回调时必须判定是否当前注册源,现增加新方法可以不用判定
3、将之前程序一运行就播放,调整为手动指定 IsRunning 值来控制
This commit is contained in:
2025-12-26 06:14:55 +08:00
parent 6281f4248e
commit e9f5975a79
5 changed files with 226 additions and 137 deletions

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
[Bb]in/
[Oo]bj/
.vs/
*.user
*.suo
*.rar
*.zip
ExportCode_Dump.txt
Publish/
Bat/

25
Ayay.Solution.sln Normal file
View File

@@ -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

View File

@@ -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;

View File

@@ -66,6 +66,29 @@ public static class GlobalStreamDispatcher
);
}
/// <summary>
/// [新增] 精准订阅:仅监听指定设备的特定 AppId 帧
/// 优势:内部自动过滤 DeviceId回调函数无需再写 if 判断
/// </summary>
/// <param name="appId">需求标识</param>
/// <param name="specificDeviceId">只接收此设备的帧</param>
/// <param name="handler">处理回调(注意:此处签名不含 deviceId因为已隐式确定</param>
public static void Subscribe(string appId, long specificDeviceId, Action<SmartFrame> handler)
{
// 创建一个“过滤器”闭包
Action<long, SmartFrame> wrapper = (id, frame) =>
{
// 只有当来源 ID 与订阅 ID 一致时,才触发用户的业务回调
if (id == specificDeviceId)
{
handler(frame);
}
};
// 将过滤器注册到基础路由表中
Subscribe(appId, wrapper);
}
/// <summary>
/// 取消订阅:移除指定 AppId 的帧处理回调
/// 线程安全:支持多线程并发调用,无订阅时静默处理

View File

@@ -1,15 +1,32 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using OpenCvSharp;
using SHH.CameraSdk;
using System.Diagnostics;
namespace SHH.CameraSdk
{
/// <summary>
/// A 方案:标准控制台结构 (显式 Main 方法 + STAThread)
/// </summary>
public class Program
{
// [关键点 1] 显式声明 STA 线程模式,确保 OpenCV/GUI 窗口消息循环正常
[STAThread]
public static async Task Main(string[] args)
{
// ==============================================================================
// 1. 基础设施初始化
// ==============================================================================
InitHardwareEnv();
using var cameraManager = new CameraManager();
// [关键点 2] 提升变量作用域
// 将渲染器(消费者)在 Main 中声明,确保它与主程序同寿命,不会被中途回收
using var remoteRenderer = new FrameConsumer("Process A Remote Preview");
remoteRenderer.Start();
// ==============================================================================
// 2. 启动 Web 监控与诊断服务
// ==============================================================================
@@ -18,7 +35,8 @@ var app = await StartWebMonitoring(cameraManager);
// ==============================================================================
// 3. 业务编排:配置设备与流控策略 (8+2 演示)
// ==============================================================================
await ConfigureBusinessLogic(cameraManager);
// [关键点 3] 将渲染器作为参数传递进去
await ConfigureBusinessLogic(cameraManager, remoteRenderer);
// ==============================================================================
// 4. 启动引擎与交互
@@ -29,19 +47,24 @@ await cameraManager.StartAsync();
Console.WriteLine(">> 系统就绪。访问 http://localhost:5000/swagger 查看诊断信息。");
Console.WriteLine(">> 按 'S' 键退出...");
while (Console.ReadKey(true).Key != ConsoleKey.S) { Thread.Sleep(100); }
// [关键点 4] 阻塞主线程
// 只要这个循环在跑remoteRenderer 就不会被 Dispose窗口就会一直存在
while (Console.ReadKey(true).Key != ConsoleKey.S)
{
Thread.Sleep(100);
}
Console.WriteLine("[系统] 正在停机...");
await app.StopAsync();
}
// ==============================================================================
// Local Functions (方法拆分)
// Static Methods (原 Local Functions 转换为类的静态方法)
// ==============================================================================
static void InitHardwareEnv()
{
Console.WriteLine("=== 工业级视频 SDK 架构测试 (V3.3 分层版) ===");
Console.WriteLine("=== 工业级视频 SDK 架构测试 (V3.3 分层版 - STA模式) ===");
Console.WriteLine("[硬件] 海康驱动预热中...");
HikNativeMethods.NET_DVR_Init();
HikSdkManager.ForceWarmUp(); // 强制加载 PlayCtrl.dll
@@ -79,17 +102,19 @@ static async Task<WebApplication> StartWebMonitoring(CameraManager manager)
return webApp;
}
static async Task ConfigureBusinessLogic(CameraManager manager)
// [关键点 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 = "172.16.41.206",
IpAddress = "192.168.5.9",
Port = 8000,
Username = "admin",
Password = "abcd1234",
Password = "RRYFOA",
StreamType = 0 // 主码流
};
manager.AddDevice(config);
@@ -104,7 +129,7 @@ static async Task ConfigureBusinessLogic(CameraManager manager)
// 1. 注册差异化需求 (给每个消费者唯一的 AppId)
// ----------------------------------------------------
// 模拟A 进程(如远程预览)带宽有限,只要 3fps
hikCamera.Controller.Register("Process_A_Remote", 3);
hikCamera.Controller.Register("Process_A_Remote", 20);
// 模拟B 进程(如本地大屏)性能强劲,要 8fps
hikCamera.Controller.Register("Process_B_Local", 8);
@@ -112,18 +137,22 @@ static async Task ConfigureBusinessLogic(CameraManager manager)
// 模拟AI 引擎
hikCamera.Controller.Register("AI_Engine_Core", 2);
// [已移除] 这里的 using var remoteRenderer = ... 已被移除
// 改为使用传入的 renderer 参数,确保其生命周期受控于 Main
// 2. 精准订阅 (Subscribe 替代了 +=)
// ----------------------------------------------------
// [消费者 A] - 绝对只会收到 3fps
GlobalStreamDispatcher.Subscribe("Process_A_Remote", (deviceId, frame) =>
GlobalStreamDispatcher.Subscribe("Process_A_Remote", 101, frame =>
{
// 这里不需要判断 deviceId也不需要判断 frame 类型
// 能进这个回调,说明这帧就是专为 Process_A_Remote 准备的
if (deviceId == 101)
{
Console.WriteLine($"[Process A] 远程推流一帧 (3fps节奏)");
}
// 关键:增加引用计数,防止在投递过程中被 Pipeline 回收
frame.AddRef();
// 投递到渲染线程 (FrameConsumer)
renderer.Enqueue(frame);
Console.WriteLine("Frame Enqueued");
});
// [消费者 B] - 绝对只会收到 8fps
@@ -145,3 +174,5 @@ static async Task ConfigureBusinessLogic(CameraManager manager)
});
}
}
}
}