2026-01-16 07:23:56 +08:00
|
|
|
|
using Ayay.SerilogLogs;
|
|
|
|
|
|
using OpenCvSharp;
|
|
|
|
|
|
using Serilog;
|
2025-12-28 13:14:40 +08:00
|
|
|
|
using SHH.CameraSdk.HikFeatures;
|
2026-01-07 10:59:03 +08:00
|
|
|
|
using System.Runtime.ExceptionServices;
|
|
|
|
|
|
using System.Security;
|
2025-12-26 03:18:21 +08:00
|
|
|
|
|
|
|
|
|
|
namespace SHH.CameraSdk;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-01-17 14:03:33 +08:00
|
|
|
|
/// [海康驱动] 工业级视频源实现 V3.5.0 (运维增强版)
|
|
|
|
|
|
/// <para>技术支撑:基于 Hikvision CH-NetSDK V6.1.x 开发</para>
|
|
|
|
|
|
/// <para>核心职责:深度封装海康私有协议,实现从原始私有码流到 BGR 零拷贝帧池的高性能转换与分发</para>
|
|
|
|
|
|
/// 关键修复与增强记录:
|
|
|
|
|
|
/// <para>✅ 1. [Fix Bug Z] 架构修正:移除子类冗余的 Controller 定义,强制复用基类 <see cref="BaseVideoSource.Controller"/>,彻底修复多路并发下的 FPS 流控失效问题</para>
|
|
|
|
|
|
/// <para>✅ 2. [Feat A] 热更新支持:重写 <see cref="OnApplyOptions"/>,实现码流类型(Main/Sub)与渲染句柄的动态热切换,无需重启设备链路即可生效</para>
|
|
|
|
|
|
/// <para>✅ 3. [Feat B] 运维集成:全链路接入 <see cref="BaseVideoSource.AddAuditLog"/>,将登录、取流、重连及 SDK 报警实时推送至 Web 运维仪表盘</para>
|
|
|
|
|
|
/// <para>✅ 4. [Feat C] 性能优化:在解码回调中使用 <see cref="Monitor.TryEnter"/> 竞争锁,有效规避在设备断开瞬间可能产生的驱动层死锁</para>
|
2025-12-26 03:18:21 +08:00
|
|
|
|
/// </summary>
|
2025-12-28 13:14:40 +08:00
|
|
|
|
public class HikVideoSource : BaseVideoSource,
|
|
|
|
|
|
IHikContext, ITimeSyncFeature, IRebootFeature, IPtzFeature
|
2025-12-26 03:18:21 +08:00
|
|
|
|
{
|
2026-01-17 14:03:33 +08:00
|
|
|
|
#region --- 1. 静态资源与全局路由 (Static Resources) ---
|
2025-12-26 03:18:21 +08:00
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
/// <summary> 海康 SDK 专用日志实例 </summary>
|
2026-01-16 15:17:23 +08:00
|
|
|
|
protected override ILogger _sdkLog => Log.ForContext("SourceContext", LogModules.HikVisionSdk);
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
/// <summary> 全局句柄映射表:用于静态异常回调分发至具体实例 </summary>
|
2025-12-26 03:18:21 +08:00
|
|
|
|
private static readonly ConcurrentDictionary<int, HikVideoSource> _instances = new();
|
2026-01-17 14:03:33 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary> 静态异常回调委托引用 </summary>
|
2025-12-26 03:18:21 +08:00
|
|
|
|
private static readonly HikNativeMethods.EXCEPTION_CALLBACK _globalExceptionCallback = StaticOnSdkException;
|
2026-01-17 14:03:33 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary> 全局播放端口抢占锁 </summary>
|
2025-12-26 03:18:21 +08:00
|
|
|
|
private static readonly object _globalPortLock = new();
|
2025-12-26 12:15:10 +08:00
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
#region --- 2. 实例成员与组件引用 (Instance Members) ---
|
|
|
|
|
|
|
|
|
|
|
|
// 协议功能组件
|
2025-12-28 13:14:40 +08:00
|
|
|
|
private readonly HikTimeSyncProvider _timeProvider;
|
|
|
|
|
|
private readonly HikRebootProvider _rebootProvider;
|
|
|
|
|
|
private readonly HikPtzProvider _ptzProvider;
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
// SDK 句柄与资源
|
2025-12-26 12:15:10 +08:00
|
|
|
|
private int _userId = -1; // SDK 登录句柄
|
|
|
|
|
|
private int _realPlayHandle = -1; // 预览句柄
|
|
|
|
|
|
private int _playPort = -1; // 播放端口
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
// 同步控制
|
|
|
|
|
|
private readonly object _initLock = new(); // 登录句柄初始化锁
|
|
|
|
|
|
private readonly object _bufferLock = new(); // 帧缓冲锁
|
|
|
|
|
|
private volatile int _connectionEpoch = 0; // 连接轮询版本号
|
2025-12-26 03:18:21 +08:00
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
// 回调委托(强引用防止GC回收)
|
2025-12-26 03:18:21 +08:00
|
|
|
|
private HikNativeMethods.REALDATACALLBACK? _realDataCallBack;
|
|
|
|
|
|
private HikPlayMethods.DECCBFUN? _decCallBack;
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
// 图像处理资源, 内存复用对象
|
2025-12-26 03:18:21 +08:00
|
|
|
|
private Mat? _sharedYuvMat;
|
2026-01-17 14:03:33 +08:00
|
|
|
|
private Mat? _sharedBgrMat;
|
2025-12-26 03:18:21 +08:00
|
|
|
|
private FramePool? _framePool;
|
2025-12-26 12:15:10 +08:00
|
|
|
|
private bool _isPoolReady = false;
|
|
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
#region --- 3. 构造函数 (Constructor) ---
|
2025-12-26 12:15:10 +08:00
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 海康视频源实现
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="config"></param>
|
2025-12-26 12:15:10 +08:00
|
|
|
|
public HikVideoSource(VideoSourceConfig config) : base(config)
|
|
|
|
|
|
{
|
2025-12-28 13:14:40 +08:00
|
|
|
|
// 初始化组件,将 "this" 作为上下文传进去
|
|
|
|
|
|
_timeProvider = new HikTimeSyncProvider(this);
|
|
|
|
|
|
_rebootProvider = new HikRebootProvider(this);
|
|
|
|
|
|
_ptzProvider = new HikPtzProvider(this);
|
2025-12-26 12:15:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
#region --- 4. 接口实现:IHikContext & Features (Interface Impls) ---
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取登录句柄
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public int GetUserId() => _userId; // 暴露父类或私有的 _userId
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取设备IP
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public string GetDeviceIp() => Config.IpAddress;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 核心逻辑:全部委托给 _timeProvider 处理,自己不写一行逻辑
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public Task<DateTime> GetTimeAsync() => _timeProvider.GetTimeAsync();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 设置设备时间
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="time"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public Task SetTimeAsync(DateTime time) => _timeProvider.SetTimeAsync(time);
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 重启设备
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public Task RebootAsync() => _rebootProvider.RebootAsync();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// PTZ 控制
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="action"></param>
|
|
|
|
|
|
/// <param name="stop"></param>
|
|
|
|
|
|
/// <param name="speed"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public Task PtzControlAsync(PtzAction action, bool stop, int speed = 4)
|
|
|
|
|
|
=> _ptzProvider.PtzControlAsync(action, stop, speed);
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// PTZ 步长
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="action"></param>
|
|
|
|
|
|
/// <param name="durationMs"></param>
|
|
|
|
|
|
/// <param name="speed"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public Task PtzStepAsync(PtzAction action, int durationMs, int speed = 4)
|
|
|
|
|
|
=> _ptzProvider.PtzStepAsync(action, durationMs, speed);
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region --- 5. 生命周期重写 (Lifecycle Overrides) ---
|
2025-12-26 12:15:10 +08:00
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 启动逻辑
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="token"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
/// <exception cref="CameraException"></exception>
|
|
|
|
|
|
/// <exception cref="OperationCanceledException"></exception>
|
2025-12-26 03:18:21 +08:00
|
|
|
|
protected override async Task OnStartAsync(CancellationToken token)
|
|
|
|
|
|
{
|
|
|
|
|
|
int currentEpoch = Interlocked.Increment(ref _connectionEpoch);
|
|
|
|
|
|
|
|
|
|
|
|
await Task.Run(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (currentEpoch != _connectionEpoch) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (!HikSdkManager.Initialize())
|
2026-01-16 07:23:56 +08:00
|
|
|
|
{
|
2026-01-16 15:17:23 +08:00
|
|
|
|
_sdkLog.Error("[SDK] HikVision Sdk 初始化失败.");
|
2026-01-16 17:45:27 +08:00
|
|
|
|
AddAuditLog($"[SDK] HikVision Sdk 初始化失败.");
|
|
|
|
|
|
|
2026-01-16 07:23:56 +08:00
|
|
|
|
throw new CameraException(CameraErrorCode.SdkNotInitialized, "HikVision Sdk 初始化失败.", DeviceBrand.HikVision);
|
|
|
|
|
|
}
|
2025-12-26 03:18:21 +08:00
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
HikNativeMethods.NET_DVR_SetExceptionCallBack_V30(0, IntPtr.Zero, _globalExceptionCallback, IntPtr.Zero);
|
|
|
|
|
|
|
2025-12-26 12:15:10 +08:00
|
|
|
|
// [审计] 记录登录动作
|
2026-01-16 15:17:23 +08:00
|
|
|
|
_sdkLog.Information($"[SDK] Hik 正在执行登录 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}");
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik 正在执行登录 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}");
|
2025-12-26 12:15:10 +08:00
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
var devInfo = new HikNativeMethods.NET_DEVICEINFO_V30();
|
|
|
|
|
|
int newUserId = HikNativeMethods.NET_DVR_Login_V30(
|
|
|
|
|
|
_config.IpAddress, _config.Port, _config.Username, _config.Password, ref devInfo);
|
|
|
|
|
|
|
|
|
|
|
|
if (currentEpoch != _connectionEpoch)
|
|
|
|
|
|
{
|
2025-12-26 12:15:10 +08:00
|
|
|
|
if (newUserId >= 0) HikNativeMethods.NET_DVR_Logout(newUserId);
|
2026-01-16 17:45:27 +08:00
|
|
|
|
_sdkLog.Information($"[SDK] Hik 启动任务已过期 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}");
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik 启动任务已过期 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}");
|
2025-12-26 12:15:10 +08:00
|
|
|
|
throw new OperationCanceledException("启动任务已过期");
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 17:45:27 +08:00
|
|
|
|
if (newUserId < 0)
|
2025-12-26 03:18:21 +08:00
|
|
|
|
{
|
|
|
|
|
|
uint err = HikNativeMethods.NET_DVR_GetLastError();
|
2026-01-16 17:45:27 +08:00
|
|
|
|
|
|
|
|
|
|
_sdkLog.Warning($"[SDK] Hik 登录失败 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}");
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik 登录失败 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}");
|
2025-12-26 03:18:21 +08:00
|
|
|
|
throw new CameraException(HikErrorMapper.Map(err), $"登录失败: {err}", DeviceBrand.HikVision, (int)err);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-16 17:45:27 +08:00
|
|
|
|
_userId = newUserId;
|
|
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
_instances.TryAdd(_userId, this);
|
2026-01-16 15:17:23 +08:00
|
|
|
|
|
|
|
|
|
|
_sdkLog.Information($"[SDK] Hik 登录成功 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik 登录成功 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
|
2025-12-26 03:18:21 +08:00
|
|
|
|
|
2025-12-26 12:15:10 +08:00
|
|
|
|
// 开启取流
|
2025-12-26 03:18:21 +08:00
|
|
|
|
if (!StartRealPlay())
|
|
|
|
|
|
{
|
|
|
|
|
|
uint err = HikNativeMethods.NET_DVR_GetLastError();
|
|
|
|
|
|
throw new CameraException(HikErrorMapper.Map(err), $"预览失败: {err}", DeviceBrand.HikVision, (int)err);
|
|
|
|
|
|
}
|
2025-12-26 12:15:10 +08:00
|
|
|
|
|
2026-01-17 07:55:21 +08:00
|
|
|
|
_sdkLog.Information($"[SDK] Hik 网络取流成功 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
|
2026-01-16 15:17:23 +08:00
|
|
|
|
AddAuditLog($"[SDK] Hik 网络取流成功 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
2025-12-26 12:15:10 +08:00
|
|
|
|
catch (Exception ex)
|
2025-12-26 03:18:21 +08:00
|
|
|
|
{
|
2026-01-16 17:45:27 +08:00
|
|
|
|
_sdkLog.Error($"[SDK] Hik 启动异常. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, " + "Exception:{Exp}", ex);
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik 启动异常. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, Exception:{ex.Message}");
|
2025-12-26 03:18:21 +08:00
|
|
|
|
CleanupSync();
|
|
|
|
|
|
throw;
|
|
|
|
|
|
}
|
|
|
|
|
|
}, token);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 停止逻辑
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
2025-12-26 03:18:21 +08:00
|
|
|
|
protected override async Task OnStopAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
Interlocked.Increment(ref _connectionEpoch);
|
2026-01-16 15:17:23 +08:00
|
|
|
|
|
|
|
|
|
|
_sdkLog.Debug($"[SDK] Hik 正在执行停止流程. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik 正在执行停止流程. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
|
|
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
await Task.Run(() => CleanupSync());
|
2026-01-16 15:17:23 +08:00
|
|
|
|
|
|
|
|
|
|
_sdkLog.Information($"[SDK] Hik 设备已停止. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik 设备已停止. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}");
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 同步清理所有 SDK 资源
|
|
|
|
|
|
/// </summary>
|
2025-12-26 03:18:21 +08:00
|
|
|
|
private void CleanupSync()
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (_initLock)
|
|
|
|
|
|
{
|
2025-12-26 12:15:10 +08:00
|
|
|
|
// 1. 停止预览
|
2026-01-16 17:45:27 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_realPlayHandle >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
HikNativeMethods.NET_DVR_StopRealPlay(_realPlayHandle);
|
|
|
|
|
|
_realPlayHandle = -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch(Exception ex)
|
2025-12-26 03:18:21 +08:00
|
|
|
|
{
|
2026-01-16 17:45:27 +08:00
|
|
|
|
_sdkLog.Debug($"[SDK] Hik 停止预览失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}" + "Exception: {Exp}", ex);
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik 停止预览失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId} Exception: {ex.Message}");
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 12:15:10 +08:00
|
|
|
|
// 2. 停止解码
|
2025-12-26 03:18:21 +08:00
|
|
|
|
if (_playPort >= 0)
|
|
|
|
|
|
{
|
2026-01-16 17:45:27 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
HikPlayMethods.PlayM4_Stop(_playPort);
|
|
|
|
|
|
HikPlayMethods.PlayM4_CloseStream(_playPort);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_sdkLog.Debug($"[SDK] Hik 停止解码失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}" + "Exception: {Exp}", ex);
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik 停止解码失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId} Exception: {ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
HikPlayMethods.PlayM4_FreePort(_playPort);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_sdkLog.Warning($"[SDK] Hik 端口资源释放失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}" + "Exception: {Exp}", ex);
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik 端口资源释放失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId} Exception: {ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
_playPort = -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
lock (_bufferLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
_sharedYuvMat?.Dispose(); _sharedYuvMat = null;
|
|
|
|
|
|
_sharedBgrMat?.Dispose(); _sharedBgrMat = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 12:15:10 +08:00
|
|
|
|
// 3. 注销登录
|
2026-01-16 17:45:27 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_userId >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
_instances.TryRemove(_userId, out _);
|
|
|
|
|
|
HikNativeMethods.NET_DVR_Logout(_userId);
|
|
|
|
|
|
_userId = -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
2025-12-26 03:18:21 +08:00
|
|
|
|
{
|
2026-01-16 17:45:27 +08:00
|
|
|
|
_sdkLog.Warning($"[SDK] Hik 注销登录失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}" + "Exception: {Exp}", ex);
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik 注销登录失败. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId} Exception: {ex.Message}");
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_framePool?.Dispose();
|
|
|
|
|
|
_framePool = null;
|
|
|
|
|
|
_isPoolReady = false;
|
|
|
|
|
|
}
|
2026-01-16 17:45:27 +08:00
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
HikSdkManager.Uninitialize();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取设备元数据
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
protected override Task<DeviceMetadata> OnFetchMetadataAsync() => Task.FromResult(new DeviceMetadata());
|
|
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
#region --- 6. 驱动逻辑:热切换重写 (OnApplyOptions) ---
|
2025-12-26 12:15:10 +08:00
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 配置更新
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="options"></param>
|
2025-12-26 12:15:10 +08:00
|
|
|
|
protected override void OnApplyOptions(DynamicStreamOptions options)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. 码流热切换逻辑
|
|
|
|
|
|
if (options.StreamType.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
int targetStream = options.StreamType.Value;
|
2026-01-16 15:17:23 +08:00
|
|
|
|
|
|
|
|
|
|
_sdkLog.Debug($"[SDK] Hik 收到码流切换请求. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放句柄:{_realPlayHandle}");
|
2025-12-26 12:15:10 +08:00
|
|
|
|
AddAuditLog($"收到码流切换请求: {targetStream},开始执行热切换...");
|
|
|
|
|
|
|
|
|
|
|
|
lock (_initLock)
|
|
|
|
|
|
{
|
2026-01-16 17:45:27 +08:00
|
|
|
|
// 【修复点】双重检查在线状态
|
|
|
|
|
|
// 如果在拿锁的过程中,外部已经调用了 StopAsync,这里必须停止,否则会创建"僵尸句柄"
|
2026-01-17 00:03:16 +08:00
|
|
|
|
if (!IsActived || !IsPhysicalOnline || _userId < 0)
|
2026-01-16 17:45:27 +08:00
|
|
|
|
{
|
|
|
|
|
|
_sdkLog.Warning($"[SDK] 码流切换被取消,设备已离线.");
|
|
|
|
|
|
AddAuditLog($"[SDK] 码流切换被取消,设备已离线.");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 12:15:10 +08:00
|
|
|
|
// A. 停止预览 (Keep Login)
|
|
|
|
|
|
if (_realPlayHandle >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
HikNativeMethods.NET_DVR_StopRealPlay(_realPlayHandle);
|
|
|
|
|
|
_realPlayHandle = -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// B. 清理播放库 (防止旧流数据残留)
|
|
|
|
|
|
if (_playPort >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
HikPlayMethods.PlayM4_Stop(_playPort);
|
|
|
|
|
|
HikPlayMethods.PlayM4_CloseStream(_playPort);
|
|
|
|
|
|
HikPlayMethods.PlayM4_FreePort(_playPort);
|
|
|
|
|
|
_playPort = -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// C. 更新内部配置状态
|
|
|
|
|
|
_config.StreamType = targetStream;
|
|
|
|
|
|
|
|
|
|
|
|
// D. 重新开启预览
|
|
|
|
|
|
if (StartRealPlay())
|
|
|
|
|
|
{
|
2026-01-16 15:17:23 +08:00
|
|
|
|
_sdkLog.Information($"[SDK] Hik 码流热切换成功. => 当前: {(_config.StreamType == 0 ? "主" : "子")}码流), ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放句柄:{_realPlayHandle}");
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik 码流热切换成功. => 当前: {(_config.StreamType == 0 ? "主" : "子")}码流), ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放句柄:{_realPlayHandle}");
|
2025-12-26 12:15:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
uint err = HikNativeMethods.NET_DVR_GetLastError();
|
2026-01-16 15:17:23 +08:00
|
|
|
|
_sdkLog.Information($"[SDK] Hik 码流切换失败. => Err:{err}, ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放句柄:{_realPlayHandle}");
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik 码流切换失败. => Err:{err}, ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放句柄:{_realPlayHandle}");
|
2026-01-16 17:45:27 +08:00
|
|
|
|
|
|
|
|
|
|
// 【修复点】主动上报错误,让基类感知到当前已经断流了
|
|
|
|
|
|
// 这会将状态置为 Reconnecting,并可能触发自动重连
|
|
|
|
|
|
ReportError(new CameraException(HikErrorMapper.Map(err), "Hik 码流切换失败.", DeviceBrand.HikVision));
|
2025-12-26 12:15:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 句柄动态更新逻辑 (如有需要)
|
|
|
|
|
|
if (options.RenderHandle.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果是硬解码模式,可以在这里调用 PlayM4_Play(port, newHandle)
|
2026-01-16 15:17:23 +08:00
|
|
|
|
_sdkLog.Information($"[SDK] Hik 收到新句柄绑定请求, 新句柄:{options.RenderHandle}. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放句柄:{_realPlayHandle}");
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik 收到新句柄绑定请求, 新句柄:{options.RenderHandle}. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}, 播放句柄:{_realPlayHandle}");
|
2025-12-26 12:15:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
#region --- 7. 驱动逻辑:取流与解码 (Streaming & Decoding) ---
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 开始预览
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
2025-12-26 03:18:21 +08:00
|
|
|
|
private bool StartRealPlay()
|
|
|
|
|
|
{
|
|
|
|
|
|
var previewInfo = new HikNativeMethods.NET_DVR_PREVIEWINFO
|
|
|
|
|
|
{
|
2025-12-28 08:07:55 +08:00
|
|
|
|
hPlayWnd = (IntPtr)_config.RenderHandle,
|
2025-12-26 12:15:10 +08:00
|
|
|
|
lChannel = _config.ChannelIndex,
|
|
|
|
|
|
dwStreamType = (uint)_config.StreamType,
|
|
|
|
|
|
bBlocked = false
|
2025-12-26 03:18:21 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
_realDataCallBack = new HikNativeMethods.REALDATACALLBACK(SafeOnRealDataReceived);
|
|
|
|
|
|
_realPlayHandle = HikNativeMethods.NET_DVR_RealPlay_V40(_userId, ref previewInfo, _realDataCallBack, IntPtr.Zero);
|
|
|
|
|
|
|
|
|
|
|
|
return _realPlayHandle >= 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 预览数据回调
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="lRealHandle"></param>
|
|
|
|
|
|
/// <param name="dwDataType"></param>
|
|
|
|
|
|
/// <param name="pBuffer"></param>
|
|
|
|
|
|
/// <param name="dwBufSize"></param>
|
|
|
|
|
|
/// <param name="pUser"></param>
|
2025-12-26 03:18:21 +08:00
|
|
|
|
private void SafeOnRealDataReceived(int lRealHandle, uint dwDataType, IntPtr pBuffer, uint dwBufSize, IntPtr pUser)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-12-26 13:11:58 +08:00
|
|
|
|
// 【关键位置】:在此处调用,统计网络层收到的每一字节数据
|
|
|
|
|
|
// 因为 dwBufSize > 0,MarkFrameReceived 内部只会累加码流,不会增加 FPS 计数
|
2025-12-26 12:15:10 +08:00
|
|
|
|
MarkFrameReceived(dwBufSize);
|
|
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
if (_realPlayHandle == -1) return;
|
|
|
|
|
|
|
2025-12-26 12:15:10 +08:00
|
|
|
|
// 处理系统头
|
2025-12-26 03:18:21 +08:00
|
|
|
|
if (dwDataType == HikNativeMethods.NET_DVR_SYSHEAD && _playPort == -1)
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (_initLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_realPlayHandle == -1 || _playPort != -1) return;
|
|
|
|
|
|
|
|
|
|
|
|
bool getPortSuccess;
|
|
|
|
|
|
lock (_globalPortLock)
|
|
|
|
|
|
{
|
|
|
|
|
|
getPortSuccess = HikPlayMethods.PlayM4_GetPort(ref _playPort);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!getPortSuccess) return;
|
|
|
|
|
|
|
2025-12-26 12:15:10 +08:00
|
|
|
|
HikPlayMethods.PlayM4_SetDisplayBuf(_playPort, 1); // 极速模式
|
|
|
|
|
|
HikPlayMethods.PlayM4_SetStreamOpenMode(_playPort, 0);
|
2025-12-26 03:18:21 +08:00
|
|
|
|
|
|
|
|
|
|
if (!HikPlayMethods.PlayM4_OpenStream(_playPort, pBuffer, dwBufSize, 2 * 1024 * 1024))
|
|
|
|
|
|
{
|
|
|
|
|
|
HikPlayMethods.PlayM4_FreePort(_playPort);
|
|
|
|
|
|
_playPort = -1;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_decCallBack = new HikPlayMethods.DECCBFUN(SafeOnDecodingCallBack);
|
|
|
|
|
|
HikPlayMethods.PlayM4_SetDecCallBackEx(_playPort, _decCallBack, IntPtr.Zero, 0);
|
|
|
|
|
|
HikPlayMethods.PlayM4_Play(_playPort, IntPtr.Zero);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-26 12:15:10 +08:00
|
|
|
|
// 处理流数据
|
2025-12-26 03:18:21 +08:00
|
|
|
|
else if (dwDataType == HikNativeMethods.NET_DVR_STREAMDATA && _playPort != -1)
|
|
|
|
|
|
{
|
|
|
|
|
|
HikPlayMethods.PlayM4_InputData(_playPort, pBuffer, dwBufSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-16 17:45:27 +08:00
|
|
|
|
catch(Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_sdkLog.Debug($"[SDK] Hik SafeOnRealDataReceived 异常. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}" + "Exception: {Exp}", ex);
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik SafeOnRealDataReceived 异常. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId} Exception: {ex.Message}");
|
|
|
|
|
|
}
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 必须同时加上 SecurityCritical
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="nPort"></param>
|
|
|
|
|
|
/// <param name="pBuf"></param>
|
|
|
|
|
|
/// <param name="nSize"></param>
|
|
|
|
|
|
/// <param name="pFrameInfo"></param>
|
|
|
|
|
|
/// <param name="nReserved1"></param>
|
|
|
|
|
|
/// <param name="nReserved2"></param>
|
2026-01-17 13:13:17 +08:00
|
|
|
|
[HandleProcessCorruptedStateExceptions]
|
|
|
|
|
|
[SecurityCritical]
|
2025-12-26 03:18:21 +08:00
|
|
|
|
private void SafeOnDecodingCallBack(int nPort, IntPtr pBuf, int nSize, ref HikPlayMethods.FRAME_INFO pFrameInfo, int nReserved1, int nReserved2)
|
|
|
|
|
|
{
|
2026-01-17 13:13:17 +08:00
|
|
|
|
//Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff")} 帧抵达.");
|
|
|
|
|
|
|
|
|
|
|
|
// Optimized: [原因] 增加前置防御性检查,若回调入参异常立即退出,防止后续 OpenCV 封装崩溃
|
|
|
|
|
|
if (pBuf == IntPtr.Zero || nSize <= 0
|
|
|
|
|
|
|| pFrameInfo.nWidth <= 0 || pFrameInfo.nHeight <= 0) return;
|
2026-01-16 17:45:27 +08:00
|
|
|
|
|
2025-12-26 18:55:04 +08:00
|
|
|
|
// [优化] 维持心跳,防止被哨兵误杀
|
|
|
|
|
|
MarkFrameReceived(0);
|
|
|
|
|
|
|
|
|
|
|
|
// [新增] 捕获并更新分辨率
|
|
|
|
|
|
// 只有当分辨率发生变化时才写入,减少属性赋值开销
|
|
|
|
|
|
if (Width != pFrameInfo.nWidth || Height != pFrameInfo.nHeight)
|
|
|
|
|
|
{
|
|
|
|
|
|
Width = pFrameInfo.nWidth;
|
|
|
|
|
|
Height = pFrameInfo.nHeight;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 12:15:10 +08:00
|
|
|
|
// 1. [核心流控] 询问基类控制器:这帧要不要?
|
|
|
|
|
|
// 之前失效是因为操作的是子类被遮蔽的 Controller,现在复用基类 Controller,逻辑就通了。
|
2025-12-26 13:38:28 +08:00
|
|
|
|
// 传入真实的输入帧率作为参考基准
|
|
|
|
|
|
var decision = Controller.MakeDecision(Environment.TickCount64, (int)RealFps);
|
2025-12-26 12:15:10 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果没人要,直接丢弃,不进行 Mat 转换,节省 CPU
|
2025-12-26 03:18:21 +08:00
|
|
|
|
if (!decision.IsCaptured) return;
|
|
|
|
|
|
|
|
|
|
|
|
int width = pFrameInfo.nWidth;
|
|
|
|
|
|
int height = pFrameInfo.nHeight;
|
|
|
|
|
|
|
2025-12-26 12:15:10 +08:00
|
|
|
|
// 2. 初始化帧池
|
2025-12-26 03:18:21 +08:00
|
|
|
|
if (!_isPoolReady)
|
|
|
|
|
|
{
|
2026-01-16 17:45:27 +08:00
|
|
|
|
// ====================================================================================
|
|
|
|
|
|
// 【修改点 Start】: 使用 Monitor.TryEnter 替换 lock
|
|
|
|
|
|
// 原因:防止死锁。如果主线程 CleanupSync 持有 _initLock 正在 Stop,
|
|
|
|
|
|
// 这里如果用 lock 会死等,导致 StopRealPlay 无法返回。
|
|
|
|
|
|
// 改用 TryEnter,如果拿不到锁(说明正在停止),直接放弃这一帧并退出。
|
|
|
|
|
|
// ====================================================================================
|
|
|
|
|
|
bool lockTaken = false;
|
|
|
|
|
|
try
|
2025-12-26 03:18:21 +08:00
|
|
|
|
{
|
2026-01-17 13:13:17 +08:00
|
|
|
|
// 尝试获取锁,超时时间 20ms (拿不到立即返回 false)
|
|
|
|
|
|
Monitor.TryEnter(_initLock, 20, ref lockTaken);
|
2026-01-16 17:45:27 +08:00
|
|
|
|
|
|
|
|
|
|
if (lockTaken)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 拿到锁了,执行原有的初始化逻辑 (Double Check)
|
|
|
|
|
|
if (!_isPoolReady)
|
|
|
|
|
|
{
|
|
|
|
|
|
_framePool?.Dispose();
|
|
|
|
|
|
_framePool = new FramePool(width, height, MatType.CV_8UC3, initialSize: 3, maxSize: 5);
|
|
|
|
|
|
_isPoolReady = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
2025-12-26 03:18:21 +08:00
|
|
|
|
{
|
2026-01-17 13:13:17 +08:00
|
|
|
|
// 【关键逻辑】如果 20ms 没拿到锁,说明主线程正在操作 (通常是正在 Stop)
|
2026-01-16 17:45:27 +08:00
|
|
|
|
// 既然都要停止了,这一帧直接丢弃,立即返回,防止死锁
|
|
|
|
|
|
return;
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-16 17:45:27 +08:00
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
if (lockTaken) Monitor.Exit(_initLock);
|
|
|
|
|
|
}
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_framePool == null) return;
|
|
|
|
|
|
|
2026-01-17 13:13:17 +08:00
|
|
|
|
// Optimized: [原因] 将 smartFrame 定义在 try 外部,确保 finally 块能够可靠执行 Dispose 归还逻辑
|
|
|
|
|
|
SmartFrame? smartFrame = null;
|
2026-01-16 17:45:27 +08:00
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
try
|
2026-01-17 13:13:17 +08:00
|
|
|
|
{ // 3. 转换与分发
|
|
|
|
|
|
smartFrame = _framePool.Get();
|
2025-12-26 12:15:10 +08:00
|
|
|
|
if (smartFrame == null) return; // 池满丢帧
|
2025-12-26 03:18:21 +08:00
|
|
|
|
|
2026-01-17 13:13:17 +08:00
|
|
|
|
// Optimized: [原因] 使用局部作用域封装 YUV 转换,确保原生指针尽快脱离
|
2025-12-26 12:15:10 +08:00
|
|
|
|
using (var rawYuvWrapper = Mat.FromPixelData(height + height / 2, width, MatType.CV_8UC1, pBuf))
|
2025-12-26 03:18:21 +08:00
|
|
|
|
{
|
2025-12-26 12:15:10 +08:00
|
|
|
|
Cv2.CvtColor(rawYuvWrapper, smartFrame.InternalMat, ColorConversionCodes.YUV2BGR_YV12);
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-17 07:55:21 +08:00
|
|
|
|
foreach(var targetAppId in decision.TargetAppIds)
|
|
|
|
|
|
smartFrame.SubscriberIds.Enqueue(targetAppId);
|
2025-12-31 20:43:54 +08:00
|
|
|
|
|
2025-12-29 08:09:14 +08:00
|
|
|
|
// =========================================================================
|
2026-01-05 14:54:06 +08:00
|
|
|
|
// 【修正】删除这里的 GlobalStreamDispatcher.Dispatch!
|
|
|
|
|
|
// 严禁在这里分发,因为这时的图是“生的”,还没经过 Pipeline 处理。
|
2026-01-12 18:27:58 +08:00
|
|
|
|
// =========================================================================
|
|
|
|
|
|
//GlobalStreamDispatcher.Dispatch(Id, smartFrame);
|
2025-12-29 08:09:14 +08:00
|
|
|
|
|
2025-12-26 12:15:10 +08:00
|
|
|
|
// 4. [分发] 将决策结果传递给处理中心
|
|
|
|
|
|
// decision.TargetAppIds 包含了 "谁需要这一帧" 的信息
|
2025-12-27 07:05:07 +08:00
|
|
|
|
//GlobalProcessingCenter.Submit(this.Id, smartFrame, decision);
|
|
|
|
|
|
GlobalPipelineRouter.Enqueue(Id, smartFrame, decision);
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
2025-12-26 12:15:10 +08:00
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 这里为了性能不频繁写日志,仅在调试时开启
|
2026-01-16 17:45:27 +08:00
|
|
|
|
_sdkLog.Debug($"[SDK] Hik SafeOnDecodingCallBack 异常. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId}" + "Exception: {Exp}", ex);
|
|
|
|
|
|
AddAuditLog($"[SDK] Hik SafeOnDecodingCallBack 异常. => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}, UserID: {_userId} Exception: {ex.Message}");
|
2025-12-26 12:15:10 +08:00
|
|
|
|
}
|
2025-12-26 03:18:21 +08:00
|
|
|
|
finally
|
|
|
|
|
|
{
|
2026-01-17 07:55:21 +08:00
|
|
|
|
// Optimized: [原因] 驱动层必须释放它持有的初始引用。
|
|
|
|
|
|
// 如果 Dispatch 内部已经 AddRef,此处 Dispose 只会让计数从 2 降到 1,帧不会回池。
|
|
|
|
|
|
// 如果没有其他人持有,此处 Dispose 会让计数从 1 降到 0,帧安全回池。
|
|
|
|
|
|
smartFrame?.Dispose();
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
#region --- 8. 全局异常静态回调 ---
|
2025-12-26 03:18:21 +08:00
|
|
|
|
|
2026-01-17 14:03:33 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// SDK 报警回调
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="dwType"></param>
|
|
|
|
|
|
/// <param name="lUserID"></param>
|
|
|
|
|
|
/// <param name="lHandle"></param>
|
|
|
|
|
|
/// <param name="pUser"></param>
|
2025-12-26 03:18:21 +08:00
|
|
|
|
private static void StaticOnSdkException(uint dwType, int lUserID, int lHandle, IntPtr pUser)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_instances.TryGetValue(lUserID, out var instance))
|
|
|
|
|
|
{
|
2026-01-16 15:17:23 +08:00
|
|
|
|
Log.ForContext("SourceContext", LogModules.HikVisionSdk)
|
|
|
|
|
|
.Error($"Hik SDK 报警异常: 0x{dwType:X}, UserId: {lUserID} ");
|
|
|
|
|
|
|
|
|
|
|
|
instance.AddAuditLog($"SDK报警 User:{pUser} 异常: 0x{dwType:X}, UserId: {lUserID}"); // 写入审计
|
2025-12-26 03:18:21 +08:00
|
|
|
|
instance.ReportError(new CameraException(
|
|
|
|
|
|
CameraErrorCode.NetworkUnreachable,
|
2025-12-26 12:15:10 +08:00
|
|
|
|
$"SDK全局异常: 0x{dwType:X}",
|
2025-12-26 03:18:21 +08:00
|
|
|
|
DeviceBrand.HikVision));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-26 12:15:10 +08:00
|
|
|
|
catch { }
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|