diff --git a/lib/talk/call/g711.dart b/lib/talk/call/g711.dart index dfc331be..23b7f9fa 100755 --- a/lib/talk/call/g711.dart +++ b/lib/talk/call/g711.dart @@ -3,12 +3,54 @@ import 'dart:math'; import 'package:flutter/services.dart'; class G711 { + + List _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> readAssetFile(String assetPath) async { final ByteData data = await rootBundle.load(assetPath); final List bytes = data.buffer.asUint8List(); return bytes; } + List encodeALaw(List pcmSamples) { + final List 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; diff --git a/lib/talk/startChart/command/message_command.dart b/lib/talk/startChart/command/message_command.dart index e8ba8d03..a9018fbe 100644 --- a/lib/talk/startChart/command/message_command.dart +++ b/lib/talk/startChart/command/message_command.dart @@ -276,6 +276,11 @@ class MessageCommand { int? SpTotal, int? SpIndex, }) { + // 将 payload 中的每个整数转换为 4 字节序列 + // final payloadBytes = ByteData(payload!.length * 4); + // for (int i = 0; i < payload.length; i++) { + // payloadBytes.setInt32(i * 4, payload[i], Endian.big); // 使用大端序 + // } // final payload = talkData.writeToBuffer(); ScpMessage message = ScpMessage( ProtocolFlag: ProtocolFlagConstant.scp01, @@ -286,7 +291,7 @@ class MessageCommand { FromPeerId: FromPeerId, ToPeerId: ToPeerId, Payload: payload, - PayloadCRC: calculationCrc(Uint8List.fromList(payload!)), + PayloadCRC: calculationCrcFromIntList(payload!), PayloadLength: payload.length, PayloadType: PayloadTypeConstant.talkData, ); @@ -428,7 +433,7 @@ class MessageCommand { FromPeerId: FromPeerId, ToPeerId: ToPeerId, Payload: payload, - PayloadCRC: calculationCrc(Uint8List.fromList(payload)), + PayloadCRC: calculationCrcFromIntList(payload), PayloadLength: payload.length, PayloadType: PayloadTypeConstant.blePassthrough, ); @@ -438,10 +443,30 @@ class MessageCommand { // 辅助方法:将16进制字符串转换为字节列表 static List _hexToBytes(String hex) { - final bytes = []; - for (int i = 0; i < hex.length; i += 2) { - bytes.add(int.parse(hex.substring(i, i + 2), radix: 16)); + // 清理输入字符串,移除所有非十六进制字符 + hex = hex.replaceAll(RegExp(r'[^0-9a-fA-F]'), ''); + + // 如果字符串长度为奇数,在前面补 0 + if (hex.length % 2 != 0) { + hex = '0$hex'; } + + final bytes = []; + + for (int i = 0; i < hex.length; i += 2) { + // 确保 i + 2 不超过 hex 的长度 + final end = i + 2 <= hex.length ? i + 2 : hex.length; + final hexByte = hex.substring(i, end); + + // 解析十六进制字符串为整数 + try { + bytes.add(int.parse(hexByte, radix: 16)); + } catch (e) { + // 如果解析失败,抛出异常或记录日志 + throw FormatException('Invalid hex byte: $hexByte'); + } + } + return bytes; } diff --git a/lib/talk/startChart/start_chart_manage.dart b/lib/talk/startChart/start_chart_manage.dart index 28c9551d..ce5fd29b 100644 --- a/lib/talk/startChart/start_chart_manage.dart +++ b/lib/talk/startChart/start_chart_manage.dart @@ -426,7 +426,7 @@ class StartChartManage { // 发送对讲数据 // 现在的场景只有给锁板发送音频数据 Future sendTalkDataMessage({required TalkData talkData}) async { - String toPeerId = lockPeerId; + String toPeerId = ToPeerId; final List payload = talkData.content; // 计算需要分多少个包发送 final int totalPackets = (payload.length / _maxPayloadSize).ceil(); @@ -452,6 +452,7 @@ class StartChartManage { SpIndex: i + 1, MessageId: messageId, ); + // 发送消息 await _sendMessage(message: message); } diff --git a/lib/talk/startChart/views/talkView/talk_view_logic.dart b/lib/talk/startChart/views/talkView/talk_view_logic.dart index 98d042c0..7cee1f12 100644 --- a/lib/talk/startChart/views/talkView/talk_view_logic.dart +++ b/lib/talk/startChart/views/talkView/talk_view_logic.dart @@ -10,6 +10,7 @@ import 'package:flutter_pcm_sound/flutter_pcm_sound.dart'; import 'package:flutter_screen_recording/flutter_screen_recording.dart'; import 'package:flutter_voice_processor/flutter_voice_processor.dart'; +import 'package:g711_flutter/g711_flutter.dart'; import 'package:gallery_saver/gallery_saver.dart'; import 'package:get/get.dart'; @@ -128,9 +129,10 @@ class TalkViewLogic extends BaseGetXController { /// 播放音频数据 void _playAudioData(TalkData talkData) async { - final list = G711().decodeAndDenoise(talkData.content, true, 8000, 100, 50); - - // 将 PCM 数据转换为 PcmArrayInt16 + // 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); if (!state.isPlaying.value) { @@ -459,14 +461,22 @@ class TalkViewLogic extends BaseGetXController { state.videoBuffer.clear(); // 清空视频缓冲区 _syncTimer?.cancel(); // 取消定时器 _syncTimer = null; // 释放定时器引用 + stopProcessingAudio(); super.onClose(); } + @override + void dispose() { + stopProcessingAudio(); + super.dispose(); + } + /// 处理无效通话状态 void _handleInvalidTalkStatus() { state.listData.value = Uint8List(0); // 停止播放音频 _stopPlayG711Data(); + stopProcessingAudio(); // 状态错误,返回页面 Get.back(); } @@ -576,20 +586,33 @@ class TalkViewLogic extends BaseGetXController { } Future _onFrame(List frame) async { - state.recordingAudioAllFrames.add(frame); // 将帧添加到状态变量中 + // state.recordingAudioAllFrames.add(frame); // 将帧添加到状态变量中 // final List concatenatedFrames = - // _concatenateFrames(state.recordingAudioAllFrames); // 连接所有帧 - final List pcmBytes = _listLinearToULaw(frame); + // concatenateFrames(state.recordingAudioAllFrames); // 连接所有帧 + // final List pcmBytes = _listLinearToULaw(frame); + // final aLaw = G711().encodeALaw(frame); + // final aLawFrame = listLinearToALaw(frame); + // 创建 640 个 0 的 PCM 数据 + // 创建 640 个 0 的 8 位 PCM 数据(无符号) + final pcmSamples = List.filled(640, 0); // 128 是 8 位 PCM 的 0 值 + + // 编码为 A-law + final aLawSamples = listLinearToALaw(pcmSamples, isUnsigned: true); + + final encode = DartG711Codec().encode(Uint8List.fromList(pcmSamples)); + AppLog.log('msg'); + + // AppLog.log('录制的音频数据(A-law):$aLawFrame, size:${aLawFrame.length}'); + // 发送音频数据 await StartChartManage().sendTalkDataMessage( talkData: TalkData( - content: pcmBytes, + content: aLawSamples, contentType: TalkData_ContentTypeE.G711, durationMs: DateTime.now().millisecondsSinceEpoch - state.startRecordingAudioTime.value.millisecondsSinceEpoch, ), ); - AppLog.log('发送音频数据'); } void _onError(VoiceProcessorException error) { @@ -597,57 +620,77 @@ class TalkViewLogic extends BaseGetXController { AppLog.log(error.message!); } -// 拿到的音频转化成pcm - List _listLinearToULaw(List pcmList) { - final List uLawList = []; - for (int pcmVal in pcmList) { - final int uLawVal = _linearToULaw(pcmVal); - uLawList.add(uLawVal); + int linearToALaw(int pcmVal) { + const int ALAW_MAX = 0x7FFF; // 16 位 PCM 的最大值 + const int ALAW_BIAS = 0x84; // A-law 偏置值 + + // 处理符号位 + int sign = (pcmVal & 0x8000) != 0 ? 0x00 : 0x80; // A-law 符号位 + if (sign == 0x80) { + pcmVal = -pcmVal; // 取绝对值 } - return uLawList; + + // 限制 PCM 值在有效范围内 + if (pcmVal > ALAW_MAX) { + pcmVal = ALAW_MAX; + } + + // 添加偏置 + pcmVal += ALAW_BIAS; + + // 查找段和量化值 + int seg = searchALawSegment(pcmVal); + int quantizedValue = (pcmVal >> (seg + 3)) & 0x0F; + + // 生成 A-law 编码 + int aLawVal = sign | (seg << 4) | quantizedValue; + return aLawVal; } - // 拿到的音频转化成pcm - int _linearToULaw(int pcmVal) { - int mask; - int seg; - int uval; - - if (pcmVal < 0) { - pcmVal = 0x84 - pcmVal; - mask = 0x7F; - } else { - pcmVal += 0x84; - mask = 0xFF; - } - - seg = search(pcmVal); - if (seg >= 8) { - return 0x7F ^ mask; - } else { - uval = seg << 4; - uval |= (pcmVal >> (seg + 3)) & 0xF; - return uval ^ mask; - } - } - - int search(int val) { - final List table = [ + int searchALawSegment(int val) { + const List ALAW_SEGMENT_TABLE = [ + 0x1F, + 0x3F, + 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, - 0xFFF, - 0x1FFF, - 0x3FFF, - 0x7FFF + 0xFFF ]; const int size = 8; + for (int i = 0; i < size; i++) { - if (val <= table[i]) { + if (val <= ALAW_SEGMENT_TABLE[i]) { return i; } } return size; } + + List listLinearToALaw(List pcmList, {bool isUnsigned = true}) { + final List aLawList = []; + + // 每两个 8 位 PCM 数据组合成一个 16 位 PCM 数据 + for (int i = 0; i < pcmList.length; i += 2) { + int pcm8High = pcmList[i]; + int pcm8Low = (i + 1 < pcmList.length) ? pcmList[i + 1] : 0; // 如果不足,补 0 + + // 将两个 8 位 PCM 数据组合成一个 16 位 PCM 数据 + int pcm16; + if (isUnsigned) { + // 无符号 8 位 PCM 数据扩展为 16 位 PCM 数据 + pcm16 = ((pcm8High - 128) << 8) | (pcm8Low - 128); + } else { + // 有符号 8 位 PCM 数据扩展为 16 位 PCM 数据 + pcm16 = (pcm8High << 8) | pcm8Low; + } + + // 将 16 位 PCM 数据编码为 A-law + final int aLawVal = linearToALaw(pcm16); + aLawList.add(aLawVal); + } + + return aLawList; + } } diff --git a/lib/talk/startChart/views/talkView/talk_view_state.dart b/lib/talk/startChart/views/talkView/talk_view_state.dart index 18a1da89..33a97af9 100644 --- a/lib/talk/startChart/views/talkView/talk_view_state.dart +++ b/lib/talk/startChart/views/talkView/talk_view_state.dart @@ -56,9 +56,8 @@ class TalkViewState { // 星图对讲相关状态 List audioBuffer = [].obs; List audioBuffer2 = [].obs; - List activeAudioBuffer =[].obs; - List activeVideoBuffer =[].obs; - + List activeAudioBuffer = [].obs; + List activeVideoBuffer = [].obs; List videoBuffer = [].obs; List videoBuffer2 = [].obs; @@ -83,9 +82,9 @@ class TalkViewState { RxInt recordingAudioTime = 0.obs; // 录音时间持续时间 RxDouble fps = 0.0.obs; // 添加 FPS 计数 late VoiceProcessor? voiceProcessor; // 音频处理器、录音 - final int frameLength = 320; //录音视频帧长度为320 + final int frameLength = 640; //录音视频帧长度为640 final int sampleRate = 8000; //录音频采样率为8000 List> recordingAudioAllFrames = >[]; // 录制音频的所有帧 RxInt rotateAngle = 0.obs; // 旋转角度(以弧度为单位) - RxBool isLongPressing = false.obs; // 旋转角度(以弧度为单位) + RxBool isLongPressing = false.obs; // 旋转角度(以弧度为单位) } diff --git a/pubspec.yaml b/pubspec.yaml index bd8d52e2..7df549f9 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -263,7 +263,7 @@ dependencies: fixnum: ^1.1.1 # 图片预览 photo_view: ^0.15.0 - + g711_flutter: ^2.1.1