From 0d8d5cb0c83c92d40aec0aa78661fd0d2230d5a5 Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 18 Apr 2025 10:33:51 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4h264=20webview?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/html/h264.html | 133 +++++----- .../lockDetail/lockDetail_logic.dart | 1 + .../normallyOpenMode_logic.dart | 73 +++--- .../starChart/constant/talk_constant.dart | 12 + .../handle/impl/udp_talk_data_handler.dart | 78 ++---- .../handle/other/h264_frame_handler.dart | 94 +------ .../handle/other/packet_loss_statistics.dart | 94 +++++++ lib/talk/starChart/star_chart_manage.dart | 36 ++- .../views/talkView/talk_view_page.dart | 2 +- .../starChart/webView/h264_web_logic.dart | 231 +++++++++--------- 10 files changed, 391 insertions(+), 363 deletions(-) create mode 100644 lib/talk/starChart/handle/other/packet_loss_statistics.dart diff --git a/assets/html/h264.html b/assets/html/h264.html index 63303e3c..0995b612 100644 --- a/assets/html/h264.html +++ b/assets/html/h264.html @@ -1,86 +1,87 @@ + - + play + - - - + + // Function to return to Flutter page + function returnToFlutter() { + notifyFlutter("Returning to Flutter page"); + } + + // 添加清理方法 + function cleanupJMuxer() { + if (jmuxer) { + try { + jmuxer.destroy(); + jmuxer = null; + console.log('JMuxer cleaned up successfully'); + window.Flutter.postMessage('cleanup_complete'); + } catch (e) { + console.error('Error cleaning up JMuxer:', e); + window.Flutter.postMessage('cleanup_error'); + } + } + } + - + + \ No newline at end of file diff --git a/lib/main/lockDetail/lockDetail/lockDetail_logic.dart b/lib/main/lockDetail/lockDetail/lockDetail_logic.dart index 03b69a5b..f3668a2c 100755 --- a/lib/main/lockDetail/lockDetail/lockDetail_logic.dart +++ b/lib/main/lockDetail/lockDetail/lockDetail_logic.dart @@ -7,6 +7,7 @@ import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:star_lock/apm/apm_helper.dart'; +import 'package:star_lock/appRouters.dart'; import 'package:star_lock/common/XSConstantMacro/XSConstantMacro.dart'; import 'package:star_lock/login/login/entity/LoginEntity.dart'; import 'package:star_lock/main/lockDetail/electronicKey/electronicKeyList/entity/ElectronicKeyListEntity.dart'; diff --git a/lib/main/lockDetail/lockSet/normallyOpenMode/normallyOpenMode_logic.dart b/lib/main/lockDetail/lockSet/normallyOpenMode/normallyOpenMode_logic.dart index c748da0c..c63750d7 100755 --- a/lib/main/lockDetail/lockSet/normallyOpenMode/normallyOpenMode_logic.dart +++ b/lib/main/lockDetail/lockSet/normallyOpenMode/normallyOpenMode_logic.dart @@ -1,4 +1,3 @@ - import 'dart:async'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; @@ -17,51 +16,59 @@ import '../../../../tools/eventBusEventManage.dart'; import '../../../../tools/storage.dart'; import 'normallyOpenMode_state.dart'; -class NormallyOpenModeLogic extends BaseGetXController{ +class NormallyOpenModeLogic extends BaseGetXController { NormallyOpenModeState state = NormallyOpenModeState(); // 配置锁的常开模式设置 - Future configPassageMode() async{ - if(state.weekDays.value.isEmpty){ + Future configPassageMode() async { + if (state.weekDays.value.isEmpty) { showToast('请选择常开日期'.tr); return; } - if(state.endTimeMinute.value < state.beginTimeMinute.value){ + if (state.endTimeMinute.value < state.beginTimeMinute.value) { showToast('结束时间不能小于开始时间哦'.tr); return; } final List passageModeConfig = []; final Map map = { - 'isAllDay':state.isAllDay.value, - 'weekDays':state.weekDays.value, - 'startDate':state.beginTimeMinute.value, - 'endDate':state.endTimeMinute.value, + 'isAllDay': state.isAllDay.value, + 'weekDays': state.weekDays.value, + 'startDate': state.beginTimeMinute.value, + 'endDate': state.endTimeMinute.value, }; passageModeConfig.add(map); final LoginEntity entity = await ApiRepository.to.setNormallyModeData( lockId: state.lockSetInfoData.value.lockId!, - passageMode:state.isOpenNormallyOpenMode.value == true ? 1:0, + passageMode: state.isOpenNormallyOpenMode.value == true ? 1 : 0, passageModeConfig: passageModeConfig, ); - if(entity.errorCode!.codeIsSuccessful){ - showToast('操作成功'.tr, something: (){ + if (entity.errorCode!.codeIsSuccessful) { + showToast('操作成功'.tr, something: () { eventBus.fire(RefreshLockListInfoDataEvent()); - state.lockSetInfoData.value.lockSettingInfo!.passageMode = state.isOpenNormallyOpenMode.value == true ? 1:0; - eventBus.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value)); - eventBus.fire(LockSetChangeSetRefreshLockDetailWithType(2, state.lockSetInfoData.value.lockSettingInfo!.passageMode!.toString())); + state.lockSetInfoData.value.lockSettingInfo!.passageMode = + state.isOpenNormallyOpenMode.value == true ? 1 : 0; + eventBus + .fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value)); + eventBus.fire(LockSetChangeSetRefreshLockDetailWithType( + 2, + state.lockSetInfoData.value.lockSettingInfo!.passageMode! + .toString())); + Get.back(); }); } } // 获取解析后的数据 late StreamSubscription _replySubscription; + void _initReplySubscription() { - _replySubscription = EventBusManager().eventBus!.on().listen((Reply reply) { - if(reply is SetSupportFunctionsWithParametersReply) { + _replySubscription = + EventBusManager().eventBus!.on().listen((Reply reply) { + if (reply is SetSupportFunctionsWithParametersReply) { _replySetSupportFunctionsWithParameters(reply); } @@ -93,7 +100,7 @@ class NormallyOpenModeLogic extends BaseGetXController{ // 设置自动落锁数据解析 Future _replySetSupportFunctionsWithParameters(Reply reply) async { final int status = reply.data[2]; - switch(status){ + switch (status) { case 0x00: //成功 state.sureBtnState.value = 0; @@ -111,38 +118,44 @@ class NormallyOpenModeLogic extends BaseGetXController{ // 设置支持功能(带参数) Future sendAutoLock() async { - if(state.sureBtnState.value == 1){ + if (state.sureBtnState.value == 1) { return; } state.sureBtnState.value = 1; showEasyLoading(); - showBlueConnetctToastTimer(action: (){ + showBlueConnetctToastTimer(action: () { dismissEasyLoading(); state.sureBtnState.value = 0; }); - BlueManage().blueSendData(BlueManage().connectDeviceName, (BluetoothConnectionState connectionState) async { + BlueManage().blueSendData(BlueManage().connectDeviceName, + (BluetoothConnectionState connectionState) async { if (connectionState == BluetoothConnectionState.connected) { - final List? privateKey = await Storage.getStringList(saveBluePrivateKey); - final List getPrivateKeyList = changeStringListToIntList(privateKey!); + final List? privateKey = + await Storage.getStringList(saveBluePrivateKey); + final List getPrivateKeyList = + changeStringListToIntList(privateKey!); final List? token = await Storage.getStringList(saveBlueToken); final List getTokenList = changeStringListToIntList(token!); - final List? publicKey = await Storage.getStringList(saveBluePublicKey); - final List getPublicKeyList = changeStringListToIntList(publicKey!); + final List? publicKey = + await Storage.getStringList(saveBluePublicKey); + final List getPublicKeyList = + changeStringListToIntList(publicKey!); String weekStr = '00000000'; for (var day in state.weekDays.value) { - final int index = day % 7; // 将周日的索引转换为 0 - weekStr = '${weekStr.substring(0, index)}1${weekStr.substring(index + 1)}'; + final int index = day % 7; // 将周日的索引转换为 0 + weekStr = + '${weekStr.substring(0, index)}1${weekStr.substring(index + 1)}'; } // 倒序 weekStr weekStr = weekStr.split('').reversed.join(''); final int number = int.parse(weekStr, radix: 2); final List list = []; - list.add(state.isOpenNormallyOpenMode.value == true ? 1:0); + list.add(state.isOpenNormallyOpenMode.value == true ? 1 : 0); final int bieginTime = state.beginTimeMinute.value; final double bieginDouble = bieginTime / 256; @@ -159,7 +172,7 @@ class NormallyOpenModeLogic extends BaseGetXController{ list.add(end1); list.add(end2); - list.add(state.isAllDay.value == 1 ? 1:0); + list.add(state.isAllDay.value == 1 ? 1 : 0); list.add(number); list.add(0); @@ -177,7 +190,7 @@ class NormallyOpenModeLogic extends BaseGetXController{ dismissEasyLoading(); cancelBlueConnetctToastTimer(); state.sureBtnState.value = 0; - if(state.ifCurrentScreen.value == true){ + if (state.ifCurrentScreen.value == true) { showBlueConnetctToast(); } } diff --git a/lib/talk/starChart/constant/talk_constant.dart b/lib/talk/starChart/constant/talk_constant.dart index a3847d50..60d57a0d 100644 --- a/lib/talk/starChart/constant/talk_constant.dart +++ b/lib/talk/starChart/constant/talk_constant.dart @@ -1,7 +1,19 @@ +import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart'; + class TalkConstant { // TalkPing 未收到回复超时时间(s) static const int talkePingOverTime = 10; static const int talkeDataOverTime = 10; + // 收到TalkRequest 未处理超时时间(s) static const int talkeRequestOverTime = 30; + + static TalkExpectReq ImageExpect = TalkExpectReq( + videoType: [VideoTypeE.IMAGE], + audioType: [AudioTypeE.G711], + ); + static TalkExpectReq H264Expect = TalkExpectReq( + videoType: [VideoTypeE.H264], + audioType: [AudioTypeE.G711], + ); } 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 e2caed39..0a9e7ab3 100644 --- a/lib/talk/starChart/handle/impl/udp_talk_data_handler.dart +++ b/lib/talk/starChart/handle/impl/udp_talk_data_handler.dart @@ -4,6 +4,7 @@ import 'package:star_lock/talk/call/g711.dart'; import 'package:star_lock/talk/starChart/constant/message_type_constant.dart'; import 'package:star_lock/talk/starChart/entity/scp_message.dart'; import 'package:star_lock/talk/starChart/handle/other/h264_frame_handler.dart'; +import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart'; import 'package:star_lock/talk/starChart/handle/scp_message_base_handle.dart'; import 'package:star_lock/talk/starChart/handle/scp_message_handle.dart'; import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart'; @@ -14,62 +15,6 @@ import 'package:star_lock/talk/starChart/proto/talk_data_h264_frame.pb.dart'; // implements ScpMessageHandler { class UdpTalkDataHandler extends ScpMessageBaseHandle implements ScpMessageHandler { - factory UdpTalkDataHandler() { - return _instance; - } - - UdpTalkDataHandler._internal(); - - static final UdpTalkDataHandler _instance = UdpTalkDataHandler._internal(); - - int _recentRecvDataRate = 0; - int _recentRecvPacketCount = 0; - int _recentSendDataRate = 0; - int _recentSendPacketCount = 0; - - int _lastRecvDataRate = 0; - int _lastRecvPacketCount = 0; - int _lastSendDataRate = 0; - int _lastSendPacketCount = 0; - - void updateRecvDataRate(int dataSize) { - _recentRecvDataRate += dataSize; - _recentRecvPacketCount++; - } - - void updateSendDataRate(int dataSize) { - _recentSendDataRate += dataSize; - _recentSendPacketCount++; - } - - void resetDataRates() { - _lastRecvDataRate = _recentRecvDataRate; - _lastRecvPacketCount = _recentRecvPacketCount; - _lastSendDataRate = _recentSendDataRate; - _lastSendPacketCount = _recentSendPacketCount; - - _recentRecvDataRate = 0; - _recentRecvPacketCount = 0; - _recentSendDataRate = 0; - _recentSendPacketCount = 0; - } - - int getLastRecvDataRate() { - return _lastRecvDataRate; - } - - int getLastRecvPacketCount() { - return _lastRecvPacketCount; - } - - int getLastSendDataRate() { - return _lastSendDataRate; - } - - int getLastSendPacketCount() { - return _lastSendPacketCount; - } - @override void handleReq(ScpMessage scpMessage) {} @@ -95,6 +40,17 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle return buffer.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(); } + // 在类顶部添加异步日志方法 + void _asyncLog(String message) { + Future.microtask(() { + try { + AppLog.log(message); + } catch (e) { + // 错误处理 + } + }); + } + @override deserializePayload( {required int payloadType, @@ -105,10 +61,12 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle int? spTotal, int? spIndex, int? messageId}) { - // AppLog.log( - // '没有组包之前的每一个包的数据:${byte.length} messageId:$messageId spTotal:$spTotal spIndex:$spIndex PayloadLength:$PayloadLength,byte:${bufferToHexString(byte)}'); + // 获取统计信息 + final stats = PacketLossStatistics().getStatistics(); + _asyncLog('丢包统计: $stats'); + // _asyncLog( + // '分包数据:messageId:$messageId [$spIndex/$spTotal] PayloadLength:$PayloadLength'); if (messageType == MessageTypeConstant.RealTimeData) { - // 回声测试 if (spTotal != null && spTotal > 1 && messageId != null && @@ -161,8 +119,6 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle final TalkDataH264Frame talkDataH264Frame = TalkDataH264Frame(); talkDataH264Frame.mergeFromBuffer(talkData.content); frameHandler.handleFrame(talkDataH264Frame); - // AppLog.log( - // "帧:${talkDataH264Frame.frameType},帧序号:${talkDataH264Frame.frameSeq},对应I帧序号:${talkDataH264Frame.frameSeqI}"); } /// 处理图片数据 diff --git a/lib/talk/starChart/handle/other/h264_frame_handler.dart b/lib/talk/starChart/handle/other/h264_frame_handler.dart index 2b4ea1ee..cd2f8bc2 100644 --- a/lib/talk/starChart/handle/other/h264_frame_handler.dart +++ b/lib/talk/starChart/handle/other/h264_frame_handler.dart @@ -1,105 +1,17 @@ import 'dart:collection'; import 'dart:typed_data'; +import 'package:flutter/services.dart'; import 'package:star_lock/app_settings/app_settings.dart'; import '../../proto/talk_data_h264_frame.pb.dart'; class H264FrameHandler { - final LinkedHashMap _frameBuffer = LinkedHashMap(); - final void Function(List frameData) onCompleteFrame; - final LinkedHashMap _frameTypeIndex = LinkedHashMap(); + final void Function(List frameData) onCompleteFrame; H264FrameHandler({required this.onCompleteFrame}); void handleFrame(TalkDataH264Frame frame) { - // 存储帧 - _frameBuffer[frame.frameSeq] = frame; - _frameTypeIndex[frame.frameSeq] = frame.frameType; - - // 检查是否可以组装完整的 GOP (Group of Pictures) - _tryAssembleFrames(frame.frameSeq); - } - - void _tryAssembleFrames(int currentSeq) { - final List framesToProcess = []; - int? startFrameSeq; - - // 从当前帧开始向前找到最近的 I 帧或 P 帧 - for (int seq = currentSeq; seq >= 0; seq--) { - final frameType = _frameTypeIndex[seq]; - if (frameType == null) continue; - if (frameType == TalkDataH264Frame_FrameTypeE.I) { - startFrameSeq = seq; - break; - } else if (frameType == TalkDataH264Frame_FrameTypeE.P) { - if (_frameBuffer.containsKey(_frameBuffer[seq]!.frameSeqI)) { - startFrameSeq = seq; - break; - } else { - _frameBuffer.remove(seq); - _frameTypeIndex.remove(seq); - } - } - } - - if (startFrameSeq != null) { - for (int seq = startFrameSeq; _frameBuffer.containsKey(seq); seq++) { - framesToProcess.add(seq); - } - - if (framesToProcess.isNotEmpty) { - _processFrames(framesToProcess); - } - } else { - _clearOldFrames(currentSeq); - } - } - - void _processFrames(List frameSeqs) { - // 按顺序组装帧数据 - // final List assembledData = []; - // - // for (var seq in frameSeqs) { - // final frame = _frameBuffer[seq]!; - // assembledData.addAll(frame.frameData); - // - // // 处理完后从缓冲区移除 - // _frameBuffer.remove(seq); - // } - // - // // 回调完整的帧数据 - // onCompleteFrame(assembledData); - // Calculate the total length of the assembled data - int totalLength = frameSeqs.fold( - 0, (sum, seq) => sum + _frameBuffer[seq]!.frameData.length); - - // Allocate a buffer for the assembled data - final assembledData = Uint8List(totalLength); - int offset = 0; - - for (var seq in frameSeqs) { - final frame = _frameBuffer[seq]!; - assembledData.setRange( - offset, offset + frame.frameData.length, frame.frameData); - offset += frame.frameData.length; - - // Remove the frame from the buffer after processing - _frameBuffer.remove(seq); - _frameTypeIndex.remove(seq); - } - - // Callback with the complete frame data - onCompleteFrame(assembledData); - } - - void clear() { - _frameBuffer.clear(); - } - - void _clearOldFrames(int currentSeq) { - // 清理比当前帧序列旧的帧 - _frameBuffer.removeWhere((seq, frame) => seq < currentSeq - 200); // 调整阈值 - _frameTypeIndex.removeWhere((seq, frameType) => seq < currentSeq - 200); + onCompleteFrame(frame.frameData); } } diff --git a/lib/talk/starChart/handle/other/packet_loss_statistics.dart b/lib/talk/starChart/handle/other/packet_loss_statistics.dart new file mode 100644 index 00000000..34dd2282 --- /dev/null +++ b/lib/talk/starChart/handle/other/packet_loss_statistics.dart @@ -0,0 +1,94 @@ +import 'dart:collection'; + +class PacketLossStatistics { + static final PacketLossStatistics _instance = + PacketLossStatistics._internal(); + factory PacketLossStatistics() => _instance; + PacketLossStatistics._internal(); + + // 记录每个messageId的分包信息 + // key: messageId, value: {totalPackets, receivedPackets} + final Map _packetsMap = HashMap(); + + // 统计信息 + int _totalMessages = 0; // 总消息数 + int _lostMessages = 0; // 丢包的消息数 + int _totalPackets = 0; // 总分包数 + int _lostPackets = 0; // 丢失的分包数 + + // 记录分包数据 + void recordPacket(int messageId, int currentIndex, int totalPackets) { + if (!_packetsMap.containsKey(messageId)) { + _packetsMap[messageId] = PacketInfo(totalPackets); + _totalMessages++; + _totalPackets += totalPackets; + } + + _packetsMap[messageId]!.receivedPackets.add(currentIndex); + + // 如果收到了该messageId的最后一个包,进行统计 + if (currentIndex == totalPackets) { + _checkPacketLoss(messageId); + } + } + + // 检查丢包情况 + void _checkPacketLoss(int messageId) { + final info = _packetsMap[messageId]!; + + // 检查是否有丢失的包 + int received = info.receivedPackets.length; + if (received < info.totalPackets) { + _lostMessages++; + _lostPackets += (info.totalPackets - received); + } + + // 清理该messageId的记录,避免内存泄漏 + _packetsMap.remove(messageId); + } + + // 获取丢包率统计信息 + PacketLossInfo getStatistics() { + if (_totalMessages == 0 || _totalPackets == 0) { + return PacketLossInfo(0.0, 0.0); + } + + // 计算消息级别的丢包率 + double messageLossRate = (_lostMessages / _totalMessages) * 100; + + // 计算分包级别的丢包率 + double packetLossRate = (_lostPackets / _totalPackets) * 100; + + return PacketLossInfo(messageLossRate, packetLossRate); + } + + // 重置统计数据 + void reset() { + _packetsMap.clear(); + _totalMessages = 0; + _lostMessages = 0; + _totalPackets = 0; + _lostPackets = 0; + } +} + +// 分包信息类 +class PacketInfo { + final int totalPackets; + final Set receivedPackets = HashSet(); + + PacketInfo(this.totalPackets); +} + +// 丢包统计信息类 +class PacketLossInfo { + final double messageLossRate; // 消息丢失率 + final double packetLossRate; // 分包丢失率 + + PacketLossInfo(this.messageLossRate, this.packetLossRate); + + @override + String toString() { + return 'Message Loss Rate: ${messageLossRate.toStringAsFixed(2)}%, Packet Loss Rate: ${packetLossRate.toStringAsFixed(2)}%'; + } +} diff --git a/lib/talk/starChart/star_chart_manage.dart b/lib/talk/starChart/star_chart_manage.dart index 0e0e9eb8..85785514 100644 --- a/lib/talk/starChart/star_chart_manage.dart +++ b/lib/talk/starChart/star_chart_manage.dart @@ -22,6 +22,7 @@ import 'package:star_lock/talk/starChart/constant/ip_constant.dart'; import 'package:star_lock/talk/starChart/constant/listen_addr_type_constant.dart'; import 'package:star_lock/talk/starChart/constant/message_type_constant.dart'; import 'package:star_lock/talk/starChart/constant/payload_type_constant.dart'; +import 'package:star_lock/talk/starChart/constant/talk_constant.dart'; import 'package:star_lock/talk/starChart/constant/talk_status.dart'; import 'package:star_lock/talk/starChart/entity/relay_info_entity.dart'; import 'package:star_lock/talk/starChart/entity/report_information_data.dart'; @@ -31,6 +32,7 @@ import 'package:star_lock/talk/starChart/exception/start_chart_message_exception import 'package:star_lock/talk/starChart/handle/impl/debug_Info_model.dart'; import 'package:star_lock/talk/starChart/handle/impl/udp_talk_data_handler.dart'; import 'package:star_lock/talk/starChart/handle/other/do_sign.dart'; +import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart'; import 'package:star_lock/talk/starChart/handle/other/talke_data_over_time_timer_manager.dart'; import 'package:star_lock/talk/starChart/handle/other/talke_ping_over_time_timer_manager.dart'; import 'package:star_lock/talk/starChart/handle/other/talke_request_over_time_timer_manager.dart'; @@ -111,10 +113,7 @@ class StartChartManage { final int _maxPayloadSize = 8 * 1024; // 分包大小 // 默认通话的期望数据格式 - TalkExpectReq _defaultTalkExpect = TalkExpectReq( - videoType: [VideoTypeE.IMAGE], - audioType: [AudioTypeE.G711], - ); + TalkExpectReq _defaultTalkExpect = TalkConstant.H264Expect; String relayPeerId = ''; // 中继peerId @@ -342,7 +341,7 @@ class StartChartManage { } } - // 发送RbcuConfirm 打洞确认 + // 发送打洞确认包 void _sendRbcuConfirmMessage() async { RbcuConfirm( sessionId: _rbcuSessionId, @@ -596,10 +595,18 @@ class StartChartManage { // 发送拒绝接听消息 void startTalkRejectMessageTimer() async { try { + int count = 0; + final int maxCount = 10; // 最大执行次数为10秒 + talkRejectTimer ??= Timer.periodic( Duration(seconds: _defaultIntervalTime), (Timer timer) async { _sendTalkRejectMessage(); + count++; + if (count >= maxCount) { + timer.cancel(); + talkRejectTimer = null; + } }, ); } catch (e) { @@ -1035,6 +1042,14 @@ 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) { @@ -1130,10 +1145,7 @@ class StartChartManage { } void reSetDefaultTalkExpect() { - _defaultTalkExpect = TalkExpectReq( - videoType: [VideoTypeE.IMAGE], - audioType: [AudioTypeE.G711], - ); + _defaultTalkExpect = TalkConstant.H264Expect; } TalkExpectReq getDefaultTalkExpect() { @@ -1152,10 +1164,7 @@ class StartChartManage { /// 修改预期接收到的数据 void sendImageVideoAndG711AudioTalkExpectData() { - final talkExpectReq = TalkExpectReq( - videoType: [VideoTypeE.IMAGE], - audioType: [AudioTypeE.G711], - ); + final talkExpectReq = TalkConstant.H264Expect; changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer( talkExpect: talkExpectReq); } @@ -1216,6 +1225,7 @@ class StartChartManage { await Storage.removerStarChartRegisterNodeInfo(); // 关闭udp服务 closeUdpSocket(); + PacketLossStatistics().reset(); } /// 重置数据 diff --git a/lib/talk/starChart/views/talkView/talk_view_page.dart b/lib/talk/starChart/views/talkView/talk_view_page.dart index 9492bc59..38b22343 100644 --- a/lib/talk/starChart/views/talkView/talk_view_page.dart +++ b/lib/talk/starChart/views/talkView/talk_view_page.dart @@ -615,7 +615,7 @@ class _TalkViewPageState extends State state.videoBuffer.clear(); state.listData.value = Uint8List(0); CallTalk().finishAVData(); - UdpTalkDataHandler().resetDataRates(); + // UdpTalkDataHandler().resetDataRates(); super.dispose(); } } diff --git a/lib/talk/starChart/webView/h264_web_logic.dart b/lib/talk/starChart/webView/h264_web_logic.dart index 4b7edc5e..fdbaad3f 100644 --- a/lib/talk/starChart/webView/h264_web_logic.dart +++ b/lib/talk/starChart/webView/h264_web_logic.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:collection'; import 'dart:io'; import 'dart:ui' as ui; import 'dart:math'; // Import the math package to use sqrt @@ -23,11 +24,13 @@ 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'; import 'package:star_lock/talk/starChart/constant/talk_status.dart'; +import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart'; 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/talkView/talk_view_state.dart'; import 'package:star_lock/talk/starChart/webView/h264_web_view_state.dart'; +import 'package:star_lock/tools/G711Tool.dart'; import 'package:star_lock/tools/bugly/bugly_tool.dart'; import 'package:webview_flutter/webview_flutter.dart'; @@ -38,6 +41,14 @@ class H264WebViewLogic extends BaseGetXController { final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state; + // 添加模拟数据相关变量 + static const int CHUNK_SIZE = 4096; + Timer? _mockDataTimer; + // 定义音频帧缓冲和发送函数 + final List _bufferedAudioFrames = []; + final Queue> _frameBuffer = Queue>(); + static const int FRAME_BUFFER_SIZE = 25; + @override void onInit() { super.onInit(); @@ -57,7 +68,7 @@ class H264WebViewLogic extends BaseGetXController { _loadLocalHtml(); // 创建流数据监听 _createFramesStreamListen(); - + // playLocalTestVideo(); _startListenTalkStatus(); state.talkStatus.value = state.startChartTalkStatus.status; // 初始化音频播放器 @@ -86,11 +97,43 @@ class H264WebViewLogic extends BaseGetXController { void _createFramesStreamListen() async { state.talkDataRepository.talkDataStream.listen((TalkData event) async { - // 发送数据给js处理 - _sendBufferedData(event.content); + // 添加新帧到缓冲区 + _frameBuffer.add(event.content); + + // 当缓冲区超过最大容量时,发送最早的帧并移除 + while (_frameBuffer.length > FRAME_BUFFER_SIZE) { + if (_frameBuffer.isNotEmpty) { + final frame = _frameBuffer.removeFirst(); + await _sendBufferedData(frame); + } + } }); } + /// 播放本地测试视频文件 + // Future playLocalTestVideo() async { + // try { + // ByteData data = await rootBundle.load('assets/html/demo.h264'); + // List bytes = data.buffer.asUint8List(); + // + // int offset = 0; + // _mockDataTimer = Timer.periodic(Duration(milliseconds: 40), (timer) { + // if (offset >= bytes.length) { + // timer.cancel(); + // return; + // } + // + // int end = min(offset + CHUNK_SIZE, bytes.length); + // List chunk = bytes.sublist(offset, end); + // _sendBufferedData(chunk); + // + // offset += CHUNK_SIZE; + // }); + // } catch (e) { + // AppLog.log('加载测试视频文件失败: $e'); + // } + // } + /// 加载html文件 Future _loadLocalHtml() async { // 加载 HTML 文件内容 @@ -226,15 +269,17 @@ class H264WebViewLogic extends BaseGetXController { //开始录音 Future startProcessingAudio() async { - // 增加录音帧监听器和错误监听器 - state.voiceProcessor?.addFrameListener(_onFrame); - state.voiceProcessor?.addErrorListener(_onError); try { if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) { await state.voiceProcessor?.start(state.frameLength, state.sampleRate); final bool? isRecording = await state.voiceProcessor?.isRecording(); state.isRecordingAudio.value = isRecording!; state.startRecordingAudioTime.value = DateTime.now(); + + // 增加录音帧监听器和错误监听器 + state.voiceProcessor + ?.addFrameListeners([_onFrame]); + state.voiceProcessor?.addErrorListener(_onError); } else { // state.errorMessage.value = 'Recording permission not granted'; } @@ -254,8 +299,8 @@ class H264WebViewLogic extends BaseGetXController { state.endRecordingAudioTime.value = DateTime.now(); // 计算录音的持续时间 - final duration = state.endRecordingAudioTime.value! - .difference(state.startRecordingAudioTime.value!); + final Duration duration = state.endRecordingAudioTime.value + .difference(state.startRecordingAudioTime.value); state.recordingAudioTime.value = duration.inSeconds; } on PlatformException catch (ex) { @@ -267,25 +312,71 @@ class H264WebViewLogic extends BaseGetXController { } } - // 音频帧处理 +// 音频帧处理 Future _onFrame(List frame) async { - // 预处理和转码操作放到异步计算线程 - // final processedFrame = await compute(preprocessAudio, frame); - // final list = listLinearToALaw(processedFrame); - final List processedFrame = preprocessAudio(frame); - final List list = listLinearToALaw(processedFrame); + // 添加最大缓冲限制 + if (_bufferedAudioFrames.length > state.frameLength * 3) { + _bufferedAudioFrames.clear(); // 清空过多积累的数据 + return; + } - final int ms = DateTime.now().millisecondsSinceEpoch - - state.startRecordingAudioTime.value.millisecondsSinceEpoch; + // 首先应用固定增益提升基础音量 + List amplifiedFrame = _applyGain(frame, 1.6); + // 编码为G711数据 + List encodedData = G711Tool.encode(amplifiedFrame, 0); // 0表示A-law + _bufferedAudioFrames.addAll(encodedData); + // 使用相对时间戳 + final int ms = DateTime.now().millisecondsSinceEpoch % 1000000; // 使用循环时间戳 + int getFrameLength = state.frameLength; + if (Platform.isIOS) { + getFrameLength = state.frameLength * 2; + } - // 发送音频数据到UDP - await StartChartManage().sendTalkDataMessage( - talkData: TalkData( - content: list, - contentType: TalkData_ContentTypeE.G711, - durationMs: ms, - ), - ); + // 添加发送间隔控制 + if (_bufferedAudioFrames.length >= state.frameLength) { + try { + await StartChartManage().sendTalkDataMessage( + talkData: TalkData( + content: _bufferedAudioFrames, + contentType: TalkData_ContentTypeE.G711, + durationMs: ms, + ), + ); + } finally { + _bufferedAudioFrames.clear(); // 确保清理缓冲区 + } + } else { + _bufferedAudioFrames.addAll(encodedData); + } + } + +// 错误监听 + void _onError(VoiceProcessorException error) { + AppLog.log(error.message!); + } + +// 添加音频增益处理方法 + List _applyGain(List pcmData, double gainFactor) { + List result = List.filled(pcmData.length, 0); + + for (int i = 0; i < pcmData.length; i++) { + // PCM数据通常是有符号的16位整数 + int sample = pcmData[i]; + + // 应用增益 + double amplified = sample * gainFactor; + + // 限制在有效范围内,防止溢出 + if (amplified > 32767) { + amplified = 32767; + } else if (amplified < -32768) { + amplified = -32768; + } + + result[i] = amplified.toInt(); + } + + return result; } /// 挂断 @@ -297,6 +388,9 @@ class H264WebViewLogic extends BaseGetXController { // 拒绝 StartChartManage().startTalkRejectMessageTimer(); } + // _mockDataTimer?.cancel(); + // _mockDataTimer = null; + PacketLossStatistics().reset(); Get.back(); } @@ -315,7 +409,7 @@ class H264WebViewLogic extends BaseGetXController { }); final LockSetInfoEntity lockSetInfoEntity = - await ApiRepository.to.getLockSettingInfoData( + await ApiRepository.to.getLockSettingInfoData( lockId: lockId.toString(), ); if (lockSetInfoEntity.errorCode!.codeIsSuccessful) { @@ -333,93 +427,11 @@ class H264WebViewLogic extends BaseGetXController { } } - List preprocessAudio(List pcmList) { - // 简单的降噪处理 - final List processedList = []; - for (int pcmVal in pcmList) { - // 简单的降噪示例:将小于阈值的信号置为0 - if (pcmVal.abs() < 200) { - pcmVal = 0; - } - processedList.add(pcmVal); - } - return processedList; - } - - List listLinearToALaw(List pcmList) { - final List aLawList = []; - for (int pcmVal in pcmList) { - final int aLawVal = linearToALaw(pcmVal); - aLawList.add(aLawVal); - } - return aLawList; - } - - int linearToALaw(int pcmVal) { - const int ALAW_MAX = 0x7FFF; // 32767 - const int ALAW_BIAS = 0x84; // 132 - - int mask; - int seg; - int aLawVal; - - // Handle sign - if (pcmVal < 0) { - pcmVal = -pcmVal; - mask = 0x7F; // 127 (sign bit is 1) - } else { - mask = 0xFF; // 255 (sign bit is 0) - } - - // Add bias and clamp to ALAW_MAX - pcmVal += ALAW_BIAS; - if (pcmVal > ALAW_MAX) { - pcmVal = ALAW_MAX; - } - - // Determine segment - seg = search(pcmVal); - - // Calculate A-law value - if (seg >= 8) { - aLawVal = 0x7F ^ mask; // Clamp to maximum value - } else { - int quantized = (pcmVal >> (seg + 3)) & 0xF; - aLawVal = (seg << 4) | quantized; - aLawVal ^= 0xD5; // XOR with 0xD5 to match standard A-law table - } - - return aLawVal; - } - - int search(int val) { - final List table = [ - 0xFF, // Segment 0 - 0x1FF, // Segment 1 - 0x3FF, // Segment 2 - 0x7FF, // Segment 3 - 0xFFF, // Segment 4 - 0x1FFF, // Segment 5 - 0x3FFF, // Segment 6 - 0x7FFF // Segment 7 - ]; - const int size = 8; - for (int i = 0; i < size; i++) { - if (val <= table[i]) { - return i; - } - } - return size; - } - -// 错误监听 - void _onError(VoiceProcessorException error) { - AppLog.log(error.message!); - } @override void dispose() { - // TODO: implement dispose + // _mockDataTimer?.cancel(); + // _mockDataTimer = null; super.dispose(); StartChartManage().startTalkHangupMessageTimer(); state.animationController.dispose(); @@ -429,5 +441,6 @@ class H264WebViewLogic extends BaseGetXController { state.oneMinuteTimeTimer = null; stopProcessingAudio(); StartChartManage().reSetDefaultTalkExpect(); + _frameBuffer.clear(); } } From a0d2bdaa7a8877f6d5a6974a8d7bc403bb0e5c7e Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 18 Apr 2025 10:34:42 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E4=BD=BF=E7=94=A8Image=E5=AF=B9=E8=AE=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/talk/starChart/star_chart_manage.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/talk/starChart/star_chart_manage.dart b/lib/talk/starChart/star_chart_manage.dart index 85785514..7ffed8e5 100644 --- a/lib/talk/starChart/star_chart_manage.dart +++ b/lib/talk/starChart/star_chart_manage.dart @@ -113,7 +113,7 @@ class StartChartManage { final int _maxPayloadSize = 8 * 1024; // 分包大小 // 默认通话的期望数据格式 - TalkExpectReq _defaultTalkExpect = TalkConstant.H264Expect; + TalkExpectReq _defaultTalkExpect = TalkConstant.ImageExpect; String relayPeerId = ''; // 中继peerId @@ -1145,7 +1145,7 @@ class StartChartManage { } void reSetDefaultTalkExpect() { - _defaultTalkExpect = TalkConstant.H264Expect; + _defaultTalkExpect = TalkConstant.ImageExpect; } TalkExpectReq getDefaultTalkExpect() { @@ -1164,7 +1164,7 @@ class StartChartManage { /// 修改预期接收到的数据 void sendImageVideoAndG711AudioTalkExpectData() { - final talkExpectReq = TalkConstant.H264Expect; + final talkExpectReq = TalkConstant.ImageExpect; changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer( talkExpect: talkExpectReq); } From 8fb7ea0fe8a09fec7e9ee9cbc83cc3fbbef8df99 Mon Sep 17 00:00:00 2001 From: Liuyf Date: Fri, 18 Apr 2025 14:41:12 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20jpush=E9=80=BB=E8=BE=91=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E9=80=82=E5=BA=94=E5=BA=94=E7=94=A8=E5=B8=82=E5=9C=BA?= =?UTF-8?q?=E5=90=88=E8=A7=84=E8=A6=81=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/src/main/kotlin/com/skychip/lock/App.kt | 2 ++ lib/tools/push/xs_jPhush.dart | 1 + 2 files changed, 3 insertions(+) diff --git a/android/app/src/main/kotlin/com/skychip/lock/App.kt b/android/app/src/main/kotlin/com/skychip/lock/App.kt index 06adbaf6..e28e27f9 100755 --- a/android/app/src/main/kotlin/com/skychip/lock/App.kt +++ b/android/app/src/main/kotlin/com/skychip/lock/App.kt @@ -2,11 +2,13 @@ package com.skychip.lock import io.flutter.app.FlutterApplication import android.util.Log +import cn.jiguang.api.utils.JCollectionAuth; class App : FlutterApplication() { override fun onCreate() { super.onCreate() Log.d("MyApplication", "Application has started") + JCollectionAuth.setAuth(getApplicationContext(), false); } } \ No newline at end of file diff --git a/lib/tools/push/xs_jPhush.dart b/lib/tools/push/xs_jPhush.dart index f2f431c6..7ab5edb3 100755 --- a/lib/tools/push/xs_jPhush.dart +++ b/lib/tools/push/xs_jPhush.dart @@ -66,6 +66,7 @@ class XSJPushProvider { production: F.isProductionEnv, debug: !F.isProductionEnv, ); + jpush.setAuth(enable: true); jpush.applyPushAuthority( const NotificationSettingsIOS(sound: true, alert: true, badge: false), );