From 4c9ff85e4a8dbb13363754e9a3df6bfd6b8f579a Mon Sep 17 00:00:00 2001 From: Daisy <> Date: Mon, 1 Apr 2024 18:38:04 +0800 Subject: [PATCH 1/3] =?UTF-8?q?1=EF=BC=8C=E9=95=BF=E6=8C=89=E5=BD=95?= =?UTF-8?q?=E9=9F=B3=E7=9A=84=E9=83=A8=E5=88=86=E9=80=BB=E8=BE=91=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=A4=84=E7=90=86=202=EF=BC=8C=E4=BF=AE=E6=94=B9PCM?= =?UTF-8?q?=20=E6=95=B0=E6=8D=AE=E8=BD=AC=E6=8D=A2=E4=B8=BA=20uLaw=20?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E7=9A=84=E9=9F=B3=E9=A2=91=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../monitoring/lockMonitoring_logic.dart | 82 +++---------------- .../monitoring/lockMonitoring_page.dart | 1 + .../monitoring/lockMonitoring_state.dart | 2 + 3 files changed, 16 insertions(+), 69 deletions(-) diff --git a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart index 3085ea5c..2155bd81 100644 --- a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart +++ b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart @@ -8,7 +8,6 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:star_lock/talk/call/callTalk.dart'; import 'package:star_lock/talk/udp/udp_talkClass.dart'; -import '../../../../talk/call/g711.dart'; import '../../../../talk/udp/udp_manage.dart'; import '../../../../talk/udp/udp_senderManage.dart'; import '../../../../tools/baseGetXController.dart'; @@ -113,19 +112,6 @@ class LockMonitoringLogic extends BaseGetXController { udpHangUpAction(); state.hangUpSeconds++; - // //如果已经挂断成功,则取消定时器 - // if (UDPTalkClass().isEndCall == true) { - // print('{$clickIndex}已经挂断成功'); - // state.hangUpTimer.cancel(); - // return; - // } - - // if (UDPTalkClass().status == 6) { - // print('{$clickIndex}被叫中,已经挂断成功'); - // state.hangUpTimer.cancel(); - // return; - // } - // 检查条件,如果达到6秒且未得到应答,则认为失败 if (state.hangUpSeconds >= 6) { state.hangUpTimer.cancel(); // 取消定时器 @@ -177,51 +163,13 @@ class LockMonitoringLogic extends BaseGetXController { Get.back(); } - Future _readG711Data() async { - String filePath = 'assets/s10-g711.bin'; - List audioData = await G711().readAssetFile(filePath); - // Get.log('发送读取711文件数据为:$audioData');// 数据为:$audioData - // return; - // print('发送读取711文件数据长度为:${audioData.length}');// 数据为:$audioData - if (audioData.isNotEmpty) { - // 在这里处理你的音频数据 - // pcmBytes = G711().convertList(audioData); - // print('发送转换pcmBytes数据长度为:${pcmBytes.length}'); - - int start = 0; - int length = 320; - while (start < audioData.length) { - // await Future.delayed(const Duration(milliseconds: 50)); - - int end = (start + length > audioData.length) - ? audioData.length - : start + length; - List sublist = audioData.sublist(start, end); - sendRecordData({ - "bytes": sublist, - // "udpSendDataFrameNumber": 0, - "lockID": UDPManage().lockId, - "lockIP": UDPManage().host, - "userMobile": await state.userUid, - "userMobileIP": await state.userMobileIP, - }); - print(sublist); - start += length; - } - print('G711数据发送完成'); - } else { - print('Failed to read audio data.'); - } - } - Future startProcessing() async { + state.sendEnd.value = false; frameListener(List frame) async { - for (int i = 0; i < frame.length; i++) { - frame[i] = linearToULaw(frame[i]); - } - await Future.delayed(const Duration(milliseconds: 50)); + List pcmBytes = listLinearToULaw(frame); + await Future.delayed(const Duration(milliseconds: 10)); sendRecordData({ - "bytes": frame, + "bytes": pcmBytes, // "udpSendDataFrameNumber": 0, "lockID": UDPManage().lockId, "lockIP": UDPManage().host, @@ -234,7 +182,6 @@ class LockMonitoringLogic extends BaseGetXController { print("VoiceProcessorException: $error"); } - ; state.voiceProcessor?.addFrameListener(frameListener); state.voiceProcessor?.addErrorListener(errorListener); @@ -360,6 +307,15 @@ class LockMonitoringLogic extends BaseGetXController { // UDPManage().sendData(topBytes); } + List listLinearToULaw(List pcmList) { + List uLawList = []; + for (int pcmVal in pcmList) { + int uLawVal = linearToULaw(pcmVal); + uLawList.add(uLawVal); + } + return uLawList; + } + // 拿到的音频转化成pcm int linearToULaw(int pcmVal) { int mask; @@ -404,18 +360,6 @@ class LockMonitoringLogic extends BaseGetXController { return size; } - double _calculateVolumeLevel(List frame) { - double rms = 0.0; - for (int sample in frame) { - rms += pow(sample, 2); - } - rms = sqrt(rms / frame.length); - - double dbfs = 20 * log(rms / 32767.0) / log(10); - double normalizedValue = (dbfs + 50) / 50; - return normalizedValue.clamp(0.0, 1.0); - } - Future getPermissionStatus() async { Permission permission = Permission.microphone; //granted 通过,denied 被拒绝,permanentlyDenied 拒绝且不在提示 diff --git a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart index c5315cbd..f92f460d 100644 --- a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart +++ b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart @@ -340,5 +340,6 @@ class _LockMonitoringPageState extends State { @override void dispose() { super.dispose(); + logic.stopProcessing(); } } diff --git a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart index 115baf05..b1f820e2 100644 --- a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart +++ b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart @@ -24,6 +24,8 @@ class LockMonitoringState { var listAudioData = [].obs; //得到的音频流字节数据 late final VoiceProcessor? voiceProcessor; + var sendEnd = false.obs; //发送完成 + late StreamSubscription> frameSubscription; late Timer oneMinuteTimeTimer = Timer(const Duration(seconds: 1), () {}); // 定时器超过60秒关闭当前界面 From 266f68e5a60ce8142df991d0404cc0cc628681e5 Mon Sep 17 00:00:00 2001 From: Daisy <> Date: Tue, 2 Apr 2024 14:07:47 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=A2=B3=E7=90=86=E5=AF=B9=E8=AE=B2?= =?UTF-8?q?=E5=BD=95=E9=9F=B3=E5=8A=9F=E8=83=BD=E7=9B=B8=E5=85=B3=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=B9=B6=E4=BC=98=E5=8C=96=20=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E5=BD=95=E9=9F=B3=E5=8F=AA=E6=9C=89=E7=AC=AC=E4=B8=80=E6=AC=A1?= =?UTF-8?q?=E7=94=9F=E6=95=88=EF=BC=8C=E5=90=8E=E9=9D=A2=E4=B8=8D=E8=83=BD?= =?UTF-8?q?=E6=AD=A3=E5=B8=B8=E5=8F=91=E9=80=81=E5=BD=95=E9=9F=B3=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E9=97=AE=E9=A2=98=20=E6=96=B0=E5=A2=9E=E5=AF=B9?= =?UTF-8?q?=E8=AE=B2=E8=BF=87=E7=A8=8B=E4=B8=AD=E7=9A=84=E6=88=AA=E5=B1=8F?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../monitoring/lockMonitoring_logic.dart | 89 +++++--- .../monitoring/lockMonitoring_page.dart | 205 +++++++++++------- .../monitoring/lockMonitoring_state.dart | 14 +- 3 files changed, 197 insertions(+), 111 deletions(-) diff --git a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart index 2155bd81..58d57554 100644 --- a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart +++ b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_logic.dart @@ -163,48 +163,77 @@ class LockMonitoringLogic extends BaseGetXController { Get.back(); } +//开始录音 Future startProcessing() async { - state.sendEnd.value = false; - frameListener(List frame) async { - List pcmBytes = listLinearToULaw(frame); - await Future.delayed(const Duration(milliseconds: 10)); - sendRecordData({ - "bytes": pcmBytes, - // "udpSendDataFrameNumber": 0, - "lockID": UDPManage().lockId, - "lockIP": UDPManage().host, - "userMobile": await state.userUid, - "userMobileIP": await state.userMobileIP, - }); - } - - errorListener(VoiceProcessorException error) { - print("VoiceProcessorException: $error"); - } - - state.voiceProcessor?.addFrameListener(frameListener); - state.voiceProcessor?.addErrorListener(errorListener); + state.isButtonDisabled.value = true; + state.voiceProcessor?.addFrameListener(_onFrame); + state.voiceProcessor?.addErrorListener(_onError); try { if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) { - await state.voiceProcessor?.start(320, 8000); + await state.voiceProcessor?.start(state.frameLength, state.sampleRate); bool? isRecording = await state.voiceProcessor?.isRecording(); - } else {} + state.isProcessing.value = isRecording!; + } else { + state.errorMessage.value = "Recording permission not granted"; + } } on PlatformException catch (ex) { - Get.log("PlatformException: $ex"); - } finally {} + state.errorMessage.value = "Failed to start recorder: $ex"; + } finally { + state.isButtonDisabled.value = false; + } + } + + double _calculateVolumeLevel(List frame) { + double rms = 0.0; + for (int sample in frame) { + rms += pow(sample, 2); + } + rms = sqrt(rms / frame.length); + + double dbfs = 20 * log(rms / 32767.0) / log(10); + double normalizedValue = (dbfs + state.dbOffset) / state.dbOffset; + return normalizedValue.clamp(0.0, 1.0); + } + + Future _onFrame(List frame) async { + double volumeLevel = _calculateVolumeLevel(frame); + if (state.volumeHistory.value.length == state.volumeHistoryCapacity) { + state.volumeHistory.value.removeAt(0); + } + state.volumeHistory.value.add(volumeLevel); + + state.smoothedVolumeValue.value = + state.volumeHistory.value.reduce((a, b) => a + b) / + state.volumeHistory.value.length; + + List pcmBytes = listLinearToULaw(frame); + await Future.delayed(const Duration(milliseconds: 100)); + sendRecordData({ + "bytes": pcmBytes, + // "udpSendDataFrameNumber": 0, + "lockID": UDPManage().lockId, + "lockIP": UDPManage().host, + "userMobile": await state.userUid, + "userMobileIP": await state.userMobileIP, + }); + } + + void _onError(VoiceProcessorException error) { + state.errorMessage.value = error.message!; } Future stopProcessing() async { + state.isButtonDisabled.value = true; try { await state.voiceProcessor?.stop(); } on PlatformException catch (ex) { - Get.log("PlatformException: $ex"); - } finally {} - } - - void onError(Object e) { - print(e); + state.errorMessage.value = "Failed to stop recorder: $ex"; + } finally { + bool? isRecording = await state.voiceProcessor?.isRecording(); + state.isProcessing.value = isRecording!; + state.isButtonDisabled.value = false; + } } sendRecordData(Map args) async { diff --git a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart index f92f460d..77b6edf8 100644 --- a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart +++ b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart @@ -1,9 +1,14 @@ import 'dart:async'; +import 'dart:io'; +import 'dart:ui' as ui; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:star_lock/talk/call/callTalk.dart'; import '../../../../app_settings/app_colors.dart'; @@ -36,85 +41,88 @@ class _LockMonitoringPageState extends State { Widget build(BuildContext context) { return PopScope( canPop: false, - child: Container( - width: 1.sw, - height: 1.sh, - color: Colors.white, - child: Stack( - children: [ - Obx(() { - if (state.listPhotoData.value.isEmpty || - state.listPhotoData.value.length < 10) { - return Container(color: Colors.transparent); - } else { - return Image.memory( - state.listPhotoData.value, - gaplessPlayback: true, - width: 1.sw, - height: 1.sh, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { - return Container(color: Colors.transparent); - }, - ); - } - }), - Positioned( - top: ScreenUtil().statusBarHeight + 30.h, - width: 1.sw, - child: Obx(() { - var sec = (state.oneMinuteTime.value % 60) - .toString() - .padLeft(2, '0'); - var min = (state.oneMinuteTime.value ~/ 60) - .toString() - .padLeft(2, '0'); - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text("$min:$sec", - style: TextStyle( - fontSize: 26.sp, color: Colors.white)), - // SizedBox(width: 30.w), - // GestureDetector( - // onTap: () { - // Get.back(); - // }, - // child: Container( - // decoration: BoxDecoration( - // color: Colors.white, - // borderRadius: BorderRadius.circular(25.h)), - // padding: EdgeInsets.all(10.w), - // child: Image( - // width: 40.w, - // height: 40.w, - // image: const AssetImage("images/icon_left_black.png"), - // ), - // ), - // ), - ]); + child: RepaintBoundary( + key: state.globalKey, + child: Container( + width: 1.sw, + height: 1.sh, + color: Colors.white, + child: Stack( + children: [ + Obx(() { + if (state.listPhotoData.value.isEmpty || + state.listPhotoData.value.length < 10) { + return Container(color: Colors.transparent); + } else { + return Image.memory( + state.listPhotoData.value, + gaplessPlayback: true, + width: 1.sw, + height: 1.sh, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container(color: Colors.transparent); + }, + ); + } }), - ), - Positioned( - bottom: 10.w, - child: Container( - width: 1.sw - 30.w * 2, - // height: 300.h, - margin: EdgeInsets.all(30.w), - decoration: BoxDecoration( - color: const Color(0xC83C3F41), - borderRadius: BorderRadius.circular(20.h)), - child: Column( - children: [ - SizedBox(height: 20.h), - bottomTopBtnWidget(), - SizedBox(height: 20.h), - bottomBottomBtnWidget(), - SizedBox(height: 20.h), - ], - ), - )) - ], + Positioned( + top: ScreenUtil().statusBarHeight + 30.h, + width: 1.sw, + child: Obx(() { + var sec = (state.oneMinuteTime.value % 60) + .toString() + .padLeft(2, '0'); + var min = (state.oneMinuteTime.value ~/ 60) + .toString() + .padLeft(2, '0'); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("$min:$sec", + style: TextStyle( + fontSize: 26.sp, color: Colors.white)), + // SizedBox(width: 30.w), + // GestureDetector( + // onTap: () { + // Get.back(); + // }, + // child: Container( + // decoration: BoxDecoration( + // color: Colors.white, + // borderRadius: BorderRadius.circular(25.h)), + // padding: EdgeInsets.all(10.w), + // child: Image( + // width: 40.w, + // height: 40.w, + // image: const AssetImage("images/icon_left_black.png"), + // ), + // ), + // ), + ]); + }), + ), + Positioned( + bottom: 10.w, + child: Container( + width: 1.sw - 30.w * 2, + // height: 300.h, + margin: EdgeInsets.all(30.w), + decoration: BoxDecoration( + color: const Color(0xC83C3F41), + borderRadius: BorderRadius.circular(20.h)), + child: Column( + children: [ + SizedBox(height: 20.h), + bottomTopBtnWidget(), + SizedBox(height: 20.h), + bottomBottomBtnWidget(), + SizedBox(height: 20.h), + ], + ), + )) + ], + ), ), )); } @@ -144,6 +152,7 @@ class _LockMonitoringPageState extends State { // 截图 GestureDetector( onTap: () { + captureAndSavePng(); // Get.toNamed(Routers.monitoringRealTimeScreenPage); }, child: Container( @@ -198,13 +207,16 @@ class _LockMonitoringPageState extends State { state.udpStatus.value = 9; } // logic.readG711Data(); - logic.startProcessing(); + if (state.isProcessing.value == false) { + logic.startProcessing(); + } }, longPressUp: () async { // 长按结束 print("onLongPressUp"); if (state.udpStatus.value == 9) { state.udpStatus.value = 8; } + logic.stopProcessing(); })), bottomBtnItemWidget( "images/main/icon_lockDetail_hangUp.png", "挂断", Colors.red, () async { @@ -337,6 +349,41 @@ class _LockMonitoringPageState extends State { }); } + Future captureAndSavePng() async { + try { + if (state.globalKey.currentContext == null) { + print('截图失败: 未找到当前上下文'); + return; + } + RenderRepaintBoundary boundary = state.globalKey.currentContext! + .findRenderObject() as RenderRepaintBoundary; + ui.Image image = await boundary.toImage(); + ByteData? byteData = + await image.toByteData(format: ui.ImageByteFormat.png); + if (byteData == null) { + print('截图失败: 图像数据为空'); + return; + } + Uint8List pngBytes = byteData.buffer.asUint8List(); + + // 获取应用程序的文档目录 + final directory = await getApplicationDocumentsDirectory(); + final imagePath = '${directory.path}/screenshot.png'; + + // 将截图保存为文件 + File imgFile = File(imagePath); + await imgFile.writeAsBytes(pngBytes); + + // 将截图保存到相册 + await ImageGallerySaver.saveFile(imagePath); + + print('截图保存路径: $imagePath'); + logic.showToast('截图已保存到相册'); + } catch (e) { + print('截图失败: $e'); + } + } + @override void dispose() { super.dispose(); diff --git a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart index b1f820e2..b1d00659 100644 --- a/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart +++ b/star_lock/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_state.dart @@ -23,9 +23,19 @@ class LockMonitoringState { var listPhotoData = Uint8List(0).obs; //得到的视频流字节数据 var listAudioData = [].obs; //得到的音频流字节数据 +//录音相关 late final VoiceProcessor? voiceProcessor; - var sendEnd = false.obs; //发送完成 - late StreamSubscription> frameSubscription; + var isProcessing = false.obs; //是否正在处理音频数据 + var isButtonDisabled = false.obs; //是否禁用按钮 + final int frameLength = 320; //音视频帧长度为320 + final int sampleRate = 8000; //音频采样率为8000 + final int volumeHistoryCapacity = 5; //音量历史记录的容量 + final double dbOffset = 50.0; //用于音量计算的偏移量 + var volumeHistory = [].obs; //用于存储音量历史记录的列表 + var smoothedVolumeValue = 0.0.obs; //存储平滑后的音量值 + var errorMessage = ''.obs; + + GlobalKey globalKey = GlobalKey(); late Timer oneMinuteTimeTimer = Timer(const Duration(seconds: 1), () {}); // 定时器超过60秒关闭当前界面 From b7fcb60f289e70e86b9b540e56d24e0fd285c8fd Mon Sep 17 00:00:00 2001 From: Daisy <> Date: Tue, 2 Apr 2024 15:17:13 +0800 Subject: [PATCH 3/3] =?UTF-8?q?1=EF=BC=8C=E4=BF=AE=E5=A4=8D=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=80=83=E5=8B=A4=E5=90=8E=E9=94=81=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2item=E6=98=BE=E7=A4=BA=E4=B8=8D=E5=85=A8?= =?UTF-8?q?=E9=97=AE=E9=A2=98=202=EF=BC=8C=E6=96=B0=E5=A2=9E=E5=AF=B9?= =?UTF-8?q?=E8=AE=B2=E8=BF=87=E7=A8=8B=E4=B8=AD=E8=BF=9C=E7=A8=8B=E5=BC=80?= =?UTF-8?q?=E9=97=A8=E5=A4=B1=E8=B4=A5=E7=9A=84=E5=A4=84=E7=90=86=E5=8F=8A?= =?UTF-8?q?=E6=8F=90=E9=86=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart | 2 +- star_lock/lib/talk/udp/udp_reciverData.dart | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart b/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart index 9cee013e..71518019 100644 --- a/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart +++ b/star_lock/lib/main/lockDetail/lockDetail/lockDetail_page.dart @@ -171,7 +171,7 @@ class _LockDetailPageState extends State Stack(children: [ Container( width: 1.sw, - height: 1.sh - ScreenUtil().statusBarHeight * 2, + height: 1.sh - ScreenUtil().statusBarHeight, color: Colors.white, child: Column( children: [ diff --git a/star_lock/lib/talk/udp/udp_reciverData.dart b/star_lock/lib/talk/udp/udp_reciverData.dart index f2ea0956..2270faa8 100644 --- a/star_lock/lib/talk/udp/udp_reciverData.dart +++ b/star_lock/lib/talk/udp/udp_reciverData.dart @@ -99,6 +99,7 @@ class CommandUDPReciverManager { EasyLoading.showToast("开门成功", duration: 2000.milliseconds); } else { print("开门失败"); + EasyLoading.showToast("开门失败", duration: 2000.milliseconds); } } break;