fix:调整发送g711音频数据
This commit is contained in:
parent
7a0e8f9e28
commit
df71e2ceb7
@ -3,12 +3,54 @@ import 'dart:math';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
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 {
|
||||
final ByteData data = await rootBundle.load(assetPath);
|
||||
final List<int> bytes = data.buffer.asUint8List();
|
||||
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) {
|
||||
// 取反
|
||||
aVal = ~aVal;
|
||||
|
||||
@ -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<int> _hexToBytes(String hex) {
|
||||
final bytes = <int>[];
|
||||
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 = <int>[];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -426,7 +426,7 @@ class StartChartManage {
|
||||
// 发送对讲数据
|
||||
// 现在的场景只有给锁板发送音频数据
|
||||
Future<void> sendTalkDataMessage({required TalkData talkData}) async {
|
||||
String toPeerId = lockPeerId;
|
||||
String toPeerId = ToPeerId;
|
||||
final List<int> 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);
|
||||
}
|
||||
|
||||
@ -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<void> _onFrame(List<int> frame) async {
|
||||
state.recordingAudioAllFrames.add(frame); // 将帧添加到状态变量中
|
||||
// state.recordingAudioAllFrames.add(frame); // 将帧添加到状态变量中
|
||||
// final List<int> concatenatedFrames =
|
||||
// _concatenateFrames(state.recordingAudioAllFrames); // 连接所有帧
|
||||
final List<int> pcmBytes = _listLinearToULaw(frame);
|
||||
// concatenateFrames(state.recordingAudioAllFrames); // 连接所有帧
|
||||
// final List<int> pcmBytes = _listLinearToULaw(frame);
|
||||
// final aLaw = G711().encodeALaw(frame);
|
||||
// final aLawFrame = listLinearToALaw(frame);
|
||||
// 创建 640 个 0 的 PCM 数据
|
||||
// 创建 640 个 0 的 8 位 PCM 数据(无符号)
|
||||
final pcmSamples = List<int>.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<int> _listLinearToULaw(List<int> pcmList) {
|
||||
final List<int> 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<int> table = [
|
||||
int searchALawSegment(int val) {
|
||||
const List<int> 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<int> listLinearToALaw(List<int> pcmList, {bool isUnsigned = true}) {
|
||||
final List<int> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,9 +56,8 @@ class TalkViewState {
|
||||
// 星图对讲相关状态
|
||||
List<TalkData> audioBuffer = <TalkData>[].obs;
|
||||
List<TalkData> audioBuffer2 = <TalkData>[].obs;
|
||||
List<TalkData> activeAudioBuffer =<TalkData>[].obs;
|
||||
List<TalkData> activeVideoBuffer =<TalkData>[].obs;
|
||||
|
||||
List<TalkData> activeAudioBuffer = <TalkData>[].obs;
|
||||
List<TalkData> activeVideoBuffer = <TalkData>[].obs;
|
||||
|
||||
List<TalkData> videoBuffer = <TalkData>[].obs;
|
||||
List<TalkData> videoBuffer2 = <TalkData>[].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<List<int>> recordingAudioAllFrames = <List<int>>[]; // 录制音频的所有帧
|
||||
RxInt rotateAngle = 0.obs; // 旋转角度(以弧度为单位)
|
||||
RxBool isLongPressing = false.obs; // 旋转角度(以弧度为单位)
|
||||
RxBool isLongPressing = false.obs; // 旋转角度(以弧度为单位)
|
||||
}
|
||||
|
||||
@ -263,7 +263,7 @@ dependencies:
|
||||
fixnum: ^1.1.1
|
||||
# 图片预览
|
||||
photo_view: ^0.15.0
|
||||
|
||||
g711_flutter: ^2.1.1
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user