fix:调整发送g711音频数据、增加回声消除、增大缓冲区、调整A律解码效果
This commit is contained in:
parent
b876f608e4
commit
911396e1f3
@ -24,16 +24,28 @@ class G711 {
|
|||||||
int decodeG711(int encodedValue, bool isALaw) {
|
int decodeG711(int encodedValue, bool isALaw) {
|
||||||
if (isALaw) {
|
if (isALaw) {
|
||||||
// A律解码
|
// A律解码
|
||||||
encodedValue = ~encodedValue;
|
encodedValue ^= 0x55; // 反转最高位
|
||||||
int t = ((encodedValue & 0x0F) << 3) + 0x84;
|
int sign = encodedValue & 0x80; // 符号位
|
||||||
t <<= (encodedValue & 0x70) >> 4;
|
int exponent = (encodedValue & 0x70) >> 4; // 指数位
|
||||||
return (encodedValue & 0x80) != 0 ? 0x84 - t : t - 0x84;
|
int mantissa = encodedValue & 0x0F; // 尾数位
|
||||||
|
|
||||||
|
int pcmSample = (mantissa << 4) + 0x84; // 计算量化值
|
||||||
|
pcmSample <<= exponent; // 根据指数位移位
|
||||||
|
pcmSample = (sign == 0) ? pcmSample : -pcmSample; // 处理符号位
|
||||||
|
|
||||||
|
return pcmSample;
|
||||||
} else {
|
} else {
|
||||||
// μ律解码
|
// μ律解码
|
||||||
encodedValue = ~encodedValue;
|
encodedValue ^= 0xFF; // 反转最高位
|
||||||
int t = ((encodedValue & 0x0F) << 3) + 0x84;
|
int sign = encodedValue & 0x80; // 符号位
|
||||||
t <<= (encodedValue & 0x70) >> 4;
|
int exponent = (encodedValue & 0x70) >> 4; // 指数位
|
||||||
return (encodedValue & 0x80) != 0 ? 0x84 - t : t - 0x84;
|
int mantissa = encodedValue & 0x0F; // 尾数位
|
||||||
|
|
||||||
|
int pcmSample = (mantissa << 3) + 0x84; // 计算量化值
|
||||||
|
pcmSample <<= exponent + 1; // 根据指数位移位(μ律需要额外加1)
|
||||||
|
pcmSample = (sign == 0) ? pcmSample : -pcmSample; // 处理符号位
|
||||||
|
|
||||||
|
return pcmSample;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:star_lock/talk/startChart/constant/message_type_constant.dart';
|
import 'package:star_lock/talk/startChart/constant/message_type_constant.dart';
|
||||||
import 'package:star_lock/talk/startChart/constant/talk_status.dart';
|
import 'package:star_lock/talk/startChart/constant/talk_status.dart';
|
||||||
import 'package:star_lock/talk/startChart/entity/scp_message.dart';
|
import 'package:star_lock/talk/startChart/entity/scp_message.dart';
|
||||||
@ -34,6 +35,7 @@ class UdpTalkHangUpHandler extends ScpMessageBaseHandle
|
|||||||
talkDataOverTimeTimerManager.cancel();
|
talkDataOverTimeTimerManager.cancel();
|
||||||
|
|
||||||
EasyLoading.showToast('已挂断');
|
EasyLoading.showToast('已挂断');
|
||||||
|
Get.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -41,11 +41,12 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
final TalkViewState state = TalkViewState();
|
final TalkViewState state = TalkViewState();
|
||||||
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
|
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
|
||||||
Timer? _syncTimer; // 音视频播放刷新率定时器
|
Timer? _syncTimer; // 音视频播放刷新率定时器
|
||||||
|
Timer? _audioTimer; // 音视频播放刷新率定时器
|
||||||
int _startTime = 0; // 开始播放时间戳,用于判断帧数据中的时间戳位置
|
int _startTime = 0; // 开始播放时间戳,用于判断帧数据中的时间戳位置
|
||||||
int bufferSize = 20; // 缓冲区大小(以帧为单位)
|
int bufferSize = 50; // 缓冲区大小(以帧为单位)
|
||||||
int audioBufferSize = 640; // 缓冲区大小(以帧为单位)
|
|
||||||
final List<int> frameTimestamps = []; // 帧时间戳用于计算 FPS
|
int frameIntervalMs = 45; // 初始帧间隔设置为45毫秒(约22FPS)
|
||||||
int frameIntervalMs = 35; // 初始帧间隔设置为45毫秒(约22FPS)
|
int audioFrameIntervalMs = 20; // 初始帧间隔设置为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 +54,11 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
/// 初始化音频播放器
|
/// 初始化音频播放器
|
||||||
void _initFlutterPcmSound() {
|
void _initFlutterPcmSound() {
|
||||||
const int sampleRate = 8000;
|
const int sampleRate = 8000;
|
||||||
FlutterPcmSound.setLogLevel(LogLevel.verbose);
|
FlutterPcmSound.setLogLevel(LogLevel.none);
|
||||||
FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 1);
|
FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 1);
|
||||||
// 设置 feed 阈值
|
// 设置 feed 阈值
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
FlutterPcmSound.setFeedThreshold(sampleRate ~/ 2); // Android 平台的特殊处理
|
FlutterPcmSound.setFeedThreshold(1024); // Android 平台的特殊处理
|
||||||
} else {
|
} else {
|
||||||
FlutterPcmSound.setFeedThreshold(sampleRate ~/ 32); // 非 Android 平台的处理
|
FlutterPcmSound.setFeedThreshold(sampleRate ~/ 32); // 非 Android 平台的处理
|
||||||
}
|
}
|
||||||
@ -88,7 +89,7 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
// 判断数据类型,进行分发处理
|
// 判断数据类型,进行分发处理
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
case TalkData_ContentTypeE.G711:
|
case TalkData_ContentTypeE.G711:
|
||||||
if (state.audioBuffer.length >= audioBufferSize) {
|
if (state.audioBuffer.length >= bufferSize) {
|
||||||
state.audioBuffer.removeAt(0); // 丢弃最旧的数据
|
state.audioBuffer.removeAt(0); // 丢弃最旧的数据
|
||||||
// readAudioBufferSize.removeAt(0); // 丢弃最旧的数据
|
// readAudioBufferSize.removeAt(0); // 丢弃最旧的数据
|
||||||
}
|
}
|
||||||
@ -130,14 +131,17 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
|
|
||||||
/// 播放音频数据
|
/// 播放音频数据
|
||||||
void _playAudioData(TalkData talkData) async {
|
void _playAudioData(TalkData talkData) async {
|
||||||
// final list = G711().convertList(talkData.content);
|
if (state.isOpenVoice.value) {
|
||||||
final list = G711().decodeAndDenoise(talkData.content, true, 8000, 300, 50);
|
// final list = G711().convertList(talkData.content);
|
||||||
// // 将 PCM 数据转换为 PcmArrayInt16
|
final list = G711().convertList(talkData.content);
|
||||||
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(list);
|
// final list = G711().decodeAndDenoise(talkData.content, true,8000, 300, 50);
|
||||||
FlutterPcmSound.feed(fromList);
|
// // 将 PCM 数据转换为 PcmArrayInt16
|
||||||
if (!state.isPlaying.value) {
|
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(list);
|
||||||
FlutterPcmSound.play();
|
FlutterPcmSound.feed(fromList);
|
||||||
state.isPlaying.value = true;
|
if (!state.isPlaying.value) {
|
||||||
|
FlutterPcmSound.play();
|
||||||
|
state.isPlaying.value = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,8 +158,6 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
|
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
|
||||||
// 动态调整帧间隔
|
// 动态调整帧间隔
|
||||||
_adjustFrameInterval();
|
_adjustFrameInterval();
|
||||||
|
|
||||||
_playFrames();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -184,29 +186,37 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
_syncTimer?.cancel();
|
_syncTimer?.cancel();
|
||||||
_syncTimer =
|
_syncTimer =
|
||||||
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
|
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
|
||||||
_playFrames();
|
// 播放视频帧
|
||||||
|
_playVideoFrames();
|
||||||
|
});
|
||||||
|
|
||||||
|
_audioTimer?.cancel();
|
||||||
|
_audioTimer =
|
||||||
|
Timer.periodic(Duration(milliseconds: audioFrameIntervalMs), (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);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _playFrames() {
|
void _playVideoFrames() {
|
||||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
final elapsedTime = currentTime - _startTime;
|
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 maxFramesToProcess = 5; // 每次最多处理 5 帧
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user