修复 Bug
This commit is contained in:
@@ -276,12 +276,28 @@ public class CameraManager : IDisposable, IAsyncDisposable
|
||||
// B. 在线应用策略
|
||||
if (device.IsActived)
|
||||
{
|
||||
var options = new DynamicStreamOptions
|
||||
// Optimized: 仅构造真正发生变化的参数
|
||||
var options = new DynamicStreamOptions();
|
||||
|
||||
// 判定码流是否真的变了(或者 DTO 明确传了新值)
|
||||
if (dto.StreamType.HasValue && dto.StreamType != oldConfig.StreamType)
|
||||
{
|
||||
StreamType = dto.StreamType ?? newConfig.StreamType,
|
||||
RenderHandle = (IntPtr)dto.RenderHandle
|
||||
};
|
||||
device.ApplyOptions(options);
|
||||
options.StreamType = dto.StreamType.Value;
|
||||
}
|
||||
|
||||
// 判定句柄是否真的变了
|
||||
// Modified: 只有当 DTO 的句柄与旧配置不一致时,才放入 options
|
||||
if (dto.RenderHandle != oldConfig.RenderHandle)
|
||||
{
|
||||
options.RenderHandle = (IntPtr)dto.RenderHandle;
|
||||
}
|
||||
|
||||
// 只有当至少有一个参数需要更新时,才调用底层
|
||||
// 假设 DynamicStreamOptions 内部有检测是否有值的方法,或者判断其属性
|
||||
if (options.StreamType.HasValue || options.RenderHandle.HasValue)
|
||||
{
|
||||
device.ApplyOptions(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,9 +86,12 @@ public class SmartFrame : IDisposable
|
||||
/// </summary>
|
||||
public void Activate()
|
||||
{
|
||||
// Optimized: [原因] 使用 Exchange 强制重置归还标记,确保该帧在逻辑上完全从池中脱离,防止归还竞态
|
||||
Interlocked.Exchange(ref _isReturned, 0);
|
||||
|
||||
// 激活后引用计数设为 1,代表生产者(驱动/管道)持有该帧
|
||||
_refCount = 1;
|
||||
_isReturned = 0; // 激活时重置归还标记
|
||||
|
||||
// 记录帧被取出池的时间,用于后续延迟计算
|
||||
Timestamp = DateTime.Now;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ayay.SerilogLogs;
|
||||
using Serilog;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SHH.CameraSdk;
|
||||
|
||||
@@ -94,11 +95,19 @@ public class ProcessingPipeline
|
||||
// 异步遍历队列:收到取消信号时退出循环
|
||||
await foreach (var task in _queue.Reader.ReadAllAsync(_cts.Token))
|
||||
{
|
||||
// 使用 using 语句:处理完成后自动调用 Frame.Dispose(),引用计数-1
|
||||
using (task.Frame)
|
||||
try
|
||||
{
|
||||
// 执行具体的帧处理逻辑
|
||||
ExecuteProcessing(task);
|
||||
// 使用 using 语句:处理完成后自动调用 Frame.Dispose(),引用计数-1
|
||||
using (task.Frame)
|
||||
{
|
||||
// 执行具体的帧处理逻辑
|
||||
ExecuteProcessing(task);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Optimized: [原因] 捕获任务级的异常,防止单帧处理失败导致整个后台处理循环终止
|
||||
_sysLog.Error(ex, "[Pipeline] 关键任务执行异常 (DeviceId: {DeviceId})", task.DeviceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ public class CameraCoordinator
|
||||
private readonly SemaphoreSlim _concurrencyLimiter = new(8);
|
||||
|
||||
/// <summary> 相机流存活判定阈值(秒):超过该时间无帧则判定为流中断 </summary>
|
||||
private const int StreamAliveThresholdSeconds = 5;
|
||||
/// 海康 SDK 建立连接+首帧到达通常需 4-8 秒。阈值低了会导致刚连上就被误判为僵死而强制断开。
|
||||
private const int StreamAliveThresholdSeconds = 15;
|
||||
|
||||
/// <summary> Ping 探测超时时间(毫秒) </summary>
|
||||
private const int PingTimeoutMs = 800;
|
||||
@@ -162,7 +163,7 @@ public class CameraCoordinator
|
||||
|
||||
double secondsSinceLastFrame = (nowTick - cam.LastFrameTick) / 1000.0;
|
||||
|
||||
// 2. 判定流是否正常:设备在线 + 5秒内有帧
|
||||
// 2. 判定流是否正常:设备在线 + 15秒内有帧
|
||||
bool isFlowing = cam.IsActived && secondsSinceLastFrame < StreamAliveThresholdSeconds;
|
||||
|
||||
// 3. 判定物理连接是否正常:流正常则直接判定在线;否则执行 Ping+TCP 探测
|
||||
@@ -185,6 +186,7 @@ public class CameraCoordinator
|
||||
// 双重校验:防止等待锁期间状态已变更
|
||||
if (!cam.IsActived)
|
||||
{
|
||||
// 记录启动时刻,elapsed 将重新计时
|
||||
cam.MarkStartAttempt();
|
||||
|
||||
try
|
||||
@@ -224,8 +226,20 @@ public class CameraCoordinator
|
||||
// 【关键修复】:增加了 && cam.IsRunning 判定,防止待机状态下被误复位
|
||||
else if (isPhysicalOk && cam.IsActived && !isFlowing && cam.IsRunning) // [cite: 504]
|
||||
{
|
||||
_sysLog.Warning($"[Coordinator] [自愈] 设备 {cam.Id} 僵死({secondsSinceLastFrame:F1}秒无帧),复位中...");
|
||||
await cam.StopAsync().ConfigureAwait(false);
|
||||
// Optimized: [修复无限重启] 增加“启动保护期”检查。
|
||||
// 原问题:相机刚 StartAsync 还在握手(例如第3秒),isFlowing 为 false,会导致立即被 Stop。
|
||||
// 新逻辑:只有当“启动已超过 15秒”且“依然没流”时,才判定为真正的僵死。
|
||||
|
||||
// elapsed 是毫秒,StreamAliveThresholdSeconds 是秒,需要换算
|
||||
if (elapsed > StreamAliveThresholdSeconds * 1000)
|
||||
{
|
||||
_sysLog.Warning($"[Coordinator] [自愈] 设备 {cam.Id} 僵死({secondsSinceLastFrame:F1}秒无帧),复位中...");
|
||||
await cam.StopAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_sysLog.Debug($"[Coordinator] 设备 {cam.Id} 启动握手中 ({elapsed}ms),等待出流...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -154,4 +154,11 @@ public class FrameController
|
||||
{
|
||||
return _requirements.Values.Select(r => new { r.AppId, r.TargetFps, r.RealFps, LastActive = r.LastCaptureTick, r.Memo, r.SavePath, r.Handle, r.TargetIp, r.TargetPort, r.Protocol, r.Type }).ToList<dynamic>();
|
||||
}
|
||||
|
||||
// [新增] 专门供审计与管理层调用的强类型方法
|
||||
// Optimized: 避免匿名类型跨程序集访问失败,提供高性能的实体访问
|
||||
public IEnumerable<FrameRequirement> GetRequirements()
|
||||
{
|
||||
return _requirements.Values;
|
||||
}
|
||||
}
|
||||
@@ -537,7 +537,7 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[UIEventError] 设备 {Id} 状态回调异常: {ex.Message}");
|
||||
_sdkLog.Error(ex, "设备 {Id} 状态变更回调异常", Id);
|
||||
}
|
||||
|
||||
// 退出条件:取消令牌已触发 且 队列为空
|
||||
@@ -554,9 +554,10 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) { /* 正常退出 */ }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[DistributorFatal] 设备 {Id} 状态分发器崩溃: {ex.Message}");
|
||||
_sdkLog.Fatal(ex, "设备 {Id} 状态分发器致命异常", Id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,31 +671,46 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC
|
||||
{
|
||||
// 防止重复 Dispose
|
||||
if (_isDisposed) return;
|
||||
_isDisposed = true;
|
||||
|
||||
// 1. 停止业务逻辑
|
||||
await StopAsync().ConfigureAwait(false);
|
||||
// Optimized: [原因] 获取生命周期锁,防止在 DisposeAsync 执行期间被并发触发 Start/Stop 操作
|
||||
await _lifecycleLock.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
// 2. 优雅关闭状态分发器
|
||||
_statusQueue.Writer.TryComplete(); // 标记队列不再接受新消息
|
||||
_distributorCts?.Cancel(); // 触发分发器取消
|
||||
|
||||
// 3. 等待分发器处理完剩余消息(最多等待 500ms)
|
||||
if (_distributorTask != null)
|
||||
try
|
||||
{
|
||||
await Task.WhenAny(_distributorTask, Task.Delay(500)).ConfigureAwait(false);
|
||||
// 防止重复 Dispose
|
||||
if (_isDisposed) return;
|
||||
_isDisposed = true;
|
||||
|
||||
// 1. 停止业务逻辑
|
||||
await StopAsync().ConfigureAwait(false);
|
||||
|
||||
// 2. 优雅关闭状态分发器
|
||||
_statusQueue.Writer.TryComplete(); // 标记队列不再接受新消息
|
||||
_distributorCts?.Cancel(); // 触发分发器取消
|
||||
|
||||
// 3. 等待分发器处理完剩余消息(最多等待 500ms)
|
||||
if (_distributorTask != null)
|
||||
{
|
||||
await Task.WhenAny(_distributorTask, Task.Delay(500)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// 4. 切断事件引用,防止内存泄漏
|
||||
FrameReceived = null;
|
||||
StatusChanged = null;
|
||||
|
||||
// 5. 释放基础资源
|
||||
_lifecycleLock.Dispose();
|
||||
_distributorCts?.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Modified: [原因] 保证计数锁在任何情况下都能释放
|
||||
if (!_isDisposed)
|
||||
_lifecycleLock.Release();
|
||||
|
||||
// 4. 切断事件引用,防止内存泄漏
|
||||
FrameReceived = null;
|
||||
StatusChanged = null;
|
||||
|
||||
// 5. 释放基础资源
|
||||
_lifecycleLock.Dispose();
|
||||
_distributorCts?.Dispose();
|
||||
|
||||
// 6. 抑制垃圾回收器的终结器
|
||||
GC.SuppressFinalize(this);
|
||||
// 6. 抑制垃圾回收器的终结器
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -419,15 +419,15 @@ public class HikVideoSource : BaseVideoSource,
|
||||
#region --- 解码与帧分发 (Decoding) ---
|
||||
|
||||
// 必须同时加上 SecurityCritical
|
||||
//[HandleProcessCorruptedStateExceptions]
|
||||
//[SecurityCritical]
|
||||
[HandleProcessCorruptedStateExceptions]
|
||||
[SecurityCritical]
|
||||
private void SafeOnDecodingCallBack(int nPort, IntPtr pBuf, int nSize, ref HikPlayMethods.FRAME_INFO pFrameInfo, int nReserved1, int nReserved2)
|
||||
{
|
||||
// 防御性检查,防止传入空指针导致 OpenCV 崩溃(CSE异常)
|
||||
if (pBuf == IntPtr.Zero || nSize <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//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;
|
||||
|
||||
// [优化] 维持心跳,防止被哨兵误杀
|
||||
MarkFrameReceived(0);
|
||||
@@ -463,8 +463,8 @@ public class HikVideoSource : BaseVideoSource,
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
// 尝试获取锁,超时时间 0ms (拿不到立即返回 false)
|
||||
Monitor.TryEnter(_initLock, 0, ref lockTaken);
|
||||
// 尝试获取锁,超时时间 20ms (拿不到立即返回 false)
|
||||
Monitor.TryEnter(_initLock, 20, ref lockTaken);
|
||||
|
||||
if (lockTaken)
|
||||
{
|
||||
@@ -478,7 +478,7 @@ public class HikVideoSource : BaseVideoSource,
|
||||
}
|
||||
else
|
||||
{
|
||||
// 【关键逻辑】没拿到锁,说明主线程正在操作 (通常是正在 Stop)
|
||||
// 【关键逻辑】如果 20ms 没拿到锁,说明主线程正在操作 (通常是正在 Stop)
|
||||
// 既然都要停止了,这一帧直接丢弃,立即返回,防止死锁
|
||||
return;
|
||||
}
|
||||
@@ -491,16 +491,15 @@ public class HikVideoSource : BaseVideoSource,
|
||||
|
||||
if (_framePool == null) return;
|
||||
|
||||
// 3. 转换与分发
|
||||
SmartFrame smartFrame = _framePool.Get();
|
||||
|
||||
// 【标志位】用于判断所有权是否成功移交
|
||||
bool handoverSuccess = false;
|
||||
// Optimized: [原因] 将 smartFrame 定义在 try 外部,确保 finally 块能够可靠执行 Dispose 归还逻辑
|
||||
SmartFrame? smartFrame = null;
|
||||
|
||||
try
|
||||
{
|
||||
{ // 3. 转换与分发
|
||||
smartFrame = _framePool.Get();
|
||||
if (smartFrame == null) return; // 池满丢帧
|
||||
|
||||
// Optimized: [原因] 使用局部作用域封装 YUV 转换,确保原生指针尽快脱离
|
||||
using (var rawYuvWrapper = Mat.FromPixelData(height + height / 2, width, MatType.CV_8UC1, pBuf))
|
||||
{
|
||||
Cv2.CvtColor(rawYuvWrapper, smartFrame.InternalMat, ColorConversionCodes.YUV2BGR_YV12);
|
||||
@@ -519,9 +518,6 @@ public class HikVideoSource : BaseVideoSource,
|
||||
// decision.TargetAppIds 包含了 "谁需要这一帧" 的信息
|
||||
//GlobalProcessingCenter.Submit(this.Id, smartFrame, decision);
|
||||
GlobalPipelineRouter.Enqueue(Id, smartFrame, decision);
|
||||
|
||||
// 标记成功,禁止 finally 块销毁对象
|
||||
handoverSuccess = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user