修复 Bug

This commit is contained in:
2026-01-17 13:13:17 +08:00
parent a27045e0a0
commit 8482996a94
10 changed files with 177 additions and 59 deletions

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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),等待出流...");
}
}
}

View File

@@ -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;
}
}