修正帧数统计错误,流量统计错误的问题

确认显示帧数策略有效
This commit is contained in:
2025-12-26 13:11:58 +08:00
parent adcdc56c7a
commit 365e63c21a
7 changed files with 95 additions and 73 deletions

View File

@@ -119,6 +119,7 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable
/// <summary> 实时码率 (Mbps) </summary>
protected double _currentBitrate = 0;
public double RealBitrate => _currentBitrate;
/// <summary> 码率计算临时字节计数器 </summary>
private long _tempByteCounter = 0;
@@ -404,43 +405,66 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable
}
/// <summary>
/// 标记帧接收事件(心跳保活 + FPS/码率统计)
/// 标记数据接收(心跳保活 + 双路统计)
/// <para>调用规则:</para>
/// <para>1. 网络层收到流数据时:调用 MarkFrameReceived(dwBufSize),只统计流量。</para>
/// <para>2. 解码层流控通过后:调用 MarkFrameReceived(0),只统计有效帧率。</para>
/// </summary>
/// <param name="dataSize">当前帧字节大小</param>
/// <param name="dataSize">数据包大小字节0 表示这是一帧解码后的图像</param>
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;
}
}

View File

@@ -244,7 +244,8 @@ public class HikVideoSource : BaseVideoSource
{
try
{
// [优化] 维持心跳,防止被哨兵误杀
// 【关键位置】:在此处调用,统计网络层收到的每一字节数据
// 因为 dwBufSize > 0MarkFrameReceived 内部只会累加码流,不会增加 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;