From 41a197f25e169f0a76b40ee5b7a945693b87a320 Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 2 Apr 2025 17:36:27 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E9=9F=B3=E9=A2=91?= =?UTF-8?q?=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/talkView/talk_view_logic.dart | 202 +------------ lib/tools/G711Tool.dart | 273 ++++++++++++++++++ 2 files changed, 278 insertions(+), 197 deletions(-) create mode 100644 lib/tools/G711Tool.dart diff --git a/lib/talk/starChart/views/talkView/talk_view_logic.dart b/lib/talk/starChart/views/talkView/talk_view_logic.dart index 7ed217f6..f4de250b 100644 --- a/lib/talk/starChart/views/talkView/talk_view_logic.dart +++ b/lib/talk/starChart/views/talkView/talk_view_logic.dart @@ -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 _onFrame(List 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 processedFrame = preprocessAudio(frame); - // 添加平滑处理 - final List smoothedFrame = smoothAudio(processedFrame); - final List list = listLinearToALaw(smoothedFrame); - _bufferedAudioFrames.addAll(list); + // 编码为G711数据 + List 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 preprocessAudio(List pcmList) { - final List 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 smoothAudio(List pcmList) { - final List 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 preprocessAudio(List pcmList) { - // final List 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 adjustVolume(List pcmList, double volume) { - final List adjustedPcmList = []; - - for (final int pcmVal in pcmList) { - int adjustedPcmVal = (pcmVal * volume).round(); - adjustedPcmVal = adjustedPcmVal.clamp(-32768, 32767); - adjustedPcmList.add(adjustedPcmVal); - } - - return adjustedPcmList; - } - -// 简化的A-law编码 - List listLinearToALaw(List pcmList) { - // 调整音量,使用适中的增益值 - final List adjustedPcmList = adjustVolume(pcmList, 2.2); - - // 执行A-law编码 - final List aLawList = []; - 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 table = [ - 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; - } } diff --git a/lib/tools/G711Tool.dart b/lib/tools/G711Tool.dart new file mode 100644 index 00000000..1d8069a7 --- /dev/null +++ b/lib/tools/G711Tool.dart @@ -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 _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 _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 _segEnd = [ + 0xFF, + 0x1FF, + 0x3FF, + 0x7FF, + 0xFFF, + 0x1FFF, + 0x3FFF, + 0x7FFF + ]; + + /** + * 在表中搜索值所属的段 + * + * @param val 要搜索的值 + * @param table 段结束值表 + * @param size 表大小 + * @return 段索引 + */ + static int _search(int val, List 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 encode(List pcm, int lawFlag) { + List code = List.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 decode(List code, int lawFlag) { + List pcm = List.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; + } +}