diff --git a/lib/talk/starChart/views/imageTransmission/image_transmission_logic.dart b/lib/talk/starChart/views/imageTransmission/image_transmission_logic.dart index e0264db3..5e3d56cf 100644 --- a/lib/talk/starChart/views/imageTransmission/image_transmission_logic.dart +++ b/lib/talk/starChart/views/imageTransmission/image_transmission_logic.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:io'; import 'dart:ui' as ui; -import 'dart:math'; // Import the math package to use sqrt import 'dart:ui' show decodeImageFromList; import 'package:flutter/foundation.dart'; @@ -9,7 +8,6 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_pcm_sound/flutter_pcm_sound.dart'; import 'package:flutter_voice_processor/flutter_voice_processor.dart'; -import 'package:gallery_saver/gallery_saver.dart'; import 'package:get/get.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:path_provider/path_provider.dart'; @@ -18,8 +16,6 @@ import 'package:star_lock/app_settings/app_settings.dart'; import 'package:star_lock/login/login/entity/LoginEntity.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_logic.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_state.dart'; -import 'package:star_lock/main/lockDetail/lockDetail/lockNetToken_entity.dart'; -import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart'; import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart'; import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/talk/call/g711.dart'; @@ -29,13 +25,10 @@ import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart'; import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart'; import 'package:star_lock/talk/starChart/star_chart_manage.dart'; import 'package:star_lock/talk/starChart/views/imageTransmission/image_transmission_state.dart'; -import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart'; import 'package:star_lock/tools/G711Tool.dart'; -import 'package:star_lock/tools/bugly/bugly_tool.dart'; import 'package:star_lock/tools/commonDataManage.dart'; import 'package:star_lock/tools/storage.dart'; - -import '../../../../tools/baseGetXController.dart'; +import 'package:star_lock/tools/baseGetXController.dart'; class ImageTransmissionLogic extends BaseGetXController { ImageTransmissionState state = ImageTransmissionState(); @@ -125,7 +118,7 @@ class ImageTransmissionLogic extends BaseGetXController { _playAudioFrames(); break; case TalkData_ContentTypeE.Image: - // 固定长度缓冲区,最多保留bufferSize帧 + // 固定长度缓冲区,最多保留bufferSize帧 state.videoBuffer.add(talkData); if (state.videoBuffer.length > bufferSize) { state.videoBuffer.removeAt(0); // 移除最旧帧 @@ -181,19 +174,19 @@ class ImageTransmissionLogic extends BaseGetXController { state.oneMinuteTimeTimer?.cancel(); // 取消旧定时器 state.oneMinuteTimeTimer ??= Timer.periodic(const Duration(seconds: 1), (Timer t) { - if (state.listData.value.length > 0) { - state.oneMinuteTime.value++; - // if (state.oneMinuteTime.value >= 60) { - // t.cancel(); // 取消定时器 - // state.oneMinuteTime.value = 0; - // // 倒计时结束挂断 - // // udpHangUpAction(); - // } - } - }); + if (state.listData.value.length > 0) { + state.oneMinuteTime.value++; + // if (state.oneMinuteTime.value >= 60) { + // t.cancel(); // 取消定时器 + // state.oneMinuteTime.value = 0; + // // 倒计时结束挂断 + // // udpHangUpAction(); + // } + } + }); break; default: - // 其他状态的处理 + // 其他状态的处理 break; } }); @@ -204,7 +197,7 @@ class ImageTransmissionLogic extends BaseGetXController { if (state.isOpenVoice.value && state.isRecordingAudio.value == false) { final list = - G711().decodeAndDenoise(talkData.content, true, 8000, 300, 150); + G711().decodeAndDenoise(talkData.content, true, 8000, 300, 150); // // 将 PCM 数据转换为 PcmArrayInt16 final PcmArrayInt16 fromList = PcmArrayInt16.fromList(list); FlutterPcmSound.feed(fromList); @@ -481,7 +474,7 @@ class ImageTransmissionLogic extends BaseGetXController { .findRenderObject()! as RenderRepaintBoundary; final ui.Image image = await boundary.toImage(); final ByteData? byteData = - await image.toByteData(format: ui.ImageByteFormat.png); + await image.toByteData(format: ui.ImageByteFormat.png); if (byteData == null) { AppLog.log('截图失败: 图像数据为空'); @@ -517,7 +510,7 @@ class ImageTransmissionLogic extends BaseGetXController { final lockPeerId = StartChartManage().lockPeerId; final LockListInfoGroupEntity? lockListInfoGroupEntity = - await Storage.getLockMainListData(); + await Storage.getLockMainListData(); if (lockListInfoGroupEntity != null) { lockListInfoGroupEntity!.groupList?.forEach((element) { final lockList = element.lockList; 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 f98f3b53..330f037f 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 @@ -1,11 +1,8 @@ import 'dart:async'; import 'dart:io'; import 'dart:ui' as ui; -import 'dart:math'; // Import the math package to use sqrt -import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_pcm_sound/flutter_pcm_sound.dart'; @@ -29,139 +26,21 @@ import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart'; import 'package:star_lock/talk/starChart/star_chart_manage.dart'; import 'package:star_lock/talk/starChart/views/native/talk_view_native_decode_state.dart'; import 'package:star_lock/tools/G711Tool.dart'; +import 'package:star_lock/tools/baseGetXController.dart'; import 'package:star_lock/tools/callkit_handler.dart'; import 'package:star_lock/tools/commonDataManage.dart'; import 'package:star_lock/tools/storage.dart'; import 'package:video_decode_plugin/video_decode_plugin.dart'; -import '../../../../tools/baseGetXController.dart'; - class TalkViewNativeDecodeLogic extends BaseGetXController { final TalkViewNativeDecodeState state = TalkViewNativeDecodeState(); final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state; - // 添加成员变量缓存帧索引 - final Map>> _frameIndexCache = {}; - bool _frameIndexDirty = true; - - // 添加网络质量评估变量 - int _networkQualityScore = 5; // 1-5分,5为最佳 - int _frameDropCount = 0; - int _totalFrameCount = 0; - int bufferSize = 25; // 初始化为默认大小 int audioBufferSize = 20; // 音频默认缓冲2帧 - List? _cachedSps; - List? _cachedPps; - bool _waitingForCompleteIFrame = false; - - /// 状态变更防抖时间(毫秒) - static const int STATUS_DEBOUNCE_TIME = 500; - - /// 上次状态变更时间戳 - int _lastStatusChangeTime = 0; - - int _frameProcessCount = 0; - int _lastFrameProcessTime = 0; - double _actualFps = 0.0; - void _monitorFrameProcessingPerformance() { - _frameProcessCount++; - final now = DateTime.now().millisecondsSinceEpoch; - - if (now - _lastFrameProcessTime >= 1000) { // 每秒统计一次 - _actualFps = _frameProcessCount.toDouble(); - _frameProcessCount = 0; - _lastFrameProcessTime = now; - - // 平滑帧率调整,避免频繁大幅调整 - final targetFps = state.targetFps.toDouble(); - final actualRatio = _actualFps / targetFps; - - // 增加中间档位的调整,避免帧率波动过大 - if (actualRatio < 0.3) { - state.targetFps = (targetFps * 0.5).round().clamp(15, 60); - _startFrameProcessTimer(); - } else if (actualRatio < 0.5) { - state.targetFps = (targetFps * 0.7).round().clamp(15, 60); - _startFrameProcessTimer(); - } - - // 更加保守和稳定的调整策略 - // iOS平台使用更保守的调整策略 - if (Platform.isIOS) { - if (actualRatio < 0.25) { - // iOS处理能力严重不足,大幅降低目标帧率 - state.targetFps = (targetFps * 0.6).round().clamp(15, 60); - _startFrameProcessTimer(); - } else if (actualRatio < 0.45) { - // iOS处理能力不足,中等幅度降低帧率 - state.targetFps = (targetFps * 0.8).round().clamp(15, 60); - _startFrameProcessTimer(); - } else if (actualRatio > 2.5 && targetFps < 25) { - // iOS处理能力充足,可以提高帧率 - state.targetFps = (targetFps * 1.05).round().clamp(15, 30); - _startFrameProcessTimer(); - } - } else { - // Android平台 - if (actualRatio < 0.4) { - state.targetFps = (targetFps * 0.6).round().clamp(15, 60); - _startFrameProcessTimer(); - } else if (actualRatio < 0.6) { - state.targetFps = (targetFps * 0.8).round().clamp(15, 60); - _startFrameProcessTimer(); - } else if (actualRatio > 1.8 && targetFps < 25) { - state.targetFps = (targetFps * 1.15).round().clamp(15, 30); - _startFrameProcessTimer(); - } - } - }// 注意:避免过于积极地提高帧率,这可能导致卡顿 - } - - /// 根据网络动态调整缓冲区大小 - void _adjustBufferSizeDynamically() { - // 计算网络质量得分 - final double dropRate = _totalFrameCount > 0 - ? _frameDropCount / _totalFrameCount - : 0.0; - - // 根据丢包率评估网络质量 - if (dropRate > 0.25) { - _networkQualityScore = 1; // 很差 - } else if (dropRate > 0.15) { - _networkQualityScore = 2; // 较差 - } else if (dropRate > 0.08) { - _networkQualityScore = 3; // 一般 - } else if (dropRate > 0.03) { - _networkQualityScore = 4; // 良好 - } else { - _networkQualityScore = 5; // 优秀 - } - - // iOS平台使用更小的缓冲区以降低延迟 - if (Platform.isIOS) { - if (_networkQualityScore >= 4) { - state.maxFrameBufferSize = 8; // 网络好时最小缓冲区 - } else if (_networkQualityScore >= 3) { - state.maxFrameBufferSize = 12; // 网络一般时中等缓冲区 - } else { - state.maxFrameBufferSize = 15; // 网络差时稍大缓冲区 - } - } else { - // Android平台原有逻辑 - if (_networkQualityScore <= 2) { - state.maxFrameBufferSize = 25; - } else if (_networkQualityScore >= 4) { - state.maxFrameBufferSize = 10; - } else { - state.maxFrameBufferSize = 15; - } - } - } - // 回绕阈值,动态调整,frameSeq较小时阈值也小 int _getFrameSeqRolloverThreshold(int lastSeq) { if (lastSeq > 2000) { @@ -187,6 +66,10 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { final List> _preIFrameCache = []; bool _hasWrittenFirstIFrame = false; + // 新增:SPS/PPS状态追踪变量 + bool hasSps = false; + bool hasPps = false; + // 新增:SPS/PPS缓存 List? spsCache; List? ppsCache; @@ -217,47 +100,27 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { height: StartChartManage().videoHeight, codecType: 'h264', ); - - // 添加开始时间记录 - final startTime = DateTime.now().millisecondsSinceEpoch; - - // 设置超时时间为500ms,避免过长等待 - final timeoutMs = Platform.isIOS ? 300 : 500; - final timeoutFuture = Future.delayed(Duration(milliseconds: timeoutMs), () => null); - final decoderFuture = VideoDecodePlugin.initDecoder(config); - // 初始化解码器并获取textureId - final textureId = await Future.any([decoderFuture, timeoutFuture]); - - // 计算耗时 - final endTime = DateTime.now().millisecondsSinceEpoch; - final duration = endTime - startTime; - + final textureId = await VideoDecodePlugin.initDecoder(config); if (textureId != null) { Future.microtask(() => state.textureId.value = textureId); - AppLog.log('视频解码器初始化成功:textureId=$textureId, 耗时: ${duration}ms'); - // 异步设置帧渲染监听器 + AppLog.log('视频解码器初始化成功:textureId=$textureId'); VideoDecodePlugin.setOnFrameRenderedListener((textureId) { AppLog.log('已经开始渲染======='); + // 只有真正渲染出首帧时才关闭loading Future.microtask(() => state.isLoading.value = false); }); } else { - AppLog.log('视频解码器初始化失败或超时, 耗时: ${duration}ms'); - state.isLoading.value = false; + AppLog.log('视频解码器初始化失败'); } // 启动定时器发送帧数据 - // iOS平台使用更短的延迟启动 - final delayMs = Platform.isIOS ? 30 : 50; - Future.delayed(Duration(milliseconds: delayMs), () { - _startFrameProcessTimer(); - }); + _startFrameProcessTimer(); } catch (e) { AppLog.log('初始化视频解码器错误: $e'); - state.isLoading.value = false; - // 如果初始化失败,延迟后重试 --缩短重试延迟 - await Future.delayed(Duration(milliseconds: 300)); + // 如果初始化失败,延迟后重试 + await Future.delayed(const Duration(seconds: 2)); if (!Get.isRegistered()) { - return; + return; // 如果控制器已经被销毁,不再重试 } _initVideoDecoder(); // 重试初始化 } @@ -346,11 +209,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { } else { // 正常流程 if (_lastFrameSeq != null && frameSeq <= _lastFrameSeq!) { - // 允许小范围乱序,提高容错性 - if ((_lastFrameSeq! - frameSeq).abs() > 50) { // 缩小容错范围 - AppLog.log('丢弃乱序或重复帧: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq'); - return; - } + AppLog.log('丢弃乱序或重复帧: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq'); + return; } _lastFrameSeq = frameSeq; } @@ -364,232 +224,40 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { 'scpMessage': scpMessage, }; - // 更智能的缓冲区管理 - if (state.h264FrameBuffer.length >= state.maxFrameBufferSize) { - _manageBufferOverflow(frameType); - // 添加智能丢帧策略 - _implementSmartFrameDropping(); - } - - // 如果缓冲区仍然超出最大大小,移除最旧的帧 + // 如果缓冲区超出最大大小,优先丢弃P/B帧 while (state.h264FrameBuffer.length >= state.maxFrameBufferSize) { - state.h264FrameBuffer.removeAt(0); + 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); - _invalidateFrameIndex(); } - /// 智能丢帧策略以保持流畅性 - void _implementSmartFrameDropping() { - final bufferLength = state.h264FrameBuffer.length; - - // iOS平台更积极的丢帧策略 - if (Platform.isIOS && bufferLength > 30) { - _dropOldFramesAggressively(); - } else if (bufferLength > 40) { - // 其他平台原有逻辑 - _dropOldPFrame(); - } - } - void _dropOldFramesAggressively() { - // 查找并移除较旧的帧以降低延迟 - final oldFrameIndex = state.h264FrameBuffer.indexWhere((frame) => - frame['frameSeq'] < (_lastFrameSeq ?? 0) - 20); - - if (oldFrameIndex != -1 && oldFrameIndex < state.h264FrameBuffer.length) { - state.h264FrameBuffer.removeAt(oldFrameIndex); - _invalidateFrameIndex(); - } - } - void _dropOldPFrame() { - // 查找并移除较旧的P帧 - final pFrameIndex = state.h264FrameBuffer.indexWhere((frame) => - frame['frameType'] == TalkDataH264Frame_FrameTypeE.P && - frame['frameSeq'] < (_lastFrameSeq ?? 0) - 50); - - if (pFrameIndex != -1) { - state.h264FrameBuffer.removeAt(pFrameIndex); - _invalidateFrameIndex(); - } - } - - /// 仅保留关键帧(I帧和最近的P帧)以减少处理负载 - void _keepOnlyKeyFrames() { - final List> keyFrames = []; - int lastPFrameIndex = -1; - - // 从后向前遍历,优先保留最新的帧 - for (int i = state.h264FrameBuffer.length - 1; i >= 0; i--) { - final frame = state.h264FrameBuffer[i]; - final frameType = frame['frameType']; - - if (frameType == TalkDataH264Frame_FrameTypeE.I) { - keyFrames.add(frame); - break; // 找到最新的I帧后停止 - } else if (frameType == TalkDataH264Frame_FrameTypeE.P && lastPFrameIndex == -1) { - keyFrames.add(frame); - lastPFrameIndex = i; - } - } - - if (keyFrames.isNotEmpty) { - // 只保留关键帧,反转顺序使其按时间顺序排列 - state.h264FrameBuffer.clear(); - state.h264FrameBuffer.addAll(keyFrames.reversed); - _invalidateFrameIndex(); - } - } - - /// 启动帧处理定时器 - 优化版:动态帧率调整 + /// 启动帧处理定时器 void _startFrameProcessTimer() { // 取消已有定时器 - _stopFrameProcessTimer(); + state.frameProcessTimer?.cancel(); - // 初始间隔设置 - int baseIntervalMs = Platform.isIOS - ? max(16, min(33, (1000 / state.targetFps).round())) // iOS使用更严格的范围 - : max(16, min(40, (1000 / state.targetFps).round())); + // 计算定时器间隔,确保以目标帧率处理帧 + final int intervalMs = (1000 / state.targetFps).round(); // 创建新定时器 - state.frameProcessTimer = Timer.periodic(Duration(milliseconds: baseIntervalMs), (timer) { + state.frameProcessTimer = Timer.periodic(Duration(milliseconds: intervalMs), (timer) { _processNextFrameFromBuffer(); }); - AppLog.log('启动帧处理定时器,目标帧率: ${state.targetFps}fps,初始间隔: ${baseIntervalMs}ms'); + AppLog.log('启动帧处理定时器,目标帧率: ${state.targetFps}fps,间隔: ${intervalMs}ms'); } - /// 添加:快速清理缓冲区方法 - void _clearFrameBufferQuickly() { - // 完全清空帧缓冲区,避免旧帧影响新流 - state.h264FrameBuffer.clear(); - - // 重置帧序列状态 - _lastFrameSeq = null; - lastDecodedIFrameSeq = null; - _decodedIFrames.clear(); - state.isProcessingFrame = false; - _frameIndexDirty = true; - _frameIndexCache.clear(); - } - - /// 停止帧处理定时器 - void _stopFrameProcessTimer() { - state.frameProcessTimer?.cancel(); - state.frameProcessTimer = null; - // 注意:不清空缓冲区,避免数据丢失 - // state.h264FrameBuffer.clear(); - state.isProcessingFrame = false; - } - - /// 管理缓冲区溢出 - void _manageBufferOverflow(TalkDataH264Frame_FrameTypeE newFrameType) { - // 如果新帧是I帧,优先保留 - if (newFrameType == TalkDataH264Frame_FrameTypeE.I) { - // 查找最旧的P/B帧进行移除 - int pbIndex = state.h264FrameBuffer.indexWhere((f) => - f['frameType'] != TalkDataH264Frame_FrameTypeE.I && - f['frameSeq'] < (_lastFrameSeq ?? 0) - 100); // 只移除较旧的帧 - - if (pbIndex != -1 && pbIndex < state.h264FrameBuffer.length) { - state.h264FrameBuffer.removeAt(pbIndex); - } else if (state.h264FrameBuffer.isNotEmpty) { - // 如果没有找到合适的旧帧,移除最旧的帧 - state.h264FrameBuffer.removeAt(0); - } - } else { - // 新帧是P帧或B帧,移除最旧的帧 - if (state.h264FrameBuffer.isNotEmpty) { - state.h264FrameBuffer.removeAt(0); - _invalidateFrameIndex(); - } - } - } - - // 更新帧缓冲区时标记索引为脏数据 - void _invalidateFrameIndex() { - _frameIndexDirty = true; - } - // 构建帧索引 - List> _buildFrameIndex(TalkDataH264Frame_FrameTypeE frameType) { - if (!_frameIndexDirty && _frameIndexCache.containsKey(frameType)) { - return _frameIndexCache[frameType]!; - } - - final List> index = []; - for (int i = 0; i < state.h264FrameBuffer.length; i++) { - final frame = state.h264FrameBuffer[i]; - if (frame['frameType'] == frameType) { - index.add(MapEntry(frame['frameSeq'] as int, i)); - } - } - index.sort((a, b) => a.key.compareTo(b.key)); - - _frameIndexCache[frameType] = index; - _frameIndexDirty = false; - return index; - } - - int _findEarliestIFrame() { - final iFrameIndexes = _buildFrameIndex(TalkDataH264Frame_FrameTypeE.I); - return iFrameIndexes.isNotEmpty ? iFrameIndexes.first.value : -1; - } - - int _findRelatedPFrame(int refIFrameSeq) { - final pFrameIndexes = _buildFrameIndex(TalkDataH264Frame_FrameTypeE.P); - for (final entry in pFrameIndexes) { - final frame = state.h264FrameBuffer[entry.value]; - if (frame['frameSeqI'] == refIFrameSeq) { - return entry.value; - } - } - return -1; - } - - int _findBestFrameIndex() { - // 检查缓冲区是否为空 - if (state.h264FrameBuffer.isEmpty) { - return -1; - } - // iOS平台优先处理最新的I帧以降低延迟 - if (Platform.isIOS) { - final iFrameIndexes = _buildFrameIndex(TalkDataH264Frame_FrameTypeE.I); - if (iFrameIndexes.isNotEmpty) { - // 返回最新的I帧索引 - return iFrameIndexes.last.value; - } - } - // 优先处理与最近解码的I帧相关的P帧 - if (lastDecodedIFrameSeq != null) { - final pFrameIndex = _findRelatedPFrame(lastDecodedIFrameSeq!); - // 添加边界检查 - if (pFrameIndex >= 0 && pFrameIndex < state.h264FrameBuffer.length) { - return pFrameIndex; - } - } - - // 查找最早的I帧 - final iFrameIndex = _findEarliestIFrame(); - // 添加边界检查 - if (iFrameIndex >= 0 && iFrameIndex < state.h264FrameBuffer.length) { - return iFrameIndex; - } - - // 如果没有I帧,处理最早的帧 - return state.h264FrameBuffer.isNotEmpty ? 0 : -1; - } - - /// 从缓冲区处理下一帧 - 优化版:简化逻辑和动态调整 - Future _processNextFrameFromBuffer() async { - _monitorFrameProcessingPerformance(); + /// 从缓冲区处理下一帧 + /// 从缓冲区处理下一帧 + void _processNextFrameFromBuffer() async { final startTime = DateTime.now().microsecondsSinceEpoch; - // 定期动态调整缓冲区大小 - _totalFrameCount++; - if (_totalFrameCount % 30 == 0) { // 每处理30帧检查一次 - _adjustBufferSizeDynamically(); - } - // 避免重复处理 if (state.isProcessingFrame) { return; @@ -601,197 +269,113 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { } try { - state.isProcessingFrame = true; + // 优先查找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)); - // 智能丢帧策略:根据缓冲区大小动态调整 - final bufferLength = state.h264FrameBuffer.length; - if (bufferLength > 50) { - // 缓冲区溢出,保留最新20帧 - final newBuffer = state.h264FrameBuffer.sublist(max(0, bufferLength - 20)); - state.h264FrameBuffer.clear(); - state.h264FrameBuffer.addAll(newBuffer); - _invalidateFrameIndex(); - } else if (bufferLength > 30 && Platform.isAndroid) { - // Android平台缓冲区较大时,只处理I帧和关键P帧 - _keepOnlyKeyFrames(); - } else { - // 正常智能丢帧 - _implementSmartFrameDropping(); - } - - // 动态调整处理策略基于缓冲区长度 - // 更智能的帧率调整策略 - // iOS平台优化:更积极的缓冲区管理 - if (Platform.isIOS) { - if (bufferLength > 40) { - // iOS缓冲区过长,临时提高处理频率 - _temporarilyIncreaseProcessFrequency(); - } else if (bufferLength < 3) { - // iOS缓冲区过短,降低处理频率节省资源 - _adjustFrameProcessFrequency((state.targetFps * 0.95).toDouble()); - } - } else { - // Android平台原有逻辑 - if (bufferLength > 50) { - _temporarilyIncreaseProcessFrequency(); - } else if (bufferLength < 5) { - _adjustFrameProcessFrequency((state.targetFps * 0.9).toDouble()); - } - } - - //减少重复计算和排序操作 - final frameIndex = _findBestFrameIndex(); - - // 处理选中的帧 - if (frameIndex >= 0 && frameIndex < state.h264FrameBuffer.length) { - // 再次检查边界以确保安全 - if (frameIndex >= state.h264FrameBuffer.length) { + if (iFrames.isNotEmpty) { + final minIFrame = iFrames.first; + final minIFrameSeq = minIFrame['frameSeq']; + final targetIndex = state.h264FrameBuffer.indexWhere( + (f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.I && f['frameSeq'] == minIFrameSeq, + ); + state.isProcessingFrame = true; + final Map? frameMap = state.h264FrameBuffer.removeAt(targetIndex); + if (frameMap == null) { + state.isProcessingFrame = false; return; } - final Map frameMap = state.h264FrameBuffer.removeAt(frameIndex); - final List? frameData = frameMap['frameData']; final TalkDataH264Frame_FrameTypeE? frameType = frameMap['frameType']; final int? frameSeq = frameMap['frameSeq']; final int? frameSeqI = frameMap['frameSeqI']; final int? pts = frameMap['pts']; - - // 如果在等待完整I帧状态,检查是否是带有SPS/PPS的I帧 - if (_waitingForCompleteIFrame && frameType == TalkDataH264Frame_FrameTypeE.I) { - // 构造包含SPS/PPS的完整帧数据 - List completeFrameData = []; - - // 添加SPS(如果存在) - if (_cachedSps != null && _cachedSps!.isNotEmpty) { - completeFrameData.addAll(_cachedSps!); - } - - // 添加PPS(如果存在) - if (_cachedPps != null && _cachedPps!.isNotEmpty) { - completeFrameData.addAll(_cachedPps!); - } - - // 添加原始帧数据 - if (frameData != null) { - completeFrameData.addAll(frameData); - } - - // 发送完整帧 - if (completeFrameData.isNotEmpty && state.textureId.value != null) { - final int pluginFrameType = 0; // I帧 - - try { - await VideoDecodePlugin.sendFrame( - frameData: completeFrameData, - frameType: pluginFrameType, - frameSeq: frameSeq!, - timestamp: pts!, - splitNalFromIFrame: true, - refIFrameSeq: frameSeqI!, - ).timeout(Duration(milliseconds: 25)); - - // 成功发送后,取消等待状态 - _waitingForCompleteIFrame = false; - lastDecodedIFrameSeq = frameSeq; - } catch (e) { - AppLog.log('发送完整I帧失败: $e'); - if (e is TimeoutException) { - _frameDropCount++; - } - } - } + final ScpMessage? scpMessage = frameMap['scpMessage']; + if (frameData == null || frameType == null || frameSeq == null || frameSeqI == null || pts == null) { + state.isProcessingFrame = false; + return; } - // 正常处理其他帧 - else if (frameData != null && - frameType != null && - frameSeq != null && - frameSeqI != null && - pts != null && - state.textureId.value != null) { + if (state.textureId.value == null) { + state.isProcessingFrame = false; + return; + } + lastDecodedIFrameSeq = minIFrameSeq; - final int pluginFrameType = frameType == TalkDataH264Frame_FrameTypeE.I ? 0 : 1; + await VideoDecodePlugin.sendFrame( + frameData: frameData, + frameType: 0, + frameSeq: frameSeq, + timestamp: pts, + splitNalFromIFrame: true, + refIFrameSeq: frameSeqI, + ); + state.isProcessingFrame = false; + return; + } - // 异步发送帧,添加超时处理 - try { - // 根据网络质量动态调整超时时间 - int timeoutMs; - switch (_networkQualityScore) { - case 1: // 网络很差 - timeoutMs = 50; - break; - case 2: // 网络较差 - timeoutMs = 40; - break; - case 3: // 网络一般 - timeoutMs = 30; - break; - default: - timeoutMs = Platform.isIOS ? 25 : 30; - } - await VideoDecodePlugin.sendFrame( - frameData: frameData, - frameType: pluginFrameType, - frameSeq: frameSeq, - timestamp: pts, - splitNalFromIFrame: true, - refIFrameSeq: frameSeqI, - ).timeout(Duration(milliseconds: timeoutMs)); // 进一步缩短超时时间 - - // 更新最后解码的I帧序号 - if (frameType == TalkDataH264Frame_FrameTypeE.I) { - lastDecodedIFrameSeq = frameSeq; - _waitingForCompleteIFrame = false; // 接收到任何I帧都取消等待 - } - } catch (e) { - AppLog.log('发送帧数据超时或失败: $e'); - // 根据错误类型决定是否增加帧丢弃计数 - if (e is TimeoutException) { - _frameDropCount++; - } + // 没有I帧时,只消费refIFrameSeq等于lastDecodedIFrameSeq的P帧 + if (lastDecodedIFrameSeq != null) { + final validPFrames = + state.h264FrameBuffer.where((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P && f['frameSeqI'] == lastDecodedIFrameSeq).toList(); + if (validPFrames.isNotEmpty) { + validPFrames.sort((a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int)); + final minPFrame = validPFrames.first; + final targetIndex = state.h264FrameBuffer.indexWhere( + (f) => + f['frameType'] == TalkDataH264Frame_FrameTypeE.P && f['frameSeq'] == minPFrame['frameSeq'] && f['frameSeqI'] == lastDecodedIFrameSeq, + ); + state.isProcessingFrame = true; + final Map? frameMap = state.h264FrameBuffer.removeAt(targetIndex); + if (frameMap == null) { + state.isProcessingFrame = false; + return; } + final List? frameData = frameMap['frameData']; + final TalkDataH264Frame_FrameTypeE? frameType = frameMap['frameType']; + final int? frameSeq = frameMap['frameSeq']; + final int? frameSeqI = frameMap['frameSeqI']; + final int? pts = frameMap['pts']; + final ScpMessage? scpMessage = frameMap['scpMessage']; + if (frameData == null || frameType == null || frameSeq == null || frameSeqI == null || pts == null) { + state.isProcessingFrame = false; + return; + } + if (state.textureId.value == null) { + state.isProcessingFrame = false; + return; + } + + await VideoDecodePlugin.sendFrame( + frameData: frameData, + frameType: 1, + frameSeq: frameSeq, + timestamp: pts, + splitNalFromIFrame: true, + refIFrameSeq: frameSeqI, + ); + state.isProcessingFrame = false; + return; } } - } catch (e) { - _frameDropCount++; - AppLog.log('帧处理失败: $e'); + // 其他情况不消费,等待I帧到来 } finally { - state.isProcessingFrame = false; final endTime = DateTime.now().microsecondsSinceEpoch; final durationMs = (endTime - startTime) / 1000.0; - - // 性能监控 - 更宽松的阈值 - // iOS平台使用更严格的性能监控 - final thresholdMs = Platform.isIOS ? 25 : 30; - if (durationMs > thresholdMs) { - AppLog.log('帧处理耗时过长: ${durationMs.toStringAsFixed(2)} ms, 缓冲区长度: ${state.h264FrameBuffer.length}'); + // 可选:只在耗时较长时打印(例如 > 5ms) + if (durationMs > 5) { + debugPrint('[_processNextFrameFromBuffer] 耗时: ${durationMs.toStringAsFixed(2)} ms'); + // 或使用你的日志系统,如: + // AppLog.log('Frame processing took ${durationMs.toStringAsFixed(2)} ms'); } } } - - - Timer? _tempAdjustTimer; - - /// 临时增加处理频率 - void _temporarilyIncreaseProcessFrequency() { - // 如果已经有临时调整在运行,则跳过 - if (_tempAdjustTimer != null) return; - - final originalFps = state.targetFps; - final tempFps = (min(originalFps * 1.5, 60.0)).toInt(); - - _adjustFrameProcessFrequency(tempFps.toDouble()); - - // 2秒后恢复原频率 - _tempAdjustTimer = Timer(const Duration(seconds: 2), () { - _adjustFrameProcessFrequency(originalFps.toDouble()); - _tempAdjustTimer = null; - }); - } - - void _adjustFrameProcessFrequency(double newFps) { - state.targetFps = newFps.clamp(20.0, 60.0) as int; - _startFrameProcessTimer(); // 重新启动定时器 + /// 停止帧处理定时器 + void _stopFrameProcessTimer() { + state.frameProcessTimer?.cancel(); + state.frameProcessTimer = null; + state.h264FrameBuffer.clear(); + state.isProcessingFrame = false; } // 发起接听命令 @@ -812,7 +396,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { return; } - AppLog.log("==== 启动新的数据流监听1 ===="); + AppLog.log("==== 启动新的数据流监听 ===="); _isListening = true; _streamSubscription = state.talkDataRepository.talkDataStream.listen((TalkDataModel talkDataModel) async { @@ -851,14 +435,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { /// 监听对讲状态 void _startListenTalkStatus() { state.startChartTalkStatus.statusStream.listen((talkStatus) { - // 防抖处理:检查距离上次状态变更的时间间隔 - final int currentTime = DateTime.now().millisecondsSinceEpoch; - if (currentTime - _lastStatusChangeTime < STATUS_DEBOUNCE_TIME) { - AppLog.log('状态变更过于频繁,忽略此次变更: $talkStatus'); - return; - } - // 更新上次状态变更时间 - _lastStatusChangeTime = currentTime; state.talkStatus.value = talkStatus; switch (talkStatus) { case TalkStatus.rejected: @@ -877,7 +453,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { }); break; default: - // 其他状态的处理 + // 其他状态的处理 break; } }); @@ -891,9 +467,13 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { final PcmArrayInt16 fromList = PcmArrayInt16.fromList(encodedData); FlutterPcmSound.feed(fromList); if (!state.isPlaying.value) { + AppLog.log('play'); FlutterPcmSound.play(); state.isPlaying.value = true; } + } else if (state.isOpenVoice.isFalse) { + FlutterPcmSound.pause(); + state.isPlaying.value = false; } } @@ -950,19 +530,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { Future stopRecording() async {} - Future _checkRequiredPermissions() async { - // 检查是否已获得必要权限 - var storageStatus = await Permission.storage.status; - var microphoneStatus = await Permission.microphone.status; - - if (!storageStatus.isGranted || !microphoneStatus.isGranted) { - // 延迟请求权限,让用户理解为什么需要这些权限 - Future.delayed(Duration(seconds: 1), () { - requestPermissions(); - }); - } - } - @override void onReady() { super.onReady(); @@ -977,6 +544,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 在没有监听成功之前赋值一遍状态 // *** 由于页面会在状态变化之后才会初始化,导致识别不到最新的状态,在这里手动赋值 *** state.talkStatus.value = state.startChartTalkStatus.status; + AppLog.log("初始化对讲状态111111:${state.startChartTalkStatus.status}"); + AppLog.log("初始化对讲状态222222:${state.talkStatus.value}"); // 初始化音频播放器 _initFlutterPcmSound(); @@ -1000,117 +569,50 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { @override void onClose() { - try { - // 先停止所有处理流程 - _stopAllProcessing(); - - // 释放视频相关资源 - _releaseVideoResources(); - - // 释放音频相关资源 - _releaseAudioResources(); - - // 释放网络和状态相关资源 - _releaseNetworkAndStateResources(); - - // 清理定时器和订阅 - _cancelAllTimersAndSubscriptions(); - - } catch (e, stackTrace) { - AppLog.log('资源释放过程中出现错误: $e\n$stackTrace'); - } finally { - super.onClose(); - } - } - - /// 停止所有处理流程 - void _stopAllProcessing() { + // _closeH264File(); // 停止帧处理定时器 _stopFrameProcessTimer(); - // 停止音频处理 - stopProcessingAudio(); + _stopPlayG711Data(); // 停止播放音频 + + state.audioBuffer.clear(); // 清空音频缓冲区 + + state.oneMinuteTimeTimer?.cancel(); + state.oneMinuteTimeTimer = null; // 停止播放音频 - _stopPlayG711Data(); - } + stopProcessingAudio(); - /// 释放视频相关资源 - void _releaseVideoResources() { - try { - // 异步释放视频解码器资源 - _releaseVideoDecoderAsync(); - } catch (e) { - AppLog.log('释放视频解码器资源失败: $e'); - } - } + state.oneMinuteTimeTimer?.cancel(); // 取消旧定时器 + state.oneMinuteTimeTimer = null; // 取消旧定时器 + state.oneMinuteTime.value = 0; - /// 释放音频相关资源 - void _releaseAudioResources() { - try { - // 停止播放音频 - _stopPlayG711Data(); - - // 清空音频缓冲区 - state.audioBuffer.clear(); - - // 清空音频帧缓冲 - _bufferedAudioFrames.clear(); - } catch (e) { - AppLog.log('释放音频资源失败: $e'); - } - } - - /// 释放网络和状态相关资源 - void _releaseNetworkAndStateResources() { - try { - // 重置期望数据 - StartChartManage().reSetDefaultTalkExpect(); - StartChartManage().stopTalkExpectMessageTimer(); - - // 清空已解码I帧集合 - _decodedIFrames.clear(); - - // 重置状态 - state.oneMinuteTimeTimer?.cancel(); - state.oneMinuteTimeTimer = null; - state.oneMinuteTime.value = 0; - } catch (e) { - AppLog.log('释放网络和状态资源失败: $e'); - } - } - - /// 取消所有定时器和订阅 - void _cancelAllTimersAndSubscriptions() { - try { - // 取消数据流监听 - _streamSubscription?.cancel(); - _isListening = false; - - // 取消批处理定时器 - _batchProcessTimer?.cancel(); - _batchProcessTimer = null; - - // 取消音频处理定时器 - _startProcessingAudioTimer?.cancel(); - _startProcessingAudioTimer = null; - } catch (e) { - AppLog.log('取消定时器和订阅失败: $e'); - } - } - /// 异步释放视频解码器 - Future _releaseVideoDecoderAsync() async { - try { - if (state.textureId.value != null) { - // 添加超时处理 - await VideoDecodePlugin.releaseDecoder().timeout(const Duration(milliseconds: 500)); - Future.microtask(() => state.textureId.value = null); - } - } catch (e) { - AppLog.log('异步释放视频解码器失败: $e'); - // 即使失败也清除状态 + // 释放视频解码器资源 + if (state.textureId.value != null) { + VideoDecodePlugin.releaseDecoder(); Future.microtask(() => state.textureId.value = null); } + + // 取消数据流监听 + _streamSubscription?.cancel(); + _streamSubscription = null; + _isListening = false; + + // 重置期望数据 + StartChartManage().reSetDefaultTalkExpect(); + StartChartManage().stopTalkExpectMessageTimer(); + VideoDecodePlugin.releaseDecoder(); + + // 取消批处理定时器 + _batchProcessTimer?.cancel(); + _batchProcessTimer = null; + + // 清空已解码I帧集合 + _decodedIFrames.clear(); + _startProcessingAudioTimer?.cancel(); + _startProcessingAudioTimer = null; + _bufferedAudioFrames.clear(); + super.onClose(); } /// 处理无效通话状态 @@ -1318,7 +820,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { AppLog.log(error.message!); } - // 切换清晰度的方法,优化切换速度 + // 切换清晰度的方法,后续补充具体实现 void onQualityChanged(String quality) async { state.currentQuality.value = quality; TalkExpectReq talkExpectReq = StartChartManage().getDefaultTalkExpect(); @@ -1347,31 +849,11 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { /// 修改发送预期数据 StartChartManage().changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(talkExpect: talkExpectReq); - // 立即切换清晰度:清理旧帧缓冲区并重新初始化解码器 - // 1. 停止当前帧处理 - _stopFrameProcessTimer(); - - // 2. 立即清理旧的帧缓冲区 - _clearFrameBufferQuickly(); - - // 3. 更新视频分辨率设置 - StartChartManage().videoWidth = width; - StartChartManage().videoHeight = height; - - // 4. 立即重新初始化解码器 - await _resetDecoderForNewStream(width, height); - - // 5. 启动帧处理 - _startFrameProcessTimer(); - - // 6. 显示加载动画 - Future.microtask(() => state.isLoading.value = true); - - // 7. 重置帧序列相关状态 + // 不立即loading,继续解码旧流帧,等待frameSeq回绕检测 + // 仅重置frameSeq回绕检测标志 _pendingStreamReset = false; _pendingResetWidth = width; _pendingResetHeight = height; - } void _initHdOptions() { @@ -1387,22 +869,19 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 新增:重置解码器方法 Future _resetDecoderForNewStream(int width, int height) async { try { - state.isLoading.value = true; + // 先停止帧处理定时器 _stopFrameProcessTimer(); - _clearFrameBufferQuickly(); // 释放旧解码器 if (state.textureId.value != null) { - try { - await VideoDecodePlugin.releaseDecoder().timeout(Duration(milliseconds: 100)); - state.textureId.value = null; - } catch (e) { - AppLog.log('释放解码器超时或失败: $e'); - state.textureId.value = null; - } + await VideoDecodePlugin.releaseDecoder(); + state.textureId.value = null; } - // 创建优化的解码器配置 + // 等待一小段时间确保资源释放完成 + await Future.delayed(Duration(milliseconds: 100)); + + // 创建新的解码器配置 final config = VideoDecoderConfig( width: width, height: height, @@ -1410,46 +889,39 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { ); // 初始化新解码器 - try { - final textureId = await VideoDecodePlugin.initDecoder(config) - .timeout(Duration(milliseconds: 300)); - if (textureId != null) { - Future.microtask(() => state.textureId.value = textureId); - AppLog.log('解码器初始化成功:textureId=$textureId'); + final textureId = await VideoDecodePlugin.initDecoder(config); + if (textureId != null) { + state.textureId.value = textureId; + AppLog.log('frameSeq回绕后解码器初始化成功:textureId=$textureId'); - VideoDecodePlugin.setOnFrameRenderedListener((textureId) { - // 快速响应首帧渲染 - Future.microtask(() { - state.isLoading.value = false; - }); - }); - - // 重置相关状态 - _decodedIFrames.clear(); - state.h264FrameBuffer.clear(); - state.isProcessingFrame = false; - _lastFrameSeq = null; - lastDecodedIFrameSeq = null; - - // 重要:标记需要等待带完整信息的I帧 - _waitingForCompleteIFrame = true; - - _startFrameProcessTimer(); - } else { - AppLog.log('解码器初始化失败'); + // 设置帧渲染监听 + VideoDecodePlugin.setOnFrameRenderedListener((textureId) { + AppLog.log('已经开始渲染======='); + // 只有真正渲染出首帧时才关闭loading state.isLoading.value = false; - } - } catch (e) { - AppLog.log('解码器初始化超时或错误: $e'); + }); + + // 重新启动帧处理定时器 + _startFrameProcessTimer(); + + // 重置相关状态 + _decodedIFrames.clear(); + state.h264FrameBuffer.clear(); + state.isProcessingFrame = false; + hasSps = false; + hasPps = false; + spsCache = null; + ppsCache = null; + } else { + AppLog.log('frameSeq回绕后解码器初始化失败'); state.isLoading.value = false; } } catch (e) { - AppLog.log('解码器操作错误: $e'); + AppLog.log('frameSeq回绕时解码器初始化错误: $e'); state.isLoading.value = false; } } - void _processFrame(TalkDataModel talkDataModel) { final talkData = talkDataModel.talkData; final talkDataH264Frame = talkDataModel.talkDataH264Frame; @@ -1459,7 +931,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 判断数据类型,进行分发处理 switch (contentType) { case TalkData_ContentTypeE.G711: - // 没有开启所有和录音时不缓存和播放音频 + // 没有开启所有和录音时不缓存和播放音频 if (!state.isOpenVoice.value || state.isRecordingAudio.value) { return; } @@ -1471,7 +943,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { _playAudioFrames(); break; case TalkData_ContentTypeE.H264: - // 处理H264帧 + // 处理H264帧 if (state.textureId.value != null) { if (talkDataH264Frame != null) { _addFrameToBuffer(