|
|
@ -1,4 +1,5 @@ |
|
|
import 'dart:async'; |
|
|
import 'dart:async'; |
|
|
|
|
|
import 'dart:typed_data'; |
|
|
|
|
|
|
|
|
import 'package:flutter/material.dart'; |
|
|
import 'package:flutter/material.dart'; |
|
|
import 'package:get/get.dart'; |
|
|
import 'package:get/get.dart'; |
|
|
@ -28,6 +29,8 @@ class MeetingRecordController extends GetxController |
|
|
|
|
|
|
|
|
StreamSubscription? _recognitionSubscription; |
|
|
StreamSubscription? _recognitionSubscription; |
|
|
|
|
|
|
|
|
|
|
|
BytesBuilder _originaBytes = BytesBuilder(); //原始音频 |
|
|
|
|
|
|
|
|
@override |
|
|
@override |
|
|
void onInit() { |
|
|
void onInit() { |
|
|
super.onInit(); |
|
|
super.onInit(); |
|
|
@ -45,6 +48,7 @@ class MeetingRecordController extends GetxController |
|
|
timer?.cancel(); |
|
|
timer?.cancel(); |
|
|
_asrService.dispose(); |
|
|
_asrService.dispose(); |
|
|
_recognitionSubscription?.cancel(); |
|
|
_recognitionSubscription?.cancel(); |
|
|
|
|
|
_originaBytes = BytesBuilder(); |
|
|
super.onClose(); |
|
|
super.onClose(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -95,6 +99,7 @@ class MeetingRecordController extends GetxController |
|
|
case RecognitionEventType.finalResult: |
|
|
case RecognitionEventType.finalResult: |
|
|
finalContent.value += _spliceText(event); |
|
|
finalContent.value += _spliceText(event); |
|
|
intermediateContent.value = ''; |
|
|
intermediateContent.value = ''; |
|
|
|
|
|
_originaBytes.add(event.audio!); |
|
|
break; |
|
|
break; |
|
|
default: |
|
|
default: |
|
|
// 不做任何处理 |
|
|
// 不做任何处理 |
|
|
@ -142,12 +147,20 @@ class MeetingRecordController extends GetxController |
|
|
String firstContent = finalContent.value.length >= 30 |
|
|
String firstContent = finalContent.value.length >= 30 |
|
|
? finalContent.value.substring(0, 30) |
|
|
? finalContent.value.substring(0, 30) |
|
|
: finalContent.value; |
|
|
: finalContent.value; |
|
|
|
|
|
Uint8List audio = _originaBytes.toBytes(); |
|
|
|
|
|
Uint8List wavHeader = generateWavHeader( |
|
|
|
|
|
sampleRate: 16000, |
|
|
|
|
|
numberOfChannels: 1, |
|
|
|
|
|
bitsPerSample: 16, |
|
|
|
|
|
dataLength: audio.length, |
|
|
|
|
|
); |
|
|
|
|
|
Uint8List wavData = Uint8List.fromList([...wavHeader, ...audio]); |
|
|
Map<String, Object?> data = { |
|
|
Map<String, Object?> data = { |
|
|
'title': title, |
|
|
'title': title, |
|
|
'firstContent': firstContent.replaceAll("\n", ""), |
|
|
'firstContent': firstContent.replaceAll("\n", ""), |
|
|
'seconds': seconds.value, |
|
|
'seconds': seconds.value, |
|
|
'creationTime': creationTime, |
|
|
'creationTime': creationTime, |
|
|
'audio': [], |
|
|
'audio': wavData, |
|
|
'content': finalContent.value, |
|
|
'content': finalContent.value, |
|
|
}; |
|
|
}; |
|
|
final result = await SqfliteApi.insertMeeting(data); |
|
|
final result = await SqfliteApi.insertMeeting(data); |
|
|
@ -162,4 +175,73 @@ class MeetingRecordController extends GetxController |
|
|
Get.back(); |
|
|
Get.back(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Uint8List generateWavHeader({ |
|
|
|
|
|
required int sampleRate, |
|
|
|
|
|
required int numberOfChannels, |
|
|
|
|
|
required int bitsPerSample, |
|
|
|
|
|
required int dataLength, |
|
|
|
|
|
}) { |
|
|
|
|
|
final byteRate = sampleRate * numberOfChannels * (bitsPerSample ~/ 8); |
|
|
|
|
|
final blockAlign = numberOfChannels * (bitsPerSample ~/ 8); |
|
|
|
|
|
final riffSize = 36 + dataLength; |
|
|
|
|
|
final header = Uint8List(44); |
|
|
|
|
|
var pos = 0; |
|
|
|
|
|
// RIFF 块 |
|
|
|
|
|
header.setRange(pos, pos + 4, [0x52, 0x49, 0x46, 0x46]); // 'RIFF' |
|
|
|
|
|
pos += 4; |
|
|
|
|
|
// 写入 RIFF 大小(小端) |
|
|
|
|
|
header.setRange(pos, pos + 4, [ |
|
|
|
|
|
riffSize & 0xff, |
|
|
|
|
|
(riffSize >> 8) & 0xff, |
|
|
|
|
|
(riffSize >> 16) & 0xff, |
|
|
|
|
|
(riffSize >> 24) & 0xff, |
|
|
|
|
|
]); |
|
|
|
|
|
pos += 4; |
|
|
|
|
|
// WAVE 标识 |
|
|
|
|
|
header.setRange(pos, pos + 4, [0x57, 0x41, 0x56, 0x45]); // 'WAVE' |
|
|
|
|
|
pos += 4; |
|
|
|
|
|
// fmt 子块 |
|
|
|
|
|
header.setRange(pos, pos + 4, [0x66, 0x6d, 0x74, 0x20]); // 'fmt ' |
|
|
|
|
|
pos += 4; |
|
|
|
|
|
header.setRange(pos, pos + 4, [16, 0, 0, 0]); // 块大小 |
|
|
|
|
|
pos += 4; |
|
|
|
|
|
header.setRange(pos, pos + 2, [1, 0]); // PCM 格式 |
|
|
|
|
|
pos += 2; |
|
|
|
|
|
header.setRange(pos, pos + 2, [numberOfChannels, 0]); // 声道数 |
|
|
|
|
|
pos += 2; |
|
|
|
|
|
// 采样率(小端) |
|
|
|
|
|
header.setRange(pos, pos + 4, [ |
|
|
|
|
|
sampleRate & 0xff, |
|
|
|
|
|
(sampleRate >> 8) & 0xff, |
|
|
|
|
|
(sampleRate >> 16) & 0xff, |
|
|
|
|
|
(sampleRate >> 24) & 0xff, |
|
|
|
|
|
]); |
|
|
|
|
|
pos += 4; |
|
|
|
|
|
// 字节率(小端) |
|
|
|
|
|
header.setRange(pos, pos + 4, [ |
|
|
|
|
|
byteRate & 0xff, |
|
|
|
|
|
(byteRate >> 8) & 0xff, |
|
|
|
|
|
(byteRate >> 16) & 0xff, |
|
|
|
|
|
(byteRate >> 24) & 0xff, |
|
|
|
|
|
]); |
|
|
|
|
|
pos += 4; |
|
|
|
|
|
// 块对齐 |
|
|
|
|
|
header.setRange(pos, pos + 2, [blockAlign, 0]); |
|
|
|
|
|
pos += 2; |
|
|
|
|
|
// 位深度 |
|
|
|
|
|
header.setRange(pos, pos + 2, [bitsPerSample, 0]); |
|
|
|
|
|
pos += 2; |
|
|
|
|
|
// data 子块 |
|
|
|
|
|
header.setRange(pos, pos + 4, [0x64, 0x61, 0x74, 0x61]); // 'data' |
|
|
|
|
|
pos += 4; |
|
|
|
|
|
// 数据大小(小端) |
|
|
|
|
|
header.setRange(pos, pos + 4, [ |
|
|
|
|
|
dataLength & 0xff, |
|
|
|
|
|
(dataLength >> 8) & 0xff, |
|
|
|
|
|
(dataLength >> 16) & 0xff, |
|
|
|
|
|
(dataLength >> 24) & 0xff, |
|
|
|
|
|
]); |
|
|
|
|
|
return header; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|