Files
Ayay/SHH.CameraSdk/Core/Manager/CameraManager.cs
twice109 e9f5975a79 1、解决使用 Cv2.ImShow 播放画面,闪烁一下窗口不见的问题
2、之前注册播放、分析帧,回调时必须判定是否当前注册源,现增加新方法可以不用判定
3、将之前程序一运行就播放,调整为手动指定 IsRunning 值来控制
2025-12-26 06:14:55 +08:00

233 lines
8.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
namespace SHH.CameraSdk;
/// <summary>
/// [管理层] 视频源总控管理器 (V3.3.1 修复版)
/// 核心职责:统一管理所有相机设备的生命周期、状态监控与资源清理,对接协调器实现自动自愈
/// 核心修复:
/// <para>1. [Bug γ] 二次伤害:强化销毁流程,防止 Dispose 阶段因 GC 乱序导致的非托管内存非法访问</para>
/// <para>2. [Bug A/L] 继承之前的动态感知与末日销毁协同修复,保障多线程环境下的状态一致性</para>
/// </summary>
public class CameraManager : IDisposable, IAsyncDisposable
{
#region --- 1. (Fields & States) ---
/// <summary> 全局设备实例池线程安全Key = 设备唯一标识 </summary>
private readonly ConcurrentDictionary<long, BaseVideoSource> _cameraPool = new();
/// <summary> 后台协调器实例:负责心跳检测、断线重连、僵尸流恢复 </summary>
private readonly CameraCoordinator _coordinator = new();
/// <summary> 全局取消令牌源:用于销毁时瞬间关停所有异步扫描任务 </summary>
private readonly CancellationTokenSource _globalCts = new();
/// <summary> 销毁状态标记:防止重复销毁或销毁过程中执行操作 </summary>
private volatile bool _isDisposed;
/// <summary>
/// [Fix Bug A: 动态失效] 协调器引擎运行状态标记
/// 使用 volatile 关键字确保多线程环境下的内存可见性,避免指令重排导致的状态不一致
/// </summary>
private volatile bool _isEngineStarted = false;
#endregion
#region --- 2. (Device Management) ---
/// <summary>
/// 向管理池添加新相机设备
/// </summary>
/// <param name="config">相机设备配置信息</param>
public void AddDevice(VideoSourceConfig config)
{
// [安全防护] 销毁过程中禁止添加新设备
if (_isDisposed) return;
// 防止重复添加同一设备
if (_cameraPool.ContainsKey(config.Id)) return;
// 1. 根据设备品牌实例化对应的驱动实现类
BaseVideoSource source = config.Brand switch
{
DeviceBrand.HikVision => new HikVideoSource(config),
_ => throw new NotSupportedException($"不支持的相机品牌: {config.Brand}")
};
// 2. [Fix Bug A] 动态激活逻辑:引擎已启动时,新设备直接标记为运行状态
if (_isEngineStarted)
{
source.IsRunning = true;
}
// 3. 将设备注册到内存池与协调器,纳入统一管理
if (_cameraPool.TryAdd(config.Id, source))
{
_coordinator.Register(source);
}
}
/// <summary>
/// 根据设备ID获取指定的视频源实例
/// </summary>
/// <param name="id">设备唯一标识</param>
/// <returns>视频源实例 / 不存在则返回 null</returns>
public BaseVideoSource? GetDevice(long id)
=> _cameraPool.TryGetValue(id, out var source) ? source : null;
#endregion
#region --- 3. (Engine Lifecycle) ---
/// <summary>
/// 启动视频管理引擎初始化SDK并启动协调器自愈循环
/// </summary>
public async Task StartAsync()
{
// 防护:已销毁则抛出异常
if (_isDisposed) throw new ObjectDisposedException(nameof(CameraManager));
// 防护:避免重复启动
if (_isEngineStarted) return;
// 1. 全局驱动环境预初始化:初始化厂商 SDK 运行环境
HikSdkManager.Initialize();
//// 2. 激活现有设备池中所有设备的“运行意图”,触发设备连接流程
//foreach (var source in _cameraPool.Values)
//{
// source.IsRunning = true;
//}
// 标记引擎启动状态,后续新增设备自动激活
_isEngineStarted = true;
// 3. 启动协调器后台自愈循环(标记为 LongRunning 提升调度优先级)
_ = Task.Factory.StartNew(
() => _coordinator.RunCoordinationLoopAsync(_globalCts.Token),
_globalCts.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
Console.WriteLine($"[CameraManager] 引擎启动成功,当前管理 {_cameraPool.Count} 路相机设备。");
await Task.CompletedTask;
}
/// <summary>
/// 获取当前所有相机的全局状态简报
/// </summary>
/// <returns>包含设备ID、IP、运行状态的元组集合</returns>
public IEnumerable<(long Id, string Ip, VideoSourceStatus Status)> GetGlobalStatus()
{
return _cameraPool.Values.Select(v => (v.Id, v.Config.IpAddress, v.Status));
}
#endregion
#region --- 4. (Telemetry Collection) ---
/// <summary>
/// 获取所有相机的健康度报告
/// </summary>
/// <returns>相机健康度报告集合</returns>
public IEnumerable<CameraHealthReport> GetDetailedTelemetry()
{
return _cameraPool.Values.Select(cam => new CameraHealthReport
{
DeviceId = cam.Id,
Ip = cam.Config.IpAddress,
Status = cam.Status.ToString(),
LastError = cam.Status == VideoSourceStatus.Faulted ? "设备故障或网络中断" : "运行正常"
// 扩展:可补充 RealFps/DropFrames/ReconnectCount 等指标
});
}
/// <summary>
/// [新增] 获取全量相机实时遥测数据快照
/// 用于 WebAPI 实时监控大屏展示
/// </summary>
/// <returns>相机遥测数据快照集合</returns>
public IEnumerable<CameraTelemetryInfo> GetTelemetrySnapshot()
{
// 立即物化列表,防止枚举过程中集合被修改导致异常
return _cameraPool.Values.Select(cam =>
{
// 健康度评分算法(示例):基于设备状态与实时帧率综合判定
int healthScore = 100;
if (cam.Status == VideoSourceStatus.Faulted)
healthScore = 0;
else if (cam.Status == VideoSourceStatus.Reconnecting)
healthScore = 60;
else if (cam.RealFps < 1.0 && cam.Status == VideoSourceStatus.Playing)
healthScore = 40; // 有连接状态但无有效流
return new CameraTelemetryInfo
{
DeviceId = cam.Id,
Name = cam.Config.Name,
IpAddress = cam.Config.IpAddress,
Status = cam.Status.ToString(),
IsOnline = cam.IsOnline,
Fps = cam.RealFps,
TotalFrames = cam.TotalFrames,
HealthScore = healthScore,
LastErrorMessage = cam.Status == VideoSourceStatus.Faulted ? "设备故障或网络中断" : null,
Timestamp = DateTime.Now
};
}).ToList();
}
#endregion
#region --- 5. (Disposal) ---
/// <summary>
/// 同步销毁:内部调用异步销毁逻辑,等待销毁完成
/// </summary>
public void Dispose() => DisposeAsync().AsTask().GetAwaiter().GetResult();
/// <summary>
/// [修复 Bug L & Bug γ] 异步执行全局资源清理
/// 严格遵循销毁顺序:停止任务 → 销毁设备 → 卸载SDK防止非托管内存泄漏
/// </summary>
public async ValueTask DisposeAsync()
{
// 防护:避免重复销毁
if (_isDisposed) return;
// 标记为已销毁,禁止后续操作
_isDisposed = true;
_isEngineStarted = false;
try
{
// 1. 发送全局取消信号,立即停止协调器所有后台扫描任务
_globalCts.Cancel();
// 2. [Fix Bug L] 锁定设备池快照并清空,防止并发修改导致异常
var devices = _cameraPool.Values.ToArray();
_cameraPool.Clear();
// 3. 并行销毁所有相机设备,释放设备持有的非托管资源
var disposeTasks = devices.Select(async device =>
{
try { await device.DisposeAsync(); }
catch { /* 隔离单个设备销毁异常,不影响其他设备 */ }
});
await Task.WhenAll(disposeTasks);
// 4. [Fix Bug γ: 二次伤害] 彻底卸载全局 SDK 环境
// 加 try-catch 防护极端场景(如进程强制终止时 SDK 已被系统回收)
try
{
HikSdkManager.Uninitialize();
}
catch
{
// 忽略卸载异常,保证销毁流程正常结束
}
}
finally
{
// 释放取消令牌源资源
_globalCts.Dispose();
}
}
#endregion
}