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