From bf4c2b47508bd9b908382bfe95e7f84f127bcf4f Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 18 Jun 2025 14:59:34 +0800 Subject: [PATCH 1/7] =?UTF-8?q?fix:=E5=A2=9E=E5=8A=A0=E9=9F=B3=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E6=95=B0=E6=8D=AE=E7=9A=84=E4=B8=A2=E5=8C=85=E7=8E=87?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E3=80=81=E8=B0=83=E6=95=B4udp=E5=8F=91?= =?UTF-8?q?=E9=80=81=E5=92=8C=E6=8E=A5=E6=94=B6=E7=BC=93=E5=86=B2=E5=8C=BA?= =?UTF-8?q?=E5=A4=A7=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/talk/starChart/star_chart_manage.dart | 101 +++++++++++++++++----- 1 file changed, 77 insertions(+), 24 deletions(-) diff --git a/lib/talk/starChart/star_chart_manage.dart b/lib/talk/starChart/star_chart_manage.dart index 7a185c47..13228e01 100644 --- a/lib/talk/starChart/star_chart_manage.dart +++ b/lib/talk/starChart/star_chart_manage.dart @@ -50,6 +50,10 @@ import 'package:star_lock/tools/deviceInfo_utils.dart'; import 'package:star_lock/tools/storage.dart'; import 'package:uuid/uuid.dart'; +// Socket选项常量 +const int SO_RCVBUF = 8; // 接收缓冲区 +const int SO_SNDBUF = 7; // 发送缓冲区 + class StartChartManage { // 私有构造函数,防止外部直接new对象 StartChartManage._internal(); @@ -125,6 +129,17 @@ class StartChartManage { // 获取 StartChartTalkStatus 的唯一实例 StartChartTalkStatus talkStatus = StartChartTalkStatus.instance; + // 音视频帧级丢包统计变量 + final Map> _avFrameParts = {}; + int _avFrameTotal = 0; + int _avFrameLost = 0; + + // 查询音视频帧丢包率 + double getAvFrameLossRate() { + if (_avFrameTotal == 0) return 0.0; + return _avFrameLost / _avFrameTotal; + } + // 星图服务初始化 Future init() async { if (F.isXHJ) { @@ -225,6 +240,25 @@ 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 + 2 * 1024 * 1024, // 2MB receive buffer + ), + ); + + // 设置发送缓冲区大小 (SO_SNDBUF = 7) + socket.setRawOption( + RawSocketOption.fromInt( + RawSocketOption.levelSocket, + 7, // SO_SNDBUF for Android/iOS + 2 * 1024 * 1024, // 2MB send buffer + ), + ); + _udpSocket = socket; /// 广播功能 @@ -1017,35 +1051,54 @@ class StartChartManage { void _onReceiveData(RawDatagramSocket socket, BuildContext context) { socket.listen((RawSocketEvent event) { if (event == RawSocketEvent.read) { - Datagram? dg = socket.receive(); - try { - if (dg?.data != null) { - final deserialize = ScpMessage.deserialize(dg!.data); + Datagram? dg; + while ((dg = socket.receive()) != null) { + try { + if (dg?.data != null) { + final deserialize = ScpMessage.deserialize(dg!.data); - // //ToDo: 增加对讲调试、正式可删除 - // UdpTalkDataHandler().updateRecvDataRate(dg.data.length); - - // // 更新调试信息 - // Provider.of(context, listen: false).updateDebugInfo( - // UdpTalkDataHandler().getLastRecvDataRate() ~/ 1024, // 转换为KB - // UdpTalkDataHandler().getLastRecvPacketCount(), - // UdpTalkDataHandler().getLastSendDataRate() ~/ 1024, // 转换为KB - // UdpTalkDataHandler().getLastSendPacketCount(), - // ); - - if (deserialize != null) { - // 处理返回数据 - _handleUdpResultData(deserialize); - } - if (deserialize.PayloadType != PayloadTypeConstant.heartbeat) { - if (deserialize.Payload != null) { - // _log(text: 'Udp收到结构体数据---》$deserialize'); + // 音视频帧丢包统计:只统计PayloadType==talkData的数据包,结合分包信息 + if (deserialize != null && + deserialize.PayloadType == PayloadTypeConstant.talkData) { + int? msgId = deserialize.MessageId; + int spTotal = deserialize.SpTotal ?? 1; + int spIndex = deserialize.SpIndex ?? 1; + if (msgId != null) { + // 记录收到的分包 + _avFrameParts.putIfAbsent(msgId, () => {}); + _avFrameParts[msgId]!.add(spIndex); + // 如果收到最后一个分包,判断该帧是否完整 + if (spIndex == spTotal) { + _avFrameTotal++; + if (_avFrameParts[msgId]!.length < spTotal) { + _avFrameLost++; + // _log(text: '音视频丢包,丢失的messageId: $msgId'); + } + _avFrameParts.remove(msgId); + // 可选:每100帧打印一次丢包率 + if (_avFrameTotal % 100 == 0) { + _log( + text: + '音视频帧丢包率: ${(getAvFrameLossRate() * 100).toStringAsFixed(2)}%'); + } + } + } } + + if (deserialize != null) { + // 处理返回数据 + _handleUdpResultData(deserialize); + } + // if (deserialize.PayloadType != PayloadTypeConstant.heartbeat) { + // if (deserialize.Payload != null) { + // _log(text: 'Udp收到结构体数据---》$deserialize'); + // } // _log(text: 'text---》${utf8.decode(deserialize.Payload)}'); + // } } + } catch (e, stackTrace) { + throw StartChartMessageException('$e\n,$stackTrace'); } - } catch (e, stackTrace) { - throw StartChartMessageException('$e\n,$stackTrace'); } } }); From 51ca6e1f23593e006b99058b245f117261d2eefb Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 18 Jun 2025 15:12:51 +0800 Subject: [PATCH 2/7] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E5=AF=B9=E8=AE=B2?= =?UTF-8?q?=E7=9A=84=E9=AB=98=E6=B8=85=E3=80=81=E6=A0=87=E6=B8=85=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E5=8A=9F=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 | 417 +++++++++++------- 1 file changed, 260 insertions(+), 157 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 9ddf4a57..53dc4837 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 @@ -25,6 +25,7 @@ import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/talk/call/callTalk.dart'; import 'package:star_lock/talk/call/g711.dart'; import 'package:star_lock/talk/starChart/constant/talk_status.dart'; +import 'package:star_lock/talk/starChart/entity/scp_message.dart'; import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart'; import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart'; import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart'; @@ -87,15 +88,17 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 新增:等待新I帧状态 bool _waitingForIFrame = false; + int? lastDecodedIFrameSeq; + // 初始化视频解码器 Future _initVideoDecoder() async { try { state.isLoading.value = true; // 创建解码器配置 final config = VideoDecoderConfig( - width: 864, + width: StartChartManage().videoWidth, // 实际视频宽度 - height: 480, + height: StartChartManage().videoHeight, codecType: 'h264', ); // 初始化解码器并获取textureId @@ -157,6 +160,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { int pts, int frameSeq, int frameSeqI, + ScpMessage scpMessage, ) { // 检测frameSeq回绕,且为I帧 if (!_pendingStreamReset && @@ -212,6 +216,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { 'frameSeq': frameSeq, 'frameSeqI': frameSeqI, 'pts': pts, + 'scpMessage': scpMessage, }; // 如果缓冲区超出最大大小,优先丢弃P/B帧 @@ -257,14 +262,25 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { return; } - // 设置正在处理标志 - 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)); - try { - // 取出最早的帧 - final Map? frameMap = state.h264FrameBuffer.isNotEmpty - ? state.h264FrameBuffer.removeAt(0) - : null; + if (iFrames.isNotEmpty) { + // 有I帧,消费最小的I帧,并记录其frameSeq + 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; @@ -274,6 +290,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { 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 || @@ -282,25 +299,82 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { state.isProcessingFrame = false; return; } - // 解码器未初始化或textureId为null时跳过 if (state.textureId.value == null) { state.isProcessingFrame = false; return; } + lastDecodedIFrameSeq = minIFrameSeq; + // AppLog.log('送入解码器的P帧数据frameSeq:${frameSeq},frameSeqI:${frameSeqI},' + // 'frameType:${frameType},messageId:${scpMessage!.MessageId}'); await VideoDecodePlugin.sendFrame( frameData: frameData, - frameType: frameType == TalkDataH264Frame_FrameTypeE.I ? 0 : 1, + frameType: 0, frameSeq: frameSeq, timestamp: pts, splitNalFromIFrame: true, refIFrameSeq: frameSeqI, ); - } catch (e) { - AppLog.log('处理缓冲帧失败: $e'); - } finally { - // 重置处理标志 state.isProcessingFrame = false; + return; } + + // 没有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; + } + // AppLog.log('送入解码器的P帧数据frameSeq:${frameSeq},frameSeqI:${frameSeqI},' + // 'frameType:${frameType},messageId:${scpMessage!.MessageId}'); + await VideoDecodePlugin.sendFrame( + frameData: frameData, + frameType: 1, + frameSeq: frameSeq, + timestamp: pts, + splitNalFromIFrame: true, + refIFrameSeq: frameSeqI, + ); + state.isProcessingFrame = false; + return; + } + } + // 其他情况不消费,等待I帧到来 } /// 停止帧处理定时器 @@ -331,6 +405,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { .listen((TalkDataModel talkDataModel) async { final talkData = talkDataModel.talkData; final talkDataH264Frame = talkDataModel.talkDataH264Frame; + final scpMessage = talkDataModel.scpMessage; final contentType = talkData!.contentType; // 判断数据类型,进行分发处理 @@ -345,7 +420,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { break; case TalkData_ContentTypeE.H264: // 处理H264帧 - if (state.textureId.value != null) { + if (state.textureId.value != null || true) { if (talkDataH264Frame != null) { _addFrameToBuffer( talkData.content, @@ -353,6 +428,7 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { talkData.durationMs, talkDataH264Frame.frameSeq, talkDataH264Frame.frameSeqI, + scpMessage!, ); } } else { @@ -585,7 +661,8 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { TalkExpectReq talkExpectReq = TalkExpectReq(); state.isOpenVoice.value = !state.isOpenVoice.value; // 根据当前清晰度动态设置videoType - VideoTypeE currentVideoType = qualityToVideoType[state.currentQuality.value] ?? VideoTypeE.H264; + VideoTypeE currentVideoType = + qualityToVideoType[state.currentQuality.value] ?? VideoTypeE.H264; if (!state.isOpenVoice.value) { talkExpectReq = TalkExpectReq( videoType: [currentVideoType], @@ -1104,144 +1181,144 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { } // 新增:I帧处理方法 - void _handleIFrameWithSpsPpsAndIdr( - List frameData, int durationMs, int frameSeq, int frameSeqI) { - // 清空缓冲区,丢弃I帧前所有未处理帧(只保留SPS/PPS/I帧) - state.h264FrameBuffer.clear(); - _extractAndBufferSpsPpsForBuffer( - frameData, durationMs, frameSeq, frameSeqI); - // 只要缓存有SPS/PPS就先写入,再写I帧本体(只写IDR) - if (spsCache == null || ppsCache == null) { - // 没有SPS/PPS缓存,丢弃本次I帧 - return; - } - // 先写入SPS/PPS - _addFrameToBuffer(spsCache!, TalkDataH264Frame_FrameTypeE.I, durationMs, - frameSeq, frameSeqI); - _addFrameToBuffer(ppsCache!, TalkDataH264Frame_FrameTypeE.I, durationMs, - frameSeq, frameSeqI); - // 分割I帧包,只写入IDR(type 5) - List> nalus = []; - int i = 0; - List data = frameData; - while (i < data.length - 3) { - int start = -1; - int next = -1; - if (data[i] == 0x00 && data[i + 1] == 0x00) { - if (data[i + 2] == 0x01) { - start = i; - i += 3; - } else if (i + 3 < data.length && - data[i + 2] == 0x00 && - data[i + 3] == 0x01) { - start = i; - i += 4; - } else { - i++; - continue; - } - next = i; - while (next < data.length - 3) { - if (data[next] == 0x00 && - data[next + 1] == 0x00 && - ((data[next + 2] == 0x01) || - (data[next + 2] == 0x00 && data[next + 3] == 0x01))) { - break; - } - next++; - } - nalus.add(data.sublist(start, next)); - i = next; - } else { - i++; - } - } - int nalusTotalLen = - nalus.isNotEmpty ? nalus.fold(0, (p, n) => p + n.length) : 0; - if (nalus.isEmpty && data.isNotEmpty) { - nalus.add(data); - } else if (nalus.isNotEmpty && nalusTotalLen < data.length) { - nalus.add(data.sublist(nalusTotalLen)); - } - for (final nalu in nalus) { - int offset = 0; - if (nalu.length >= 4 && nalu[0] == 0x00 && nalu[1] == 0x00) { - if (nalu[2] == 0x01) - offset = 3; - else if (nalu[2] == 0x00 && nalu[3] == 0x01) offset = 4; - } - if (nalu.length > offset) { - int naluType = nalu[offset] & 0x1F; - if (naluType == 5) { - _addFrameToBuffer(nalu, TalkDataH264Frame_FrameTypeE.I, durationMs, - frameSeq, frameSeqI); - } - } - } - } + // void _handleIFrameWithSpsPpsAndIdr( + // List frameData, int durationMs, int frameSeq, int frameSeqI) { + // // 清空缓冲区,丢弃I帧前所有未处理帧(只保留SPS/PPS/I帧) + // state.h264FrameBuffer.clear(); + // _extractAndBufferSpsPpsForBuffer( + // frameData, durationMs, frameSeq, frameSeqI); + // // 只要缓存有SPS/PPS就先写入,再写I帧本体(只写IDR) + // if (spsCache == null || ppsCache == null) { + // // 没有SPS/PPS缓存,丢弃本次I帧 + // return; + // } + // // 先写入SPS/PPS + // _addFrameToBuffer(spsCache!, TalkDataH264Frame_FrameTypeE.I, durationMs, + // frameSeq, frameSeqI); + // _addFrameToBuffer(ppsCache!, TalkDataH264Frame_FrameTypeE.I, durationMs, + // frameSeq, frameSeqI); + // // 分割I帧包,只写入IDR(type 5) + // List> nalus = []; + // int i = 0; + // List data = frameData; + // while (i < data.length - 3) { + // int start = -1; + // int next = -1; + // if (data[i] == 0x00 && data[i + 1] == 0x00) { + // if (data[i + 2] == 0x01) { + // start = i; + // i += 3; + // } else if (i + 3 < data.length && + // data[i + 2] == 0x00 && + // data[i + 3] == 0x01) { + // start = i; + // i += 4; + // } else { + // i++; + // continue; + // } + // next = i; + // while (next < data.length - 3) { + // if (data[next] == 0x00 && + // data[next + 1] == 0x00 && + // ((data[next + 2] == 0x01) || + // (data[next + 2] == 0x00 && data[next + 3] == 0x01))) { + // break; + // } + // next++; + // } + // nalus.add(data.sublist(start, next)); + // i = next; + // } else { + // i++; + // } + // } + // int nalusTotalLen = + // nalus.isNotEmpty ? nalus.fold(0, (p, n) => p + n.length) : 0; + // if (nalus.isEmpty && data.isNotEmpty) { + // nalus.add(data); + // } else if (nalus.isNotEmpty && nalusTotalLen < data.length) { + // nalus.add(data.sublist(nalusTotalLen)); + // } + // for (final nalu in nalus) { + // int offset = 0; + // if (nalu.length >= 4 && nalu[0] == 0x00 && nalu[1] == 0x00) { + // if (nalu[2] == 0x01) + // offset = 3; + // else if (nalu[2] == 0x00 && nalu[3] == 0x01) offset = 4; + // } + // if (nalu.length > offset) { + // int naluType = nalu[offset] & 0x1F; + // if (naluType == 5) { + // _addFrameToBuffer(nalu, TalkDataH264Frame_FrameTypeE.I, durationMs, + // frameSeq, frameSeqI); + // } + // } + // } + // } // 新增:P帧处理方法 - void _handlePFrame( - List frameData, int durationMs, int frameSeq, int frameSeqI) { - // 只写入P帧(type 1) - List> nalus = []; - int i = 0; - List data = frameData; - while (i < data.length - 3) { - int start = -1; - int next = -1; - if (data[i] == 0x00 && data[i + 1] == 0x00) { - if (data[i + 2] == 0x01) { - start = i; - i += 3; - } else if (i + 3 < data.length && - data[i + 2] == 0x00 && - data[i + 3] == 0x01) { - start = i; - i += 4; - } else { - i++; - continue; - } - next = i; - while (next < data.length - 3) { - if (data[next] == 0x00 && - data[next + 1] == 0x00 && - ((data[next + 2] == 0x01) || - (data[next + 2] == 0x00 && data[next + 3] == 0x01))) { - break; - } - next++; - } - nalus.add(data.sublist(start, next)); - i = next; - } else { - i++; - } - } - int nalusTotalLen = - nalus.isNotEmpty ? nalus.fold(0, (p, n) => p + n.length) : 0; - if (nalus.isEmpty && data.isNotEmpty) { - nalus.add(data); - } else if (nalus.isNotEmpty && nalusTotalLen < data.length) { - nalus.add(data.sublist(nalusTotalLen)); - } - for (final nalu in nalus) { - int offset = 0; - if (nalu.length >= 4 && nalu[0] == 0x00 && nalu[1] == 0x00) { - if (nalu[2] == 0x01) - offset = 3; - else if (nalu[2] == 0x00 && nalu[3] == 0x01) offset = 4; - } - if (nalu.length > offset) { - int naluType = nalu[offset] & 0x1F; - if (naluType == 1) { - _addFrameToBuffer(nalu, TalkDataH264Frame_FrameTypeE.P, durationMs, - frameSeq, frameSeqI); - } - } - } - } + // void _handlePFrame( + // List frameData, int durationMs, int frameSeq, int frameSeqI) { + // // 只写入P帧(type 1) + // List> nalus = []; + // int i = 0; + // List data = frameData; + // while (i < data.length - 3) { + // int start = -1; + // int next = -1; + // if (data[i] == 0x00 && data[i + 1] == 0x00) { + // if (data[i + 2] == 0x01) { + // start = i; + // i += 3; + // } else if (i + 3 < data.length && + // data[i + 2] == 0x00 && + // data[i + 3] == 0x01) { + // start = i; + // i += 4; + // } else { + // i++; + // continue; + // } + // next = i; + // while (next < data.length - 3) { + // if (data[next] == 0x00 && + // data[next + 1] == 0x00 && + // ((data[next + 2] == 0x01) || + // (data[next + 2] == 0x00 && data[next + 3] == 0x01))) { + // break; + // } + // next++; + // } + // nalus.add(data.sublist(start, next)); + // i = next; + // } else { + // i++; + // } + // } + // int nalusTotalLen = + // nalus.isNotEmpty ? nalus.fold(0, (p, n) => p + n.length) : 0; + // if (nalus.isEmpty && data.isNotEmpty) { + // nalus.add(data); + // } else if (nalus.isNotEmpty && nalusTotalLen < data.length) { + // nalus.add(data.sublist(nalusTotalLen)); + // } + // for (final nalu in nalus) { + // int offset = 0; + // if (nalu.length >= 4 && nalu[0] == 0x00 && nalu[1] == 0x00) { + // if (nalu[2] == 0x01) + // offset = 3; + // else if (nalu[2] == 0x00 && nalu[3] == 0x01) offset = 4; + // } + // if (nalu.length > offset) { + // int naluType = nalu[offset] & 0x1F; + // if (naluType == 1) { + // _addFrameToBuffer(nalu, TalkDataH264Frame_FrameTypeE.P, durationMs, + // frameSeq, frameSeqI); + // } + // } + // } + // } // 切换清晰度的方法,后续补充具体实现 void onQualityChanged(String quality) async { @@ -1293,30 +1370,56 @@ class TalkViewNativeDecodeLogic extends BaseGetXController { // 新增:重置解码器方法 Future _resetDecoderForNewStream(int width, int height) async { try { + // 先停止帧处理定时器 + _stopFrameProcessTimer(); + + // 释放旧解码器 if (state.textureId.value != null) { await VideoDecodePlugin.releaseDecoder(); - Future.microtask(() => state.textureId.value = null); + state.textureId.value = null; } + + // 等待一小段时间确保资源释放完成 + await Future.delayed(Duration(milliseconds: 100)); + + // 创建新的解码器配置 final config = VideoDecoderConfig( width: width, height: height, codecType: 'h264', ); + + // 初始化新解码器 final textureId = await VideoDecodePlugin.initDecoder(config); if (textureId != null) { - Future.microtask(() => state.textureId.value = textureId); + state.textureId.value = textureId; AppLog.log('frameSeq回绕后解码器初始化成功:textureId=$textureId'); + + // 设置帧渲染监听 VideoDecodePlugin.setOnFrameRenderedListener((textureId) { AppLog.log('已经开始渲染======='); // 只有真正渲染出首帧时才关闭loading - Future.microtask(() => state.isLoading.value = false); + state.isLoading.value = false; }); + + // 重新启动帧处理定时器 + _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; } - _startFrameProcessTimer(); } catch (e) { AppLog.log('frameSeq回绕时解码器初始化错误: $e'); + state.isLoading.value = false; } } } From 2e9ee167b3ed54e02c6da27605e81459a83048fc Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 18 Jun 2025 15:13:24 +0800 Subject: [PATCH 3/7] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E4=BC=A0=E9=80=92?= =?UTF-8?q?=E5=88=B0=E5=AF=B9=E8=AE=B2=E9=A1=B5=E6=97=B6=E6=90=BA=E5=B8=A6?= =?UTF-8?q?=E5=8E=9F=E5=A7=8B=E6=B6=88=E6=81=AF=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handle/impl/udp_talk_data_handler.dart | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 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 028ffb73..96330232 100644 --- a/lib/talk/starChart/handle/impl/udp_talk_data_handler.dart +++ b/lib/talk/starChart/handle/impl/udp_talk_data_handler.dart @@ -33,7 +33,10 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle if (scpMessage.Payload != null) { final TalkData talkData = scpMessage.Payload; // 处理音视频数据 - _handleTalkData(talkData: talkData); + _handleTalkData( + talkData: talkData, + scpMessage: scpMessage, + ); } } @@ -93,12 +96,15 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle return hexList.join(''); } - void _handleTalkData({required TalkData talkData}) { + void _handleTalkData({ + required TalkData talkData, + required ScpMessage scpMessage, + }) { if (talkData == null) return; final contentType = talkData.contentType; switch (contentType) { case TalkData_ContentTypeE.H264: - _handleVideoH264(talkData); + _handleVideoH264(talkData, scpMessage); break; case TalkData_ContentTypeE.Image: _handleVideoImage(talkData); @@ -113,10 +119,10 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle } /// 处理h264协议的数据 - void _handleVideoH264(TalkData talkData) { + void _handleVideoH264(TalkData talkData, ScpMessage scpMessage) { final TalkDataH264Frame talkDataH264Frame = TalkDataH264Frame(); talkDataH264Frame.mergeFromBuffer(talkData.content); - frameHandler.handleFrame(talkDataH264Frame, talkData); + frameHandler.handleFrame(talkDataH264Frame, talkData, scpMessage); } /// 处理图片数据 From 370d10df7d076caa6264867e43e4254cb998306a Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 18 Jun 2025 15:13:43 +0800 Subject: [PATCH 4/7] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E5=BD=93=E5=A4=84?= =?UTF-8?q?=E4=BA=8E=E5=AF=B9=E8=AE=B2=E8=BF=9B=E8=A1=8C=E6=97=B6=E4=B8=8D?= =?UTF-8?q?=E8=BF=9B=E8=A1=8C=E8=B7=B3=E8=BD=AC=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handle/impl/udp_talk_expect_handler.dart | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/talk/starChart/handle/impl/udp_talk_expect_handler.dart b/lib/talk/starChart/handle/impl/udp_talk_expect_handler.dart index c592b454..ed975f57 100644 --- a/lib/talk/starChart/handle/impl/udp_talk_expect_handler.dart +++ b/lib/talk/starChart/handle/impl/udp_talk_expect_handler.dart @@ -93,20 +93,21 @@ class UdpTalkExpectHandler extends ScpMessageBaseHandle } if (isWifiLockType || (talkExpectResp.rotate == 0 && - talkExpectResp.width == 640 && - talkExpectResp.height == 480)) { + talkExpectResp.width == 640 && + talkExpectResp.height == 480) && + talkStatus.status != TalkStatus.answeredSuccessfully) { Get.toNamed(Routers.imageTransmissionView); - return; } if (startChartManage - .getDefaultTalkExpect() - .videoType - .contains(VideoTypeE.H264)) { + .getDefaultTalkExpect() + .videoType + .contains(VideoTypeE.H264) && + talkStatus.status != TalkStatus.answeredSuccessfully) { Get.toNamed( Routers.h264View, ); - } else { + } else if (talkStatus.status != TalkStatus.answeredSuccessfully) { Get.toNamed( Routers.starChartTalkView, ); From b9258830f22fed2cf8f4e13b932187f28ff68577 Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 18 Jun 2025 15:14:36 +0800 Subject: [PATCH 5/7] =?UTF-8?q?fix:=E4=BC=A0=E9=80=92=E5=AF=B9=E8=AE=B2?= =?UTF-8?q?=E9=A1=B5=E7=9A=84=E7=9B=91=E5=90=AC=E6=95=B0=E6=8D=AE=E6=97=B6?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8E=9F=E5=A7=8B=E9=9F=B3=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/talk/starChart/handle/other/talk_data_model.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/talk/starChart/handle/other/talk_data_model.dart b/lib/talk/starChart/handle/other/talk_data_model.dart index a84f1d3d..f40f44d7 100644 --- a/lib/talk/starChart/handle/other/talk_data_model.dart +++ b/lib/talk/starChart/handle/other/talk_data_model.dart @@ -1,9 +1,12 @@ +import 'package:star_lock/talk/starChart/entity/scp_message.dart'; import 'package:star_lock/talk/starChart/proto/talk_data.pbserver.dart'; import 'package:star_lock/talk/starChart/proto/talk_data_h264_frame.pb.dart'; class TalkDataModel { TalkData? talkData; TalkDataH264Frame? talkDataH264Frame; + ScpMessage? scpMessage; - TalkDataModel({required this.talkData, this.talkDataH264Frame}); + TalkDataModel( + {required this.talkData, this.talkDataH264Frame, this.scpMessage}); } From f8847ca483330adfe5c7c50e08530a1c217a502e Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 18 Jun 2025 15:14:57 +0800 Subject: [PATCH 6/7] =?UTF-8?q?fix:=E8=B0=83=E5=A4=A7=E5=B8=A7=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=8F=91=E9=80=81=E9=80=9F=E7=8E=87=E5=92=8C=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E7=BC=93=E5=86=B2=E5=8C=BA=E5=A4=A7=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../starChart/views/native/talk_view_native_decode_state.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 c9be5f57..7fa57cf2 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,8 +110,8 @@ class TalkViewNativeDecodeState { // H264帧缓冲区相关 final List> h264FrameBuffer = >[]; // H264帧缓冲区,存储帧数据和类型 - final int maxFrameBufferSize = 15; // 最大缓冲区大小 - final int targetFps = 30; // 目标解码帧率,只是为了快速填充native的缓冲区 + final int maxFrameBufferSize = 150; // 最大缓冲区大小 + final int targetFps = 60; // 目标解码帧率,只是为了快速填充native的缓冲区 Timer? frameProcessTimer; // 帧处理定时器 bool isProcessingFrame = false; // 是否正在处理帧 int lastProcessedTimestamp = 0; // 上次处理帧的时间戳 From c617d73bb4a2e0203f85c0e263d82d13072cd713 Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 18 Jun 2025 15:15:26 +0800 Subject: [PATCH 7/7] =?UTF-8?q?fix:=E4=BC=A0=E9=80=92=E5=AF=B9=E8=AE=B2?= =?UTF-8?q?=E9=A1=B5=E7=9A=84=E7=9B=91=E5=90=AC=E6=95=B0=E6=8D=AE=E6=97=B6?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8E=9F=E5=A7=8B=E9=9F=B3=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../starChart/handle/other/h264_frame_handler.dart | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/talk/starChart/handle/other/h264_frame_handler.dart b/lib/talk/starChart/handle/other/h264_frame_handler.dart index face6b68..1c1a87cc 100644 --- a/lib/talk/starChart/handle/other/h264_frame_handler.dart +++ b/lib/talk/starChart/handle/other/h264_frame_handler.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'package:star_lock/app_settings/app_settings.dart'; +import 'package:star_lock/talk/starChart/entity/scp_message.dart'; import 'package:star_lock/talk/starChart/handle/other/talk_data_model.dart'; import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart'; import '../../proto/talk_data_h264_frame.pb.dart'; @@ -12,8 +13,12 @@ class H264FrameHandler { H264FrameHandler({required this.onCompleteFrame}); - void handleFrame(TalkDataH264Frame frame, TalkData talkData) { - onCompleteFrame( - TalkDataModel(talkData: talkData, talkDataH264Frame: frame)); + void handleFrame( + TalkDataH264Frame frame, TalkData talkData, ScpMessage scpMessage) { + onCompleteFrame(TalkDataModel( + talkData: talkData, + talkDataH264Frame: frame, + scpMessage: scpMessage, + )); } }