海康摄像头取流示例初始签入

This commit is contained in:
2025-12-26 03:18:21 +08:00
parent 86db2cf6b4
commit 6281f4248e
44 changed files with 5588 additions and 0 deletions

View File

@@ -0,0 +1,231 @@
using System.Net.NetworkInformation;
using System.Net.Sockets;
namespace SHH.CameraSdk;
/// <summary>
/// [调度协调层] 视频自愈调度器 (V3.3.4 流量削峰版)
/// 核心职责:监控所有相机设备的运行状态,实现断线自动重连、僵死状态复位,保障视频流稳定
/// 核心修复:
/// <para>1. [Bug τ] 线程池保护:引入并发节流阀,限制同时重连/探测的任务数,防止线程池饥饿</para>
/// </summary>
public class CameraCoordinator
{
#region --- (Private Resources & Configurations) ---
/// <summary> 已注册的相机设备集合(线程安全,支持并发添加与遍历) </summary>
private readonly ConcurrentBag<BaseVideoSource> _cameras = new();
/// <summary> 全局登录单行道锁 </summary>
/// <remarks> 限制同一时刻仅允许一个相机执行登录操作,避免 SDK 登录冲突 </remarks>
private readonly SemaphoreSlim _sdkLoginLock = new(1, 1);
/// <summary> 并发节流阀:限制同时进行探测/重连的任务数 </summary>
/// <remarks> 最大并发数 8避免百路相机同时重连导致 CPU 峰值过高、UI 卡顿 </remarks>
private readonly SemaphoreSlim _concurrencyLimiter = new(8);
/// <summary> 相机流存活判定阈值(秒):超过该时间无帧则判定为流中断 </summary>
private const int StreamAliveThresholdSeconds = 5;
/// <summary> Ping 探测超时时间(毫秒) </summary>
private const int PingTimeoutMs = 800;
/// <summary> TCP 探测超时时间(毫秒) </summary>
private const int TcpTimeoutMs = 1000;
/// <summary> 调度循环间隔(毫秒):每 5 秒执行一次全量设备状态校验 </summary>
private const int CoordinationLoopIntervalMs = 5000;
#endregion
#region --- (Camera Registration) ---
/// <summary>
/// 注册相机设备到调度器
/// 功能:将相机纳入全局状态监控与自愈管理
/// </summary>
/// <param name="camera">待注册的相机设备实例</param>
public void Register(BaseVideoSource camera) => _cameras.Add(camera);
#endregion
#region --- (Core Coordination Loop) ---
/// <summary>
/// 启动调度协调循环(长期运行任务)
/// 功能:周期性校验所有相机状态,执行自愈逻辑,支持取消
/// </summary>
/// <param name="token">取消令牌:用于终止调度循环</param>
public async Task RunCoordinationLoopAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
// 削峰填谷式调度:通过并发节流阀控制任务并发数
var tasks = _cameras.Select(async cam =>
{
// 申请“重连/探测许可证”,无可用许可时阻塞等待
await _concurrencyLimiter.WaitAsync(token).ConfigureAwait(false);
try
{
// 安全执行状态调和(隔离单个相机的异常)
await SafeReconcileAsync(cam, token).ConfigureAwait(false);
}
finally
{
// 释放许可,允许其他相机执行任务
_concurrencyLimiter.Release();
}
});
// 等待所有相机的调和任务完成
await Task.WhenAll(tasks).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// 收到取消信号,退出循环
break;
}
catch (Exception ex)
{
// 捕获调度层全局异常,避免循环终止
Console.WriteLine($"[CoordinatorCritical] 调度循环异常: {ex.Message}");
}
try
{
// 等待下一个调度周期(支持响应取消)
await Task.Delay(CoordinationLoopIntervalMs, token).ConfigureAwait(false);
}
catch
{
// 延迟过程中收到取消信号,退出循环
break;
}
}
}
#endregion
#region --- (Safe Reconciliation Wrapper) ---
/// <summary>
/// 安全执行相机状态调和
/// 功能:隔离单个相机的异常,避免影响其他相机的调和逻辑
/// </summary>
/// <param name="cam">待调和的相机设备</param>
/// <param name="token">取消令牌</param>
private async Task SafeReconcileAsync(BaseVideoSource cam, CancellationToken token)
{
try
{
await ReconcileAsync(cam, token).ConfigureAwait(false);
}
catch
{
// 吞没单个相机的异常,确保其他相机正常调度
}
}
#endregion
#region --- (Reconciliation Logic) ---
/// <summary>
/// 相机状态调和(核心自愈逻辑)
/// 功能:校验相机物理连接、流状态,执行启动/停止/复位操作,确保状态一致性
/// </summary>
/// <param name="cam">待调和的相机设备</param>
/// <param name="token">取消令牌</param>
private async Task ReconcileAsync(BaseVideoSource cam, CancellationToken token)
{
// 1. 计算距离上次收到帧的时间(秒)
long nowTick = Environment.TickCount64;
double secondsSinceLastFrame = (nowTick - cam.LastFrameTick) / 1000.0;
// 2. 判定流是否正常:设备在线 + 5秒内有帧
bool isFlowing = cam.IsOnline && secondsSinceLastFrame < StreamAliveThresholdSeconds;
// 3. 判定物理连接是否正常:流正常则直接判定在线;否则执行 Ping+TCP 探测
bool isPhysicalOk = isFlowing ? true : await ProbeHardwareAsync(cam).ConfigureAwait(false);
// 4. 状态调和决策:根据物理状态与设备状态的差异执行对应操作
if (isPhysicalOk && !cam.IsOnline && cam.IsRunning)
{
// 物理在线 + 设备离线 + 需运行 → 执行启动(加登录锁防止冲突)
bool lockTaken = false;
try
{
await _sdkLoginLock.WaitAsync(token).ConfigureAwait(false);
lockTaken = true;
// 双重校验:防止等待锁期间状态已变更
if (!cam.IsOnline)
{
await cam.StartAsync().ConfigureAwait(false);
}
}
finally
{
if (lockTaken)
{
_sdkLoginLock.Release();
}
}
}
else if (!isPhysicalOk && cam.IsOnline)
{
// 物理离线 + 设备在线 → 执行停止
await cam.StopAsync().ConfigureAwait(false);
}
else if (isPhysicalOk && cam.IsOnline && !isFlowing)
{
// 物理在线 + 设备在线 + 流中断 → 判定为僵死,执行复位
Console.WriteLine($"[自愈] 设备 {cam.Id} 僵死({secondsSinceLastFrame:F1}秒无帧),复位中...");
await cam.StopAsync().ConfigureAwait(false);
}
}
#endregion
#region --- (Hardware Probing) ---
/// <summary>
/// 硬件连接探测:通过 Ping + TCP 双探测判定设备物理可达性
/// </summary>
/// <param name="cam">待探测的相机设备</param>
/// <returns>物理可达返回 true否则返回 false</returns>
private async Task<bool> ProbeHardwareAsync(BaseVideoSource cam)
{
// 1. 优先执行 Ping 探测(快速判定网络连通性)
try
{
using var ping = new Ping();
PingReply reply = await ping.SendPingAsync(cam.Config.IpAddress, PingTimeoutMs).ConfigureAwait(false);
if (reply.Status == IPStatus.Success)
{
return true;
}
}
catch
{
// Ping 探测失败,执行 TCP 探测兜底
}
// 2. TCP 探测:尝试连接设备端口(更精准的服务可达性判定)
try
{
using var tcpClient = new TcpClient();
using var cts = new CancellationTokenSource(TcpTimeoutMs);
await tcpClient.ConnectAsync(cam.Config.IpAddress, cam.Config.Port, cts.Token).ConfigureAwait(false);
return true;
}
catch
{
// TCP 探测失败,判定为物理不可达
return false;
}
}
#endregion
}