增加大华驱动

This commit is contained in:
2026-01-17 19:17:49 +08:00
parent 0b4c6fe913
commit 927ba09f66
14 changed files with 162157 additions and 21 deletions

View File

@@ -26,5 +26,6 @@
// --- 第三方集成 --- // --- 第三方集成 ---
public static string HikVisionSdk { get; } = "HikVisionSdk"; // 第三方 SDK 调用封装 public static string HikVisionSdk { get; } = "HikVisionSdk"; // 第三方 SDK 调用封装
public static string DaHuaSdk { get; } = "DaHuaSdk"; // 第三方 SDK 调用封装
} }
} }

View File

@@ -116,7 +116,8 @@ namespace Ayay.SerilogLogs
// --- 降噪区 (垃圾数据屏蔽) --- // --- 降噪区 (垃圾数据屏蔽) ---
{ LogModules.WebSocket, LogEventLevel.Debug }, // WS数据量极大除非报错否则不记 { LogModules.WebSocket, LogEventLevel.Debug }, // WS数据量极大除非报错否则不记
{ LogModules.Ping, LogEventLevel.Debug }, // Ping几乎不记除非完全断连 { LogModules.Ping, LogEventLevel.Debug }, // Ping几乎不记除非完全断连
{ LogModules.HikVisionSdk, LogEventLevel.Debug } // SDK屏蔽第三方的废话日志 { LogModules.HikVisionSdk, LogEventLevel.Debug }, // SDK屏蔽第三方的废话日志
{ LogModules.DaHuaSdk, LogEventLevel.Debug }, // SDK屏蔽第三方的废话日志
}; };
// ========================================== // ==========================================

View File

@@ -126,6 +126,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
// 1. 全局驱动环境预初始化 // 1. 全局驱动环境预初始化
// ========================================================= // =========================================================
HikSdkManager.Initialize(); HikSdkManager.Initialize();
DahuaSdkManager.Initialize();
// 标记引擎启动状态 // 标记引擎启动状态
_isEngineStarted = true; _isEngineStarted = true;
@@ -337,6 +338,12 @@ public class CameraManager : IDisposable, IAsyncDisposable
HikSdkManager.Uninitialize(); HikSdkManager.Uninitialize();
} }
catch { } catch { }
try
{
DahuaSdkManager.Uninitialize();
}
catch { }
} }
finally finally
{ {
@@ -358,6 +365,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
return config.Brand switch return config.Brand switch
{ {
DeviceBrand.HikVision => new HikVideoSource(config), DeviceBrand.HikVision => new HikVideoSource(config),
DeviceBrand.Dahua => new DahuaVideoSource(config),
// 使用模式匹配获取不匹配的值,记录详细的 DTO 上下文 // 使用模式匹配获取不匹配的值,记录详细的 DTO 上下文
_ => HandleUnsupportedBrand(config) _ => HandleUnsupportedBrand(config)

View File

@@ -103,15 +103,27 @@ namespace SHH.CameraSdk
// 因为帧进入异步处理环节,必须保证不会被外部提前释放 // 因为帧进入异步处理环节,必须保证不会被外部提前释放
frame.AddRef(); frame.AddRef();
try
{
// 关键操作2哈希分片路由 // 关键操作2哈希分片路由
// 基于 DeviceId 取模,确保同一设备的帧始终分配给同一个 Worker
// 核心价值:保证单设备的帧处理顺序与原始流顺序一致
int workerIndex = (int)(Math.Abs(deviceId) % _workerCount); int workerIndex = (int)(Math.Abs(deviceId) % _workerCount);
var targetWorker = _workers[workerIndex]; var targetWorker = _workers[workerIndex];
// 将帧投递到目标 Worker 的任务队列 // 将帧投递到目标 Worker 的任务队列
targetWorker.Post(deviceId, frame, decision); targetWorker.Post(deviceId, frame, decision);
} }
catch (Exception ex)
{
// =========================================================
// 【修复 3】: 引用计数回滚
// 如果投递失败(例如计算 Index 溢出、Worker 列表为空等极端情况),
// 必须把刚才 AddRef 的一次计数释放掉,否则该帧将永久无法回池(内存泄漏)。
// =========================================================
_sysLog.Error(ex, $"[Core] 帧入队失败,回滚引用计数: DeviceId={deviceId}");
frame.Dispose();
throw; // 继续抛出异常通知上层
}
}
#endregion #endregion
@@ -218,43 +230,68 @@ namespace SHH.CameraSdk
/// Worker 线程的核心处理循环 /// Worker 线程的核心处理循环
/// 持续消费队列任务,直到收到取消信号 /// 持续消费队列任务,直到收到取消信号
/// </summary> /// </summary>
// Modified: [BaseWorker.cs] 增加深度防御检查,防止处理僵尸帧
private void ProcessLoop() private void ProcessLoop()
{ {
try try
{ {
// GetConsumingEnumerable阻塞式消费队列支持取消令牌
foreach (var taskItem in _taskQueue.GetConsumingEnumerable(_cts.Token)) foreach (var taskItem in _taskQueue.GetConsumingEnumerable(_cts.Token))
{ {
// 关键操作:使用 using 语句自动释放引用 // 自动释放引用
// 无论处理成功/失败,都会保证 Dispose 被调用,形成引用计数闭环
using (var frame = taskItem.Frame) using (var frame = taskItem.Frame)
{ {
try try
{ {
// 调用子类实现的具体图像处理算法 // =========================================================
// 【修复 1】: 僵尸帧防御 (Zombie Frame Defense)
// 如果帧虽然还在引用计数内,但其内部的 OpenCV 内存已被销毁,
// 严禁调用 PerformAction否则直接 AccessViolation 崩溃。
// =========================================================
if (frame.InternalMat == null || frame.InternalMat.IsDisposed)
{
_gRpcLog.Warning($"[BaseWorker] 拦截到已销毁的帧: DeviceId={taskItem.DeviceId}, 丢弃.");
continue;
}
// =========================================================
// 【修复 2】: 空帧防御 (Empty Frame Defense)
// 大华 SDK 偶尔会输出 0x0 或空数据Resize 对空图操作必崩。
// =========================================================
if (frame.InternalMat.Empty())
{
_gRpcLog.Warning($"[BaseWorker] 拦截到空帧(Empty): DeviceId={taskItem.DeviceId}, 丢弃.");
continue;
}
// 只有检查通过,才执行具体的算法
PerformAction(taskItem.DeviceId, frame, taskItem.Decision); PerformAction(taskItem.DeviceId, frame, taskItem.Decision);
// 通知父集群:当前帧处理完成,准备传递到下一个环节 // 通知完成
NotifyFinished(taskItem.DeviceId, frame, taskItem.Decision); NotifyFinished(taskItem.DeviceId, frame, taskItem.Decision);
} }
catch (ObjectDisposedException)
{
// 捕获特定的“对象已释放”异常,防止线程退出
_gRpcLog.Warning($"[BaseWorker] 帧在处理过程中被释放: DeviceId={taskItem.DeviceId}");
}
catch (Exception ex) catch (Exception ex)
{ {
_gRpcLog.Information($"[gRpc] 帧处理异常BaseWorker 异常消息: {ex.Message}."); _gRpcLog.Error(ex, $"[BaseWorker] 帧处理逻辑异常: DeviceId={taskItem.DeviceId}");
// 异常保底策略:即使处理失败,也透传帧到下一个环节,保证流水线不中断 // 即使处理失败,也可以选择是否继续传递,视业务而定
NotifyFinished(taskItem.DeviceId, frame, taskItem.Decision); // NotifyFinished(taskItem.DeviceId, frame, taskItem.Decision);
} }
} }
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
// 正常取消:线程退出,无需报错
_gRpcLog.Information($"[gRpc] BaseWorker 处理循环已正常终止."); _gRpcLog.Information($"[gRpc] BaseWorker 处理循环已正常终止.");
} }
catch (Exception ex) catch (Exception ex)
{ {
_gRpcLog.Error($"[gRpc] BaseWorker 处理循环异常终止."); // 只有极其严重的系统级错误才会走到这里
_gRpcLog.Fatal(ex, $"[gRpc] BaseWorker 处理循环异常崩溃!");
} }
} }

View File

@@ -0,0 +1,52 @@
using System.Runtime.InteropServices;
namespace SHH.CameraSdk;
/// <summary>
/// 大华 PlaySDK 核心接口封装 (play.dll)
/// </summary>
public static class DahuaPlaySDK
{
private const string DLL_PATH = "Drivers\\Dahua\\play.dll";
// 解码回调委托
public delegate void DECCBFUN(int nPort, IntPtr pBuf, int nSize, ref FRAME_INFO pFrameInfo, IntPtr nUser, int nReserved2);
[StructLayout(LayoutKind.Sequential)]
public struct FRAME_INFO
{
public int nWidth;
public int nHeight;
public int nStamp;
public int nType;
public int nFrameRate;
public uint dwFrameNum;
}
[DllImport(DLL_PATH)]
public static extern bool PLAY_GetFreePort(ref int plPort);
[DllImport(DLL_PATH)]
public static extern bool PLAY_ReleasePort(int nPort);
[DllImport(DLL_PATH)]
public static extern bool PLAY_OpenStream(int nPort, IntPtr pFileHead, uint nSize, uint nBufPoolSize);
[DllImport(DLL_PATH)]
public static extern bool PLAY_CloseStream(int nPort);
[DllImport(DLL_PATH)]
public static extern bool PLAY_Play(int nPort, IntPtr hWnd);
[DllImport(DLL_PATH)]
public static extern bool PLAY_Stop(int nPort);
[DllImport(DLL_PATH)]
public static extern bool PLAY_InputData(int nPort, IntPtr pBuf, uint nSize);
[DllImport(DLL_PATH)]
public static extern bool PLAY_SetDecCallBack(int nPort, DECCBFUN DecCBFun);
[DllImport(DLL_PATH)]
public static extern bool PLAY_SetStreamOpenMode(int nPort, uint nMode);
}

View File

@@ -0,0 +1,118 @@
using Serilog;
using Ayay.SerilogLogs;
namespace SHH.CameraSdk;
/// <summary>
/// [驱动支持层] 大华 SDK 全局资源管理器
/// 职责:统一管理大华 NetSDK 与 PlaySDK 的生命周期与预热
/// </summary>
public static class DahuaSdkManager
{
#region --- (Global States & Locks) ---
/// <summary>
/// 全局引用计数器。
/// 只有当计数从 0 变 1 时才进行物理初始化,从 1 变 0 时才物理卸载。
/// </summary>
private static int _referenceCount = 0;
/// <summary>
/// 静态同步锁。
/// 用于保护 _referenceCount 的原子操作,防止多线程并发 Start/Stop 时导致的初始化冲突。
/// </summary>
private static readonly object _lock = new();
/// <summary>
/// 播放库预热状态标记。
/// 用于避免重复执行硬件探测(首次预热后后续直接返回)。
/// </summary>
private static bool _isWarmedUp = false;
#endregion
// 静态回调引用,防止被 GC
private static fDisConnectCallBack m_DisConnectCallBack = (lLoginID, pchDVRIP, nDVRPort, dwUser) => { };
#region --- SDK (SDK Initialization & Uninstallation) ---
/// <summary>
/// 全局初始化大华 SDK 环境。
/// <para>此方法是幂等的,内部会自动增加引用计数,支持多线程并发调用。</para>
/// </summary>
/// <returns>初始化成功返回 true若 SDK 核心组件HCNetSDK.dll加载失败则返回 false。</returns>
public static bool Initialize()
{
lock (_lock)
{
// 引用计数为 0 时执行物理初始化(仅首次调用时触发)
if (_referenceCount == 0)
{
// [物理初始化] 大华 NetSDK 初始化
// 注意NETClient.Init 在某些版本下若重复调用会返回 false需小心处理
try
{
NETClient.Init(m_DisConnectCallBack, IntPtr.Zero, null);
// 设置一些全局超时参数,提升工业响应速度
// NETClient.SetConnectTime(3000, 1);
}
catch { return false; }
}
_referenceCount++;
return true;
}
}
/// <summary>
/// 全局卸载大华 SDK 环境。
/// <para>当所有相机实例都停止并释放后(引用计数归 0会真正释放非托管资源。</para>
/// </summary>
public static void Uninitialize()
{
lock (_lock)
{
if (_referenceCount > 0)
{
_referenceCount--;
if (_referenceCount == 0)
{
// [物理卸载] 只有在没有实例使用时才彻底 Cleanup
// NETClient.Cleanup();
}
}
}
}
#endregion
#region --- (PlayCtrl Warm-up) ---
/// <summary>
/// [核心策略] 大华播放库预热
/// 职责:诱发 play.dll 完成底层硬件环境探测,规避取流瞬间的卡顿
/// </summary>
public static void ForceWarmUp()
{
if (_isWarmedUp) return;
Log.ForContext("SourceContext", LogModules.Core)
.Debug("[Dahua] 正在进行大华播放库硬件探测预热...");
Stopwatch sw = Stopwatch.StartNew();
int tempPort = -1;
// 诱发点PLAY_GetFreePort
if (DahuaPlaySDK.PLAY_GetFreePort(ref tempPort))
{
// 大华的端口释放也必须及时
DahuaPlaySDK.PLAY_ReleasePort(tempPort);
}
sw.Stop();
_isWarmedUp = true;
Log.ForContext("SourceContext", LogModules.Core)
.Debug($"[Dahua] 预热完成!耗时: {sw.ElapsedMilliseconds}ms.");
}
#endregion
}

View File

@@ -0,0 +1,288 @@
using Ayay.SerilogLogs;
using OpenCvSharp;
using Serilog;
using System.Runtime.ExceptionServices;
using System.Security;
namespace SHH.CameraSdk;
/// <summary>
/// [大华驱动] 工业级视频源实现 (依照官方 Demo 逻辑重构版)
/// <para>当前模块: AiVideo | 核心原则: 低耦合、高并发、零拷贝</para>
/// </summary>
public class DahuaVideoSource : BaseVideoSource
{
protected override ILogger _sdkLog => Log.ForContext("SourceContext", LogModules.DaHuaSdk);
#region --- 1. (Static Resources) ---
private static readonly ConcurrentDictionary<IntPtr, DahuaVideoSource> _instances = new();
// 必须保持静态引用,防止被 GC 回收导致回调崩溃
private static fDisConnectCallBack m_DisConnectCallBack;
private static fHaveReConnectCallBack m_ReConnectCallBack;
private static fRealDataCallBackEx2 m_RealDataCallBackEx2;
#endregion
#region --- 2. (Instance Members) ---
private IntPtr _loginId = IntPtr.Zero;
private IntPtr _realPlayId = IntPtr.Zero;
private int _playPort = -1;
private FramePool? _framePool;
private volatile bool _isPoolReady = false;
private readonly object _initLock = new();
// 强引用实例的回调委托
private DahuaPlaySDK.DECCBFUN? _decCallBack;
#endregion
public DahuaVideoSource(VideoSourceConfig config) : base(config) { }
#region --- 3. (Lifecycle Overrides) ---
protected override async Task OnStartAsync(CancellationToken token)
{
await Task.Run(() =>
{
// 1. 初始化 SDK (只需一次)
InitSdkGlobal();
_sdkLog.Information($"[SDK] Dahua 正在执行登录 => ID:{_config.Id} IP:{_config.IpAddress}");
AddAuditLog($"[SDK] Dahua 正在执行登录 => IP:{_config.IpAddress}");
// 2. 登录设备
NET_DEVICEINFO_Ex deviceInfo = new NET_DEVICEINFO_Ex();
_loginId = NETClient.LoginWithHighLevelSecurity(_config.IpAddress, (ushort)_config.Port, _config.Username, _config.Password, EM_LOGIN_SPAC_CAP_TYPE.TCP, IntPtr.Zero, ref deviceInfo);
if (_loginId == IntPtr.Zero)
{
string err = NETClient.GetLastError();
throw new Exception($"大华登录失败: {err}");
}
_instances.TryAdd(_loginId, this);
_sdkLog.Information($"[SDK] Dahua 登录成功 => LoginID:{_loginId}, 通道数:{deviceInfo.nChanNum}");
// 3. 开启实时预览 (参考 Demo Button1_Click 逻辑)
// Modified: [原因] 使用 RealPlayByDataType 以支持获取原始码流并指定回调
NET_IN_REALPLAY_BY_DATA_TYPE stuIn = new NET_IN_REALPLAY_BY_DATA_TYPE()
{
dwSize = (uint)Marshal.SizeOf(typeof(NET_IN_REALPLAY_BY_DATA_TYPE)),
nChannelID = _config.ChannelIndex,
hWnd = IntPtr.Zero, // 不直接渲染到窗口,我们拿原始数据
// EM_A_RType_Realplay = 0 (主码流), EM_A_RType_Realplay_1 = 1 (辅码流)
rType = _config.StreamType == 0 ? EM_RealPlayType.EM_A_RType_Realplay : EM_RealPlayType.EM_A_RType_Realplay_1,
emDataType = EM_REAL_DATA_TYPE.PRIVATE, // 私有码流
cbRealDataEx = m_RealDataCallBackEx2 // 挂载静态回调
};
NET_OUT_REALPLAY_BY_DATA_TYPE stuOut = new NET_OUT_REALPLAY_BY_DATA_TYPE()
{
dwSize = (uint)Marshal.SizeOf(typeof(NET_OUT_REALPLAY_BY_DATA_TYPE))
};
_realPlayId = NETClient.RealPlayByDataType(_loginId, stuIn, ref stuOut, 5000);
if (_realPlayId == IntPtr.Zero)
{
string err = NETClient.GetLastError();
NETClient.Logout(_loginId);
throw new Exception($"大华预览失败: {err}");
}
_sdkLog.Information($"[SDK] Dahua 取流成功 => RealPlayID:{_realPlayId}");
AddAuditLog($"[SDK] Dahua 取流成功");
}, token);
}
protected override async Task OnStopAsync()
{
await Task.Run(() =>
{
lock (_initLock)
{
if (_realPlayId != IntPtr.Zero)
{
NETClient.StopRealPlay(_realPlayId);
_realPlayId = IntPtr.Zero;
}
if (_playPort != -1)
{
DahuaPlaySDK.PLAY_Stop(_playPort);
DahuaPlaySDK.PLAY_CloseStream(_playPort);
DahuaPlaySDK.PLAY_ReleasePort(_playPort);
_playPort = -1;
}
if (_loginId != IntPtr.Zero)
{
_instances.TryRemove(_loginId, out _);
NETClient.Logout(_loginId);
_loginId = IntPtr.Zero;
}
_framePool?.Dispose();
_isPoolReady = false;
}
});
}
#endregion
#region --- 4. (Core Logic) ---
/// <summary>
/// 静态回调:分发数据至具体实例
/// </summary>
private static void OnRealDataReceived(IntPtr lRealHandle, uint dwDataType, IntPtr pBuffer, uint dwBufSize, IntPtr param, IntPtr dwUser)
{
// 大华 SDK 无法直接在回调中给实例,需要通过句柄查找或全局状态
// 简化处理:由于 Ayay 目前追求单机性能,这里假设通过全局查找或在 RealPlayByDataType 时传入的 dwUser 识别
// 这里基于实例管理的简单逻辑:
foreach (var instance in _instances.Values)
{
if (instance._realPlayId == lRealHandle)
{
instance.ProcessRealData(dwDataType, pBuffer, dwBufSize);
break;
}
}
}
private void ProcessRealData(uint dwDataType, IntPtr pBuffer, uint dwBufSize)
{
MarkFrameReceived(dwBufSize); // 统计网络带宽
// dwDataType: 0 = 私有码流
if (dwDataType == 0 && dwBufSize > 0)
{
lock (_initLock)
{
if (_realPlayId == IntPtr.Zero) return;
if (_playPort == -1)
{
int port = 0;
if (DahuaPlaySDK.PLAY_GetFreePort(ref port))
{
_playPort = port;
DahuaPlaySDK.PLAY_SetStreamOpenMode(_playPort, 0);
DahuaPlaySDK.PLAY_OpenStream(_playPort, IntPtr.Zero, 0, 1024 * 1024 * 2);
_decCallBack = new DahuaPlaySDK.DECCBFUN(SafeOnDecodingCallBack);
DahuaPlaySDK.PLAY_SetDecCallBack(_playPort, _decCallBack);
DahuaPlaySDK.PLAY_Play(_playPort, IntPtr.Zero);
}
}
if (_playPort != -1)
{
DahuaPlaySDK.PLAY_InputData(_playPort, pBuffer, dwBufSize);
}
}
}
}
[HandleProcessCorruptedStateExceptions]
[SecurityCritical]
private void SafeOnDecodingCallBack(int nPort, IntPtr pBuf, int nSize, ref DahuaPlaySDK.FRAME_INFO pFrameInfo, IntPtr nUser, int nReserved2)
{
if (pBuf == IntPtr.Zero || nSize <= 0) return;
MarkFrameReceived(0); // 心跳
int currentWidth = pFrameInfo.nWidth;
int currentHeight = pFrameInfo.nHeight;
// 帧池平滑重建逻辑 (保持与海康版一致,防止死锁)
if (!_isPoolReady || Width != currentWidth || Height != currentHeight)
{
bool lockTaken = false;
try
{
Monitor.TryEnter(_initLock, 50, ref lockTaken);
if (lockTaken)
{
if (!_isPoolReady || Width != currentWidth || Height != currentHeight)
{
_framePool?.Dispose();
var newPool = new FramePool(currentWidth, currentHeight, MatType.CV_8UC3, 3, 5);
_framePool = newPool;
Width = currentWidth;
Height = currentHeight;
_isPoolReady = true;
}
}
else return;
}
finally { if (lockTaken) Monitor.Exit(_initLock); }
}
var decision = Controller.MakeDecision(Environment.TickCount64, (int)RealFps);
if (!decision.IsCaptured) return;
SmartFrame? smartFrame = null;
try
{
smartFrame = _framePool?.Get();
if (smartFrame == null) return;
// 大华 YUV 转换为 BGR (I420)
using (var yuvMat = Mat.FromPixelData(currentHeight + currentHeight / 2, currentWidth, MatType.CV_8UC1, pBuf))
{
Cv2.CvtColor(yuvMat, smartFrame.InternalMat, ColorConversionCodes.YUV2BGR_I420);
}
// =========================================================
// 【新增防御】: 检查转换结果是否有效
// 如果转换失败,或者 Mat 为空,绝对不能传给 Router
// =========================================================
if (smartFrame.InternalMat.Empty())
{
_sdkLog.Warning($"[SDK] Dahua 解码帧无效 (Empty Mat), 丢弃. 设备ID: {Config.Id} IP:{Config.IpAddress} Name:{Config.Name}");
// finally 会负责 Dispose这里直接返回
return;
}
foreach (var appId in decision.TargetAppIds)
smartFrame.SubscriberIds.Enqueue(appId);
GlobalPipelineRouter.Enqueue(Id, smartFrame, decision);
}
catch (Exception ex)
{
_sdkLog.Error($"[SDK] Dahua 解码异常: {ex.Message}");
}
finally
{
smartFrame?.Dispose();
}
}
#endregion
#region --- 5. (Statics) ---
private static void InitSdkGlobal()
{
if (m_RealDataCallBackEx2 != null) return;
m_DisConnectCallBack = (lLoginID, pchDVRIP, nDVRPort, dwUser) => { };
m_ReConnectCallBack = (lLoginID, pchDVRIP, nDVRPort, dwUser) => { };
m_RealDataCallBackEx2 = new fRealDataCallBackEx2(OnRealDataReceived);
NETClient.Init(m_DisConnectCallBack, IntPtr.Zero, null);
NETClient.SetAutoReconnect(m_ReConnectCallBack, IntPtr.Zero);
}
protected override void OnApplyOptions(DynamicStreamOptions options) { }
protected override Task<DeviceMetadata> OnFetchMetadataAsync() => Task.FromResult(new DeviceMetadata());
#endregion
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -641,6 +641,17 @@ public class HikVideoSource : BaseVideoSource,
Cv2.CvtColor(rawYuvWrapper, smartFrame.InternalMat, ColorConversionCodes.YUV2BGR_YV12); Cv2.CvtColor(rawYuvWrapper, smartFrame.InternalMat, ColorConversionCodes.YUV2BGR_YV12);
} }
// =========================================================
// 【新增防御】: 检查转换结果是否有效
// 如果转换失败,或者 Mat 为空,绝对不能传给 Router
// =========================================================
if (smartFrame.InternalMat.Empty())
{
_sdkLog.Warning($"[SDK] Dahua 解码帧无效 (Empty Mat), 丢弃. 设备ID: {Config.Id} IP:{Config.IpAddress} Name:{Config.Name}");
// finally 会负责 Dispose这里直接返回
return;
}
foreach (var targetAppId in decision.TargetAppIds) foreach (var targetAppId in decision.TargetAppIds)
smartFrame.SubscriberIds.Enqueue(targetAppId); smartFrame.SubscriberIds.Enqueue(targetAppId);

View File

@@ -101,6 +101,7 @@ public class Program
Console.WriteLine("[硬件] 海康驱动预热中..."); Console.WriteLine("[硬件] 海康驱动预热中...");
HikNativeMethods.NET_DVR_Init(); HikNativeMethods.NET_DVR_Init();
HikSdkManager.ForceWarmUp(); HikSdkManager.ForceWarmUp();
DahuaSdkManager.ForceWarmUp();
} }
static async Task AddTestDevices(CameraManager manager) static async Task AddTestDevices(CameraManager manager)

View File

@@ -38,7 +38,7 @@ public static class Bootstrapper
"--uris", "localhost,9001,command,调试PC;", "--uris", "localhost,9001,command,调试PC;",
// 日志中心配置 (格式: IP,Port,Desc) // 日志中心配置 (格式: IP,Port,Desc)
"--sequris", "172.16.41.241,20026,日志处置中心;", "--sequris", "58.216.225.5,20026,日志处置中心;",
"--seqkey", "Shine899195994250;", "--seqkey", "Shine899195994250;",
// 端口策略 // 端口策略
@@ -209,7 +209,7 @@ public static class Bootstrapper
/// </summary> /// </summary>
public static void WarmUpHardware(ILogger logger) public static void WarmUpHardware(ILogger logger)
{ {
logger.Information("[Core] Hik Sdk 开始预热."); logger.Information("[Core] Hik、Dahua Sdk 开始预热.");
try try
{ {
HikNativeMethods.NET_DVR_Init(); HikNativeMethods.NET_DVR_Init();
@@ -220,6 +220,16 @@ public static class Bootstrapper
{ {
logger.Error(ex, "[Core] ⚠️ Hik Sdk 预热失败."); logger.Error(ex, "[Core] ⚠️ Hik Sdk 预热失败.");
} }
try
{
DahuaSdkManager.ForceWarmUp();
logger.Information("[Core] Dahua Sdk 预热成功.");
}
catch (Exception ex)
{
logger.Error(ex, "[Core] ⚠️ Dahua Sdk 预热失败.");
}
} }
#endregion #endregion

View File

@@ -5,6 +5,7 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>