fix:调整发送g711音频数据、增加回声消除、增大缓冲区
This commit is contained in:
parent
1fb9a5a188
commit
b876f608e4
@ -1599,33 +1599,8 @@ class _LockDetailPageState extends State<LockDetailPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleLockMonitor() async {
|
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
|
// 发送监控id
|
||||||
StartChartManage().startCallRequestMessageTimer(
|
StartChartManage().startCallRequestMessageTimer(
|
||||||
ToPeerId: StartChartManage().lockPeerId ?? '');
|
ToPeerId: StartChartManage().lockPeerId ?? '');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -148,7 +148,6 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
|||||||
}
|
}
|
||||||
state.sureBtnState.value = 1;
|
state.sureBtnState.value = 1;
|
||||||
|
|
||||||
showEasyLoading();
|
|
||||||
showBlueConnetctToastTimer(action: () {
|
showBlueConnetctToastTimer(action: () {
|
||||||
dismissEasyLoading();
|
dismissEasyLoading();
|
||||||
state.sureBtnState.value = 0;
|
state.sureBtnState.value = 0;
|
||||||
@ -198,8 +197,6 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
|||||||
gatewayConfigurationStr: state.getGatewayConfigurationStr,
|
gatewayConfigurationStr: state.getGatewayConfigurationStr,
|
||||||
);
|
);
|
||||||
} else if (connectionState == BluetoothConnectionState.disconnected) {
|
} else if (connectionState == BluetoothConnectionState.disconnected) {
|
||||||
dismissEasyLoading();
|
|
||||||
cancelBlueConnetctToastTimer();
|
|
||||||
state.sureBtnState.value = 0;
|
state.sureBtnState.value = 0;
|
||||||
if (state.ifCurrentScreen.value == true) {
|
if (state.ifCurrentScreen.value == true) {
|
||||||
showBlueConnetctToast();
|
showBlueConnetctToast();
|
||||||
|
|||||||
@ -3,54 +3,12 @@ import 'dart:math';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class G711 {
|
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 {
|
Future<List<int>> readAssetFile(String assetPath) async {
|
||||||
final ByteData data = await rootBundle.load(assetPath);
|
final ByteData data = await rootBundle.load(assetPath);
|
||||||
final List<int> bytes = data.buffer.asUint8List();
|
final List<int> bytes = data.buffer.asUint8List();
|
||||||
return bytes;
|
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) {
|
int ALawToLinear(int aVal) {
|
||||||
// 取反
|
// 取反
|
||||||
aVal = ~aVal;
|
aVal = ~aVal;
|
||||||
@ -107,6 +65,18 @@ class G711 {
|
|||||||
.toList();
|
.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,
|
List<int> decodeAndDenoise(List<int> encodedData, bool isALaw, int sampleRate,
|
||||||
double cutoffFreq, int threshold) {
|
double cutoffFreq, int threshold) {
|
||||||
@ -121,7 +91,10 @@ class G711 {
|
|||||||
// 噪声门
|
// 噪声门
|
||||||
List<int> denoisedData = noiseGate(filteredData, threshold);
|
List<int> denoisedData = noiseGate(filteredData, threshold);
|
||||||
|
|
||||||
return denoisedData;
|
// 回声消除处理
|
||||||
|
final List<int> processedData = removeEcho(denoisedData, 160, 0.5);
|
||||||
|
|
||||||
|
return processedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
//711解码为pcm数据
|
//711解码为pcm数据
|
||||||
|
|||||||
@ -390,7 +390,7 @@ class StartChartManage {
|
|||||||
// 如果已经处于等待接听状态就不发送
|
// 如果已经处于等待接听状态就不发送
|
||||||
if (talkStatus.status != TalkStatus.proactivelyCallWaitingAnswer) {
|
if (talkStatus.status != TalkStatus.proactivelyCallWaitingAnswer) {
|
||||||
// 停止播放铃声
|
// 停止播放铃声
|
||||||
AudioPlayerManager().playRingtone();
|
// AudioPlayerManager().playRingtone();
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
Routers.starChartTalkView,
|
Routers.starChartTalkView,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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/start_chart_manage.dart';
|
||||||
|
|
||||||
import 'package:star_lock/talk/startChart/views/talkView/talk_view_state.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/bugly/bugly_tool.dart';
|
||||||
import 'package:star_lock/tools/storage.dart';
|
import 'package:star_lock/tools/storage.dart';
|
||||||
|
|
||||||
@ -44,8 +43,9 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
Timer? _syncTimer; // 音视频播放刷新率定时器
|
Timer? _syncTimer; // 音视频播放刷新率定时器
|
||||||
int _startTime = 0; // 开始播放时间戳,用于判断帧数据中的时间戳位置
|
int _startTime = 0; // 开始播放时间戳,用于判断帧数据中的时间戳位置
|
||||||
int bufferSize = 20; // 缓冲区大小(以帧为单位)
|
int bufferSize = 20; // 缓冲区大小(以帧为单位)
|
||||||
|
int audioBufferSize = 640; // 缓冲区大小(以帧为单位)
|
||||||
final List<int> frameTimestamps = []; // 帧时间戳用于计算 FPS
|
final List<int> frameTimestamps = []; // 帧时间戳用于计算 FPS
|
||||||
int frameIntervalMs = 45; // 初始帧间隔设置为45毫秒(约22FPS)
|
int frameIntervalMs = 35; // 初始帧间隔设置为45毫秒(约22FPS)
|
||||||
int minFrameIntervalMs = 30; // 最小帧间隔(约33 FPS)
|
int minFrameIntervalMs = 30; // 最小帧间隔(约33 FPS)
|
||||||
int maxFrameIntervalMs = 100; // 最大帧间隔(约1 FPS)
|
int maxFrameIntervalMs = 100; // 最大帧间隔(约1 FPS)
|
||||||
// int maxFrameIntervalMs = 100; // 最大帧间隔(约10 FPS)
|
// int maxFrameIntervalMs = 100; // 最大帧间隔(约10 FPS)
|
||||||
@ -53,11 +53,11 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
/// 初始化音频播放器
|
/// 初始化音频播放器
|
||||||
void _initFlutterPcmSound() {
|
void _initFlutterPcmSound() {
|
||||||
const int sampleRate = 8000;
|
const int sampleRate = 8000;
|
||||||
FlutterPcmSound.setLogLevel(LogLevel.none);
|
FlutterPcmSound.setLogLevel(LogLevel.verbose);
|
||||||
FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 1);
|
FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 1);
|
||||||
// 设置 feed 阈值
|
// 设置 feed 阈值
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
FlutterPcmSound.setFeedThreshold(-1); // Android 平台的特殊处理
|
FlutterPcmSound.setFeedThreshold(sampleRate ~/ 2); // Android 平台的特殊处理
|
||||||
} else {
|
} else {
|
||||||
FlutterPcmSound.setFeedThreshold(sampleRate ~/ 32); // 非 Android 平台的处理
|
FlutterPcmSound.setFeedThreshold(sampleRate ~/ 32); // 非 Android 平台的处理
|
||||||
}
|
}
|
||||||
@ -88,10 +88,14 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
// 判断数据类型,进行分发处理
|
// 判断数据类型,进行分发处理
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
case TalkData_ContentTypeE.G711:
|
case TalkData_ContentTypeE.G711:
|
||||||
if (state.audioBuffer.length >= bufferSize) {
|
if (state.audioBuffer.length >= audioBufferSize) {
|
||||||
state.audioBuffer.removeAt(0); // 丢弃最旧的数据
|
state.audioBuffer.removeAt(0); // 丢弃最旧的数据
|
||||||
|
// readAudioBufferSize.removeAt(0); // 丢弃最旧的数据
|
||||||
}
|
}
|
||||||
state.audioBuffer.add(talkData); // 添加新数据
|
state.audioBuffer.add(talkData); // 添加新数据
|
||||||
|
|
||||||
|
// readAudioBufferSize.add(talkData.content);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TalkData_ContentTypeE.Image:
|
case TalkData_ContentTypeE.Image:
|
||||||
if (state.videoBuffer.length >= bufferSize) {
|
if (state.videoBuffer.length >= bufferSize) {
|
||||||
@ -128,7 +132,6 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
void _playAudioData(TalkData talkData) async {
|
void _playAudioData(TalkData talkData) async {
|
||||||
// final list = G711().convertList(talkData.content);
|
// final list = G711().convertList(talkData.content);
|
||||||
final list = G711().decodeAndDenoise(talkData.content, true, 8000, 300, 50);
|
final list = G711().decodeAndDenoise(talkData.content, true, 8000, 300, 50);
|
||||||
//
|
|
||||||
// // 将 PCM 数据转换为 PcmArrayInt16
|
// // 将 PCM 数据转换为 PcmArrayInt16
|
||||||
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(list);
|
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(list);
|
||||||
FlutterPcmSound.feed(fromList);
|
FlutterPcmSound.feed(fromList);
|
||||||
@ -145,44 +148,14 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
|
|
||||||
/// 启动播放
|
/// 启动播放
|
||||||
void _startPlayback() {
|
void _startPlayback() {
|
||||||
int frameIntervalMs = 45; // 初始帧间隔设置为45毫秒(约22FPS)
|
|
||||||
|
|
||||||
Future.delayed(Duration(milliseconds: 800), () {
|
Future.delayed(Duration(milliseconds: 800), () {
|
||||||
_startTime = DateTime.now().millisecondsSinceEpoch;
|
_startTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
_syncTimer ??=
|
_syncTimer ??=
|
||||||
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
|
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
|
||||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
final elapsedTime = currentTime - _startTime;
|
|
||||||
|
|
||||||
// 动态调整帧间隔
|
// 动态调整帧间隔
|
||||||
_adjustFrameInterval();
|
_adjustFrameInterval();
|
||||||
|
|
||||||
// 播放合适的音频帧
|
_playFrames();
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -211,6 +184,12 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
_syncTimer?.cancel();
|
_syncTimer?.cancel();
|
||||||
_syncTimer =
|
_syncTimer =
|
||||||
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
|
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
|
||||||
|
_playFrames();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _playFrames() {
|
||||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
final elapsedTime = currentTime - _startTime;
|
final elapsedTime = currentTime - _startTime;
|
||||||
|
|
||||||
@ -243,8 +222,6 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
}
|
}
|
||||||
processedFrames++;
|
processedFrames++;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 修改网络状态
|
/// 修改网络状态
|
||||||
@ -474,8 +451,6 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
// 停止播放音频
|
// 停止播放音频
|
||||||
_stopPlayG711Data();
|
_stopPlayG711Data();
|
||||||
stopProcessingAudio();
|
stopProcessingAudio();
|
||||||
// 状态错误,返回页面
|
|
||||||
Get.back();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 更新发送预期数据
|
/// 更新发送预期数据
|
||||||
|
|||||||
@ -62,7 +62,12 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return WillPopScope(
|
||||||
|
onWillPop: () async {
|
||||||
|
// 返回 false 表示禁止退出
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
width: 1.sw,
|
width: 1.sw,
|
||||||
height: 1.sh,
|
height: 1.sh,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
@ -75,7 +80,8 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
|
|
||||||
final logicalWidth = MediaQuery.of(context).size.width;
|
final logicalWidth = MediaQuery.of(context).size.width;
|
||||||
final logicalHeight = MediaQuery.of(context).size.height;
|
final logicalHeight = MediaQuery.of(context).size.height;
|
||||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
final devicePixelRatio =
|
||||||
|
MediaQuery.of(context).devicePixelRatio;
|
||||||
|
|
||||||
// 计算物理像素值
|
// 计算物理像素值
|
||||||
final physicalWidth = logicalWidth * devicePixelRatio;
|
final physicalWidth = logicalWidth * devicePixelRatio;
|
||||||
@ -189,8 +195,8 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
SizedBox(width: 10.w),
|
SizedBox(width: 10.w),
|
||||||
Text(
|
Text(
|
||||||
'正在说话...',
|
'正在说话...',
|
||||||
style:
|
style: TextStyle(
|
||||||
TextStyle(fontSize: 20.sp, color: Colors.white),
|
fontSize: 20.sp, color: Colors.white),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -200,6 +206,7 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
: Container()),
|
: Container()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -87,4 +87,6 @@ class TalkViewState {
|
|||||||
List<List<int>> recordingAudioAllFrames = <List<int>>[]; // 录制音频的所有帧
|
List<List<int>> recordingAudioAllFrames = <List<int>>[]; // 录制音频的所有帧
|
||||||
RxInt rotateAngle = 0.obs; // 旋转角度(以弧度为单位)
|
RxInt rotateAngle = 0.obs; // 旋转角度(以弧度为单位)
|
||||||
RxBool isLongPressing = false.obs; // 旋转角度(以弧度为单位)
|
RxBool isLongPressing = false.obs; // 旋转角度(以弧度为单位)
|
||||||
|
RxBool hasAudioData = false.obs; // 是否有音频数据
|
||||||
|
RxInt lastAudioTimestamp = 0.obs; // 最后接收到的音频数据的时间戳
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user