2025-12-26 03:18:21 +08:00
|
|
|
|
namespace SHH.CameraSdk;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 帧控制器(帧调度核心)
|
|
|
|
|
|
/// 功能:管理订阅者的帧需求,基于需求动态判定每帧的处理命运(保留/丢弃、分发目标)
|
|
|
|
|
|
/// 核心逻辑:采用“并集采样”策略,满足任意订阅者的帧率需求即保留帧,避免重复采样浪费资源
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class FrameController
|
|
|
|
|
|
{
|
|
|
|
|
|
#region --- 私有资源与状态 (Private Resources & States) ---
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary> 订阅者帧需求集合(线程安全) </summary>
|
|
|
|
|
|
/// <remarks> Key:订阅者AppId,Value:该订阅者的帧需求配置 </remarks>
|
|
|
|
|
|
private readonly ConcurrentDictionary<string, FrameRequirement> _requirements = new();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary> 全局决策序列号(原子递增,确保决策唯一标识) </summary>
|
|
|
|
|
|
private long _globalSequence = 0;
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region --- 需求管理 (Requirement Management) ---
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 注册/更新订阅者的帧需求
|
|
|
|
|
|
/// 功能:新增订阅者需求或更新已有订阅者的目标帧率
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="appId">订阅者唯一标识(如 "RemoteClient_01"、"AI_Behavior_Engine")</param>
|
|
|
|
|
|
/// <param name="fps">目标帧率(单位:fps,需大于0,否则视为无效需求)</param>
|
|
|
|
|
|
public void Register(string appId, int fps)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 新增或更新需求:不存在则创建,存在则更新目标帧率
|
|
|
|
|
|
_requirements.AddOrUpdate(appId,
|
|
|
|
|
|
addValueFactory: _ => new FrameRequirement { AppId = appId, TargetFps = fps },
|
|
|
|
|
|
updateValueFactory: (_, oldRequirement) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
oldRequirement.TargetFps = fps;
|
|
|
|
|
|
return oldRequirement;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 12:15:10 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// [新增] 注销订阅者的帧需求
|
|
|
|
|
|
/// 功能:移除指定订阅者的配置,该订阅者将不再收到任何分发帧
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="appId">订阅者唯一标识</param>
|
|
|
|
|
|
public void Unregister(string appId)
|
|
|
|
|
|
{
|
|
|
|
|
|
// ConcurrentDictionary.TryRemove 是原子的、线程安全的
|
|
|
|
|
|
// out _ 表示我们要丢弃移除出的对象,因为我们只关心移除动作本身
|
|
|
|
|
|
_requirements.TryRemove(appId, out _);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 03:18:21 +08:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region --- 帧决策生成 (Frame Decision Generation) ---
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// [热路径] 判定当前物理帧是否需要保留并分发
|
|
|
|
|
|
/// 核心逻辑:并集采样,只要任意订阅者达到采样间隔,就保留该帧并分发至对应订阅者
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="currentTick">当前系统时间戳(单位:毫秒,建议使用 Environment.TickCount64)</param>
|
|
|
|
|
|
/// <returns>帧决策结果(包含是否保留、分发目标等信息)</returns>
|
|
|
|
|
|
public FrameDecision MakeDecision(long currentTick)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 初始化决策对象,生成唯一序列号与时间戳
|
|
|
|
|
|
var decision = new FrameDecision
|
|
|
|
|
|
{
|
|
|
|
|
|
Sequence = Interlocked.Increment(ref _globalSequence), // 原子递增,线程安全
|
|
|
|
|
|
Timestamp = DateTime.Now
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历所有订阅者需求,判定是否需要为该订阅者保留当前帧
|
|
|
|
|
|
foreach (var req in _requirements.Values)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 跳过无效需求(目标帧率≤0)
|
|
|
|
|
|
if (req.TargetFps <= 0) continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算该订阅者的采样间隔(毫秒):1000ms / 目标帧率
|
|
|
|
|
|
long interval = 1000 / req.TargetFps;
|
|
|
|
|
|
|
|
|
|
|
|
// 判定是否达到采样时间:当前时间 - 上次采样时间 ≥ 采样间隔(允许1s内相位对齐,自动合并)
|
|
|
|
|
|
if (currentTick - req.LastCaptureTick >= interval)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 加入分发目标列表
|
|
|
|
|
|
decision.TargetAppIds.Add(req.AppId);
|
|
|
|
|
|
// 更新该订阅者的上次采样时间,避免重复采样
|
|
|
|
|
|
req.LastCaptureTick = currentTick;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判定是否保留该帧:存在分发目标则保留(IsCaptured=true),否则丢弃
|
|
|
|
|
|
decision.IsCaptured = decision.TargetAppIds.Count > 0;
|
|
|
|
|
|
|
|
|
|
|
|
return decision;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
2025-12-26 13:11:58 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// [新增] 获取当前所有活跃的订阅需求快照
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public List<dynamic> GetCurrentRequirements()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 将 ConcurrentDictionary 转换为列表返回
|
|
|
|
|
|
return _requirements.Values.Select(r => new
|
|
|
|
|
|
{
|
|
|
|
|
|
r.AppId,
|
|
|
|
|
|
r.TargetFps,
|
|
|
|
|
|
// 还可以计算一个预计带宽占用,或者上次取帧时间
|
|
|
|
|
|
LastActive = r.LastCaptureTick
|
|
|
|
|
|
}).ToList<dynamic>();
|
|
|
|
|
|
}
|
2025-12-26 03:18:21 +08:00
|
|
|
|
}
|