增加本地Cv2.ImShow播放的动态增加、移除的支持
This commit is contained in:
@@ -8,9 +8,11 @@ public class CamerasController : ControllerBase
|
|||||||
{
|
{
|
||||||
private readonly CameraManager _manager;
|
private readonly CameraManager _manager;
|
||||||
|
|
||||||
public CamerasController(CameraManager manager)
|
// 构造函数注入管理器
|
||||||
|
public CamerasController(CameraManager manager, DisplayWindowManager displayManager)
|
||||||
{
|
{
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
|
_displayManager = displayManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@@ -91,52 +93,6 @@ public class CamerasController : ControllerBase
|
|||||||
// 区域 B: 多进程流控订阅 (Subscription Strategy)
|
// 区域 B: 多进程流控订阅 (Subscription Strategy)
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 5. 注册/更新进程的流需求 (A/B/C/D 场景核心)
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// 示例场景:
|
|
||||||
/// - 主进程配置(B): { "appId": "Main_Config", "displayFps": 25, "analysisFps": 0 }
|
|
||||||
/// - AI进程(C): { "appId": "AI_Core", "displayFps": 0, "analysisFps": 5 }
|
|
||||||
/// </remarks>
|
|
||||||
[HttpPost("{id}/subscriptions")]
|
|
||||||
public IActionResult UpdateSubscription(long id, [FromBody] SubscriptionDto sub)
|
|
||||||
{
|
|
||||||
var device = _manager.GetDevice(id);
|
|
||||||
if (device == null) return NotFound();
|
|
||||||
|
|
||||||
// 逻辑转换:将 "显示帧" 和 "分析帧" 映射到底层控制器的注册表
|
|
||||||
|
|
||||||
// 1. 处理显示需求
|
|
||||||
string displayKey = $"{sub.AppId}_Display";
|
|
||||||
if (sub.DisplayFps > 0)
|
|
||||||
{
|
|
||||||
// 告诉控制器:这个 App 需要 X 帧用于显示
|
|
||||||
device.Controller.Register(displayKey, sub.DisplayFps);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 如果不需要,移除注册
|
|
||||||
device.Controller.Unregister(displayKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 处理分析需求
|
|
||||||
string analysisKey = $"{sub.AppId}_Analysis";
|
|
||||||
if (sub.AnalysisFps > 0)
|
|
||||||
{
|
|
||||||
// 告诉控制器:这个 App 需要 Y 帧用于分析
|
|
||||||
device.Controller.Register(analysisKey, sub.AnalysisFps);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
device.Controller.Unregister(analysisKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 运维审计
|
|
||||||
device.AddAuditLog($"更新订阅策略 [{sub.AppId}]: Display={sub.DisplayFps}, Analysis={sub.AnalysisFps}");
|
|
||||||
|
|
||||||
return Ok(new { Message = "订阅策略已更新", DeviceId = id });
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// 区域 C: 句柄动态绑定 (Handle Binding)
|
// 区域 C: 句柄动态绑定 (Handle Binding)
|
||||||
@@ -295,4 +251,93 @@ public class CamerasController : ControllerBase
|
|||||||
|
|
||||||
return Ok(subs);
|
return Ok(subs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly DisplayWindowManager _displayManager; // [新增]
|
||||||
|
|
||||||
|
[HttpPost("{id}/subscriptions")]
|
||||||
|
public IActionResult UpdateSubscription(int id, [FromBody] SubscriptionDto dto)
|
||||||
|
{
|
||||||
|
var device = _manager.GetDevice(id);
|
||||||
|
if (device == null) return NotFound("设备不存在");
|
||||||
|
|
||||||
|
if (device is HikVideoSource hikCam)
|
||||||
|
{
|
||||||
|
// 1. 更新流控策略 (FrameController)
|
||||||
|
// 告诉底层:这个 AppId 需要多少帧
|
||||||
|
int totalFps = dto.DisplayFps + dto.AnalysisFps;
|
||||||
|
|
||||||
|
if (totalFps > 0)
|
||||||
|
{
|
||||||
|
// 情况 A: 这是一个新增或更新订阅
|
||||||
|
hikCam.Controller.Register(dto.AppId, totalFps);
|
||||||
|
|
||||||
|
// 如果是预览模式,启动窗口
|
||||||
|
if (dto.DisplayFps > 0)
|
||||||
|
{
|
||||||
|
_displayManager.StartDisplay(dto.AppId, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 情况 B: 这是一个停止订阅请求 (FPS 为 0)
|
||||||
|
// 1. 【核心修复】从调度中心物理删除,不再出现在列表中
|
||||||
|
hikCam.Controller.Unregister(dto.AppId);
|
||||||
|
|
||||||
|
// 2. 关闭可能存在的本地窗口
|
||||||
|
_displayManager.StopDisplay(dto.AppId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(new { message = "Policy updated", currentConfig = hikCam.Controller.GetCurrentRequirements() });
|
||||||
|
}
|
||||||
|
|
||||||
|
return BadRequest("Device implies no controller");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// 5. 注册/更新进程的流需求 (A/B/C/D 场景核心)
|
||||||
|
///// </summary>
|
||||||
|
///// <remarks>
|
||||||
|
///// 示例场景:
|
||||||
|
///// - 主进程配置(B): { "appId": "Main_Config", "displayFps": 25, "analysisFps": 0 }
|
||||||
|
///// - AI进程(C): { "appId": "AI_Core", "displayFps": 0, "analysisFps": 5 }
|
||||||
|
///// </remarks>
|
||||||
|
//[HttpPost("{id}/subscriptions")]
|
||||||
|
//public IActionResult UpdateSubscription(long id, [FromBody] SubscriptionDto sub)
|
||||||
|
//{
|
||||||
|
// var device = _manager.GetDevice(id);
|
||||||
|
// if (device == null) return NotFound();
|
||||||
|
|
||||||
|
// // 逻辑转换:将 "显示帧" 和 "分析帧" 映射到底层控制器的注册表
|
||||||
|
|
||||||
|
// // 1. 处理显示需求
|
||||||
|
// string displayKey = $"{sub.AppId}_Display";
|
||||||
|
// if (sub.DisplayFps > 0)
|
||||||
|
// {
|
||||||
|
// // 告诉控制器:这个 App 需要 X 帧用于显示
|
||||||
|
// device.Controller.Register(displayKey, sub.DisplayFps);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// // 如果不需要,移除注册
|
||||||
|
// device.Controller.Unregister(displayKey);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 2. 处理分析需求
|
||||||
|
// string analysisKey = $"{sub.AppId}_Analysis";
|
||||||
|
// if (sub.AnalysisFps > 0)
|
||||||
|
// {
|
||||||
|
// // 告诉控制器:这个 App 需要 Y 帧用于分析
|
||||||
|
// device.Controller.Register(analysisKey, sub.AnalysisFps);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// device.Controller.Unregister(analysisKey);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 运维审计
|
||||||
|
// device.AddAuditLog($"更新订阅策略 [{sub.AppId}]: Display={sub.DisplayFps}, Analysis={sub.AnalysisFps}");
|
||||||
|
|
||||||
|
// return Ok(new { Message = "订阅策略已更新", DeviceId = id });
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
@@ -177,4 +177,25 @@ public static class GlobalStreamDispatcher
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Unsubscribe
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [新增重载] 强制取消订阅:直接移除指定 AppId 的整个路由项
|
||||||
|
/// 用途:当业务模块(如播放窗口)销毁时,彻底切断该 AppId 的数据流
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appId">业务唯一标识</param>
|
||||||
|
public static void Unsubscribe(string appId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(appId)) return;
|
||||||
|
|
||||||
|
// 直接从字典中移除 Key,这将丢弃该 Key 下挂载的所有委托链
|
||||||
|
// TryRemove 是原子的、线程安全的
|
||||||
|
if (_routingTable.TryRemove(appId, out _))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[Dispatcher] 已强制移除 AppId [{appId}] 的所有订阅路由");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@@ -36,8 +36,15 @@ public class FrameController
|
|||||||
|
|
||||||
public void Unregister(string appId)
|
public void Unregister(string appId)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(appId)) return;
|
||||||
|
|
||||||
|
// 1. 从需求配置中移除
|
||||||
_requirements.TryRemove(appId, out _);
|
_requirements.TryRemove(appId, out _);
|
||||||
_accumulators.TryRemove(appId, out _); // 同步清理,防止内存泄漏
|
|
||||||
|
// 2. 从积分累加器中移除(防止内存泄漏)
|
||||||
|
_accumulators.TryRemove(appId, out _);
|
||||||
|
|
||||||
|
Console.WriteLine($"[Scheduler] 已从调度中心彻底移除 AppId: {appId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
|
|||||||
63
SHH.CameraSdk/Core/Services/DisplayWindowManager.cs
Normal file
63
SHH.CameraSdk/Core/Services/DisplayWindowManager.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
namespace SHH.CameraSdk;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 动态窗口管理器
|
||||||
|
/// 职责:根据业务指令动态创建/销毁 OpenCV 播放窗口,并管理流订阅
|
||||||
|
/// </summary>
|
||||||
|
public class DisplayWindowManager
|
||||||
|
{
|
||||||
|
// 存储活跃的渲染器实例:Key = AppId (如 "UI_Preview_Main")
|
||||||
|
private readonly ConcurrentDictionary<string, FrameConsumer> _activeWindows = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开启一个本地播放窗口
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appId">业务标识 (将作为窗口标题)</param>
|
||||||
|
/// <param name="deviceId">要观看的设备ID</param>
|
||||||
|
public void StartDisplay(string appId, int deviceId)
|
||||||
|
{
|
||||||
|
// 如果窗口已存在,直接返回(防止重复创建)
|
||||||
|
if (_activeWindows.ContainsKey(appId)) return;
|
||||||
|
|
||||||
|
Console.WriteLine($"[DisplayManager] 正在创建窗口: {appId} -> Device {deviceId}...");
|
||||||
|
|
||||||
|
// 1. 动态创建渲染器
|
||||||
|
var renderer = new FrameConsumer(appId);
|
||||||
|
|
||||||
|
// 2. 启动渲染循环 (由于我们之前加了懒加载逻辑,此时不会立即弹窗,直到有帧数据过来)
|
||||||
|
renderer.Start();
|
||||||
|
|
||||||
|
// 3. 存入字典管理
|
||||||
|
if (_activeWindows.TryAdd(appId, renderer))
|
||||||
|
{
|
||||||
|
// 4. 【关键】建立数据订阅:将设备流导向这个渲染器
|
||||||
|
GlobalStreamDispatcher.Subscribe(appId, deviceId, frame =>
|
||||||
|
{
|
||||||
|
// 引用计数 +1,防止在渲染前被回收
|
||||||
|
frame.AddRef();
|
||||||
|
renderer.Enqueue(frame);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
renderer.Dispose(); // 并发冲突处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭并销毁窗口
|
||||||
|
/// </summary>
|
||||||
|
public void StopDisplay(string appId)
|
||||||
|
{
|
||||||
|
if (_activeWindows.TryRemove(appId, out var renderer))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[DisplayManager] 正在关闭窗口: {appId}");
|
||||||
|
|
||||||
|
// 1. 取消订阅 (停止接收数据)
|
||||||
|
GlobalStreamDispatcher.Unsubscribe(appId);
|
||||||
|
|
||||||
|
// 2. 销毁渲染器 (OpenCV DestroyWindow 会在 FrameConsumer 内部触发)
|
||||||
|
renderer.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,18 +2,14 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using OpenCvSharp;
|
|
||||||
using SHH.CameraSdk;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace SHH.CameraSdk
|
namespace SHH.CameraSdk;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A 方案:标准控制台结构 (显式 Main 方法 + STAThread)
|
/// A 方案:标准控制台结构 (动态窗口版)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
// [关键点 1] 显式声明 STA 线程模式,确保 OpenCV/GUI 窗口消息循环正常
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
public static async Task Main(string[] args)
|
public static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
@@ -21,27 +17,26 @@ namespace SHH.CameraSdk
|
|||||||
// 1. 基础设施初始化
|
// 1. 基础设施初始化
|
||||||
// ==============================================================================
|
// ==============================================================================
|
||||||
InitHardwareEnv();
|
InitHardwareEnv();
|
||||||
|
|
||||||
|
// 核心设备管理器
|
||||||
using var cameraManager = new CameraManager();
|
using var cameraManager = new CameraManager();
|
||||||
|
|
||||||
// [关键点 2] 提升变量作用域
|
// [新增] 动态窗口管理器 (不再直接 new FrameConsumer)
|
||||||
// 将渲染器(消费者)在 Main 中声明,确保它与主程序同寿命,不会被中途回收
|
// 这是一个单例服务,负责在运行期间管理所有弹出的窗口
|
||||||
using var remoteRenderer = new FrameConsumer("Process A Remote Preview");
|
var displayManager = new DisplayWindowManager();
|
||||||
remoteRenderer.Start();
|
|
||||||
|
|
||||||
// ==============================================================================
|
// ==============================================================================
|
||||||
// 2. 启动 Web 监控与诊断服务
|
// 2. 启动 Web 监控与诊断服务 (注入两个管理器)
|
||||||
// ==============================================================================
|
// ==============================================================================
|
||||||
var app = await StartWebMonitoring(cameraManager);
|
var app = await StartWebMonitoring(cameraManager, displayManager);
|
||||||
|
|
||||||
// [新增] 启动网络哨兵 (它会自动在后台跑)
|
// 启动网络哨兵 (后台 Ping)
|
||||||
// 就像保安一样,你不需要管它,它每3秒会把所有摄像头的 IsOnline 状态刷一遍
|
|
||||||
var sentinel = new ConnectivitySentinel(cameraManager);
|
var sentinel = new ConnectivitySentinel(cameraManager);
|
||||||
|
|
||||||
// ==============================================================================
|
// ==============================================================================
|
||||||
// 3. 业务编排:配置设备与流控策略 (8+2 演示)
|
// 3. 业务编排:仅配置设备,不配置窗口
|
||||||
// ==============================================================================
|
// ==============================================================================
|
||||||
// [关键点 3] 将渲染器作为参数传递进去
|
await ConfigureBusinessLogic(cameraManager);
|
||||||
await ConfigureBusinessLogic(cameraManager, remoteRenderer);
|
|
||||||
|
|
||||||
// ==============================================================================
|
// ==============================================================================
|
||||||
// 4. 启动引擎与交互
|
// 4. 启动引擎与交互
|
||||||
@@ -49,11 +44,11 @@ namespace SHH.CameraSdk
|
|||||||
Console.WriteLine("\n[系统] 正在启动全局管理引擎...");
|
Console.WriteLine("\n[系统] 正在启动全局管理引擎...");
|
||||||
await cameraManager.StartAsync();
|
await cameraManager.StartAsync();
|
||||||
|
|
||||||
Console.WriteLine(">> 系统就绪。访问 http://localhost:5000/swagger 查看诊断信息。");
|
Console.WriteLine(">> 系统就绪。");
|
||||||
|
Console.WriteLine(">>当前无播放窗口。请通过 Web 界面 '新增订阅' -> 模式选 'UI_Preview' 来动态打开。");
|
||||||
Console.WriteLine(">> 按 'S' 键退出...");
|
Console.WriteLine(">> 按 'S' 键退出...");
|
||||||
|
|
||||||
// [关键点 4] 阻塞主线程
|
// 阻塞主线程,保持程序运行
|
||||||
// 只要这个循环在跑,remoteRenderer 就不会被 Dispose,窗口就会一直存在
|
|
||||||
while (Console.ReadKey(true).Key != ConsoleKey.S)
|
while (Console.ReadKey(true).Key != ConsoleKey.S)
|
||||||
{
|
{
|
||||||
Thread.Sleep(100);
|
Thread.Sleep(100);
|
||||||
@@ -64,28 +59,38 @@ namespace SHH.CameraSdk
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ==============================================================================
|
// ==============================================================================
|
||||||
// Static Methods (原 Local Functions 转换为类的静态方法)
|
// Static Methods
|
||||||
// ==============================================================================
|
// ==============================================================================
|
||||||
|
|
||||||
static void InitHardwareEnv()
|
static void InitHardwareEnv()
|
||||||
{
|
{
|
||||||
Console.WriteLine("=== 工业级视频 SDK 架构测试 (V3.3 分层版 - STA模式) ===");
|
Console.WriteLine("=== 工业级视频 SDK 架构测试 (V3.3 动态窗口版) ===");
|
||||||
Console.WriteLine("[硬件] 海康驱动预热中...");
|
Console.WriteLine("[硬件] 海康驱动预热中...");
|
||||||
HikNativeMethods.NET_DVR_Init();
|
HikNativeMethods.NET_DVR_Init();
|
||||||
HikSdkManager.ForceWarmUp(); // 强制加载 PlayCtrl.dll
|
HikSdkManager.ForceWarmUp();
|
||||||
Console.WriteLine("[硬件] 预热完成。");
|
Console.WriteLine("[硬件] 预热完成。");
|
||||||
}
|
}
|
||||||
|
|
||||||
static async Task<WebApplication> StartWebMonitoring(CameraManager manager)
|
// [修改] 签名增加 DisplayWindowManager 参数
|
||||||
|
static async Task<WebApplication> StartWebMonitoring(CameraManager manager, DisplayWindowManager displayMgr)
|
||||||
{
|
{
|
||||||
var builder = WebApplication.CreateBuilder();
|
var builder = WebApplication.CreateBuilder();
|
||||||
|
|
||||||
// [新增] 屏蔽日志配置
|
builder.Services.AddCors(options =>
|
||||||
builder.Logging.AddFilter("Microsoft", Microsoft.Extensions.Logging.LogLevel.Warning);
|
{
|
||||||
builder.Logging.AddFilter("System", Microsoft.Extensions.Logging.LogLevel.Warning);
|
options.AddPolicy("AllowAll", policy =>
|
||||||
builder.Logging.AddFilter("Microsoft.AspNetCore.Hosting.Diagnostics", Microsoft.Extensions.Logging.LogLevel.Warning);
|
{
|
||||||
|
policy.AllowAnyOrigin()
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowAnyMethod();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 日志屏蔽
|
||||||
|
builder.Logging.AddFilter("Microsoft", LogLevel.Warning);
|
||||||
|
builder.Logging.AddFilter("System", LogLevel.Warning);
|
||||||
|
builder.Logging.AddFilter("Microsoft.AspNetCore.Hosting.Diagnostics", LogLevel.Warning);
|
||||||
|
|
||||||
// 注入服务
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen(c =>
|
builder.Services.AddSwaggerGen(c =>
|
||||||
@@ -94,57 +99,39 @@ namespace SHH.CameraSdk
|
|||||||
});
|
});
|
||||||
builder.Services.AddCors(o => o.AddPolicy("AllowAll", p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));
|
builder.Services.AddCors(o => o.AddPolicy("AllowAll", p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));
|
||||||
|
|
||||||
// 关键:注入单例 Manager
|
// [关键] 注入两个单例服务,让 Controller 能调用它们
|
||||||
builder.Services.AddSingleton(manager);
|
builder.Services.AddSingleton(manager);
|
||||||
|
builder.Services.AddSingleton(displayMgr);
|
||||||
|
|
||||||
var webApp = builder.Build();
|
var webApp = builder.Build();
|
||||||
|
|
||||||
// 配置管道
|
|
||||||
webApp.UseSwagger();
|
webApp.UseSwagger();
|
||||||
webApp.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Diagnostics V1"));
|
webApp.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Diagnostics V1"));
|
||||||
webApp.UseCors("AllowAll");
|
webApp.UseCors("AllowAll");
|
||||||
webApp.MapControllers();
|
webApp.MapControllers();
|
||||||
|
|
||||||
// 异步启动,不阻塞主线程
|
|
||||||
_ = webApp.RunAsync("http://0.0.0.0:5000");
|
_ = webApp.RunAsync("http://0.0.0.0:5000");
|
||||||
Console.WriteLine("[Web] 监控API已启动: http://localhost:5000");
|
Console.WriteLine("[Web] 监控API已启动: http://localhost:5000");
|
||||||
|
|
||||||
return webApp;
|
return webApp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// [关键点 5] 方法签名修改:接收 FrameConsumer 参数
|
// [修改] 移除 FrameConsumer 参数,不再进行硬编码订阅
|
||||||
static async Task ConfigureBusinessLogic(CameraManager manager, FrameConsumer renderer)
|
static async Task ConfigureBusinessLogic(CameraManager manager)
|
||||||
{
|
{
|
||||||
// 1. 配置设备
|
// 1. 仅添加设备配置
|
||||||
var config = new VideoSourceConfig
|
var config = new VideoSourceConfig
|
||||||
{
|
{
|
||||||
Id = 101,
|
Id = 101,
|
||||||
Brand = DeviceBrand.HikVision,
|
Brand = DeviceBrand.HikVision,
|
||||||
IpAddress = "172.16.41.206",
|
IpAddress = "172.16.41.206",
|
||||||
//IpAddress = "192.168.5.9",
|
|
||||||
Port = 8000,
|
Port = 8000,
|
||||||
Username = "admin",
|
Username = "admin",
|
||||||
Password = "abcd1234",
|
Password = "abcd1234",
|
||||||
//Password = "RRYFOA",
|
StreamType = 0
|
||||||
StreamType = 0 // 主码流
|
|
||||||
};
|
};
|
||||||
manager.AddDevice(config);
|
manager.AddDevice(config);
|
||||||
|
|
||||||
if (manager.GetDevice(101) is HikVideoSource hikCamera)
|
|
||||||
{
|
|
||||||
// 1. 注册差异化需求 (给每个消费者唯一的 AppId)
|
|
||||||
// ----------------------------------------------------
|
|
||||||
// 1. 注册需求时,手动加上 _Display 后缀
|
|
||||||
hikCamera.Controller.Register("Process_A_Remote_Display", 20);
|
|
||||||
|
|
||||||
// 2. 订阅时,也改用带后缀的名称
|
|
||||||
GlobalStreamDispatcher.Subscribe("Process_A_Remote_Display", 101, frame =>
|
|
||||||
{
|
|
||||||
frame.AddRef();
|
|
||||||
renderer.Enqueue(frame);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var config2 = new VideoSourceConfig
|
var config2 = new VideoSourceConfig
|
||||||
{
|
{
|
||||||
Id = 102,
|
Id = 102,
|
||||||
@@ -153,14 +140,11 @@ namespace SHH.CameraSdk
|
|||||||
Port = 8000,
|
Port = 8000,
|
||||||
Username = "admin",
|
Username = "admin",
|
||||||
Password = "abcd1234",
|
Password = "abcd1234",
|
||||||
StreamType = 0 // 主码流
|
StreamType = 0
|
||||||
};
|
};
|
||||||
manager.AddDevice(config2);
|
manager.AddDevice(config2);
|
||||||
|
|
||||||
//if (manager.GetDevice(102) is HikVideoSource hikCamera2)
|
// 注意:此处不再调用 Register 或 Subscribe
|
||||||
//{
|
// 所有的播放请求都将由 WebAPI 收到前端指令后,调用 DisplayWindowManager 来动态发起
|
||||||
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user