优化视频对讲
This commit is contained in:
parent
35fd6dae4f
commit
83b3908826
@ -17,9 +17,6 @@ class LockDetailState {
|
|||||||
StreamSubscription? DetailLockInfo;
|
StreamSubscription? DetailLockInfo;
|
||||||
StreamSubscription? SuccessfulDistributionNetworkEvent;
|
StreamSubscription? SuccessfulDistributionNetworkEvent;
|
||||||
|
|
||||||
// 添加网络质量状态变量
|
|
||||||
final RxInt networkQualityScore = 1.obs; // 1-5分,5为最佳
|
|
||||||
final RxString networkQualityMessage = ''.obs; // 网络质量提示信息
|
|
||||||
String lockNetToken = '0';
|
String lockNetToken = '0';
|
||||||
int differentialTime = 0; // 服务器时间与本地时间差值
|
int differentialTime = 0; // 服务器时间与本地时间差值
|
||||||
bool isHaveNetwork = true;
|
bool isHaveNetwork = true;
|
||||||
|
|||||||
@ -408,6 +408,9 @@ class LockMonitoringLogic extends BaseGetXController {
|
|||||||
_getUDPStatusRefreshUIAction();
|
_getUDPStatusRefreshUIAction();
|
||||||
|
|
||||||
initRecorder();
|
initRecorder();
|
||||||
|
|
||||||
|
// 页面加载时自动发起接听请求
|
||||||
|
initiateUdpAnswerAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -47,7 +47,7 @@ class LockMonitoringState {
|
|||||||
// 定时器如果发送了接听的命令 而没收到回复就每秒重复发送10次
|
// 定时器如果发送了接听的命令 而没收到回复就每秒重复发送10次
|
||||||
late Timer answerTimer = Timer(const Duration(seconds: 1), () {}); //接听命令定时器
|
late Timer answerTimer = Timer(const Duration(seconds: 1), () {}); //接听命令定时器
|
||||||
RxInt answerSeconds = 0.obs;
|
RxInt answerSeconds = 0.obs;
|
||||||
RxBool isClickAnswer = false.obs; //是否点击了接听按钮
|
RxBool isClickAnswer = true.obs; //是否点击了接听按钮
|
||||||
|
|
||||||
late Timer hangUpTimer = Timer(const Duration(seconds: 1), () {}); //挂断命令定时器
|
late Timer hangUpTimer = Timer(const Duration(seconds: 1), () {}); //挂断命令定时器
|
||||||
RxInt hangUpSeconds = 0.obs;
|
RxInt hangUpSeconds = 0.obs;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -37,9 +38,9 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
|
|
||||||
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
|
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
|
||||||
|
|
||||||
int bufferSize = 25; // 初始化为默认大小
|
int bufferSize = 2; // 初始化为默认大小,减小缓冲区以降低延迟
|
||||||
|
|
||||||
int audioBufferSize = 20; // 音频默认缓冲2帧
|
int audioBufferSize = 3; // 音频默认缓冲3帧,减少音频延迟
|
||||||
|
|
||||||
// 回绕阈值,动态调整,frameSeq较小时阈值也小
|
// 回绕阈值,动态调整,frameSeq较小时阈值也小
|
||||||
int _getFrameSeqRolloverThreshold(int lastSeq) {
|
int _getFrameSeqRolloverThreshold(int lastSeq) {
|
||||||
@ -88,6 +89,44 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
bool _waitingForIFrame = false;
|
bool _waitingForIFrame = false;
|
||||||
|
|
||||||
int? lastDecodedIFrameSeq;
|
int? lastDecodedIFrameSeq;
|
||||||
|
|
||||||
|
// 新增:缓冲区满载处理相关变量
|
||||||
|
int _consecutiveFullBufferCount = 0; // 连续缓冲区满载次数
|
||||||
|
int _maxConsecutiveFullBufferCount = 3; // 最大连续满载次数,超过此值将采取措施,降低为3以更快响应
|
||||||
|
bool _isAdjustingForBufferFull = false; // 是否正在为缓冲区满载调整
|
||||||
|
|
||||||
|
// 新增:记录最近一次视频画面开始渲染的时间
|
||||||
|
DateTime? _lastVideoRenderTime;
|
||||||
|
|
||||||
|
// 新增:自适应缓冲区大小相关变量
|
||||||
|
int _currentBufferSize = 3; // 当前缓冲区大小
|
||||||
|
int _lastBufferSizeAdjustmentTime = 0; // 上次缓冲区大小调整时间
|
||||||
|
int _bufferSizeAdjustmentCooldown = 2000; // 缓冲区大小调整冷却时间(毫秒)
|
||||||
|
|
||||||
|
// 新增:网络状况评估相关变量
|
||||||
|
int _lastNetworkQualityCheckTime = 0; // 上次网络质量检查时间
|
||||||
|
int _framesProcessedSinceLastCheck = 0; // 自上次检查以来处理的帧数
|
||||||
|
int _framesDroppedSinceLastCheck = 0; // 自上次检查以来丢弃的帧数
|
||||||
|
double _currentNetworkQualityScore = 1.0; // 当前网络质量评分(0.0-1.0, 1.0为最佳)
|
||||||
|
|
||||||
|
// 网络质量评估相关变量
|
||||||
|
List<int> _frameReceiveTimes = []; // 存储帧接收时间戳
|
||||||
|
List<int> _frameSeqList = []; // 存储帧序列号用于计算丢包
|
||||||
|
int _totalFramesReceived = 0; // 总接收帧数
|
||||||
|
int _lostFrames = 0; // 丢失帧数
|
||||||
|
int _lastFrameSeqNum = -1; // 上一帧序列号
|
||||||
|
DateTime? _testStartTime; // 测试开始时间
|
||||||
|
Timer? _networkQualityTestTimer; // 网络质量评估定时器
|
||||||
|
|
||||||
|
// 包、帧和关键帧统计变量
|
||||||
|
int _totalPacketsReceived = 0; // 总接收包数
|
||||||
|
int _totalFramesReceivedCount = 0; // 总接收帧数
|
||||||
|
int _iFramesReceived = 0; // 接收到的关键帧数(I帧)
|
||||||
|
int _pFramesReceived = 0; // 接收到的预测帧数(P帧)
|
||||||
|
int _processedFrames = 0; // 已处理的帧数
|
||||||
|
int _droppedFrames = 0; // 丢弃的帧数
|
||||||
|
int _framesInBuffer = 0; // 进入缓冲区的帧数
|
||||||
|
int _bufferSize = 0; // 缓冲区大小
|
||||||
|
|
||||||
// 初始化视频解码器
|
// 初始化视频解码器
|
||||||
Future<void> _initVideoDecoder() async {
|
Future<void> _initVideoDecoder() async {
|
||||||
@ -128,16 +167,20 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
height: height,
|
height: height,
|
||||||
codecType: 'h264',
|
codecType: 'h264',
|
||||||
);
|
);
|
||||||
AppLog.log('解码器配置的宽高为:${config.width}x${config.height}');
|
|
||||||
// 初始化解码器并获取textureId
|
// 初始化解码器并获取textureId
|
||||||
final textureId = await VideoDecodePlugin.initDecoder(config);
|
final textureId = await VideoDecodePlugin.initDecoder(config);
|
||||||
if (textureId != null) {
|
if (textureId != null) {
|
||||||
Future.microtask(() => state.textureId.value = textureId);
|
Future.microtask(() => state.textureId.value = textureId);
|
||||||
AppLog.log('视频解码器初始化成功:textureId=$textureId');
|
|
||||||
VideoDecodePlugin.setOnFrameRenderedListener((textureId) {
|
VideoDecodePlugin.setOnFrameRenderedListener((textureId) {
|
||||||
AppLog.log('已经开始渲染=======');
|
AppLog.log('已经开始渲染=======');
|
||||||
|
// 记录视频开始渲染时间
|
||||||
|
_lastVideoRenderTime = DateTime.now();
|
||||||
// 只有真正渲染出首帧时才关闭loading
|
// 只有真正渲染出首帧时才关闭loading
|
||||||
Future.microtask(() => state.isLoading.value = false);
|
if (state.isLoading.value) {
|
||||||
|
Future.microtask(() => state.isLoading.value = false);
|
||||||
|
} else {
|
||||||
|
AppLog.log('视频已在渲染状态,保持当前状态');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
AppLog.log('视频解码器初始化失败');
|
AppLog.log('视频解码器初始化失败');
|
||||||
@ -243,6 +286,110 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
}
|
}
|
||||||
_lastFrameSeq = frameSeq;
|
_lastFrameSeq = frameSeq;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 记录帧接收
|
||||||
|
recordFrameReceived(frameSeq, frameType);
|
||||||
|
|
||||||
|
// 优化:对于低延迟场景,仅保留最新的帧以减少延迟
|
||||||
|
bool isVideoRendering = state.textureId.value != null &&
|
||||||
|
(state.isLoading.isFalse || _lastVideoRenderTime != null);
|
||||||
|
|
||||||
|
if (isVideoRendering) {
|
||||||
|
// 如果视频已经开始渲染,优先保留最新的I帧和其关联的P帧
|
||||||
|
if (frameType == TalkDataH264Frame_FrameTypeE.I) {
|
||||||
|
// 对于新的I帧,移除旧的I帧和其关联的P帧
|
||||||
|
_removeOldFramesForIFrame(frameSeq);
|
||||||
|
|
||||||
|
// 当接收到新I帧时,动态调整缓冲区大小以适应当前网络状况
|
||||||
|
_adjustBufferSizeForNetworkCondition();
|
||||||
|
} else {
|
||||||
|
// 对于P帧,确保它关联到有效的I帧
|
||||||
|
_cleanOldPFrameForCurrentIFrame(frameSeq, frameSeqI);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 视频未渲染时,按原有逻辑处理
|
||||||
|
if (state.h264FrameBuffer.length >= state.maxFrameBufferSize) {
|
||||||
|
// 检查是否已经有视频开始渲染
|
||||||
|
bool isVideoRendering = state.textureId.value != null &&
|
||||||
|
(state.isLoading.isFalse || _lastVideoRenderTime != null);
|
||||||
|
|
||||||
|
// 如果视频已经在渲染,我们采用更智能的缓冲区清理策略
|
||||||
|
if (isVideoRendering) {
|
||||||
|
_consecutiveFullBufferCount++;
|
||||||
|
|
||||||
|
// 当连续多次缓冲区满载时,采取特殊处理策略
|
||||||
|
if (_consecutiveFullBufferCount >= _maxConsecutiveFullBufferCount) {
|
||||||
|
|
||||||
|
// 计算当前网络质量并调整缓冲区大小
|
||||||
|
_evaluateCurrentNetworkQuality();
|
||||||
|
|
||||||
|
// 查找所有非关键帧(非I帧)进行清理
|
||||||
|
final framesToRemove = <int>[];
|
||||||
|
for (int i = 0; i < state.h264FrameBuffer.length; i++) {
|
||||||
|
if (state.h264FrameBuffer[i]['frameType'] != TalkDataH264Frame_FrameTypeE.I) {
|
||||||
|
framesToRemove.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从后往前删除,避免索引变化问题
|
||||||
|
framesToRemove.reversed.forEach((index) {
|
||||||
|
state.h264FrameBuffer.removeAt(index);
|
||||||
|
recordDroppedFrame();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果仍有过多帧,保留最新的I帧及相关的P帧
|
||||||
|
if (state.h264FrameBuffer.length > _currentBufferSize ~/ 2) {
|
||||||
|
// 找到最后一个I帧的位置
|
||||||
|
int lastIFrameIndex = -1;
|
||||||
|
for (int i = state.h264FrameBuffer.length - 1; i >= 0; i--) {
|
||||||
|
if (state.h264FrameBuffer[i]['frameType'] == TalkDataH264Frame_FrameTypeE.I) {
|
||||||
|
lastIFrameIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果找到I帧,只保留I帧及其后续的P帧,删除前面的帧
|
||||||
|
if (lastIFrameIndex > 0) {
|
||||||
|
for (int i = 0; i < lastIFrameIndex; i++) {
|
||||||
|
state.h264FrameBuffer.removeAt(0);
|
||||||
|
recordDroppedFrame();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有I帧,只保留最新的半数帧
|
||||||
|
while (state.h264FrameBuffer.length > _currentBufferSize ~/ 2) {
|
||||||
|
state.h264FrameBuffer.removeAt(0);
|
||||||
|
recordDroppedFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_consecutiveFullBufferCount = 0; // 重置计数
|
||||||
|
_isAdjustingForBufferFull = false;
|
||||||
|
} else {
|
||||||
|
// 非连续满载情况下,采用保守清理策略
|
||||||
|
int pbIndex = state.h264FrameBuffer.indexWhere((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P);
|
||||||
|
if (pbIndex != -1) {
|
||||||
|
state.h264FrameBuffer.removeAt(pbIndex);
|
||||||
|
recordDroppedFrame(); // 记录丢弃的帧
|
||||||
|
} else {
|
||||||
|
state.h264FrameBuffer.removeAt(0);
|
||||||
|
recordDroppedFrame(); // 记录丢弃的帧
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 视频尚未渲染时,使用原始策略
|
||||||
|
int pbIndex = state.h264FrameBuffer.indexWhere((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P);
|
||||||
|
if (pbIndex != -1) {
|
||||||
|
state.h264FrameBuffer.removeAt(pbIndex);
|
||||||
|
recordDroppedFrame(); // 记录丢弃的帧
|
||||||
|
} else {
|
||||||
|
state.h264FrameBuffer.removeAt(0);
|
||||||
|
recordDroppedFrame(); // 记录丢弃的帧
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 创建包含帧数据和类型的Map
|
// 创建包含帧数据和类型的Map
|
||||||
final Map<String, dynamic> frameMap = {
|
final Map<String, dynamic> frameMap = {
|
||||||
'frameData': frameData,
|
'frameData': frameData,
|
||||||
@ -253,18 +400,169 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
'scpMessage': scpMessage,
|
'scpMessage': scpMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果缓冲区超出最大大小,优先丢弃P/B帧
|
|
||||||
while (state.h264FrameBuffer.length >= state.maxFrameBufferSize) {
|
|
||||||
int pbIndex = state.h264FrameBuffer.indexWhere((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P);
|
|
||||||
if (pbIndex != -1) {
|
|
||||||
state.h264FrameBuffer.removeAt(pbIndex);
|
|
||||||
} else {
|
|
||||||
state.h264FrameBuffer.removeAt(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将帧添加到缓冲区
|
// 将帧添加到缓冲区
|
||||||
state.h264FrameBuffer.add(frameMap);
|
state.h264FrameBuffer.add(frameMap);
|
||||||
|
recordFrameInBuffer(); // 记录进入缓冲区的帧
|
||||||
|
|
||||||
|
// 重置连续满载计数(只有在缓冲区未满时)
|
||||||
|
if (state.h264FrameBuffer.length < state.maxFrameBufferSize) {
|
||||||
|
_consecutiveFullBufferCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助方法:移除旧的I帧和其关联的P帧
|
||||||
|
void _removeOldFramesForIFrame(int newIFrameSeq) {
|
||||||
|
// 保留最新的I帧和相关的P帧,移除旧的I帧和其关联的P帧
|
||||||
|
final List<Map<String, dynamic>> framesToKeep = [];
|
||||||
|
|
||||||
|
// 分别收集I帧和P帧
|
||||||
|
final List<Map<String, dynamic>> iFrames = [];
|
||||||
|
final List<Map<String, dynamic>> pFrames = [];
|
||||||
|
|
||||||
|
for (var frame in state.h264FrameBuffer) {
|
||||||
|
if (frame['frameType'] == TalkDataH264Frame_FrameTypeE.I) {
|
||||||
|
iFrames.add(frame);
|
||||||
|
} else {
|
||||||
|
pFrames.add(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按frameSeq排序I帧
|
||||||
|
iFrames.sort((a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int));
|
||||||
|
|
||||||
|
// 保留最新的I帧(即当前正在添加的新I帧)
|
||||||
|
final int maxIFramesToKeep = 2; // 保留最多2个I帧以确保平滑过渡
|
||||||
|
final int startIdx = max(0, iFrames.length - maxIFramesToKeep);
|
||||||
|
|
||||||
|
for (int i = startIdx; i < iFrames.length; i++) {
|
||||||
|
framesToKeep.add(iFrames[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于P帧,只保留与保留的I帧关联的P帧
|
||||||
|
for (var pFrame in pFrames) {
|
||||||
|
int refIFrameSeq = pFrame['frameSeqI'];
|
||||||
|
bool shouldKeep = false;
|
||||||
|
|
||||||
|
// 检查该P帧引用的I帧是否被保留
|
||||||
|
for (var keptIFrame in framesToKeep) {
|
||||||
|
if (keptIFrame['frameType'] == TalkDataH264Frame_FrameTypeE.I &&
|
||||||
|
keptIFrame['frameSeq'] == refIFrameSeq) {
|
||||||
|
shouldKeep = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldKeep) {
|
||||||
|
framesToKeep.add(pFrame);
|
||||||
|
} else {
|
||||||
|
recordDroppedFrame(); // 记录丢弃的帧
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用筛选后的帧替换原缓冲区
|
||||||
|
state.h264FrameBuffer.clear();
|
||||||
|
state.h264FrameBuffer.addAll(framesToKeep);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助方法:清理当前I帧的旧P帧
|
||||||
|
void _cleanOldPFrameForCurrentIFrame(int frameSeq, int frameSeqI) {
|
||||||
|
// 为当前I帧保留最新的P帧,移除过旧的P帧
|
||||||
|
final List<Map<String, dynamic>> framesToKeep = [];
|
||||||
|
final List<Map<String, dynamic>> framesToRemove = [];
|
||||||
|
|
||||||
|
// 首先添加所有非当前I帧引用的帧
|
||||||
|
for (var frame in state.h264FrameBuffer) {
|
||||||
|
if (frame['frameSeqI'] != frameSeqI || frame['frameType'] == TalkDataH264Frame_FrameTypeE.I) {
|
||||||
|
framesToKeep.add(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集当前I帧引用的P帧
|
||||||
|
final List<Map<String, dynamic>> currentIFramePFrames = [];
|
||||||
|
for (var frame in state.h264FrameBuffer) {
|
||||||
|
if (frame['frameSeqI'] == frameSeqI && frame['frameType'] == TalkDataH264Frame_FrameTypeE.P) {
|
||||||
|
currentIFramePFrames.add(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按frameSeq排序P帧
|
||||||
|
currentIFramePFrames.sort((a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int));
|
||||||
|
|
||||||
|
// 保留最新的P帧(例如保留最近的5个)
|
||||||
|
final int maxPFramesToKeep = 5;
|
||||||
|
final int startIdx = max(0, currentIFramePFrames.length - maxPFramesToKeep);
|
||||||
|
|
||||||
|
for (int i = startIdx; i < currentIFramePFrames.length; i++) {
|
||||||
|
framesToKeep.add(currentIFramePFrames[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录被丢弃的P帧
|
||||||
|
for (int i = 0; i < startIdx; i++) {
|
||||||
|
recordDroppedFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用筛选后的帧替换原缓冲区
|
||||||
|
state.h264FrameBuffer.clear();
|
||||||
|
state.h264FrameBuffer.addAll(framesToKeep);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据网络状况动态调整缓冲区大小
|
||||||
|
void _adjustBufferSizeForNetworkCondition() {
|
||||||
|
// 获取当前时间
|
||||||
|
int currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
||||||
|
// 检查是否在冷却期内
|
||||||
|
if (currentTime - _lastBufferSizeAdjustmentTime < _bufferSizeAdjustmentCooldown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新最后调整时间
|
||||||
|
_lastBufferSizeAdjustmentTime = currentTime;
|
||||||
|
|
||||||
|
// 根据当前网络质量评分调整缓冲区大小
|
||||||
|
if (_currentNetworkQualityScore > 0.7) {
|
||||||
|
// 网络状况良好,使用较小缓冲区以降低延迟
|
||||||
|
_currentBufferSize = state.adaptiveBufferSizeMin;
|
||||||
|
} else if (_currentNetworkQualityScore > 0.4) {
|
||||||
|
// 网络状况一般,使用中等缓冲区以平衡延迟和稳定性
|
||||||
|
_currentBufferSize = (state.adaptiveBufferSizeMin + state.adaptiveBufferSizeMax) ~/ 2;
|
||||||
|
} else {
|
||||||
|
// 网络状况较差,使用较大缓冲区以减少卡顿
|
||||||
|
_currentBufferSize = state.adaptiveBufferSizeMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppLog.log('根据网络状况调整缓冲区大小: ${_currentBufferSize} (当前网络质量评分: ${_currentNetworkQualityScore.toStringAsFixed(2)})');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 评估当前网络质量
|
||||||
|
void _evaluateCurrentNetworkQuality() {
|
||||||
|
int currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
||||||
|
// 检查是否到了网络质量检查间隔
|
||||||
|
if (currentTime - _lastNetworkQualityCheckTime < state.networkQualityCheckIntervalMs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastNetworkQualityCheckTime = currentTime;
|
||||||
|
|
||||||
|
// 计算丢帧率
|
||||||
|
double dropRate = 0.0;
|
||||||
|
int totalProcessed = _framesProcessedSinceLastCheck + _framesDroppedSinceLastCheck;
|
||||||
|
if (totalProcessed > 0) {
|
||||||
|
dropRate = _framesDroppedSinceLastCheck / totalProcessed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新网络质量评分 (基于丢帧率,评分越高网络越好)
|
||||||
|
_currentNetworkQualityScore = 1.0 - dropRate;
|
||||||
|
if (_currentNetworkQualityScore < 0) {
|
||||||
|
_currentNetworkQualityScore = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppLog.log('网络质量评估: 丢帧率=${dropRate.toStringAsFixed(2)}, 网络质量评分=${_currentNetworkQualityScore.toStringAsFixed(2)}');
|
||||||
|
|
||||||
|
// 重置计数器
|
||||||
|
_framesProcessedSinceLastCheck = 0;
|
||||||
|
_framesDroppedSinceLastCheck = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 启动帧处理定时器
|
/// 启动帧处理定时器
|
||||||
@ -272,8 +570,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
// 取消已有定时器
|
// 取消已有定时器
|
||||||
state.frameProcessTimer?.cancel();
|
state.frameProcessTimer?.cancel();
|
||||||
|
|
||||||
// 计算定时器间隔,确保以目标帧率处理帧
|
// 使用配置的间隔时间
|
||||||
final int intervalMs = (1000 / state.targetFps).round();
|
final int intervalMs = state.frameProcessIntervalMs;
|
||||||
|
|
||||||
// 创建新定时器
|
// 创建新定时器
|
||||||
state.frameProcessTimer = Timer.periodic(Duration(milliseconds: intervalMs), (timer) {
|
state.frameProcessTimer = Timer.periodic(Duration(milliseconds: intervalMs), (timer) {
|
||||||
@ -984,4 +1282,215 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 记录帧接收
|
||||||
|
void recordFrameReceived(int frameSeq, TalkDataH264Frame_FrameTypeE frameType) {
|
||||||
|
_totalFramesReceivedCount++;
|
||||||
|
|
||||||
|
// 根据帧类型更新计数
|
||||||
|
if (frameType == TalkDataH264Frame_FrameTypeE.I) {
|
||||||
|
_iFramesReceived++;
|
||||||
|
} else if (frameType == TalkDataH264Frame_FrameTypeE.P) {
|
||||||
|
_pFramesReceived++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算丢包 - 修复:添加合理的帧序号差异限制,避免异常高的丢包计算
|
||||||
|
if (_lastFrameSeqNum != -1) {
|
||||||
|
if (frameSeq > _lastFrameSeqNum + 1) {
|
||||||
|
// 计算中间缺失的帧数,但限制最大丢失数量以防止异常值
|
||||||
|
int gap = frameSeq - _lastFrameSeqNum - 1;
|
||||||
|
// 设置最大允许的帧间隙,防止因为帧序列号回绕或异常导致的错误计算
|
||||||
|
int maxAllowedGap = 50; // 可根据实际情况调整
|
||||||
|
if (gap > maxAllowedGap) {
|
||||||
|
// 如果间隙过大,可能是帧序列号回绕或其他异常,不计入丢失帧
|
||||||
|
AppLog.log('检测到帧序列号异常跳跃: gap=$gap, 当前frameSeq=$frameSeq, 上一个frameSeq=$_lastFrameSeqNum');
|
||||||
|
} else {
|
||||||
|
_lostFrames += gap;
|
||||||
|
}
|
||||||
|
} else if (frameSeq <= _lastFrameSeqNum && frameSeq < 100 && _lastFrameSeqNum > 1000) {
|
||||||
|
// 检测到帧序列号回绕(从大数值回到小数值),重置参考值
|
||||||
|
// 这种情况通常发生在新的视频流开始或流重启
|
||||||
|
AppLog.log('检测到帧序列号回绕: 从 $_lastFrameSeqNum 回到 $frameSeq');
|
||||||
|
_lastFrameSeqNum = frameSeq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_lastFrameSeqNum = frameSeq;
|
||||||
|
|
||||||
|
// 记录接收时间
|
||||||
|
_frameReceiveTimes.add(DateTime.now().millisecondsSinceEpoch);
|
||||||
|
_frameSeqList.add(frameSeq);
|
||||||
|
|
||||||
|
// 只保留最近的100个帧记录
|
||||||
|
if (_frameReceiveTimes.length > 100) {
|
||||||
|
_frameReceiveTimes.removeAt(0);
|
||||||
|
_frameSeqList.removeAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 记录包接收
|
||||||
|
void recordPacketReceived() {
|
||||||
|
_totalPacketsReceived++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 记录已处理的帧
|
||||||
|
void recordProcessedFrame() {
|
||||||
|
_processedFrames++;
|
||||||
|
_framesProcessedSinceLastCheck++; // 用于网络质量评估
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 记录丢弃的帧
|
||||||
|
void recordDroppedFrame() {
|
||||||
|
_droppedFrames++;
|
||||||
|
_framesDroppedSinceLastCheck++; // 用于网络质量评估
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 记录进入缓冲区的帧
|
||||||
|
void recordFrameInBuffer() {
|
||||||
|
_framesInBuffer++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 开始网络质量评估
|
||||||
|
void startNetworkQualityAssessment() {
|
||||||
|
resetNetworkQualityAssessmentVariables();
|
||||||
|
_testStartTime = DateTime.now();
|
||||||
|
|
||||||
|
// 初始化统计计数器
|
||||||
|
_bufferSize = state.maxFrameBufferSize; // 记录缓冲区大小
|
||||||
|
|
||||||
|
// 启动定时器每秒评估一次网络质量
|
||||||
|
_networkQualityTestTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
|
_evaluateNetworkQuality();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 停止网络质量评估
|
||||||
|
void stopNetworkQualityAssessment() {
|
||||||
|
_networkQualityTestTimer?.cancel();
|
||||||
|
_networkQualityTestTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 重置网络质量评估变量
|
||||||
|
void resetNetworkQualityAssessmentVariables() {
|
||||||
|
_totalFramesReceived = 0;
|
||||||
|
_lostFrames = 0;
|
||||||
|
_lastFrameSeqNum = -1;
|
||||||
|
_frameReceiveTimes.clear();
|
||||||
|
_frameSeqList.clear();
|
||||||
|
_testStartTime = null;
|
||||||
|
|
||||||
|
// 重置统计计数器
|
||||||
|
_totalPacketsReceived = 0;
|
||||||
|
_totalFramesReceivedCount = 0;
|
||||||
|
_iFramesReceived = 0;
|
||||||
|
_pFramesReceived = 0;
|
||||||
|
_processedFrames = 0;
|
||||||
|
_droppedFrames = 0;
|
||||||
|
_framesInBuffer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 评估网络质量
|
||||||
|
void _evaluateNetworkQuality() {
|
||||||
|
if (_testStartTime == null) return;
|
||||||
|
|
||||||
|
final elapsed = DateTime.now().difference(_testStartTime!).inSeconds;
|
||||||
|
if (elapsed == 0) return;
|
||||||
|
|
||||||
|
// 计算丢包率 - 确保丢包率不会超过100%
|
||||||
|
double lossRate = 0.0;
|
||||||
|
if (_totalFramesReceivedCount > 0) {
|
||||||
|
lossRate = (_lostFrames / _totalFramesReceivedCount) * 100;
|
||||||
|
// 限制丢包率不超过100%
|
||||||
|
if (lossRate > 100.0) {
|
||||||
|
lossRate = 100.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 计算抖动 (基于相邻帧的时间间隔变化)
|
||||||
|
if (_frameReceiveTimes.length >= 2) {
|
||||||
|
List<int> intervals = [];
|
||||||
|
for (int i = 1; i < _frameReceiveTimes.length; i++) {
|
||||||
|
intervals.add(_frameReceiveTimes[i] - _frameReceiveTimes[i-1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据网络质量动态调整缓冲区大小
|
||||||
|
_adjustBufferSizeBasedOnNetworkQuality();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据丢包率、帧率和抖动确定网络质量等级
|
||||||
|
String _getNetworkQualityLevel(double lossRate, double frameRate, double jitter) {
|
||||||
|
if (lossRate < 1.0 && frameRate > 15.0 && jitter < 50.0) {
|
||||||
|
return "优秀";
|
||||||
|
} else if (lossRate < 3.0 && frameRate > 10.0 && jitter < 100.0) {
|
||||||
|
return "良好";
|
||||||
|
} else if (lossRate < 5.0 && frameRate > 5.0 && jitter < 200.0) {
|
||||||
|
return "一般";
|
||||||
|
} else {
|
||||||
|
return "较差";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 预测视频卡顿概率
|
||||||
|
String _predictStutterProbability(double lossRate, double frameRate, double jitter) {
|
||||||
|
if (lossRate < 2.0 && frameRate > 10.0 && jitter < 100.0) {
|
||||||
|
return "低风险 - 视频流畅";
|
||||||
|
} else if (lossRate <= 5.0 && frameRate >= 5.0 && jitter <= 200.0) {
|
||||||
|
return "中风险 - 可能轻微卡顿";
|
||||||
|
} else {
|
||||||
|
return "高风险 - 可能严重卡顿";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据网络质量动态调整缓冲区大小
|
||||||
|
void _adjustBufferSizeBasedOnNetworkQuality() {
|
||||||
|
if (_testStartTime == null) return;
|
||||||
|
|
||||||
|
final elapsed = DateTime.now().difference(_testStartTime!).inSeconds;
|
||||||
|
if (elapsed == 0) return;
|
||||||
|
|
||||||
|
// 计算丢包率
|
||||||
|
double lossRate = 0.0;
|
||||||
|
if (_totalFramesReceivedCount > 0) {
|
||||||
|
lossRate = (_lostFrames / _totalFramesReceivedCount) * 100;
|
||||||
|
if (lossRate > 100.0) lossRate = 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算平均接收帧率
|
||||||
|
final avgFrameRate = _totalFramesReceivedCount / elapsed;
|
||||||
|
|
||||||
|
// 计算抖动
|
||||||
|
double jitter = 0.0;
|
||||||
|
if (_frameReceiveTimes.length >= 2) {
|
||||||
|
List<int> intervals = [];
|
||||||
|
for (int i = 1; i < _frameReceiveTimes.length; i++) {
|
||||||
|
intervals.add(_frameReceiveTimes[i] - _frameReceiveTimes[i-1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intervals.length > 1) {
|
||||||
|
double mean = intervals.reduce((a, b) => a + b) / intervals.length;
|
||||||
|
double variance = 0.0;
|
||||||
|
for (int interval in intervals) {
|
||||||
|
variance += pow(interval - mean, 2).toDouble();
|
||||||
|
}
|
||||||
|
variance /= intervals.length;
|
||||||
|
jitter = sqrt(variance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据网络质量调整缓冲区大小
|
||||||
|
if (lossRate < 2.0 && avgFrameRate > 15.0 && jitter < 50.0) {
|
||||||
|
// 网络质量优秀 - 使用最小缓冲区以降低延迟
|
||||||
|
_currentBufferSize = state.adaptiveBufferSizeMin;
|
||||||
|
} else if (lossRate < 5.0 && avgFrameRate > 10.0 && jitter < 100.0) {
|
||||||
|
// 网络质量良好 - 使用中等偏小缓冲区
|
||||||
|
_currentBufferSize = (state.adaptiveBufferSizeMin + state.adaptiveBufferSizeMax) ~/ 3;
|
||||||
|
} else if (lossRate < 10.0 && avgFrameRate > 5.0 && jitter < 200.0) {
|
||||||
|
// 网络质量一般 - 使用中等偏大缓冲区
|
||||||
|
_currentBufferSize = ((state.adaptiveBufferSizeMin + state.adaptiveBufferSizeMax) ~/ 2 + state.adaptiveBufferSizeMax) ~/ 2;
|
||||||
|
} else {
|
||||||
|
// 网络质量差 - 使用最大缓冲区以减少卡顿
|
||||||
|
_currentBufferSize = state.adaptiveBufferSizeMax;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -109,8 +109,12 @@ class TalkViewNativeDecodeState {
|
|||||||
|
|
||||||
// H264帧缓冲区相关
|
// H264帧缓冲区相关
|
||||||
final List<Map<String, dynamic>> h264FrameBuffer = <Map<String, dynamic>>[]; // H264帧缓冲区,存储帧数据和类型
|
final List<Map<String, dynamic>> h264FrameBuffer = <Map<String, dynamic>>[]; // H264帧缓冲区,存储帧数据和类型
|
||||||
int maxFrameBufferSize = 25; // 最大缓冲区大小
|
int maxFrameBufferSize = 3; // 最大缓冲区大小,减小以降低延迟
|
||||||
final int targetFps = 25; // 目标解码帧率,只是为了快速填充native的缓冲区
|
final int targetFps = 25; // 目标解码帧率,只是为了快速填充native的缓冲区
|
||||||
|
final int adaptiveBufferSizeMin = 2; // 自适应缓冲区最小大小
|
||||||
|
final int adaptiveBufferSizeMax = 6; // 自适应缓冲区最大大小
|
||||||
|
final int networkQualityCheckIntervalMs = 2000; // 网络质量检查间隔(毫秒)
|
||||||
|
final int frameProcessIntervalMs = 10; // 帧处理间隔(毫秒),提高响应速度
|
||||||
Timer? frameProcessTimer; // 帧处理定时器
|
Timer? frameProcessTimer; // 帧处理定时器
|
||||||
bool isProcessingFrame = false; // 是否正在处理帧
|
bool isProcessingFrame = false; // 是否正在处理帧
|
||||||
int lastProcessedTimestamp = 0; // 上次处理帧的时间戳
|
int lastProcessedTimestamp = 0; // 上次处理帧的时间戳
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user