增加大华驱动
This commit is contained in:
@@ -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 调用封装
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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:屏蔽第三方的废话日志
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 处理循环异常崩溃!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
52
SHH.CameraSdk/Drivers/DaHua/DahuaPlaySDK.cs
Normal file
52
SHH.CameraSdk/Drivers/DaHua/DahuaPlaySDK.cs
Normal 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);
|
||||||
|
}
|
||||||
118
SHH.CameraSdk/Drivers/DaHua/DahuaSdkManager.cs
Normal file
118
SHH.CameraSdk/Drivers/DaHua/DahuaSdkManager.cs
Normal 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
|
||||||
|
}
|
||||||
288
SHH.CameraSdk/Drivers/DaHua/DahuaVideoSource.cs
Normal file
288
SHH.CameraSdk/Drivers/DaHua/DahuaVideoSource.cs
Normal 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
|
||||||
|
}
|
||||||
9941
SHH.CameraSdk/Drivers/DaHua/NetSDK.cs
Normal file
9941
SHH.CameraSdk/Drivers/DaHua/NetSDK.cs
Normal file
File diff suppressed because it is too large
Load Diff
150146
SHH.CameraSdk/Drivers/DaHua/NetSDKStruct.cs
Normal file
150146
SHH.CameraSdk/Drivers/DaHua/NetSDKStruct.cs
Normal file
File diff suppressed because it is too large
Load Diff
1521
SHH.CameraSdk/Drivers/DaHua/OriginalSDK.cs
Normal file
1521
SHH.CameraSdk/Drivers/DaHua/OriginalSDK.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user