From de6d2628f617dce082140a99e2b65b4502470b92 Mon Sep 17 00:00:00 2001 From: liyi Date: Mon, 23 Jun 2025 15:28:43 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=E4=BC=98=E5=8C=96=E5=AF=B9=E8=AE=B2?= =?UTF-8?q?=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handle/impl/udp_talk_data_handler.dart | 9 +- .../handle/other/h264_frame_handler.dart | 3 + .../handle/other/talk_data_repository.dart | 2 +- .../handle/scp_message_base_handle.dart | 34 ------ .../handle/scp_message_handler_factory.dart | 2 +- lib/talk/starChart/star_chart_manage.dart | 115 ++---------------- .../native/talk_view_native_decode_logic.dart | 112 ++++++++++------- .../native/talk_view_native_decode_page.dart | 20 +-- .../native/talk_view_native_decode_state.dart | 2 +- 9 files changed, 108 insertions(+), 191 deletions(-) diff --git a/lib/talk/starChart/handle/impl/udp_talk_data_handler.dart b/lib/talk/starChart/handle/impl/udp_talk_data_handler.dart index 96330232..702072b4 100644 --- a/lib/talk/starChart/handle/impl/udp_talk_data_handler.dart +++ b/lib/talk/starChart/handle/impl/udp_talk_data_handler.dart @@ -16,6 +16,11 @@ import 'package:star_lock/talk/starChart/proto/talk_data_h264_frame.pb.dart'; // implements ScpMessageHandler { class UdpTalkDataHandler extends ScpMessageBaseHandle implements ScpMessageHandler { + // 单例实现 + static final UdpTalkDataHandler instance = UdpTalkDataHandler(); + + UdpTalkDataHandler(); // 保持默认构造函数 + @override void handleReq(ScpMessage scpMessage) {} @@ -32,7 +37,7 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle if (scpMessage.Payload != null) { final TalkData talkData = scpMessage.Payload; - // 处理音视频数据 + _handleTalkData( talkData: talkData, scpMessage: scpMessage, @@ -122,7 +127,9 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle void _handleVideoH264(TalkData talkData, ScpMessage scpMessage) { final TalkDataH264Frame talkDataH264Frame = TalkDataH264Frame(); talkDataH264Frame.mergeFromBuffer(talkData.content); + // AppLog.log('处理H264帧: frameType=${talkDataH264Frame.frameType}, frameSeq=${talkDataH264Frame.frameSeq},MessageId:${scpMessage.MessageId}'); frameHandler.handleFrame(talkDataH264Frame, talkData, scpMessage); + } /// 处理图片数据 diff --git a/lib/talk/starChart/handle/other/h264_frame_handler.dart b/lib/talk/starChart/handle/other/h264_frame_handler.dart index 1c1a87cc..018af2a4 100644 --- a/lib/talk/starChart/handle/other/h264_frame_handler.dart +++ b/lib/talk/starChart/handle/other/h264_frame_handler.dart @@ -15,6 +15,9 @@ class H264FrameHandler { void handleFrame( TalkDataH264Frame frame, TalkData talkData, ScpMessage scpMessage) { + // AppLog.log( + // '送入stream的帧数据: frameSeq=${frame.frameSeq},frameType=${frame + // .frameType},MessageId:${scpMessage.MessageId}'); onCompleteFrame(TalkDataModel( talkData: talkData, talkDataH264Frame: frame, diff --git a/lib/talk/starChart/handle/other/talk_data_repository.dart b/lib/talk/starChart/handle/other/talk_data_repository.dart index 587a481f..0d73ed83 100644 --- a/lib/talk/starChart/handle/other/talk_data_repository.dart +++ b/lib/talk/starChart/handle/other/talk_data_repository.dart @@ -11,7 +11,7 @@ class TalkDataRepository { onCancel: () { _isListening = false; }, - sync: false, // 改为同步模式以提高实时性 + sync: true, // 改为同步模式以提高实时性 ); } diff --git a/lib/talk/starChart/handle/scp_message_base_handle.dart b/lib/talk/starChart/handle/scp_message_base_handle.dart index 495db676..d16e5e58 100644 --- a/lib/talk/starChart/handle/scp_message_base_handle.dart +++ b/lib/talk/starChart/handle/scp_message_base_handle.dart @@ -181,40 +181,6 @@ class ScpMessageBaseHandle { } return null; - - // if (!_packetBuffer.containsKey(key)) { - // _packetBuffer[key] = List.filled(spTotal, []); - // _startTimer(key); - // } - // - // // 检查分包索引是否在合法范围内 - // if (spIndex < 1 || spIndex > spTotal) { - // // print( - // // 'Invalid spTotal: $spTotal spIndex: $spIndex for messageId: $messageId'); - // return null; - // } - // - // // 存储当前分包 - // _packetBuffer[key]![spIndex - 1] = byte; - // - // // 检查是否接收到所有分包 - // if (_packetBuffer[key]!.every((packet) => packet.isNotEmpty)) { - // // 重组所有分包 - // Uint8List completePayload = Uint8List.fromList( - // _packetBuffer[key]!.expand((packet) => packet).toList()); - // // 清除已重组和超时的分包数据 - // _clearPacketData(key); - // - // // 使用重组的包构造成TalkData - // if (payloadType == PayloadTypeConstant.talkData) { - // final talkData = TalkData(); - // talkData.mergeFromBuffer(completePayload); - // return talkData; - // } - // } else { - // // 如果分包尚未接收完全,返回 null 或其他指示符 - // return null; - // } } // 启动定时器 diff --git a/lib/talk/starChart/handle/scp_message_handler_factory.dart b/lib/talk/starChart/handle/scp_message_handler_factory.dart index 67c7a963..fabf7429 100644 --- a/lib/talk/starChart/handle/scp_message_handler_factory.dart +++ b/lib/talk/starChart/handle/scp_message_handler_factory.dart @@ -52,7 +52,7 @@ class ScpMessageHandlerFactory { case PayloadTypeConstant.talkExpect: return UdpTalkExpectHandler(); case PayloadTypeConstant.talkData: - return UdpTalkDataHandler(); + return UdpTalkDataHandler.instance; case PayloadTypeConstant.talkHangup: return UdpTalkHangUpHandler(); case PayloadTypeConstant.RbcuInfo: diff --git a/lib/talk/starChart/star_chart_manage.dart b/lib/talk/starChart/star_chart_manage.dart index ec465953..81d36ccc 100644 --- a/lib/talk/starChart/star_chart_manage.dart +++ b/lib/talk/starChart/star_chart_manage.dart @@ -141,11 +141,6 @@ class StartChartManage { return _avFrameLost / _avFrameTotal; } - // ====== Isolate相关成员变量 ====== - Isolate? _udpIsolate; - SendPort? _udpIsolateSendPort; - ReceivePort? _mainReceivePort; - // 星图服务初始化 Future init() async { if (F.isXHJ) { @@ -167,8 +162,6 @@ class StartChartManage { await _onlineRelayService(); // 上报 await reportInformation(); - // 初始化Isolate - await _initUdpIsolate(); } /// 客户端注册 @@ -248,21 +241,20 @@ class StartChartManage { var addressIListenFrom = InternetAddress.anyIPv4; RawDatagramSocket.bind(addressIListenFrom, localPort) .then((RawDatagramSocket socket) { - // 设置接收缓冲区大小 (SO_RCVBUF = 8) socket.setRawOption( RawSocketOption.fromInt( RawSocketOption.levelSocket, - 8, // SO_RCVBUF for Android/iOS + 8, // SO_RCVBUF for Android/iOS 2 * 1024 * 1024, // 2MB receive buffer ), ); - + // 设置发送缓冲区大小 (SO_SNDBUF = 7) socket.setRawOption( RawSocketOption.fromInt( RawSocketOption.levelSocket, - 7, // SO_SNDBUF for Android/iOS + 7, // SO_SNDBUF for Android/iOS 2 * 1024 * 1024, // 2MB send buffer ), ); @@ -1057,29 +1049,22 @@ class StartChartManage { // 接收返回的数据 void _onReceiveData(RawDatagramSocket socket, BuildContext context) { - // ====== Isolate初始化握手 ====== - if (_udpIsolateSendPort == null && _mainReceivePort != null) { - _mainReceivePort!.listen((dynamic message) { - if (message is SendPort) { - _udpIsolateSendPort = message; - } - }); - } socket.listen((RawSocketEvent event) { if (event == RawSocketEvent.read) { Datagram? dg; while ((dg = socket.receive()) != null) { try { if (dg?.data != null) { - // 直接将bytes发送到Isolate - if (_udpIsolateSendPort != null) { - _udpIsolateSendPort!.send(dg!.data); - } else { - // Fallback: 主线程处理(极端情况) - final deserialize = ScpMessage.deserialize(dg!.data); - if (deserialize != null) { - _handleUdpResultData(deserialize); - } + // Fallback: 主线程处理(极端情况) + // 更好的做法:批量处理 + final deserialize = ScpMessage.deserialize(dg!.data); + // if (deserialize.PayloadType == PayloadTypeConstant.talkData) { + // _log( + // text: 'mesaageId:${deserialize.MessageId},' + // 'SpTotal:${deserialize.SpTotal},SpIndex:${deserialize.SpIndex}'); + // } + if (deserialize != null) { + _handleUdpResultData(deserialize); } } } catch (e, stackTrace) { @@ -1095,14 +1080,6 @@ class StartChartManage { final int payloadType = scpMessage.PayloadType ?? 0; final int messageType = scpMessage.MessageType ?? 0; try { - // 记录分包数据用于统计丢包率 - // if (scpMessage.SpIndex != null && - // scpMessage.SpTotal != null && - // scpMessage.MessageId != null) { - // PacketLossStatistics().recordPacket( - // scpMessage.MessageId!, scpMessage.SpIndex!, scpMessage.SpTotal!); - // } - final ScpMessageHandler handler = ScpMessageHandlerFactory.createHandler(payloadType); if (messageType == MessageTypeConstant.Req) { @@ -1303,8 +1280,6 @@ class StartChartManage { await Storage.removerStarChartRegisterNodeInfo(); // 关闭udp服务 closeUdpSocket(); - // 关闭Isolate - _disposeUdpIsolate(); } /// 重置数据 @@ -1313,68 +1288,4 @@ class StartChartManage { isOnlineStarChartServer = false; talkStatus.setUninitialized(); } - - // 初始化Isolate - Future _initUdpIsolate() async { - _mainReceivePort = ReceivePort(); - _mainReceivePort!.listen((dynamic message) { - // 目前不需要主线程回调,如需日志可在此处理 - // if (message is String) { - // _log(text: '[Isolate] $message'); - // } - }); - _udpIsolate = await Isolate.spawn<_UdpIsolateInitParams>( - _udpIsolateEntry, - _UdpIsolateInitParams( - _mainReceivePort!.sendPort, - _handleUdpResultDataStatic, - ), - debugName: 'UdpDataIsolate', - ); - } - - // 销毁Isolate - void _disposeUdpIsolate() { - _udpIsolateSendPort = null; - _mainReceivePort?.close(); - _mainReceivePort = null; - _udpIsolate?.kill(priority: Isolate.immediate); - _udpIsolate = null; - } - - // 静态代理,便于Isolate调用成员方法 - static void _handleUdpResultDataStatic(Map args) { - final instance = StartChartManage(); - final ScpMessage scpMessage = args['scpMessage'] as ScpMessage; - instance._handleUdpResultData(scpMessage); - } - - // Isolate入口函数 - static void _udpIsolateEntry(_UdpIsolateInitParams params) { - final SendPort mainSendPort = params.mainSendPort; - final Function handleUdpResultData = params.handleUdpResultData; - final ReceivePort isolateReceivePort = ReceivePort(); - // 首次将SendPort发回主线程 - mainSendPort.send(isolateReceivePort.sendPort); - isolateReceivePort.listen((dynamic message) { - try { - if (message is List) { - // 修正:List转Uint8List - final scpMessage = ScpMessage.deserialize(Uint8List.fromList(message)); - if (scpMessage != null) { - // 通过静态代理调用主线程的_handleUdpResultData - handleUdpResultData({'scpMessage': scpMessage}); - } - } - } catch (e, stack) { - // 可选:mainSendPort.send('[Isolate Error] $e\n$stack'); - } - }); - } -} - -class _UdpIsolateInitParams { - final SendPort mainSendPort; - final Function handleUdpResultData; - _UdpIsolateInitParams(this.mainSendPort, this.handleUdpResultData); } 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 53dc4837..59b9a604 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 @@ -38,6 +38,7 @@ 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 'package:video_decode_plugin/nalu_utils.dart'; import 'package:video_decode_plugin/video_decode_plugin.dart'; import '../../../../tools/baseGetXController.dart'; @@ -90,6 +91,9 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { int? lastDecodedIFrameSeq; + // 新增:只允许切换清晰度后一次回绕重置的标志 + bool _allowStreamResetOnce = false; + // 初始化视频解码器 Future _initVideoDecoder() async { try { @@ -162,16 +166,17 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { int frameSeqI, ScpMessage scpMessage, ) { - // 检测frameSeq回绕,且为I帧 - if (!_pendingStreamReset && + // 只允许切换清晰度后一次回绕重置 + if (_allowStreamResetOnce && _lastFrameSeq != null && frameType == TalkDataH264Frame_FrameTypeE.I && frameSeq < _lastFrameSeq!) { // 检测到新流I帧,进入loading并重置所有本地状态 AppLog.log( - '检测到新流I帧,frameSeq回绕,进入loading并重置: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq'); + '检测到新流I帧(仅切换清晰度后允许),frameSeq回绕,进入loading并重置: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq'); Future.microtask(() => state.isLoading.value = true); _pendingStreamReset = true; + _allowStreamResetOnce = false; // 只允许一次 // 先暂停帧处理定时器,防止竞态 _stopFrameProcessTimer(); // 先释放并重新初始化解码器 @@ -184,6 +189,13 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { _startFrameProcessTimer(); // 不return,直接用该I帧初始化解码器并解码 // 继续往下执行 + } else if (!_allowStreamResetOnce && + _lastFrameSeq != null && + frameType == TalkDataH264Frame_FrameTypeE.I && + frameSeq < _lastFrameSeq!) { + // 非切换清晰度场景下的回绕,直接丢弃 + AppLog.log('检测到I帧回绕,但未切换清晰度,不重置: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq'); + return; } // 如果处于pendingStreamReset,等待新I帧 if (_pendingStreamReset) { @@ -306,6 +318,9 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { lastDecodedIFrameSeq = minIFrameSeq; // AppLog.log('送入解码器的P帧数据frameSeq:${frameSeq},frameSeqI:${frameSeqI},' // 'frameType:${frameType},messageId:${scpMessage!.MessageId}'); + // final spsData = NaluUtils.filterNalusByType(frameData, 7); + // final ppsData = NaluUtils.filterNalusByType(frameData, 8); + // AppLog.log('SPSDATA:${spsData},ppsData:${ppsData}'); await VideoDecodePlugin.sendFrame( frameData: frameData, frameType: 0, @@ -360,8 +375,9 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { state.isProcessingFrame = false; return; } - // AppLog.log('送入解码器的P帧数据frameSeq:${frameSeq},frameSeqI:${frameSeqI},' + // AppLog.log('送入解码器的I帧数据frameSeq:${frameSeq},frameSeqI:${frameSeqI},' // 'frameType:${frameType},messageId:${scpMessage!.MessageId}'); + await VideoDecodePlugin.sendFrame( frameData: frameData, frameType: 1, @@ -392,6 +408,11 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 监听音视频数据流 void _startListenTalkData() { + // 取消旧订阅 + if (_streamSubscription != null) { + _streamSubscription!.cancel(); + _streamSubscription = null; + } // 防止重复监听 if (_isListening) { AppLog.log("已经存在数据流监听,避免重复监听"); @@ -403,39 +424,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { _streamSubscription = state.talkDataRepository.talkDataStream .listen((TalkDataModel talkDataModel) async { - final talkData = talkDataModel.talkData; - final talkDataH264Frame = talkDataModel.talkDataH264Frame; - final scpMessage = talkDataModel.scpMessage; - final contentType = talkData!.contentType; - - // 判断数据类型,进行分发处理 - switch (contentType) { - case TalkData_ContentTypeE.G711: - if (state.audioBuffer.length >= audioBufferSize) { - state.audioBuffer.removeAt(0); // 丢弃最旧的数据 - } - state.audioBuffer.add(talkData); // 添加新数据 - // 添加音频播放逻辑,与视频类似 - _playAudioFrames(); - break; - case TalkData_ContentTypeE.H264: - // 处理H264帧 - if (state.textureId.value != null || true) { - if (talkDataH264Frame != null) { - _addFrameToBuffer( - talkData.content, - talkDataH264Frame.frameType, - talkData.durationMs, - talkDataH264Frame.frameSeq, - talkDataH264Frame.frameSeqI, - scpMessage!, - ); - } - } else { - AppLog.log('无法处理H264帧:textureId为空'); - } - break; - } + _processFrame(talkDataModel); }); } @@ -1355,6 +1344,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { _pendingStreamReset = false; _pendingResetWidth = width; _pendingResetHeight = height; + // 新增:切换清晰度后允许一次回绕重置 + _allowStreamResetOnce = true; } void _initHdOptions() { @@ -1372,7 +1363,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { try { // 先停止帧处理定时器 _stopFrameProcessTimer(); - + // 释放旧解码器 if (state.textureId.value != null) { await VideoDecodePlugin.releaseDecoder(); @@ -1381,7 +1372,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 等待一小段时间确保资源释放完成 await Future.delayed(Duration(milliseconds: 100)); - + // 创建新的解码器配置 final config = VideoDecoderConfig( width: width, @@ -1394,7 +1385,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { if (textureId != null) { state.textureId.value = textureId; AppLog.log('frameSeq回绕后解码器初始化成功:textureId=$textureId'); - + // 设置帧渲染监听 VideoDecodePlugin.setOnFrameRenderedListener((textureId) { AppLog.log('已经开始渲染======='); @@ -1402,9 +1393,12 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { state.isLoading.value = false; }); + // 防止极端情况下回调未触发导致loading卡住,手动关闭loading + state.isLoading.value = false; + // 重新启动帧处理定时器 _startFrameProcessTimer(); - + // 重置相关状态 _decodedIFrames.clear(); state.h264FrameBuffer.clear(); @@ -1422,4 +1416,40 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { state.isLoading.value = false; } } + + void _processFrame(TalkDataModel talkDataModel) { + final talkData = talkDataModel.talkData; + final talkDataH264Frame = talkDataModel.talkDataH264Frame; + final scpMessage = talkDataModel.scpMessage; + final contentType = talkData!.contentType; + + // 判断数据类型,进行分发处理 + switch (contentType) { + case TalkData_ContentTypeE.G711: + if (state.audioBuffer.length >= audioBufferSize) { + state.audioBuffer.removeAt(0); // 丢弃最旧的数据 + } + state.audioBuffer.add(talkData); // 添加新数据 + // 添加音频播放逻辑,与视频类似 + _playAudioFrames(); + break; + case TalkData_ContentTypeE.H264: + // 处理H264帧 + if (state.textureId.value != null) { + if (talkDataH264Frame != null) { + _addFrameToBuffer( + talkData.content, + talkDataH264Frame.frameType, + talkData.durationMs, + talkDataH264Frame.frameSeq, + talkDataH264Frame.frameSeqI, + scpMessage!, + ); + } + } else { + AppLog.log('无法处理H264帧:textureId为空'); + } + break; + } + } } diff --git a/lib/talk/starChart/views/native/talk_view_native_decode_page.dart b/lib/talk/starChart/views/native/talk_view_native_decode_page.dart index 84c29c3d..eeeea7cf 100644 --- a/lib/talk/starChart/views/native/talk_view_native_decode_page.dart +++ b/lib/talk/starChart/views/native/talk_view_native_decode_page.dart @@ -114,16 +114,16 @@ class _TalkViewNativeDecodePageState extends State key: state.globalKey, child: SizedBox.expand( child: RotatedBox( - // 解码器不支持硬件旋转,使用RotatedBox - quarterTurns: startChartManage.rotateAngle ~/ 90, - child: Platform.isIOS - ? Transform.scale( - scale: 1.008, // 轻微放大,消除iOS白边 - child: Texture( - textureId: state.textureId.value!, - filterQuality: FilterQuality.medium, - ), - ) + // 解码器不支持硬件旋转,使用RotatedBox + quarterTurns: startChartManage.rotateAngle ~/ 90, + child: Platform.isIOS + ? Transform.scale( + scale: 1.008, // 轻微放大,消除iOS白边 + child: Texture( + textureId: state.textureId.value!, + filterQuality: FilterQuality.medium, + ), + ) : Texture( textureId: state.textureId.value!, filterQuality: FilterQuality.medium, 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 7fa57cf2..e3bf6908 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 @@ -110,7 +110,7 @@ class TalkViewNativeDecodeState { // H264帧缓冲区相关 final List> h264FrameBuffer = >[]; // H264帧缓冲区,存储帧数据和类型 - final int maxFrameBufferSize = 150; // 最大缓冲区大小 + final int maxFrameBufferSize = 50; // 最大缓冲区大小 final int targetFps = 60; // 目标解码帧率,只是为了快速填充native的缓冲区 Timer? frameProcessTimer; // 帧处理定时器 bool isProcessingFrame = false; // 是否正在处理帧 From ce193a2195fa2e88ec39a73f1b66466f068f710f Mon Sep 17 00:00:00 2001 From: liyi Date: Mon, 23 Jun 2025 15:39:00 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E6=B8=85=E6=99=B0=E5=BA=A6=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../native/talk_view_native_decode_logic.dart | 22 +++---------------- 1 file changed, 3 insertions(+), 19 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 59b9a604..e7570394 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 @@ -91,9 +91,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { int? lastDecodedIFrameSeq; - // 新增:只允许切换清晰度后一次回绕重置的标志 - bool _allowStreamResetOnce = false; - // 初始化视频解码器 Future _initVideoDecoder() async { try { @@ -166,17 +163,16 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { int frameSeqI, ScpMessage scpMessage, ) { - // 只允许切换清晰度后一次回绕重置 - if (_allowStreamResetOnce && + // 检测frameSeq回绕,且为I帧 + if (!_pendingStreamReset && _lastFrameSeq != null && frameType == TalkDataH264Frame_FrameTypeE.I && frameSeq < _lastFrameSeq!) { // 检测到新流I帧,进入loading并重置所有本地状态 AppLog.log( - '检测到新流I帧(仅切换清晰度后允许),frameSeq回绕,进入loading并重置: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq'); + '检测到新流I帧,frameSeq回绕,进入loading并重置: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq'); Future.microtask(() => state.isLoading.value = true); _pendingStreamReset = true; - _allowStreamResetOnce = false; // 只允许一次 // 先暂停帧处理定时器,防止竞态 _stopFrameProcessTimer(); // 先释放并重新初始化解码器 @@ -189,13 +185,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { _startFrameProcessTimer(); // 不return,直接用该I帧初始化解码器并解码 // 继续往下执行 - } else if (!_allowStreamResetOnce && - _lastFrameSeq != null && - frameType == TalkDataH264Frame_FrameTypeE.I && - frameSeq < _lastFrameSeq!) { - // 非切换清晰度场景下的回绕,直接丢弃 - AppLog.log('检测到I帧回绕,但未切换清晰度,不重置: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq'); - return; } // 如果处于pendingStreamReset,等待新I帧 if (_pendingStreamReset) { @@ -1344,8 +1333,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { _pendingStreamReset = false; _pendingResetWidth = width; _pendingResetHeight = height; - // 新增:切换清晰度后允许一次回绕重置 - _allowStreamResetOnce = true; } void _initHdOptions() { @@ -1393,9 +1380,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { state.isLoading.value = false; }); - // 防止极端情况下回调未触发导致loading卡住,手动关闭loading - state.isLoading.value = false; - // 重新启动帧处理定时器 _startFrameProcessTimer(); From 293716f146ad47fd7b470e96c2aa7a7dc9a54c3a Mon Sep 17 00:00:00 2001 From: liyi Date: Mon, 23 Jun 2025 16:00:37 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E6=B8=85=E6=99=B0=E5=BA=A6=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../native/talk_view_native_decode_logic.dart | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 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 e7570394..97522643 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 @@ -52,6 +52,15 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { int audioBufferSize = 2; // 音频默认缓冲2帧 + // 回绕阈值,动态调整,frameSeq较小时阈值也小 + int _getFrameSeqRolloverThreshold(int lastSeq) { + if (lastSeq > 2000) { + return 1000; + } else { + return (lastSeq / 2).round(); + } + } + // 定义音频帧缓冲和发送函数 final List _bufferedAudioFrames = []; @@ -163,28 +172,35 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { int frameSeqI, ScpMessage scpMessage, ) { - // 检测frameSeq回绕,且为I帧 + + // 动态回绕阈值判断,frameSeq较小时阈值也小 if (!_pendingStreamReset && _lastFrameSeq != null && frameType == TalkDataH264Frame_FrameTypeE.I && frameSeq < _lastFrameSeq!) { - // 检测到新流I帧,进入loading并重置所有本地状态 - AppLog.log( - '检测到新流I帧,frameSeq回绕,进入loading并重置: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq'); - Future.microtask(() => state.isLoading.value = true); - _pendingStreamReset = true; - // 先暂停帧处理定时器,防止竞态 - _stopFrameProcessTimer(); - // 先释放并重新初始化解码器 - _resetDecoderForNewStream(_pendingResetWidth, _pendingResetHeight); - // 重置所有本地状态 - _lastFrameSeq = null; - _decodedIFrames.clear(); - state.h264FrameBuffer.clear(); - // 再恢复帧处理定时器 - _startFrameProcessTimer(); - // 不return,直接用该I帧初始化解码器并解码 - // 继续往下执行 + int dynamicThreshold = _getFrameSeqRolloverThreshold(_lastFrameSeq!); + if ((_lastFrameSeq! - frameSeq) > dynamicThreshold) { + // 检测到新流I帧,frameSeq大幅回绕,进入loading并重置所有本地状态 + AppLog.log('检测到新流I帧,frameSeq大幅回绕,进入loading并重置: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq, 阈值=$dynamicThreshold'); + Future.microtask(() => state.isLoading.value = true); + _pendingStreamReset = true; + // 先暂停帧处理定时器,防止竞态 + _stopFrameProcessTimer(); + // 先释放并重新初始化解码器 + _resetDecoderForNewStream(_pendingResetWidth, _pendingResetHeight); + // 重置所有本地状态 + _lastFrameSeq = null; + _decodedIFrames.clear(); + state.h264FrameBuffer.clear(); + // 再恢复帧处理定时器 + _startFrameProcessTimer(); + // 不return,直接用该I帧初始化解码器并解码 + // 继续往下执行 + } else { + // 小幅度乱序,直接丢弃 + AppLog.log('检测到I帧乱序(未超过回绕阈值$dynamicThreshold),丢弃: frameSeq=$frameSeq, lastFrameSeq=$_lastFrameSeq'); + return; + } } // 如果处于pendingStreamReset,等待新I帧 if (_pendingStreamReset) {