From 113a6a345ed5a15379e5575ca37f930c0f6c09a5 Mon Sep 17 00:00:00 2001 From: "sky.min" Date: Wed, 21 Jan 2026 08:56:46 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=86=E9=A2=91=E5=AF=B9=E8=AE=B2---?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BC=93=E5=86=B2=E5=8C=BA+=E8=A7=A3?= =?UTF-8?q?=E7=A0=81=E5=99=A8=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../native/talk_view_native_decode_logic.dart | 267 +++++++++++++++++- .../native/talk_view_native_decode_state.dart | 6 +- 2 files changed, 259 insertions(+), 14 deletions(-) diff --git a/lib/talk/starChart/views/native/talk_view_native_decode_logic.dart b/lib/talk/starChart/views/native/talk_view_native_decode_logic.dart index de9dcee0..120fe0a5 100644 --- a/lib/talk/starChart/views/native/talk_view_native_decode_logic.dart +++ b/lib/talk/starChart/views/native/talk_view_native_decode_logic.dart @@ -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 _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(); diff --git a/lib/talk/starChart/views/native/talk_view_native_decode_state.dart b/lib/talk/starChart/views/native/talk_view_native_decode_state.dart index 7fd70e97..bf16fc12 100644 --- a/lib/talk/starChart/views/native/talk_view_native_decode_state.dart +++ b/lib/talk/starChart/views/native/talk_view_native_decode_state.dart @@ -109,12 +109,12 @@ class TalkViewNativeDecodeState { // H264帧缓冲区相关 final List> h264FrameBuffer = >[]; // 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; // 上次处理帧的时间戳