Files
Ayay/SHH.CameraSdk/Core/Scheduling/FrameController.cs
2026-01-17 13:13:17 +08:00

164 lines
6.0 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Ayay.SerilogLogs;
using Serilog;
namespace SHH.CameraSdk;
/// <summary>
/// 帧控制器(混合模式最终版)
/// 策略:
/// 1. 高速直通 (> 20 FPS):直接放行,保留硬件原始流畅度与波动。
/// 2. 低速精控 (<= 20 FPS):使用积分算法进行精准降采样。
/// </summary>
public class FrameController
{
private ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
// 需求字典
private readonly ConcurrentDictionary<string, FrameRequirement> _requirements = new();
// 积分累加器(仅用于 <= 20 FPS 的精准控制)
private readonly ConcurrentDictionary<string, int> _accumulators = new();
private long _globalSequence = 0;
// 逻辑基准分母:假设标准输入是 25 帧
// 用于低帧率时的积分计算基准
private const int LOGICAL_BASE_FPS = 25;
// ---------------------------------------------------------
// 注册与注销
// ---------------------------------------------------------
public void Register(string appId, int fps)
{
_requirements.AddOrUpdate(appId,
_ => new FrameRequirement { AppId = appId, TargetFps = fps },
(_, old) => { old.TargetFps = fps; return old; });
// 重置该用户的积分器,确保新策略从零开始
_accumulators.TryRemove(appId, out _);
}
// 修改 Register 方法,接收整个 Requirement 对象或多个参数
public void Register(FrameRequirement req)
{
_requirements.AddOrUpdate(req.AppId,
_ => req, // 如果不存在,直接添加整个对象
(_, old) =>
{
// 如果已存在,更新关键业务字段,同时保留统计状态
old.TargetFps = req.TargetFps;
old.Memo = req.Memo;
old.Handle = req.Handle;
old.Type = req.Type;
old.SavePath = req.SavePath;
// 注意:不要覆盖 old.RealFps保留之前的统计值
return old;
});
// 如果是降频(<=20确保积分器存在
if (req.TargetFps <= 20)
{
_accumulators.GetOrAdd(req.AppId, 0);
}
}
public void Unregister(string appId)
{
if (string.IsNullOrWhiteSpace(appId)) return;
// 1. 从需求配置中移除
_requirements.TryRemove(appId, out _);
// 2. 从积分累加器中移除(防止内存泄漏)
_accumulators.TryRemove(appId, out _);
_sysLog.Warning($"[Core] 帧控制器已从调度中心彻底移除 AppId: {appId}");
}
// ---------------------------------------------------------
// 核心决策逻辑
// ---------------------------------------------------------
/// <summary>
/// 混合决策:高速直通 + 低速精控
/// </summary>
/// <param name="currentTick">兼容参数(内部逻辑不强依赖)</param>
/// <param name="ignoredRealFps">兼容参数(已忽略,防止震荡)</param>
public FrameDecision MakeDecision(long currentTick, int ignoredRealFps = 0)
{
var decision = new FrameDecision
{
Sequence = Interlocked.Increment(ref _globalSequence),
Timestamp = DateTime.Now
};
foreach (var req in _requirements.Values)
{
if (req.TargetFps <= 0) continue;
//// =========================================================
//// 【策略 A】 高速直通区 (> 20 FPS)
//// =========================================================
//// 用户想要 21~25+ 帧,或者全速。
//// 此时不做任何干预,相机来多少发多少,保留原始的 24-26 波动。
//if (req.TargetFps > 20)
//{
// decision.TargetAppIds.Add(req.AppId);
// req.LastCaptureTick = currentTick; // 更新活跃状态
// continue;
//}
// =========================================================
// 【策略 B】 低速精控区 (<= 20 FPS) -> 积分算法
// =========================================================
// 解决 "16帧" 问题,保证 1帧、5帧、15帧 的绝对精准
// 1. 获取积分
int acc = _accumulators.GetOrAdd(req.AppId, 0);
// 2. 累加:每来一帧,积攒 "TargetFps" 分
acc += req.TargetFps;
// 3. 判定:是否攒够了 25 分 (逻辑基准)
if (acc >= LOGICAL_BASE_FPS)
{
// 发货
decision.TargetAppIds.Add(req.AppId);
// 扣除成本,保留余数 (余数是精度的关键)
acc -= LOGICAL_BASE_FPS;
// 【核心修复】在此处触发统计RealFps 才会开始跳动
req.UpdateRealFps();
req.LastCaptureTick = currentTick;
}
// 4. 防爆桶机制:如果累积太多(例如相机推流极快),限制封顶
// 防止下一秒瞬间吐出太多帧
if (acc > LOGICAL_BASE_FPS) acc = LOGICAL_BASE_FPS;
// 5. 写回状态
_accumulators[req.AppId] = acc;
}
decision.IsCaptured = decision.TargetAppIds.Count > 0;
return decision;
}
// ---------------------------------------------------------
// 辅助状态查询
// ---------------------------------------------------------
public List<dynamic> GetCurrentRequirements()
{
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;
}
}