fix:调整发送g711音频数据、增加回声消除、增大缓冲区

This commit is contained in:
liyi 2025-01-16 14:02:22 +08:00
parent 1fb9a5a188
commit b876f608e4
7 changed files with 200 additions and 271 deletions

View File

@ -1599,33 +1599,8 @@ class _LockDetailPageState extends State<LockDetailPage>
}
Future<void> _handleLockMonitor() async {
final lockId = state.keyInfos.value.lockId;
final LockSetInfoEntity entity =
await ApiRepository.to.getLockSettingInfoData(
lockId: lockId.toString(),
);
if (entity.errorCode!.codeIsSuccessful) {
final LockSetInfoData data = entity.data!;
final mac = data.lockBasicInfo?.mac;
if (mac != null && mac.isNotEmpty) {
final DeviceNetwork deviceNetworkInfo = await ApiRepository.to
.getDeviceNetwork(deviceType: 2, deviceMac: mac);
if (deviceNetworkInfo.data?.wifiName == null) {
//
logic.showToast('请先进行配网');
return;
} else {
final peerId = deviceNetworkInfo?.data?.peerId;
if (peerId == null || peerId.isEmpty) {
throw Exception('设备peerId为空');
}
// peerID
StartChartManage().lockPeerId = peerId;
// id
StartChartManage().startCallRequestMessageTimer(
ToPeerId: StartChartManage().lockPeerId ?? '');
}
}
}
// id
StartChartManage().startCallRequestMessageTimer(
ToPeerId: StartChartManage().lockPeerId ?? '');
}
}

View File

@ -148,7 +148,6 @@ class ConfiguringWifiLogic extends BaseGetXController {
}
state.sureBtnState.value = 1;
showEasyLoading();
showBlueConnetctToastTimer(action: () {
dismissEasyLoading();
state.sureBtnState.value = 0;
@ -198,8 +197,6 @@ class ConfiguringWifiLogic extends BaseGetXController {
gatewayConfigurationStr: state.getGatewayConfigurationStr,
);
} else if (connectionState == BluetoothConnectionState.disconnected) {
dismissEasyLoading();
cancelBlueConnetctToastTimer();
state.sureBtnState.value = 0;
if (state.ifCurrentScreen.value == true) {
showBlueConnetctToast();

View File

@ -3,54 +3,12 @@ import 'dart:math';
import 'package:flutter/services.dart';
class G711 {
List<int> _aLawTable = [
1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
];
Future<List<int>> readAssetFile(String assetPath) async {
final ByteData data = await rootBundle.load(assetPath);
final List<int> bytes = data.buffer.asUint8List();
return bytes;
}
List<int> encodeALaw(List<int> pcmSamples) {
final List<int> aLawSamples = [];
for (final sample in pcmSamples) {
// 16 PCM 13
int normalizedSample = sample >> 3;
//
int sign = (normalizedSample & 0x8000) != 0 ? 0x80 : 0x00;
//
normalizedSample = normalizedSample.abs();
//
int segment = _aLawTable[normalizedSample >> 8];
//
int quantizedValue = (normalizedSample >> (segment + 3)) & 0x0F;
// A-law
int aLawSample = sign | (segment << 4) | quantizedValue;
//
aLawSamples.add(aLawSample);
}
return aLawSamples;
}
int ALawToLinear(int aVal) {
//
aVal = ~aVal;
@ -107,6 +65,18 @@ class G711 {
.toList();
}
List<int> removeEcho(List<int> audioData, int delaySamples, double decay) {
final List<int> processedData = [];
for (int i = 0; i < audioData.length; i++) {
int sample = audioData[i];
if (i >= delaySamples) {
sample -= (audioData[i - delaySamples] * decay).round();
}
processedData.add(sample);
}
return processedData;
}
///
List<int> decodeAndDenoise(List<int> encodedData, bool isALaw, int sampleRate,
double cutoffFreq, int threshold) {
@ -121,7 +91,10 @@ class G711 {
//
List<int> denoisedData = noiseGate(filteredData, threshold);
return denoisedData;
//
final List<int> processedData = removeEcho(denoisedData, 160, 0.5);
return processedData;
}
//711pcm数据

View File

@ -390,7 +390,7 @@ class StartChartManage {
//
if (talkStatus.status != TalkStatus.proactivelyCallWaitingAnswer) {
//
AudioPlayerManager().playRingtone();
// AudioPlayerManager().playRingtone();
Get.toNamed(
Routers.starChartTalkView,
);

View File

@ -31,8 +31,7 @@ import 'package:star_lock/talk/startChart/proto/talk_expect.pb.dart';
import 'package:star_lock/talk/startChart/start_chart_manage.dart';
import 'package:star_lock/talk/startChart/views/talkView/talk_view_state.dart';
import 'package:star_lock/talk/udp/udp_manage.dart';
import 'package:star_lock/talk/udp/udp_senderManage.dart';
import 'package:star_lock/tools/bugly/bugly_tool.dart';
import 'package:star_lock/tools/storage.dart';
@ -44,8 +43,9 @@ class TalkViewLogic extends BaseGetXController {
Timer? _syncTimer; //
int _startTime = 0; //
int bufferSize = 20; //
int audioBufferSize = 640; //
final List<int> frameTimestamps = []; // FPS
int frameIntervalMs = 45; // 4522FPS
int frameIntervalMs = 35; // 4522FPS
int minFrameIntervalMs = 30; // 33 FPS
int maxFrameIntervalMs = 100; // 1 FPS
// int maxFrameIntervalMs = 100; // 10 FPS
@ -53,11 +53,11 @@ class TalkViewLogic extends BaseGetXController {
///
void _initFlutterPcmSound() {
const int sampleRate = 8000;
FlutterPcmSound.setLogLevel(LogLevel.none);
FlutterPcmSound.setLogLevel(LogLevel.verbose);
FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 1);
// feed
if (Platform.isAndroid) {
FlutterPcmSound.setFeedThreshold(-1); // Android
FlutterPcmSound.setFeedThreshold(sampleRate ~/ 2); // Android
} else {
FlutterPcmSound.setFeedThreshold(sampleRate ~/ 32); // Android
}
@ -88,10 +88,14 @@ class TalkViewLogic extends BaseGetXController {
//
switch (contentType) {
case TalkData_ContentTypeE.G711:
if (state.audioBuffer.length >= bufferSize) {
if (state.audioBuffer.length >= audioBufferSize) {
state.audioBuffer.removeAt(0); //
// readAudioBufferSize.removeAt(0); //
}
state.audioBuffer.add(talkData); //
// readAudioBufferSize.add(talkData.content);
break;
case TalkData_ContentTypeE.Image:
if (state.videoBuffer.length >= bufferSize) {
@ -128,7 +132,6 @@ class TalkViewLogic extends BaseGetXController {
void _playAudioData(TalkData talkData) async {
// final list = G711().convertList(talkData.content);
final list = G711().decodeAndDenoise(talkData.content, true, 8000, 300, 50);
//
// // PCM PcmArrayInt16
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(list);
FlutterPcmSound.feed(fromList);
@ -145,44 +148,14 @@ class TalkViewLogic extends BaseGetXController {
///
void _startPlayback() {
int frameIntervalMs = 45; // 4522FPS
Future.delayed(Duration(milliseconds: 800), () {
_startTime = DateTime.now().millisecondsSinceEpoch;
_syncTimer ??=
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
final currentTime = DateTime.now().millisecondsSinceEpoch;
final elapsedTime = currentTime - _startTime;
//
_adjustFrameInterval();
//
if (state.audioBuffer.isNotEmpty &&
state.audioBuffer.first.durationMs <= elapsedTime) {
//
if (state.isOpenVoice.value) {
AppLog.log('播放音频:${state.audioBuffer[0]}');
_playAudioData(state.audioBuffer.removeAt(0));
} else {
//
//
//
state.audioBuffer.removeAt(0);
}
}
//
//
while (state.videoBuffer.isNotEmpty &&
state.videoBuffer.first.durationMs <= elapsedTime) {
//
if (state.videoBuffer.length > 1) {
state.videoBuffer.removeAt(0);
} else {
_playVideoData(state.videoBuffer.removeAt(0));
}
}
_playFrames();
});
});
}
@ -211,42 +184,46 @@ class TalkViewLogic extends BaseGetXController {
_syncTimer?.cancel();
_syncTimer =
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
final currentTime = DateTime.now().millisecondsSinceEpoch;
final elapsedTime = currentTime - _startTime;
//
if (state.audioBuffer.isNotEmpty &&
state.audioBuffer.first.durationMs <= elapsedTime) {
//
if (state.isOpenVoice.value) {
_playAudioData(state.audioBuffer.removeAt(0));
} else {
//
//
//
state.audioBuffer.removeAt(0);
}
}
//
//
int maxFramesToProcess = 5; // 5
int processedFrames = 0;
while (state.videoBuffer.isNotEmpty &&
state.videoBuffer.first.durationMs <= elapsedTime &&
processedFrames < maxFramesToProcess) {
if (state.videoBuffer.length > 1) {
state.videoBuffer.removeAt(0);
} else {
_playVideoData(state.videoBuffer.removeAt(0));
}
processedFrames++;
}
_playFrames();
});
}
}
void _playFrames() {
final currentTime = DateTime.now().millisecondsSinceEpoch;
final elapsedTime = currentTime - _startTime;
//
if (state.audioBuffer.isNotEmpty &&
state.audioBuffer.first.durationMs <= elapsedTime) {
//
if (state.isOpenVoice.value) {
_playAudioData(state.audioBuffer.removeAt(0));
} else {
//
//
//
state.audioBuffer.removeAt(0);
}
}
//
//
int maxFramesToProcess = 5; // 5
int processedFrames = 0;
while (state.videoBuffer.isNotEmpty &&
state.videoBuffer.first.durationMs <= elapsedTime &&
processedFrames < maxFramesToProcess) {
if (state.videoBuffer.length > 1) {
state.videoBuffer.removeAt(0);
} else {
_playVideoData(state.videoBuffer.removeAt(0));
}
processedFrames++;
}
}
///
void updateNetworkStatus(int currentTimestamp) {
if (state.lastFrameTimestamp.value != 0) {
@ -474,8 +451,6 @@ class TalkViewLogic extends BaseGetXController {
//
_stopPlayG711Data();
stopProcessingAudio();
//
Get.back();
}
///

View File

@ -62,143 +62,150 @@ class _TalkViewPageState extends State<TalkViewPage>
@override
Widget build(BuildContext context) {
return SizedBox(
width: 1.sw,
height: 1.sh,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Obx(
() {
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
return WillPopScope(
onWillPop: () async {
// false 退
return false;
},
child: SizedBox(
width: 1.sw,
height: 1.sh,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Obx(
() {
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
final logicalWidth = MediaQuery.of(context).size.width;
final logicalHeight = MediaQuery.of(context).size.height;
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
final logicalWidth = MediaQuery.of(context).size.width;
final logicalHeight = MediaQuery.of(context).size.height;
final devicePixelRatio =
MediaQuery.of(context).devicePixelRatio;
//
final physicalWidth = logicalWidth * devicePixelRatio;
final physicalHeight = logicalHeight * devicePixelRatio;
//
final physicalWidth = logicalWidth * devicePixelRatio;
final physicalHeight = logicalHeight * devicePixelRatio;
//
final rotatedImageWidth = 480; //
final rotatedImageHeight = 864; //
//
final rotatedImageWidth = 480; //
final rotatedImageHeight = 864; //
//
final scaleWidth = physicalWidth / rotatedImageWidth;
final scaleHeight = physicalHeight / rotatedImageHeight;
final scale = max(scaleWidth, scaleHeight); //
//
final scaleWidth = physicalWidth / rotatedImageWidth;
final scaleHeight = physicalHeight / rotatedImageHeight;
final scale = max(scaleWidth, scaleHeight); //
return state.listData.value.isEmpty
? Image.asset(
'images/main/monitorBg.png',
width: screenWidth,
height: screenHeight,
fit: BoxFit.cover,
)
: PopScope(
canPop: false,
child: RepaintBoundary(
key: state.globalKey,
child: Transform.rotate(
angle:
state.rotateAngle.value * (pi / 180), // 90
child: Transform.scale(
scale: scale, //
child: Image.memory(
state.listData.value,
gaplessPlayback: true,
fit: BoxFit.cover,
filterQuality: FilterQuality.high,
errorBuilder: (
BuildContext context,
Object error,
StackTrace? stackTrace,
) {
return Container(color: Colors.transparent);
},
return state.listData.value.isEmpty
? Image.asset(
'images/main/monitorBg.png',
width: screenWidth,
height: screenHeight,
fit: BoxFit.cover,
)
: PopScope(
canPop: false,
child: RepaintBoundary(
key: state.globalKey,
child: Transform.rotate(
angle:
state.rotateAngle.value * (pi / 180), // 90
child: Transform.scale(
scale: scale, //
child: Image.memory(
state.listData.value,
gaplessPlayback: true,
fit: BoxFit.cover,
filterQuality: FilterQuality.high,
errorBuilder: (
BuildContext context,
Object error,
StackTrace? stackTrace,
) {
return Container(color: Colors.transparent);
},
),
),
),
),
),
);
},
),
Obx(() => state.listData.value.isEmpty
? Positioned(
bottom: 300.h,
child: Text(
'正在创建安全连接...'.tr,
style: TextStyle(color: Colors.black, fontSize: 26.sp),
))
: Container()),
Positioned(
bottom: 10.w,
child: Container(
width: 1.sw - 30.w * 2,
// height: 300.h,
margin: EdgeInsets.all(30.w),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.2),
borderRadius: BorderRadius.circular(20.h)),
child: Column(
children: <Widget>[
SizedBox(height: 20.h),
bottomTopBtnWidget(),
SizedBox(height: 20.h),
bottomBottomBtnWidget(),
SizedBox(height: 20.h),
],
);
},
),
Obx(() => state.listData.value.isEmpty
? Positioned(
bottom: 300.h,
child: Text(
'正在创建安全连接...'.tr,
style: TextStyle(color: Colors.black, fontSize: 26.sp),
))
: Container()),
Positioned(
bottom: 10.w,
child: Container(
width: 1.sw - 30.w * 2,
// height: 300.h,
margin: EdgeInsets.all(30.w),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.2),
borderRadius: BorderRadius.circular(20.h)),
child: Column(
children: <Widget>[
SizedBox(height: 20.h),
bottomTopBtnWidget(),
SizedBox(height: 20.h),
bottomBottomBtnWidget(),
SizedBox(height: 20.h),
],
),
),
),
),
// Positioned(
// top: 100.h,
// left: 10.w,
// child: Obx(
// () => Text(
// 'FPS:${state.fps.value}',
// style: TextStyle(
// fontSize: 30.sp,
// color: Colors.orange,
// fontWeight: FontWeight.bold),
// ),
// ),
// ),
Obx(() => state.listData.value.isEmpty
? buildRotationTransition()
: Container()),
// Positioned(
// top: 100.h,
// left: 10.w,
// child: Obx(
// () => Text(
// 'FPS:${state.fps.value}',
// style: TextStyle(
// fontSize: 30.sp,
// color: Colors.orange,
// fontWeight: FontWeight.bold),
// ),
// ),
// ),
Obx(() => state.listData.value.isEmpty
? buildRotationTransition()
: Container()),
Obx(() => state.isLongPressing.value
? Positioned(
top: 80.h,
left: 0,
right: 0,
child: Center(
child: Container(
padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.7),
borderRadius: BorderRadius.circular(10.w),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.mic, color: Colors.white, size: 24.w),
SizedBox(width: 10.w),
Text(
'正在说话...',
style:
TextStyle(fontSize: 20.sp, color: Colors.white),
),
],
Obx(() => state.isLongPressing.value
? Positioned(
top: 80.h,
left: 0,
right: 0,
child: Center(
child: Container(
padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.7),
borderRadius: BorderRadius.circular(10.w),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.mic, color: Colors.white, size: 24.w),
SizedBox(width: 10.w),
Text(
'正在说话...',
style: TextStyle(
fontSize: 20.sp, color: Colors.white),
),
],
),
),
),
),
)
: Container()),
],
)
: Container()),
],
),
),
);
}

View File

@ -87,4 +87,6 @@ class TalkViewState {
List<List<int>> recordingAudioAllFrames = <List<int>>[]; //
RxInt rotateAngle = 0.obs; //
RxBool isLongPressing = false.obs; //
RxBool hasAudioData = false.obs; //
RxInt lastAudioTimestamp = 0.obs; //
}