From 365e63c21a77bc82f35aa15e0c7c26ee673e7449 Mon Sep 17 00:00:00 2001
From: twice109 <3518499@qq.com>
Date: Fri, 26 Dec 2025 13:11:58 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=B8=A7=E6=95=B0=E7=BB=9F?=
=?UTF-8?q?=E8=AE=A1=E9=94=99=E8=AF=AF=EF=BC=8C=E6=B5=81=E9=87=8F=E7=BB=9F?=
=?UTF-8?q?=E8=AE=A1=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98=20?=
=?UTF-8?q?=E7=A1=AE=E8=AE=A4=E6=98=BE=E7=A4=BA=E5=B8=A7=E6=95=B0=E7=AD=96?=
=?UTF-8?q?=E7=95=A5=E6=9C=89=E6=95=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Controllers/CamerasController.cs | 15 ++++
SHH.CameraSdk/Core/Manager/CameraManager.cs | 1 +
.../Core/Scheduling/FrameController.cs | 15 ++++
.../Core/Telemetry/CameraTelemetryInfo.cs | 3 +
SHH.CameraSdk/Drivers/BaseVideoSource.cs | 80 ++++++++++++-------
.../Drivers/HikVision/HikVideoSource.cs | 6 +-
SHH.CameraSdk/Program.cs | 48 +----------
7 files changed, 95 insertions(+), 73 deletions(-)
diff --git a/SHH.CameraSdk/Controllers/CamerasController.cs b/SHH.CameraSdk/Controllers/CamerasController.cs
index cac6691..ed42fbf 100644
--- a/SHH.CameraSdk/Controllers/CamerasController.cs
+++ b/SHH.CameraSdk/Controllers/CamerasController.cs
@@ -280,4 +280,19 @@ public class CamerasController : ControllerBase
UseGrayscale = dto.UseGrayscale
};
}
+
+ ///
+ /// [新增] 查询某台设备的当前流控策略
+ ///
+ [HttpGet("{id}/subscriptions")]
+ public IActionResult GetSubscriptions(long id)
+ {
+ var device = _manager.GetDevice(id);
+ if (device == null) return NotFound();
+
+ // 调用刚才在 FrameController 写的方法
+ var subs = device.Controller.GetCurrentRequirements();
+
+ return Ok(subs);
+ }
}
\ No newline at end of file
diff --git a/SHH.CameraSdk/Core/Manager/CameraManager.cs b/SHH.CameraSdk/Core/Manager/CameraManager.cs
index dd82281..6599c7d 100644
--- a/SHH.CameraSdk/Core/Manager/CameraManager.cs
+++ b/SHH.CameraSdk/Core/Manager/CameraManager.cs
@@ -197,6 +197,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
Status = cam.Status.ToString(),
IsOnline = cam.IsOnline,
Fps = cam.RealFps,
+ Bitrate = cam.RealBitrate, // [新增] 映射基类属性
TotalFrames = cam.TotalFrames,
HealthScore = healthScore,
LastErrorMessage = cam.Status == VideoSourceStatus.Faulted ? "设备故障或网络中断" : null,
diff --git a/SHH.CameraSdk/Core/Scheduling/FrameController.cs b/SHH.CameraSdk/Core/Scheduling/FrameController.cs
index 9304c42..899b2fb 100644
--- a/SHH.CameraSdk/Core/Scheduling/FrameController.cs
+++ b/SHH.CameraSdk/Core/Scheduling/FrameController.cs
@@ -95,4 +95,19 @@ public class FrameController
}
#endregion
+
+ ///
+ /// [新增] 获取当前所有活跃的订阅需求快照
+ ///
+ public List GetCurrentRequirements()
+ {
+ // 将 ConcurrentDictionary 转换为列表返回
+ return _requirements.Values.Select(r => new
+ {
+ r.AppId,
+ r.TargetFps,
+ // 还可以计算一个预计带宽占用,或者上次取帧时间
+ LastActive = r.LastCaptureTick
+ }).ToList();
+ }
}
\ No newline at end of file
diff --git a/SHH.CameraSdk/Core/Telemetry/CameraTelemetryInfo.cs b/SHH.CameraSdk/Core/Telemetry/CameraTelemetryInfo.cs
index 0d3158f..16c6e17 100644
--- a/SHH.CameraSdk/Core/Telemetry/CameraTelemetryInfo.cs
+++ b/SHH.CameraSdk/Core/Telemetry/CameraTelemetryInfo.cs
@@ -41,6 +41,9 @@ public class CameraTelemetryInfo
/// 累计接收帧数(相机启动后接收的总帧数,用于统计数据完整性)
public long TotalFrames { get; set; }
+ /// 实时码率 (Mbps)
+ public double Bitrate { get; set; }
+
#endregion
#region --- 健康度与统计 (Health & Statistics) ---
diff --git a/SHH.CameraSdk/Drivers/BaseVideoSource.cs b/SHH.CameraSdk/Drivers/BaseVideoSource.cs
index c372cb9..c584e37 100644
--- a/SHH.CameraSdk/Drivers/BaseVideoSource.cs
+++ b/SHH.CameraSdk/Drivers/BaseVideoSource.cs
@@ -119,6 +119,7 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable
/// 实时码率 (Mbps)
protected double _currentBitrate = 0;
+ public double RealBitrate => _currentBitrate;
/// 码率计算临时字节计数器
private long _tempByteCounter = 0;
@@ -404,43 +405,66 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable
}
///
- /// 标记帧接收事件(心跳保活 + FPS/码率统计)
+ /// 标记数据接收(心跳保活 + 双路统计)
+ /// 调用规则:
+ /// 1. 网络层收到流数据时:调用 MarkFrameReceived(dwBufSize),只统计流量。
+ /// 2. 解码层流控通过后:调用 MarkFrameReceived(0),只统计有效帧率。
///
- /// 当前帧字节大小
+ /// 数据包大小(字节),0 表示这是一帧解码后的图像
protected void MarkFrameReceived(uint dataSize = 0)
{
- var now = Environment.TickCount64;
+ long now = Environment.TickCount64;
- // 1. 更新心跳时间戳(原子操作)
+ // 1. [心跳保活] 无论网络包还是解码帧,都视为设备“活着”
+ // 使用 Interlocked 保证多线程读写安全
Interlocked.Exchange(ref _lastFrameTick, now);
- // 2. 累加总帧数(原子操作)
- Interlocked.Increment(ref _totalFramesReceived);
-
- // 3. 累加临时计数器(用于 FPS/码率计算)
- _tempFrameCounter++;
- _tempByteCounter += dataSize;
-
- // 4. 每秒结算一次统计指标
- var timeDiff = now - _lastFpsCalcTick;
- if (timeDiff >= 1000 && _lastFpsCalcTick > 0)
+ // 2. [分流累加] 根据来源不同,累加不同的计数器
+ if (dataSize > 0)
{
- var duration = timeDiff / 1000.0;
-
- // 计算实时 FPS (保留 1 位小数)
- RealFps = Math.Round(_tempFrameCounter / duration, 1);
-
- // 计算实时码率 (Mbps) = (字节数 * 8) / 1024 / 1024 / 秒
- _currentBitrate = Math.Round((_tempByteCounter * 8.0) / 1024 / 1024 / duration, 2);
-
- // 重置临时计数器
- _lastFpsCalcTick = now;
- _tempFrameCounter = 0;
- _tempByteCounter = 0;
+ // --- 来源:网络层回调 (SafeOnRealDataReceived) ---
+ // 只累加字节数,用于计算带宽 (Mbps)
+ // 绝对不能在这里累加帧数,否则会被网络包的数量误导(导致 FPS 虚高)
+ Interlocked.Add(ref _tempByteCounter, dataSize);
}
- else if (_lastFpsCalcTick == 0)
+ else
{
- // 初始化 FPS 计算起始时间
+ // --- 来源:解码层回调 (SafeOnDecodingCallBack) ---
+ // 只累加帧数,用于计算有效 FPS
+ // 只有经过 MakeDecision() 筛选保留下来的帧才走到这里,所以是真实的 "Output FPS"
+ Interlocked.Increment(ref _tempFrameCounter);
+
+ // 累加生命周期总帧数
+ Interlocked.Increment(ref _totalFramesReceived);
+ }
+
+ // 3. [定期结算] 每 1000ms (1秒) 结算一次统计指标
+ long timeDiff = now - _lastFpsCalcTick;
+ if (timeDiff >= 1000)
+ {
+ // 忽略第一次冷启动的数据(避免除以 0 或时间跨度过大)
+ if (_lastFpsCalcTick > 0)
+ {
+ double duration = timeDiff / 1000.0;
+
+ // --- A. 结算有效帧率 (FPS) ---
+ // 原子读取并重置计数器,防止漏算
+ int frames = Interlocked.Exchange(ref _tempFrameCounter, 0);
+ RealFps = Math.Round(frames / duration, 1);
+
+ // --- B. 结算网络带宽 (Mbps) ---
+ // 公式: (字节数 * 8位) / 1024 / 1024 / 秒数
+ long bytes = Interlocked.Exchange(ref _tempByteCounter, 0);
+ _currentBitrate = Math.Round((bytes * 8.0) / 1024 / 1024 / duration, 2);
+ }
+ else
+ {
+ // 初始化重置
+ _tempFrameCounter = 0;
+ _tempByteCounter = 0;
+ }
+
+ // 更新结算时间锚点
_lastFpsCalcTick = now;
}
}
diff --git a/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs b/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs
index a53552c..279e3b3 100644
--- a/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs
+++ b/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs
@@ -244,7 +244,8 @@ public class HikVideoSource : BaseVideoSource
{
try
{
- // [优化] 维持心跳,防止被哨兵误杀
+ // 【关键位置】:在此处调用,统计网络层收到的每一字节数据
+ // 因为 dwBufSize > 0,MarkFrameReceived 内部只会累加码流,不会增加 FPS 计数
MarkFrameReceived(dwBufSize);
if (_realPlayHandle == -1) return;
@@ -301,6 +302,9 @@ public class HikVideoSource : BaseVideoSource
// 如果没人要,直接丢弃,不进行 Mat 转换,节省 CPU
if (!decision.IsCaptured) return;
+ // [优化] 维持心跳,防止被哨兵误杀
+ MarkFrameReceived(0);
+
int width = pFrameInfo.nWidth;
int height = pFrameInfo.nHeight;
diff --git a/SHH.CameraSdk/Program.cs b/SHH.CameraSdk/Program.cs
index cc11a0c..9e4be59 100644
--- a/SHH.CameraSdk/Program.cs
+++ b/SHH.CameraSdk/Program.cs
@@ -122,56 +122,16 @@ namespace SHH.CameraSdk
if (manager.GetDevice(101) is HikVideoSource hikCamera)
{
- // 2. 注册需求 (告诉控制器我要什么)
- // ----------------------------------------------------
- hikCamera.Controller.Register("WPF_Display_Main", 8); // UI 要 8 帧
- hikCamera.Controller.Register("AI_Behavior_Engine", 2); // AI 要 2 帧
-
// 1. 注册差异化需求 (给每个消费者唯一的 AppId)
// ----------------------------------------------------
- // 模拟:A 进程(如远程预览)带宽有限,只要 3fps
- hikCamera.Controller.Register("Process_A_Remote", 20);
+ // 1. 注册需求时,手动加上 _Display 后缀
+ hikCamera.Controller.Register("Process_A_Remote_Display", 20);
- // 模拟:B 进程(如本地大屏)性能强劲,要 8fps
- hikCamera.Controller.Register("Process_B_Local", 8);
-
- // 模拟:AI 引擎
- hikCamera.Controller.Register("AI_Engine_Core", 2);
-
- // [已移除] 这里的 using var remoteRenderer = ... 已被移除
- // 改为使用传入的 renderer 参数,确保其生命周期受控于 Main
-
- // 2. 精准订阅 (Subscribe 替代了 +=)
- // ----------------------------------------------------
-
- // [消费者 A] - 绝对只会收到 3fps
- GlobalStreamDispatcher.Subscribe("Process_A_Remote", 101, frame =>
+ // 2. 订阅时,也改用带后缀的名称
+ GlobalStreamDispatcher.Subscribe("Process_A_Remote_Display", 101, frame =>
{
- // 关键:增加引用计数,防止在投递过程中被 Pipeline 回收
frame.AddRef();
-
- // 投递到渲染线程 (FrameConsumer)
renderer.Enqueue(frame);
-
- //Console.WriteLine("Frame Enqueued");
- });
-
- // [消费者 B] - 绝对只会收到 8fps
- GlobalStreamDispatcher.Subscribe("Process_B_Local", (deviceId, frame) =>
- {
- //if (deviceId == 101)
- //{
- // Console.WriteLine($"[Process B] 本地渲染一帧 (8fps节奏)");
- //}
- });
-
- // [消费者 AI]
- GlobalStreamDispatcher.Subscribe("AI_Engine_Core", (deviceId, frame) =>
- {
- //if (deviceId == 101)
- //{
- // Console.WriteLine($" >>> [AI] 分析一帧...");
- //}
});
}
}