fix:调整音频效果
This commit is contained in:
parent
295995abd7
commit
41a197f25e
@ -28,6 +28,7 @@ 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/tools/G711Tool.dart';
|
||||
import 'package:star_lock/tools/bugly/bugly_tool.dart';
|
||||
|
||||
import '../../../../tools/baseGetXController.dart';
|
||||
@ -658,23 +659,11 @@ class TalkViewLogic extends BaseGetXController {
|
||||
|
||||
// 音频帧处理
|
||||
Future<void> _onFrame(List<int> frame) async {
|
||||
// 添加数据监控
|
||||
int maxVal = 0;
|
||||
int minVal = 0;
|
||||
int sum = 0;
|
||||
for (int val in frame) {
|
||||
maxVal = max(maxVal, val);
|
||||
minVal = min(minVal, val);
|
||||
sum += val;
|
||||
}
|
||||
double average = sum / frame.length;
|
||||
AppLog.log('音频数据特征 - 最大值: $maxVal, 最小值: $minVal, 平均值: $average');
|
||||
|
||||
final List<int> processedFrame = preprocessAudio(frame);
|
||||
// 添加平滑处理
|
||||
final List<int> smoothedFrame = smoothAudio(processedFrame);
|
||||
final List<int> list = listLinearToALaw(smoothedFrame);
|
||||
_bufferedAudioFrames.addAll(list);
|
||||
// 编码为G711数据
|
||||
List<int> encodedData = G711Tool.encode(frame, 0); // 0表示A-law
|
||||
|
||||
_bufferedAudioFrames.addAll(encodedData);
|
||||
|
||||
final int ms = DateTime.now().millisecondsSinceEpoch -
|
||||
state.startRecordingAudioTime.value.millisecondsSinceEpoch;
|
||||
@ -685,15 +674,6 @@ class TalkViewLogic extends BaseGetXController {
|
||||
}
|
||||
|
||||
if (_bufferedAudioFrames.length >= getFrameLength) {
|
||||
// 添加数据监控
|
||||
int maxVal = 0;
|
||||
int minVal = 255;
|
||||
for (int val in _bufferedAudioFrames) {
|
||||
maxVal = max(maxVal, val);
|
||||
minVal = min(minVal, val);
|
||||
}
|
||||
AppLog.log(
|
||||
'发送音频数据 - G711编码后 - 最大值: $maxVal, 最小值: $minVal, 长度: ${_bufferedAudioFrames.length}');
|
||||
// 发送音频数据到UDP
|
||||
await StartChartManage()
|
||||
.sendTalkDataMessage(
|
||||
@ -714,177 +694,5 @@ class TalkViewLogic extends BaseGetXController {
|
||||
AppLog.log(error.message!);
|
||||
}
|
||||
|
||||
// 简化的音频预处理函数
|
||||
List<int> preprocessAudio(List<int> pcmList) {
|
||||
final List<int> processedList = [];
|
||||
final int noiseThreshold = 300; // 噪音阈值
|
||||
|
||||
for (int pcmVal in pcmList) {
|
||||
// 简单的噪音门控:小于阈值的信号会被减弱
|
||||
if (pcmVal.abs() < noiseThreshold) {
|
||||
pcmVal = (pcmVal * 0.3).round(); // 减弱噪音
|
||||
}
|
||||
|
||||
// 简单的压缩:防止大信号失真
|
||||
if (pcmVal.abs() > 20000) {
|
||||
double factor = 1.0 - ((pcmVal.abs() - 20000) / 12768) * 0.3;
|
||||
pcmVal = (pcmVal * factor).round();
|
||||
}
|
||||
|
||||
processedList.add(pcmVal);
|
||||
}
|
||||
|
||||
return processedList;
|
||||
}
|
||||
|
||||
// 简单的平滑处理
|
||||
List<int> smoothAudio(List<int> pcmList) {
|
||||
final List<int> smoothedList = [];
|
||||
|
||||
for (int i = 0; i < pcmList.length; i++) {
|
||||
if (i > 0 && i < pcmList.length - 1) {
|
||||
// 简单的三点平均
|
||||
int avg = (pcmList[i - 1] + pcmList[i] * 2 + pcmList[i + 1]) ~/ 4;
|
||||
smoothedList.add(avg);
|
||||
} else {
|
||||
smoothedList.add(pcmList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return smoothedList;
|
||||
}
|
||||
|
||||
//test测试降噪算法
|
||||
// List<int> preprocessAudio(List<int> pcmList) {
|
||||
// final List<int> processedList = [];
|
||||
// final int windowSize = 5;
|
||||
// final int thresholdFactor = 2; // 动态阈值的倍数
|
||||
|
||||
// for (int i = 0; i < pcmList.length; i++) {
|
||||
// int pcmVal = pcmList[i];
|
||||
|
||||
// // 计算当前窗口内的标准差
|
||||
// int sum = 0;
|
||||
// int count = 0;
|
||||
// for (int j = i; j < i + windowSize && j < pcmList.length; j++) {
|
||||
// sum += pcmList[j];
|
||||
// count++;
|
||||
// }
|
||||
// int mean = sum ~/ count;
|
||||
|
||||
// // 计算标准差
|
||||
// int varianceSum = 0;
|
||||
// for (int j = i; j < i + windowSize && j < pcmList.length; j++) {
|
||||
// varianceSum += (pcmList[j] - mean) * (pcmList[j] - mean);
|
||||
// }
|
||||
// double standardDeviation =
|
||||
// sqrt(varianceSum / count); // Use sqrt from dart:math
|
||||
|
||||
// // 动态阈值
|
||||
// int dynamicThreshold = (standardDeviation * thresholdFactor).toInt();
|
||||
|
||||
// // 动态降噪:如果信号小于动态阈值,则设为0
|
||||
// if (pcmVal.abs() < dynamicThreshold) {
|
||||
// pcmVal = 0;
|
||||
// }
|
||||
|
||||
// // 移动平均滤波器
|
||||
// int sumFilter = 0;
|
||||
// int countFilter = 0;
|
||||
// for (int j = i; j < i + windowSize && j < pcmList.length; j++) {
|
||||
// sumFilter += pcmList[j];
|
||||
// countFilter++;
|
||||
// }
|
||||
// int average = sumFilter ~/ countFilter;
|
||||
|
||||
// processedList.add(average);
|
||||
// }
|
||||
|
||||
// return processedList;
|
||||
// }
|
||||
|
||||
// 简化的音量调整
|
||||
List<int> adjustVolume(List<int> pcmList, double volume) {
|
||||
final List<int> adjustedPcmList = <int>[];
|
||||
|
||||
for (final int pcmVal in pcmList) {
|
||||
int adjustedPcmVal = (pcmVal * volume).round();
|
||||
adjustedPcmVal = adjustedPcmVal.clamp(-32768, 32767);
|
||||
adjustedPcmList.add(adjustedPcmVal);
|
||||
}
|
||||
|
||||
return adjustedPcmList;
|
||||
}
|
||||
|
||||
// 简化的A-law编码
|
||||
List<int> listLinearToALaw(List<int> pcmList) {
|
||||
// 调整音量,使用适中的增益值
|
||||
final List<int> adjustedPcmList = adjustVolume(pcmList, 2.2);
|
||||
|
||||
// 执行A-law编码
|
||||
final List<int> aLawList = <int>[];
|
||||
for (final int pcmVal in adjustedPcmList) {
|
||||
final int aLawVal = linearToALaw(pcmVal);
|
||||
aLawList.add(aLawVal);
|
||||
}
|
||||
|
||||
return aLawList;
|
||||
}
|
||||
|
||||
int linearToALaw(int pcmVal) {
|
||||
const int alawMax = 0x7FFF; // 32767
|
||||
const int alawBias = 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 += alawBias;
|
||||
if (pcmVal > alawMax) {
|
||||
pcmVal = alawMax;
|
||||
}
|
||||
|
||||
// Determine segment
|
||||
seg = search(pcmVal);
|
||||
|
||||
// Calculate A-law value
|
||||
if (seg >= 8) {
|
||||
aLawVal = 0x7F ^ mask; // Clamp to maximum value
|
||||
} else {
|
||||
final 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<int> table = <int>[
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
273
lib/tools/G711Tool.dart
Normal file
273
lib/tools/G711Tool.dart
Normal file
@ -0,0 +1,273 @@
|
||||
/**
|
||||
* G711Tool - G.711音频编解码工具类
|
||||
*
|
||||
* G.711是一种用于音频压缩的ITU-T标准,主要用于电话系统。
|
||||
* 它有两种主要变体:A-law(主要用于欧洲和国际电话系统)和μ-law(主要用于北美和日本)。
|
||||
*
|
||||
* 该类提供了PCM线性音频数据与G.711 A-law/μ-law格式之间的转换功能。
|
||||
* 编码过程将16位线性PCM样本压缩为8位,解码过程则相反。
|
||||
*/
|
||||
class G711Tool {
|
||||
// 常量定义
|
||||
static const int SIGN_BIT = 0x80; // A-law字节的符号位
|
||||
static const int QUANT_MASK = 0xf; // 量化字段掩码
|
||||
static const int NSEGS = 8; // A-law段数
|
||||
static const int SEG_SHIFT = 4; // 段号左移位数
|
||||
static const int SEG_MASK = 0x70; // 段字段掩码
|
||||
static const int BIAS = 0x84; // 线性编码的偏置值
|
||||
|
||||
// 查找表
|
||||
/**
|
||||
* μ-law到A-law的转换表
|
||||
* 用于在两种编码格式之间进行转换而不需要先解码为线性PCM
|
||||
*/
|
||||
static final List<int> _u2a = [
|
||||
// u- to A-law conversions
|
||||
1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8,
|
||||
9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
|
||||
25, 27, 29, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
|
||||
46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
|
||||
64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
|
||||
81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
|
||||
97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
|
||||
113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
|
||||
128
|
||||
];
|
||||
|
||||
/**
|
||||
* A-law到μ-law的转换表
|
||||
* 用于在两种编码格式之间进行转换而不需要先解码为线性PCM
|
||||
*/
|
||||
static final List<int> _a2u = [
|
||||
// A- to u-law conversions
|
||||
1, 3, 5, 7, 9, 11, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
||||
24, 25, 26, 27, 28, 29, 30, 31, 32, 32, 33, 33, 34, 34, 35, 35,
|
||||
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 48, 49, 49,
|
||||
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64,
|
||||
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 79,
|
||||
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
|
||||
96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
|
||||
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126,
|
||||
127
|
||||
];
|
||||
|
||||
/**
|
||||
* 段结束值表
|
||||
* 用于确定PCM值应该映射到哪个段
|
||||
* 每个段代表不同的量化步长
|
||||
*/
|
||||
static final List<int> _segEnd = [
|
||||
0xFF,
|
||||
0x1FF,
|
||||
0x3FF,
|
||||
0x7FF,
|
||||
0xFFF,
|
||||
0x1FFF,
|
||||
0x3FFF,
|
||||
0x7FFF
|
||||
];
|
||||
|
||||
/**
|
||||
* 在表中搜索值所属的段
|
||||
*
|
||||
* @param val 要搜索的值
|
||||
* @param table 段结束值表
|
||||
* @param size 表大小
|
||||
* @return 段索引
|
||||
*/
|
||||
static int _search(int val, List<int> table, int size) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (val <= table[i]) return i;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将线性PCM值转换为A-law编码值
|
||||
*
|
||||
* 转换步骤:
|
||||
* 1. 确定符号位和掩码
|
||||
* 2. 对负值进行特殊处理
|
||||
* 3. 确定段号
|
||||
* 4. 根据段号计算A-law值
|
||||
*
|
||||
* @param pcmVal 16位线性PCM值
|
||||
* @return 8位A-law编码值
|
||||
*/
|
||||
static int _linear2alaw(int pcmVal) {
|
||||
int mask;
|
||||
int seg;
|
||||
int aval;
|
||||
|
||||
if (pcmVal >= 0) {
|
||||
mask = 0xD5; // sign (7th) bit = 1
|
||||
} else {
|
||||
mask = 0x55; // sign bit = 0
|
||||
pcmVal = -pcmVal - 1;
|
||||
if (pcmVal < 0) pcmVal = 32767;
|
||||
}
|
||||
|
||||
seg = _search(pcmVal, _segEnd, 8);
|
||||
|
||||
if (seg >= 8) return 0x7F ^ mask;
|
||||
|
||||
aval = seg << SEG_SHIFT;
|
||||
if (seg < 2) {
|
||||
aval |= (pcmVal >> 4) & QUANT_MASK;
|
||||
} else {
|
||||
aval |= (pcmVal >> (seg + 3)) & QUANT_MASK;
|
||||
}
|
||||
return aval ^ mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将A-law编码值转换为线性PCM值
|
||||
*
|
||||
* 转换步骤:
|
||||
* 1. 异或操作恢复原始位模式
|
||||
* 2. 提取量化值和段号
|
||||
* 3. 根据段号计算线性值
|
||||
* 4. 应用符号位
|
||||
*
|
||||
* @param aVal 8位A-law编码值
|
||||
* @return 16位线性PCM值
|
||||
*/
|
||||
static int _alaw2linear(int aVal) {
|
||||
int t;
|
||||
int seg;
|
||||
|
||||
aVal ^= 0x55;
|
||||
|
||||
t = (aVal & QUANT_MASK) << 4;
|
||||
seg = (aVal & SEG_MASK) >> SEG_SHIFT;
|
||||
|
||||
switch (seg) {
|
||||
case 0:
|
||||
t += 8;
|
||||
break;
|
||||
case 1:
|
||||
t += 0x108;
|
||||
break;
|
||||
default:
|
||||
t += 0x108;
|
||||
t <<= seg - 1;
|
||||
}
|
||||
return (aVal & SIGN_BIT) != 0 ? t : -t;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将线性PCM值转换为μ-law编码值
|
||||
*
|
||||
* 转换步骤与A-law类似,但使用不同的偏置和掩码
|
||||
*
|
||||
* @param pcmVal 16位线性PCM值
|
||||
* @return 8位μ-law编码值
|
||||
*/
|
||||
static int _linear2ulaw(int pcmVal) {
|
||||
int mask;
|
||||
int seg;
|
||||
int uval;
|
||||
|
||||
if (pcmVal < 0) {
|
||||
pcmVal = BIAS - pcmVal;
|
||||
mask = 0x7F;
|
||||
} else {
|
||||
pcmVal += BIAS;
|
||||
mask = 0xFF;
|
||||
}
|
||||
|
||||
seg = _search(pcmVal, _segEnd, 8);
|
||||
|
||||
if (seg >= 8) return 0x7F ^ mask;
|
||||
|
||||
uval = (seg << 4) | ((pcmVal >> (seg + 3)) & 0xF);
|
||||
return uval ^ mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将μ-law编码值转换为线性PCM值
|
||||
*
|
||||
* @param uVal 8位μ-law编码值
|
||||
* @return 16位线性PCM值
|
||||
*/
|
||||
static int _ulaw2linear(int uVal) {
|
||||
uVal = ~uVal;
|
||||
int t = ((uVal & QUANT_MASK) << 3) + BIAS;
|
||||
t <<= (uVal & SEG_MASK) >> SEG_SHIFT;
|
||||
return (uVal & SIGN_BIT) != 0 ? (BIAS - t) : (t - BIAS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将A-law编码值转换为μ-law编码值
|
||||
*
|
||||
* 使用查找表直接转换,避免解码再编码的性能损失
|
||||
*
|
||||
* @param aval 8位A-law编码值
|
||||
* @return 8位μ-law编码值
|
||||
*/
|
||||
static int alaw2ulaw(int aval) {
|
||||
aval &= 0xff;
|
||||
return (aval & 0x80) != 0
|
||||
? (0xFF ^ _a2u[aval ^ 0xD5])
|
||||
: (0x7F ^ _a2u[aval ^ 0x55]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将μ-law编码值转换为A-law编码值
|
||||
*
|
||||
* 使用查找表直接转换,避免解码再编码的性能损失
|
||||
*
|
||||
* @param uval 8位μ-law编码值
|
||||
* @return 8位A-law编码值
|
||||
*/
|
||||
static int ulaw2alaw(int uval) {
|
||||
uval &= 0xff;
|
||||
return (uval & 0x80) != 0
|
||||
? (0xD5 ^ (_u2a[0xFF ^ uval] - 1))
|
||||
: (0x55 ^ (_u2a[0x7F ^ uval] - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将PCM数据编码为G.711格式
|
||||
*
|
||||
* @param pcm 输入PCM数据
|
||||
* @param lawFlag 编码类型标志: 0表示A-law, 1表示μ-law
|
||||
* @return 编码后的G.711数据
|
||||
*/
|
||||
static List<int> encode(List<int> pcm, int lawFlag) {
|
||||
List<int> code = List<int>.filled(pcm.length, 0);
|
||||
|
||||
if (lawFlag == 0) {
|
||||
for (int i = 0; i < pcm.length; i++) {
|
||||
code[i] = _linear2alaw(pcm[i]);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < pcm.length; i++) {
|
||||
code[i] = _linear2ulaw(pcm[i]);
|
||||
}
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将G.711数据解码为PCM格式
|
||||
*
|
||||
* @param code 输入G.711数据
|
||||
* @param lawFlag 编码类型标志: 0表示A-law, 1表示μ-law
|
||||
* @return 解码后的PCM数据
|
||||
*/
|
||||
static List<int> decode(List<int> code, int lawFlag) {
|
||||
List<int> pcm = List<int>.filled(code.length, 0);
|
||||
|
||||
if (lawFlag == 0) {
|
||||
for (int i = 0; i < code.length; i++) {
|
||||
pcm[i] = _alaw2linear(code[i]);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < code.length; i++) {
|
||||
pcm[i] = _ulaw2linear(code[i]);
|
||||
}
|
||||
}
|
||||
return pcm;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user