视频对讲---优化缓冲区+解码器性能
This commit is contained in:
parent
c3750ff5a3
commit
113a6a345e
@ -40,9 +40,38 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
|
||||
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
|
||||
|
||||
int bufferSize = 25; // 初始化为默认大小
|
||||
int bufferSize = 40; // 初始化为更大缓冲区大小,从25增加到40
|
||||
|
||||
int audioBufferSize = 20; // 音频默认缓冲2帧
|
||||
int audioBufferSize = 30; // 音频默认缓冲更多帧,从20增加到30
|
||||
|
||||
// 网络质量评分,用于动态调整缓冲区大小
|
||||
double networkQualityScore = 1.0; // 初始网络质量评分为1.0(满分)
|
||||
|
||||
// 上次调整缓冲区的时间
|
||||
int _lastBufferSizeAdjustmentTime = 0;
|
||||
|
||||
// 帧率统计相关变量
|
||||
int _frameCount = 0;
|
||||
int _lastFpsCalculationTime = 0;
|
||||
Timer? _fpsUpdateTimer;
|
||||
|
||||
// 连续满载计数器
|
||||
int _consecutiveFullBufferCount = 0;
|
||||
static const int _maxConsecutiveFullBuffer = 5; // 最大连续满载次数阈值
|
||||
|
||||
// 解码性能监控
|
||||
int _lastSlowDecodeTime = 0;
|
||||
int _slowDecodeCount = 0;
|
||||
static const int _slowDecodeThreshold = 50; // 慢解码阈值(毫秒)
|
||||
static const int _slowDecodeAdjustmentThreshold = 3; // 慢解码次数阈值
|
||||
|
||||
// 记录首帧时间戳,用于计算首帧渲染延迟
|
||||
int? _firstFrameReceivedTime;
|
||||
int? _firstFrameRenderedTime;
|
||||
|
||||
// 防止缓冲区大小频繁调整的标志
|
||||
bool _isAdjustingBuffer = false;
|
||||
static const int _minAdjustmentInterval = 3000; // 最小调整间隔3秒
|
||||
|
||||
// 回绕阈值,动态调整,frameSeq较小时阈值也小
|
||||
int _getFrameSeqRolloverThreshold(int lastSeq) {
|
||||
@ -96,6 +125,9 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
Future<void> _initVideoDecoder() async {
|
||||
try {
|
||||
state.isLoading.value = true;
|
||||
// 记录首帧渲染开始时间
|
||||
_firstFrameReceivedTime = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
// 创建解码器配置
|
||||
final config = VideoDecoderConfig(
|
||||
width: StartChartManage().videoWidth,
|
||||
@ -110,14 +142,27 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
AppLog.log('视频解码器初始化成功:textureId=$textureId');
|
||||
VideoDecodePlugin.setOnFrameRenderedListener((textureId) {
|
||||
AppLog.log('已经开始渲染=======');
|
||||
// 记录首帧渲染完成时间
|
||||
_firstFrameRenderedTime = DateTime.now().millisecondsSinceEpoch;
|
||||
if (_firstFrameReceivedTime != null) {
|
||||
final renderTime = _firstFrameRenderedTime! - _firstFrameReceivedTime!;
|
||||
AppLog.log('首帧渲染耗时: ${renderTime}ms (${renderTime/1000.0}s)');
|
||||
}
|
||||
|
||||
// 只有真正渲染出首帧时才关闭loading
|
||||
Future.microtask(() => state.isLoading.value = false);
|
||||
|
||||
// 开始帧率统计
|
||||
_startFpsCalculation();
|
||||
});
|
||||
} else {
|
||||
AppLog.log('视频解码器初始化失败');
|
||||
}
|
||||
// 启动定时器发送帧数据
|
||||
_startFrameProcessTimer();
|
||||
|
||||
// 启动网络质量监测定时器
|
||||
_startNetworkQualityMonitor();
|
||||
} catch (e) {
|
||||
AppLog.log('初始化视频解码器错误: $e');
|
||||
// 如果初始化失败,延迟后重试
|
||||
@ -129,6 +174,96 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
}
|
||||
}
|
||||
|
||||
/// 开始帧率统计
|
||||
void _startFpsCalculation() {
|
||||
_fpsUpdateTimer?.cancel();
|
||||
_fpsUpdateTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
_updateFps();
|
||||
});
|
||||
}
|
||||
|
||||
/// 更新帧率
|
||||
void _updateFps() {
|
||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||
final timeDiff = currentTime - _lastFpsCalculationTime;
|
||||
|
||||
if (timeDiff > 0) {
|
||||
final fps = (_frameCount / (timeDiff / 1000)).toDouble();
|
||||
state.decoderFps.value = fps;
|
||||
state.totalFrames.value += _frameCount;
|
||||
_frameCount = 0;
|
||||
_lastFpsCalculationTime = currentTime;
|
||||
} else if (_lastFpsCalculationTime == 0) {
|
||||
// 初始化时间
|
||||
_lastFpsCalculationTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
/// 网络质量监测
|
||||
void _startNetworkQualityMonitor() {
|
||||
Timer.periodic(Duration(milliseconds: state.networkQualityCheckIntervalMs), (timer) {
|
||||
_calculateNetworkQuality();
|
||||
_adjustBufferSizeBasedOnNetworkQuality();
|
||||
});
|
||||
}
|
||||
|
||||
/// 计算网络质量评分
|
||||
void _calculateNetworkQuality() {
|
||||
// 基于丢包率、延迟等因素计算网络质量评分
|
||||
// 这里简化处理,实际应用中可以基于更多因素计算
|
||||
double lossRateImpact = 1.0 - state.packetLossRate.value; // 丢包率越低越好
|
||||
// 使用目标FPS作为基准,避免FPS为0时的影响
|
||||
double actualFps = state.decoderFps.value;
|
||||
if (actualFps <= 0) actualFps = 0.1; // 防止除零和负影响
|
||||
double fpsImpact = (actualFps / state.targetFps).clamp(0.0, 1.0); // 实际FPS与目标FPS的比例
|
||||
|
||||
// 综合评分,可根据实际情况调整权重
|
||||
networkQualityScore = (lossRateImpact * 0.6 + fpsImpact * 0.4).clamp(0.0, 1.0);
|
||||
|
||||
AppLog.log('网络质量评分: ${networkQualityScore.toStringAsFixed(2)}, 丢包率: ${state.packetLossRate.value.toStringAsFixed(2)}, FPS: ${state.decoderFps.value.toStringAsFixed(2)}');
|
||||
}
|
||||
|
||||
/// 根据网络质量动态调整缓冲区大小
|
||||
void _adjustBufferSizeBasedOnNetworkQuality() async {
|
||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
// 避免过于频繁的调整
|
||||
if (currentTime - _lastBufferSizeAdjustmentTime < _minAdjustmentInterval || _isAdjustingBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
_isAdjustingBuffer = true;
|
||||
|
||||
int newBufferSize;
|
||||
|
||||
if (networkQualityScore < 0.5) {
|
||||
// 网络质量差,增加缓冲区大小
|
||||
newBufferSize = state.adaptiveBufferSizeMax;
|
||||
} else if (networkQualityScore > 0.8) {
|
||||
// 网络质量好,减少缓冲区大小以降低延迟
|
||||
newBufferSize = state.adaptiveBufferSizeMin;
|
||||
} else {
|
||||
// 中等网络质量,使用中等缓冲区大小
|
||||
newBufferSize = ((state.adaptiveBufferSizeMin + state.adaptiveBufferSizeMax) / 2).round();
|
||||
}
|
||||
|
||||
// 应用新的缓冲区大小
|
||||
if (newBufferSize != state.maxFrameBufferSize) {
|
||||
// 限制缓冲区增长幅度,防止无限增长
|
||||
int maxAllowedIncrease = (state.maxFrameBufferSize * 0.5).round();
|
||||
if (newBufferSize > state.maxFrameBufferSize && (newBufferSize - state.maxFrameBufferSize) > maxAllowedIncrease) {
|
||||
newBufferSize = state.maxFrameBufferSize + maxAllowedIncrease;
|
||||
}
|
||||
|
||||
state.maxFrameBufferSize = newBufferSize;
|
||||
AppLog.log('缓冲区大小调整为: $newBufferSize (网络质量评分: ${networkQualityScore.toStringAsFixed(2)})');
|
||||
}
|
||||
|
||||
_lastBufferSizeAdjustmentTime = currentTime;
|
||||
await Future.delayed(const Duration(milliseconds: 100)); // 短暂延迟确保调整生效
|
||||
_isAdjustingBuffer = false;
|
||||
}
|
||||
|
||||
/// 初始化音频播放器
|
||||
void _initFlutterPcmSound() {
|
||||
const int sampleRate = 8000;
|
||||
@ -227,12 +362,24 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
'scpMessage': scpMessage,
|
||||
};
|
||||
|
||||
// 如果缓冲区超出最大大小,优先丢弃P/B帧
|
||||
// 检查缓冲区是否连续满载,如果是则触发紧急清理
|
||||
if (state.h264FrameBuffer.length >= state.maxFrameBufferSize) {
|
||||
_consecutiveFullBufferCount++;
|
||||
if (_consecutiveFullBufferCount >= _maxConsecutiveFullBuffer) {
|
||||
_handleConsecutiveFullBuffer();
|
||||
_consecutiveFullBufferCount = 0;
|
||||
}
|
||||
} else {
|
||||
_consecutiveFullBufferCount = 0;
|
||||
}
|
||||
|
||||
// 如果缓冲区超出最大大小,优先丢弃P帧
|
||||
while (state.h264FrameBuffer.length >= state.maxFrameBufferSize) {
|
||||
int pbIndex = state.h264FrameBuffer.indexWhere((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P);
|
||||
if (pbIndex != -1) {
|
||||
state.h264FrameBuffer.removeAt(pbIndex);
|
||||
int pIndex = state.h264FrameBuffer.indexWhere((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P);
|
||||
if (pIndex != -1) {
|
||||
state.h264FrameBuffer.removeAt(pIndex);
|
||||
} else {
|
||||
// 如果没有P帧,则移除队列头部的帧(最旧的帧)
|
||||
state.h264FrameBuffer.removeAt(0);
|
||||
}
|
||||
}
|
||||
@ -241,13 +388,48 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
state.h264FrameBuffer.add(frameMap);
|
||||
}
|
||||
|
||||
/// 处理连续缓冲区满载情况
|
||||
void _handleConsecutiveFullBuffer() {
|
||||
AppLog.log('缓冲区连续满载 $_consecutiveFullBufferCount 次,执行紧急清理');
|
||||
|
||||
// 优先保留I帧及关联的P帧,清理多余的P帧
|
||||
final iFrames = state.h264FrameBuffer.where((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.I).toList();
|
||||
|
||||
if (iFrames.length > 1) {
|
||||
// 如果有多个I帧,保留最新的,删除较旧的
|
||||
iFrames.sort((a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int));
|
||||
// 保留最新的I帧,删除其他的
|
||||
for (int i = 0; i < iFrames.length - 1; i++) {
|
||||
state.h264FrameBuffer.remove(iFrames[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// 保持缓冲区大小在合理范围内,保留一半的缓冲区内容
|
||||
int targetSize = state.maxFrameBufferSize ~/ 2;
|
||||
while (state.h264FrameBuffer.length > targetSize) {
|
||||
// 优先移除P帧,最后才是I帧(如果必须的话)
|
||||
int pIndex = state.h264FrameBuffer.indexWhere((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P);
|
||||
if (pIndex != -1) {
|
||||
state.h264FrameBuffer.removeAt(pIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果仍有需要清理的空间且没有P帧,移除最旧的帧
|
||||
if (state.h264FrameBuffer.length > targetSize) {
|
||||
state.h264FrameBuffer.removeAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
AppLog.log('紧急清理完成,当前缓冲区大小: ${state.h264FrameBuffer.length}');
|
||||
}
|
||||
|
||||
/// 启动帧处理定时器
|
||||
void _startFrameProcessTimer() {
|
||||
// 取消已有定时器
|
||||
state.frameProcessTimer?.cancel();
|
||||
|
||||
// 计算定时器间隔,确保以目标帧率处理帧
|
||||
final int intervalMs = (1000 / state.targetFps).round();
|
||||
final int intervalMs = state.frameProcessIntervalMs; // 使用可调整的帧处理间隔
|
||||
|
||||
// 创建新定时器
|
||||
state.frameProcessTimer = Timer.periodic(Duration(milliseconds: intervalMs), (timer) {
|
||||
@ -274,9 +456,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
try {
|
||||
// 优先查找I帧,按frameSeq最小的I帧消费
|
||||
final iFrames = state.h264FrameBuffer.where((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.I).toList();
|
||||
iFrames.sort((a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int));
|
||||
|
||||
if (iFrames.isNotEmpty) {
|
||||
iFrames.sort((a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int));
|
||||
final minIFrame = iFrames.first;
|
||||
final minIFrameSeq = minIFrame['frameSeq'];
|
||||
final targetIndex = state.h264FrameBuffer.indexWhere(
|
||||
@ -303,6 +484,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
}
|
||||
lastDecodedIFrameSeq = minIFrameSeq;
|
||||
|
||||
final decodeStartTime = DateTime.now().millisecondsSinceEpoch;
|
||||
await VideoDecodePlugin.sendFrame(
|
||||
frameData: frameData,
|
||||
frameType: 0,
|
||||
@ -311,7 +493,31 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
splitNalFromIFrame: true,
|
||||
refIFrameSeq: frameSeqI,
|
||||
);
|
||||
final decodeEndTime = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
// 监控解码性能
|
||||
final decodeDuration = decodeEndTime - decodeStartTime;
|
||||
if (decodeDuration > _slowDecodeThreshold) {
|
||||
_slowDecodeCount++;
|
||||
if (decodeEndTime - _lastSlowDecodeTime < 5000) { // 5秒内多次慢解码
|
||||
if (_slowDecodeCount >= _slowDecodeAdjustmentThreshold) {
|
||||
// 触发性能调整
|
||||
_handleSlowDecodePerformance();
|
||||
_slowDecodeCount = 0; // 重置计数
|
||||
}
|
||||
} else {
|
||||
_slowDecodeCount = 1; // 重置计数但保留记录
|
||||
}
|
||||
_lastSlowDecodeTime = decodeEndTime;
|
||||
} else {
|
||||
// 如果一段时间内没有慢解码,减少计数
|
||||
if (decodeEndTime - _lastSlowDecodeTime > 10000) { // 10秒后重置
|
||||
_slowDecodeCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
state.isProcessingFrame = false;
|
||||
_frameCount++; // 增加帧计数
|
||||
return;
|
||||
}
|
||||
|
||||
@ -346,6 +552,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
return;
|
||||
}
|
||||
|
||||
final decodeStartTime = DateTime.now().millisecondsSinceEpoch;
|
||||
await VideoDecodePlugin.sendFrame(
|
||||
frameData: frameData,
|
||||
frameType: 1,
|
||||
@ -354,7 +561,31 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
splitNalFromIFrame: true,
|
||||
refIFrameSeq: frameSeqI,
|
||||
);
|
||||
final decodeEndTime = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
// 监控解码性能
|
||||
final decodeDuration = decodeEndTime - decodeStartTime;
|
||||
if (decodeDuration > _slowDecodeThreshold) {
|
||||
_slowDecodeCount++;
|
||||
if (decodeEndTime - _lastSlowDecodeTime < 5000) { // 5秒内多次慢解码
|
||||
if (_slowDecodeCount >= _slowDecodeAdjustmentThreshold) {
|
||||
// 触发性能调整
|
||||
_handleSlowDecodePerformance();
|
||||
_slowDecodeCount = 0; // 重置计数
|
||||
}
|
||||
} else {
|
||||
_slowDecodeCount = 1; // 重置计数但保留记录
|
||||
}
|
||||
_lastSlowDecodeTime = decodeEndTime;
|
||||
} else {
|
||||
// 如果一段时间内没有慢解码,减少计数
|
||||
if (decodeEndTime - _lastSlowDecodeTime > 10000) { // 10秒后重置
|
||||
_slowDecodeCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
state.isProcessingFrame = false;
|
||||
_frameCount++; // 增加帧计数
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -362,8 +593,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
} finally {
|
||||
final endTime = DateTime.now().microsecondsSinceEpoch;
|
||||
final durationMs = (endTime - startTime) / 1000.0;
|
||||
// 可选:只在耗时较长时打印(例如 > 5ms)
|
||||
if (durationMs > 5) {
|
||||
// 可选:只在耗时较长时打印(例如 > 10ms)
|
||||
if (durationMs > 10) {
|
||||
debugPrint('[_processNextFrameFromBuffer] 耗时: ${durationMs.toStringAsFixed(2)} ms');
|
||||
// 或使用你的日志系统,如:
|
||||
// AppLog.log('Frame processing took ${durationMs.toStringAsFixed(2)} ms');
|
||||
@ -371,6 +602,17 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理慢解码性能问题
|
||||
void _handleSlowDecodePerformance() {
|
||||
AppLog.log('检测到连续慢解码,触发性能优化');
|
||||
|
||||
// 适当增加缓冲区大小以应对性能问题
|
||||
if (state.maxFrameBufferSize < state.adaptiveBufferSizeMax) {
|
||||
state.maxFrameBufferSize += 2; // 增加缓冲区
|
||||
AppLog.log('因解码性能问题,缓冲区大小调整为: ${state.maxFrameBufferSize}');
|
||||
}
|
||||
}
|
||||
|
||||
/// 停止帧处理定时器
|
||||
void _stopFrameProcessTimer() {
|
||||
state.frameProcessTimer?.cancel();
|
||||
@ -568,6 +810,9 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
// 取消FPS更新定时器
|
||||
_fpsUpdateTimer?.cancel();
|
||||
|
||||
// _closeH264File();
|
||||
// 停止帧处理定时器
|
||||
_stopFrameProcessTimer();
|
||||
|
||||
@ -109,12 +109,12 @@ class TalkViewNativeDecodeState {
|
||||
|
||||
// H264帧缓冲区相关
|
||||
final List<Map<String, dynamic>> h264FrameBuffer = <Map<String, dynamic>>[]; // H264帧缓冲区,存储帧数据和类型
|
||||
int maxFrameBufferSize = 2; // 最大缓冲区大小,减小以降低延迟
|
||||
int maxFrameBufferSize = 8; // 最大缓冲区大小,增加以改善卡顿,从2增加到8
|
||||
final int targetFps = 25; // 目标解码帧率,只是为了快速填充native的缓冲区
|
||||
final int adaptiveBufferSizeMin = 2; // 自适应缓冲区最小大小
|
||||
final int adaptiveBufferSizeMax = 6; // 自适应缓冲区最大大小
|
||||
final int adaptiveBufferSizeMax = 8; // 自适应缓冲区最大大小,从6增加到8
|
||||
final int networkQualityCheckIntervalMs = 2000; // 网络质量检查间隔(毫秒)
|
||||
int frameProcessIntervalMs = 10; // 帧处理间隔(毫秒),提高响应速度
|
||||
int frameProcessIntervalMs = 5; // 帧处理间隔(毫秒),提高响应速度,从10减到5
|
||||
Timer? frameProcessTimer; // 帧处理定时器
|
||||
bool isProcessingFrame = false; // 是否正在处理帧
|
||||
int lastProcessedTimestamp = 0; // 上次处理帧的时间戳
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user