You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

291 lines
8.1 KiB

import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'package:record/record.dart';
class TimbreController extends GetxController {
final GetStorage _storage = GetStorage();
final _record = AudioRecorder();
final String _appId = "43d87d22";
final String _appKey = "2bd01ce06ac35957fc0c082f9b6284ec";
RxList dataList = [].obs;
RxString type = '1'.obs;
RxString text = ''.obs;
String _token = '';
int _textId = 0;
int _segId = 0;
String _taskId = '';
@override
void onInit() {
super.onInit();
_getDataList();
}
@override
void onClose() {
_record.dispose();
super.onClose();
}
void _getDataList() {
List? timbreList = _storage.read('timbreList');
String? timbre = _storage.read('timbre');
if (timbreList != null) {
dataList.value = timbreList;
} else {
dataList.add({
'type': '1',
'name': 'defaultTimbre',
'description': 'microsoftCloudTimbreService',
'isUsable': true
});
_storage.write('timbreList', [
{
'type': '1',
'name': 'defaultTimbre',
'description': 'microsoftCloudTimbreService',
'isUsable': true
}
]);
}
if (timbre != null) {
type.value = timbre;
}
}
// 录制声音
void startRecorder() async {
if (_token.isEmpty) {
await _getToken();
}
await _getTraintext();
await _addTask();
final directory = await getApplicationDocumentsDirectory();
final recordDir = Directory('${directory.path}/RecordSound');
if (!await recordDir.exists()) {
await recordDir.create(recursive: true);
}
final path =
'${directory.path}/RecordSound/recording_${DateTime.now().millisecondsSinceEpoch}.wav';
await _record.start(
const RecordConfig(
encoder: AudioEncoder.wav,
sampleRate: 24000,
numChannels: 1,
),
path: path,
);
}
// 获取token
Future<void> _getToken() async {
int timeStamp = DateTime.now().millisecondsSinceEpoch;
String data =
'{"base":{"appid":"$_appId","version":"v1","timestamp":"$timeStamp"},"model":"remote"}';
String keySignInput = _appKey + timeStamp.toString();
List<int> keySignBytes = utf8.encode(keySignInput);
Digest keySignDigest = md5.convert(keySignBytes);
String keySign = keySignDigest.toString();
String signInput = keySign + data;
List<int> signBytes = utf8.encode(signInput);
Digest signDigest = md5.convert(signBytes);
String sign = signDigest.toString();
final response = await http.post(
Uri.parse('http://avatar-hci.xfyousheng.com/aiauth/v1/token'),
headers: {
'Content-Type': 'application/json',
'Authorization': sign,
},
body: data,
);
if (response.statusCode == 200) {
final body = json.decode(response.body);
if (body['retcode'] == '000000') {
_token = body['accesstoken'];
}
}
}
// 获取训练文本
Future<void> _getTraintext() async {
String data = '{"textId":"5001"}';
Map sign = _sign(data);
final response = await http.post(
Uri.parse('http://opentrain.xfyousheng.com/voice_train/task/traintext'),
headers: {
'Content-Type': 'application/json',
'X-Sign': sign['sign'],
'X-Token': _token,
'X-AppId': _appId,
'X-Time': sign['timeStamp'],
},
body: data,
);
if (response.statusCode == 200) {
final body = json.decode(response.body);
if (body['code'] == 0) {
_textId = body['data']['textId'];
Map textSegs = body['data']['textSegs'].first;
_segId = textSegs['segId'];
text.value = _decodeCorruptedText(textSegs['segText']);
}
}
}
// 创建训练任务
Future<void> _addTask() async {
String data = '{"resourceType":"12"}';
Map sign = _sign(data);
final response = await http.post(
Uri.parse('http://opentrain.xfyousheng.com/voice_train/task/add'),
headers: {
'Content-Type': 'application/json',
'X-Sign': sign['sign'],
'X-Token': _token,
'X-AppId': _appId,
'X-Time': sign['timeStamp'],
},
body: data,
);
if (response.statusCode == 200) {
final body = json.decode(response.body);
if (body['code'] == 0) {
_taskId = body['data'];
}
}
}
Map _sign(String data) {
int timeStamp = DateTime.now().millisecondsSinceEpoch;
String keySignInput = _appKey + timeStamp.toString();
List<int> keySignBytes = utf8.encode(keySignInput);
Digest keySignDigest = md5.convert(keySignBytes);
String keySign = keySignDigest.toString();
String signInput = keySign + data;
List<int> signBytes = utf8.encode(signInput);
Digest signDigest = md5.convert(signBytes);
String sign = signDigest.toString();
return {'sign': sign, 'timeStamp': timeStamp.toString()};
}
String _decodeCorruptedText(String corruptedText) {
List<int> latin1Bytes =
corruptedText.codeUnits.map((ch) => ch & 0xFF).toList();
return utf8.decode(latin1Bytes);
}
// 完成录制
void completeRecorder() async {
final audioPath = await _record.stop();
String data =
'{"taskId":"$_taskId","textId":"$_textId","textSegId":"$_segId"}';
Map sign = _sign(data);
final uri = Uri.parse(
'http://opentrain.xfyousheng.com/voice_train/task/submitWithAudio');
final request = http.MultipartRequest('POST', uri);
request.files.add(await http.MultipartFile.fromPath(
'file',
audioPath!,
filename: path.basename(audioPath),
));
request.fields.addAll({
'taskId': _taskId,
'textId': '$_textId',
'textSegId': '$_segId',
});
request.headers.addAll({
"X-Sign": sign['sign'],
"X-Token": _token,
"X-AppId": _appId,
"X-Time": sign['timeStamp'],
});
final response = await request.send();
if (response.statusCode == 200) {
final responseBody = await response.stream.bytesToString();
final body = json.decode(responseBody);
if (body['code'] == 0) {
text.value = '';
dataList.add({
'type': _taskId,
'name': '声音复刻${dataList.length}',
'description': '复刻本人音色服务',
'isUsable': false
});
await _getTaskResult(_taskId);
}
}
}
// 查询训练任务状态
Future<void> _getTaskResult(String id) async {
String data = '{"taskId":"$id"}';
Map sign = _sign(data);
final response = await http.post(
Uri.parse('http://opentrain.xfyousheng.com/voice_train/task/result'),
headers: {
'Content-Type': 'application/json',
'X-Sign': sign['sign'],
'X-Token': _token,
'X-AppId': _appId,
'X-Time': sign['timeStamp'],
},
body: data,
);
if (response.statusCode == 200) {
final body = json.decode(response.body);
if (body['code'] == 0) {
if (body['data']['trainStatus'] == -1) {
await _getTaskResult(id);
}
if (body['data']['trainStatus'] == 1) {
Map? dataMap =
dataList.firstWhereOrNull((item) => item['type'] == id);
if (dataMap != null) {
dataMap['type'] = body['data']['assetId'];
dataMap['isUsable'] = true;
dataList.refresh();
_storage.write('timbreList', dataList);
}
}
if (body['data']['trainStatus'] == 0) {
Get.snackbar(
'error'.tr, // 错误
_decodeCorruptedText(body['data']['failedDesc']),
snackPosition: SnackPosition.TOP,
);
}
}
}
}
// 取消
void cancel() {
text.value = '';
_record.stop();
Get.back();
}
// 选择音色
void selectTone(Map data) {
if (!data['isUsable'] || type.value == data['type']) return;
type.value = data['type'];
_storage.write('timbre', data['type']);
}
}