Browse Source

优化语言识别,优化ble指令发送

weicu
fdp 1 year ago
parent
commit
09e3e856b8
  1. 11
      lib/data/services/asr_service.dart
  2. 38
      lib/data/services/ble_manager.dart
  3. 57
      lib/data/services/speech_impl/azure_asr_service.dart
  4. 18
      lib/data/services/speech_impl/volcano_asr_api_service.dart
  5. 18
      lib/data/services/speech_impl/volcano_asr_service.dart
  6. 100
      lib/data/services/speech_impl/xunfei_asr_service.dart
  7. 13
      lib/modules/FTFTranslation/controllers/FTFTranslation_controller.dart
  8. 90
      lib/modules/agent/controllers/agent_controller.dart
  9. 31
      lib/modules/meeting/controllers/meeting_record_controller.dart
  10. 44
      lib/modules/settings/views/settings_view.dart
  11. 10
      lib/modules/speech_test/controllers/speech_test_controller.dart
  12. 34
      lib/modules/translation/controllers/translation_controller.dart
  13. 676
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  14. 86
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt
  15. 22
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt
  16. 53
      local_plugins/agent_service/lib/agent_service.dart
  17. 824
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  18. 128
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  19. 3
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/RecordFile.kt
  20. 55
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

11
lib/data/services/asr_service.dart

@ -19,18 +19,17 @@ abstract class AsrService {
/// 开始连续语音识别 /// 开始连续语音识别
/// ///
/// 返回一个包含识别事件的流 /// 返回一个包含识别事件的流
Future<Stream<RecognitionEvent>> startContinuousRecognition( Future<bool> startContinuousRecognition(
bool audioSourceType, bool audioSourceType,
); );
/// 停止连续语音识别 /// 停止连续语音识别
Future<bool> stopContinuousRecognition(); Future<bool> stopContinuousRecognition();
/// 停止音频输入 /// 设置识别回掉
Future<void> setPause(); ///
/// 返回一个包含识别事件的流
/// 继续音频输入 Future<Stream<RecognitionEvent>> recognizeCallback();
Future<void> setContinue();
/// 恢复原始音频状态 /// 恢复原始音频状态
Future<void> restoreOriginalAudioState(); Future<void> restoreOriginalAudioState();

38
lib/data/services/ble_manager.dart

@ -472,37 +472,13 @@ class BleManager extends GetxService {
firmwareProductColor: '', firmwareProductColor: '',
); );
} }
final commands = [
_bleService.getBatteryInfo, // 并行获取所有设备信息,不使用延迟
_bleService.getDeviceVersion, final results = await Future.wait([
_bleService.getProductId, _bleService.getBatteryInfo(),
]; _bleService.getDeviceVersion(),
_bleService.getProductId(),
final delays = [ ]);
Duration.zero, // 第一个命令立即执行
const Duration(milliseconds: 100), // 第二个命令延迟100ms
const Duration(milliseconds: 100), // 第三个命令延迟200ms
];
final futures = commands
.asMap()
.map((index, command) {
final delay = delays[index];
return MapEntry(
index,
Future.delayed(delay, command),
);
})
.values
.toList();
final results = await Future.wait(futures);
// // 并行获取所有设备信息,不使用延迟
// final results = await Future.wait([
// _bleService.getBatteryInfo(),
// _bleService.getDeviceVersion(),
// _bleService.getProductId(),
// ]);
Logger.info( Logger.info(
'设备信息获取结果: 电池=${results[0]}, 版本=${results[1]}, 产品ID=${results[2]}'); '设备信息获取结果: 电池=${results[0]}, 版本=${results[1]}, 产品ID=${results[2]}');

57
lib/data/services/speech_impl/azure_asr_service.dart

@ -150,8 +150,7 @@ class AzureAsrService extends GetxService implements AsrService {
} }
@override @override
Future<Stream<RecognitionEvent>> startContinuousRecognition( Future<bool> startContinuousRecognition(bool audioSourceType) async {
bool audioSourceType) async {
if (!_isInitialized) { if (!_isInitialized) {
await initialize(); await initialize();
} }
@ -161,8 +160,6 @@ class AzureAsrService extends GetxService implements AsrService {
} }
try { try {
_eventStreamController = StreamController<RecognitionEvent>.broadcast();
// 开始连续识别 // 开始连续识别
final bool result = final bool result =
await _channel.invokeMethod('startContinuousRecognition', { await _channel.invokeMethod('startContinuousRecognition', {
@ -170,15 +167,13 @@ class AzureAsrService extends GetxService implements AsrService {
}); });
if (!result) { if (!result) {
_cleanupEventStream();
throw Exception('启动连续识别失败'); throw Exception('启动连续识别失败');
} }
_isContinuousRecognitionActive = true; _isContinuousRecognitionActive = true;
return _eventStreamController!.stream; return result;
} catch (e) { } catch (e) {
Logger.error('开始连续语音识别失败: ${e.toString()}'); Logger.error('开始连续语音识别失败: ${e.toString()}');
_cleanupEventStream();
rethrow; rethrow;
} }
} }
@ -192,11 +187,30 @@ class AzureAsrService extends GetxService implements AsrService {
try { try {
final bool result = final bool result =
await _channel.invokeMethod('stopContinuousRecognition'); await _channel.invokeMethod('stopContinuousRecognition');
_cleanupEventStream();
return result; return result;
} catch (e) { } catch (e) {
Logger.error('停止连续语音识别失败: ${e.toString()}'); Logger.error('停止连续语音识别失败: ${e.toString()}');
_cleanupEventStream(); rethrow;
}
}
@override
Future<Stream<RecognitionEvent>> recognizeCallback() async {
if (!_isInitialized) {
await initialize();
}
try {
_eventStreamController = StreamController<RecognitionEvent>.broadcast();
// 开始连续识别
final bool result = await _channel.invokeMethod('recognizeCallback');
if (!result) {
_cleanupEventStream();
}
return _eventStreamController!.stream;
} catch (e) {
Logger.error('开始连续语音识别失败: ${e.toString()}');
rethrow; rethrow;
} }
} }
@ -415,31 +429,6 @@ class AzureAsrService extends GetxService implements AsrService {
} }
} }
@override
Future<void> setPause() async {
// TODO: implement setPause
try {
final bool result = await _channel.invokeMethod('setPause');
return;
} catch (e) {
Logger.error('开始录音: ${e.toString()}');
rethrow;
}
}
@override
Future<void> setContinue() async {
// TODO: implement setcontinue
try {
final bool result = await _channel.invokeMethod('setcontinue');
return;
} catch (e) {
Logger.error('开始录音: ${e.toString()}');
rethrow;
}
}
@override @override
Future<void> disableBluetoothAudio() async { Future<void> disableBluetoothAudio() async {
// TODO: implement disableBluetoothAudio // TODO: implement disableBluetoothAudio

18
lib/data/services/speech_impl/volcano_asr_api_service.dart

@ -842,18 +842,6 @@ class VolcanoAsrApiService implements AsrService {
throw UnimplementedError(); throw UnimplementedError();
} }
@override
Future<void> setPause() {
// TODO: implement setPause
throw UnimplementedError();
}
@override
Future<void> setContinue() {
// TODO: implement setcontinue
throw UnimplementedError();
}
@override @override
Future<void> disableBluetoothAudio() { Future<void> disableBluetoothAudio() {
// TODO: implement disableBluetoothAudio // TODO: implement disableBluetoothAudio
@ -865,4 +853,10 @@ class VolcanoAsrApiService implements AsrService {
// TODO: implement restoreOriginalAudioState // TODO: implement restoreOriginalAudioState
throw UnimplementedError(); throw UnimplementedError();
} }
@override
Future<Stream<RecognitionEvent>> recognizeCallback() {
// TODO: implement recognizeCallback
throw UnimplementedError();
}
} }

18
lib/data/services/speech_impl/volcano_asr_service.dart

@ -377,18 +377,6 @@ class VolcanoAsrService extends GetxService implements AsrService {
throw UnimplementedError(); throw UnimplementedError();
} }
@override
Future<void> setPause() {
// TODO: implement setPause
throw UnimplementedError();
}
@override
Future<void> setContinue() {
// TODO: implement setcontinue
throw UnimplementedError();
}
@override @override
Future<void> disableBluetoothAudio() { Future<void> disableBluetoothAudio() {
// TODO: implement disableBluetoothAudio // TODO: implement disableBluetoothAudio
@ -400,4 +388,10 @@ class VolcanoAsrService extends GetxService implements AsrService {
// TODO: implement restoreOriginalAudioState // TODO: implement restoreOriginalAudioState
throw UnimplementedError(); throw UnimplementedError();
} }
@override
Future<Stream<RecognitionEvent>> recognizeCallback() {
// TODO: implement recognizeCallback
throw UnimplementedError();
}
} }

100
lib/data/services/speech_impl/xunfei_asr_service.dart

@ -74,48 +74,48 @@ class XunfeiAsrService extends GetxService implements AsrService {
} }
} }
@override // @override
Future<Stream<RecognitionEvent>> startContinuousRecognition( // Future<Stream<RecognitionEvent>> startContinuousRecognition(
bool audioSourceType) async { // bool audioSourceType) async {
if (!_isInitialized) { // if (!_isInitialized) {
await initialize(); // await initialize();
} // }
try { // try {
_eventStreamController = StreamController<RecognitionEvent>.broadcast(); // _eventStreamController = StreamController<RecognitionEvent>.broadcast();
await recorder?.dispose(); // await recorder?.dispose();
recorder = AudioRecorder(); // recorder = AudioRecorder();
Stream<Uint8List>? stream = await recorder?.startStream( // Stream<Uint8List>? stream = await recorder?.startStream(
const RecordConfig( // const RecordConfig(
sampleRate: 16000, // sampleRate: 16000,
encoder: AudioEncoder.pcm16bits, // encoder: AudioEncoder.pcm16bits,
numChannels: 1, // numChannels: 1,
), // ),
); // );
isRecording = true; // isRecording = true;
stream?.listen((data) { // stream?.listen((data) {
if (isRecording) { // if (isRecording) {
_buff = _appendToBuffer(data); // _buff = _appendToBuffer(data);
_originaBytes.add(data); // _originaBytes.add(data);
} // }
}); // });
await _tryConnecting(); // await _tryConnecting();
_timer = Timer.periodic(const Duration(milliseconds: 40), (timer) async { // _timer = Timer.periodic(const Duration(milliseconds: 40), (timer) async {
//每40毫秒读取一次数据 // //每40毫秒读取一次数据
var frame = _getAudioData(); // var frame = _getAudioData();
if (frame.isNotEmpty) { // if (frame.isNotEmpty) {
_channel?.sink.add(frame); // _channel?.sink.add(frame);
} // }
}); // });
_isContinuousRecognitionActive = true; // _isContinuousRecognitionActive = true;
return _eventStreamController!.stream; // return _eventStreamController!.stream;
} catch (e) { // } catch (e) {
Logger.error('开始连续语音识别失败: ${e.toString()}'); // Logger.error('开始连续语音识别失败: ${e.toString()}');
rethrow; // rethrow;
} // }
} // }
//写入音频数据到缓存区中 //写入音频数据到缓存区中
Uint8List _appendToBuffer(Uint8List newData) { Uint8List _appendToBuffer(Uint8List newData) {
@ -302,26 +302,26 @@ class XunfeiAsrService extends GetxService implements AsrService {
} }
@override @override
Future<void> setPause() { Future<void> disableBluetoothAudio() {
// TODO: implement setPause // TODO: implement disableBluetoothAudio
throw UnimplementedError(); throw UnimplementedError();
} }
@override @override
Future<void> setContinue() { Future<void> restoreOriginalAudioState() {
// TODO: implement setcontinue // TODO: implement restoreOriginalAudioState
throw UnimplementedError(); throw UnimplementedError();
} }
@override @override
Future<void> disableBluetoothAudio() { Future<Stream<RecognitionEvent>> recognizeCallback() {
// TODO: implement disableBluetoothAudio // TODO: implement recognizeCallback
throw UnimplementedError(); throw UnimplementedError();
} }
@override @override
Future<void> restoreOriginalAudioState() { Future<bool> startContinuousRecognition(bool audioSourceType) {
// TODO: implement restoreOriginalAudioState // TODO: implement startContinuousRecognition
throw UnimplementedError(); throw UnimplementedError();
} }
} }

13
lib/modules/FTFTranslation/controllers/FTFTranslation_controller.dart

@ -49,6 +49,7 @@ class FTFTranslationController extends GetxController
Get.put(XunfeiAsrService()); Get.put(XunfeiAsrService());
final TtsService _ttsService = Get.find<TtsService>(); final TtsService _ttsService = Get.find<TtsService>();
_asrService = Get.find<XunfeiAsrService>(); _asrService = Get.find<XunfeiAsrService>();
_InfoRecorder();
} }
@override @override
@ -58,15 +59,19 @@ class FTFTranslationController extends GetxController
super.onClose(); super.onClose();
} }
//开始录音 //开始录音
Future<void> _startRecorder() async { Future<void> _InfoRecorder() async {
final recognitionStream = final recognitionStream = await _asrService.recognizeCallback();
await _asrService.startContinuousRecognition(false);
_recognitionSubscription = recognitionStream.listen( _recognitionSubscription = recognitionStream.listen(
_handleRecognitionEvent, _handleRecognitionEvent,
); );
} }
//开始录音
Future<void> _startRecorder() async {
await _asrService.startContinuousRecognition(false);
}
//结束录音 //结束录音
Future<void> _stopRecorder() async { Future<void> _stopRecorder() async {
await _asrService.stopContinuousRecognition(); await _asrService.stopContinuousRecognition();

90
lib/modules/agent/controllers/agent_controller.dart

@ -293,7 +293,8 @@ class AgentController extends GetxController {
currentPage.value = 1; currentPage.value = 1;
hasMoreMessages.value = result.totalPages > 1; hasMoreMessages.value = result.totalPages > 1;
Logger.i(TAG, '成功加载 ${historyMessages.length} 条聊天记录,总页数: ${result.totalPages}'); Logger.i(TAG,
'成功加载 ${historyMessages.length} 条聊天记录,总页数: ${result.totalPages}');
_logMessageDetails(historyMessages); _logMessageDetails(historyMessages);
} else { } else {
// 没有历史记录时添加欢迎消息 // 没有历史记录时添加欢迎消息
@ -316,7 +317,8 @@ class AgentController extends GetxController {
Future<void> loadMoreMessages() async { Future<void> loadMoreMessages() async {
// 防止重复加载 // 防止重复加载
if (isLoadingMore.value || !hasMoreMessages.value) { if (isLoadingMore.value || !hasMoreMessages.value) {
Logger.d(TAG, '跳过加载更多:isLoadingMore=${isLoadingMore.value}, hasMoreMessages=${hasMoreMessages.value}'); Logger.d(TAG,
'跳过加载更多:isLoadingMore=${isLoadingMore.value}, hasMoreMessages=${hasMoreMessages.value}');
return; return;
} }
@ -351,7 +353,8 @@ class AgentController extends GetxController {
currentPage.value = nextPage; currentPage.value = nextPage;
hasMoreMessages.value = nextPage < result.totalPages; hasMoreMessages.value = nextPage < result.totalPages;
Logger.i(TAG, '成功加载 ${newMessages.length} 条历史消息,当前页: $nextPage/${result.totalPages}'); Logger.i(TAG,
'成功加载 ${newMessages.length} 条历史消息,当前页: $nextPage/${result.totalPages}');
} else { } else {
// 没有更多消息 // 没有更多消息
hasMoreMessages.value = false; hasMoreMessages.value = false;
@ -414,7 +417,8 @@ class AgentController extends GetxController {
void _setupScrollListener() { void _setupScrollListener() {
scrollController.addListener(() { scrollController.addListener(() {
// 检查是否滚动到顶部(因为使用了reverse: true,顶部实际是最大滚动位置) // 检查是否滚动到顶部(因为使用了reverse: true,顶部实际是最大滚动位置)
if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 100) { if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 100) {
// 当距离顶部100像素时触发加载更多 // 当距离顶部100像素时触发加载更多
if (hasMoreMessages.value && !isLoadingMore.value) { if (hasMoreMessages.value && !isLoadingMore.value) {
Logger.d(TAG, '触发加载更多历史消息'); Logger.d(TAG, '触发加载更多历史消息');
@ -430,7 +434,8 @@ class AgentController extends GetxController {
Logger.i(TAG, '手动触发加载更多历史消息'); Logger.i(TAG, '手动触发加载更多历史消息');
loadMoreMessages(); loadMoreMessages();
} else { } else {
Logger.w(TAG, '无法加载更多:hasMoreMessages=${hasMoreMessages.value}, isLoadingMore=${isLoadingMore.value}'); Logger.w(TAG,
'无法加载更多:hasMoreMessages=${hasMoreMessages.value}, isLoadingMore=${isLoadingMore.value}');
} }
} }
@ -979,7 +984,8 @@ class AgentController extends GetxController {
permissionType: Permission.microphone, permissionType: Permission.microphone,
permissionName: 'microphonePermission'.tr, // 麦克风 permissionName: 'microphonePermission'.tr, // 麦克风
explanationText: 'voiceRecognitionPermissionText'.tr, // 语音识别功能需要访问麦克风。 explanationText: 'voiceRecognitionPermissionText'.tr, // 语音识别功能需要访问麦克风。
permanentDenialText: 'permissionDeniedText'.tr, // 您已永久拒绝麦克风权限,请前往系统设置开启权限。 permanentDenialText:
'permissionDeniedText'.tr, // 您已永久拒绝麦克风权限,请前往系统设置开启权限。
settingsButtonText: 'goToSettings'.tr, // 去设置 settingsButtonText: 'goToSettings'.tr, // 去设置
cancelButtonText: 'cancel'.tr, // 取消 cancelButtonText: 'cancel'.tr, // 取消
); );
@ -1005,13 +1011,14 @@ class AgentController extends GetxController {
// 开始语音输入 // 开始语音输入
Future<void> startVoiceInput() async { Future<void> startVoiceInput() async {
if (isListening.value) return; if (isListening.value) return;
print('startVoiceInput'.tr); // 开始语音输入 print('开始语音输入'); // 开始语音输入
final hasPermission = await PermissionUtil.instance.requestPermission( final hasPermission = await PermissionUtil.instance.requestPermission(
// context: Get.context!, // context: Get.context!,
permissionType: Permission.microphone, permissionType: Permission.microphone,
permissionName: 'microphonePermission'.tr, // 麦克风 permissionName: 'microphonePermission'.tr, // 麦克风
explanationText: 'voiceTranslationPermissionText'.tr, // 语音识别与翻译功能需要访问麦克风。 explanationText: 'voiceTranslationPermissionText'.tr, // 语音识别与翻译功能需要访问麦克风。
permanentDenialText: 'permissionDeniedText'.tr, // 您已永久拒绝麦克风权限,请前往系统设置开启权限。 permanentDenialText:
'permissionDeniedText'.tr, // 您已永久拒绝麦克风权限,请前往系统设置开启权限。
settingsButtonText: 'goToSettings'.tr, // 去设置 settingsButtonText: 'goToSettings'.tr, // 去设置
cancelButtonText: 'cancel'.tr, // 取消 cancelButtonText: 'cancel'.tr, // 取消
); );
@ -1020,11 +1027,11 @@ class AgentController extends GetxController {
return; return;
} }
try { // try {
await AgentService.startConversation();
} catch (e) { // } catch (e) {
Logger.e(TAG, '启动语音输入失败: $e'); // Logger.e(TAG, '启动语音输入失败: $e');
} // }
} }
// 停止语音输入 // 停止语音输入
@ -1276,32 +1283,32 @@ class AgentController extends GetxController {
isPhoneCallMode.value = true; isPhoneCallMode.value = true;
// 如果当前是文本模式,切换到语音模式???? // 如果当前是文本模式,切换到语音模式????
if (isListening.value) { // if (isListening.value) {
// 检查麦克风权限 // 检查麦克风权限
final hasPermission = await PermissionUtil.instance.requestPermission( final hasPermission = await PermissionUtil.instance.requestPermission(
permissionType: Permission.microphone, permissionType: Permission.microphone,
permissionName: 'microphonePermission'.tr, // 麦克风 permissionName: '麦克风',
explanationText: 'phoneCallPermissionText'.tr, // 打电话功能需要访问麦克风进行语音通话。 explanationText: '打电话功能需要访问麦克风进行语音通话。',
permanentDenialText: 'permissionDeniedText'.tr, // 您已永久拒绝麦克风权限,请前往系统设置开启权限。 permanentDenialText: '您已永久拒绝麦克风权限,请前往系统设置开启权限。',
settingsButtonText: 'goToSettings'.tr, // 去设置 settingsButtonText: '去设置',
cancelButtonText: 'cancel'.tr, // 取消 cancelButtonText: '取消',
); );
if (!hasPermission) {
// 权限未获取,退出打电话模式并恢复之前状态
isPhoneCallMode.value = false;
_previousInputMode = null; // 清除历史记录
return;
}
// 切换到语音模式并开始语音输入 if (!hasPermission) {
isTextInputMode.value = false; // 权限未获取,退出打电话模式并恢复之前状态
// 打电话模式不使用按住说话模式,而是持续语音输入 isPhoneCallMode.value = false;
//isPushToTalkMode.value = false; _previousInputMode = null; // 清除历史记录
// 开始语音识别 return;
print('startPhoneCallMode'.tr); // 开始打电话模式,开始语音输入
await AgentService.audioContinue();
} }
// 切换到语音模式并开始语音输入
isTextInputMode.value = false;
// 打电话模式不使用按住说话模式,而是持续语音输入
//isPushToTalkMode.value = false;
// 开始语音识别
print("开始打电话模式,开始语音输入");
await AgentService.startConversation();
//}
} }
// 结束打电话模式 // 结束打电话模式
@ -1314,7 +1321,7 @@ class AgentController extends GetxController {
// 停止语音输入 // 停止语音输入
//await stopVoiceInput(); //await stopVoiceInput();
// 开始语音识别 // 开始语音识别
await AgentService.audioPause(); await AgentService.stopConversation();
// 恢复之前的输入模式,而不是强制切换到文字模式 // 恢复之前的输入模式,而不是强制切换到文字模式
_restorePreviousInputMode(); _restorePreviousInputMode();
} }
@ -1409,9 +1416,9 @@ class AgentController extends GetxController {
// 设置按住说话状态 // 设置按住说话状态
isPushToTalkActive.value = true; isPushToTalkActive.value = true;
//await AgentService.recognizeCallback();
// 开始语音识别 // 开始语音识别
await AgentService.audioContinue(); await AgentService.startConversation();
// await startVoiceInput(); // await startVoiceInput();
} }
@ -1424,7 +1431,8 @@ class AgentController extends GetxController {
// 清除按住说话状态(但保持按住说话模式) // 清除按住说话状态(但保持按住说话模式)
isPushToTalkActive.value = false; isPushToTalkActive.value = false;
await AgentService.audioPause();
await AgentService.stopConversation();
// 停止语音识别 // 停止语音识别
// await stopVoiceInput(); // await stopVoiceInput();
} }

31
lib/modules/meeting/controllers/meeting_record_controller.dart

@ -66,6 +66,7 @@ class MeetingRecordController extends GetxController
_initializeComponents(); _initializeComponents();
_setupFromArguments(); _setupFromArguments();
generateWaveData(); generateWaveData();
_reinitializeAsrService();
} }
@override @override
@ -74,6 +75,18 @@ class MeetingRecordController extends GetxController
super.onClose(); super.onClose();
} }
// 重新初始化ASR服务以支持新的语言
Future<void> _reinitializeAsrService() async {
try {
// 重新初始化ASR服务
await _asrService.initialize();
Logger.info('ASR服务重新初始化成功');
} catch (e) {
Logger.error('重新初始化ASR服务失败: ${e.toString()}');
}
}
// 请求录音权限 // 请求录音权限
Future<bool> _requestRecordPermission() async { Future<bool> _requestRecordPermission() async {
try { try {
@ -132,6 +145,7 @@ class MeetingRecordController extends GetxController
void _cleanupResources() { void _cleanupResources() {
_timer?.cancel(); _timer?.cancel();
_recognitionSubscription?.cancel(); _recognitionSubscription?.cancel();
_recognitionSubscription = null;
titleEditingController.dispose(); titleEditingController.dispose();
titleFocusNode.dispose(); titleFocusNode.dispose();
if (audioType.value != 0) { if (audioType.value != 0) {
@ -170,7 +184,13 @@ class MeetingRecordController extends GetxController
} }
/// Toggles visibility of transcribed text content /// Toggles visibility of transcribed text content
void toggleTextDisplay() => showTextContent.toggle(); Future<void> toggleTextDisplay() async {
final recognitionStream = await _asrService.recognizeCallback();
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
showTextContent.toggle();
}
/// Toggles important mark status /// Toggles important mark status
void toggleMarked() => isMarked.toggle(); void toggleMarked() => isMarked.toggle();
@ -211,10 +231,7 @@ class MeetingRecordController extends GetxController
// Initialize speech recognition // Initialize speech recognition
final recognitionStream = final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType); await _asrService.startContinuousRecognition(_audioSourceType);
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
_asrService.setContinue();
// Prepare storage directory // Prepare storage directory
final appDir = await appDocDir; final appDir = await appDocDir;
final dir = Directory("${appDir.path}/MeetingAudio/"); final dir = Directory("${appDir.path}/MeetingAudio/");
@ -263,9 +280,7 @@ class MeetingRecordController extends GetxController
Future<void> _resumeRecording() async { Future<void> _resumeRecording() async {
final recognitionStream = final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType); await _asrService.startContinuousRecognition(_audioSourceType);
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
startTimer(); startTimer();
} }

44
lib/modules/settings/views/settings_view.dart

@ -567,28 +567,28 @@ class SettingsView extends GetView<SettingsController> {
// color: isDarkMode // color: isDarkMode
// ? Colors.white.withOpacity(0.1) // ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]), // : Colors.grey[200]),
// // BLE测试 // BLE测试
// _buildSimpleNavigationSetting( _buildSimpleNavigationSetting(
// title: 'opus解码测试', title: 'opus解码测试',
// subtitle: '测试opus解码', subtitle: '测试opus解码',
// icon: Icons.bluetooth_searching, icon: Icons.bluetooth_searching,
// iconBgColor: isDarkMode iconBgColor: isDarkMode
// ? Colors.green[900]!.withOpacity(0.3) ? Colors.green[900]!.withOpacity(0.3)
// : Colors.green[100]!, : Colors.green[100]!,
// iconColor: iconColor:
// isDarkMode ? Colors.green[300]! : Colors.green[600]!, isDarkMode ? Colors.green[300]! : Colors.green[600]!,
// titleColor: isDarkMode ? Colors.white : null, titleColor: isDarkMode ? Colors.white : null,
// subtitleColor: isDarkMode ? Colors.white70 : null, subtitleColor: isDarkMode ? Colors.white70 : null,
// onTap: () { onTap: () {
// Get.toNamed(Routes.opusTest); Get.toNamed(Routes.opusTest);
// }, },
// isDarkMode: isDarkMode, isDarkMode: isDarkMode,
// ), ),
// Divider( Divider(
// height: 1, height: 1,
// color: isDarkMode color: isDarkMode
// ? Colors.white.withOpacity(0.1) ? Colors.white.withOpacity(0.1)
// : Colors.grey[200]), : Colors.grey[200]),
// BLE测试 // BLE测试
// _buildSimpleNavigationSetting( // _buildSimpleNavigationSetting(

10
lib/modules/speech_test/controllers/speech_test_controller.dart

@ -76,7 +76,10 @@ class SpeechTestController extends GetxController {
// 监听TTS事件 // 监听TTS事件
_setupTtsEventListener(); _setupTtsEventListener();
// 获取识别事件流 - 这启动了异步识别过程
final stream = await _asrService.recognizeCallback();
// 设置事件监听 - 异步处理识别结果
_setupAsrEventListener(stream);
isInitialized.value = true; isInitialized.value = true;
ttsStatus.value = '准备就绪'; ttsStatus.value = '准备就绪';
recognitionStatus.value = '准备就绪'; recognitionStatus.value = '准备就绪';
@ -346,9 +349,6 @@ class SpeechTestController extends GetxController {
// 获取识别事件流 - 这启动了异步识别过程 // 获取识别事件流 - 这启动了异步识别过程
final stream = final stream =
await _asrService.startContinuousRecognition(_audioSourceType); await _asrService.startContinuousRecognition(_audioSourceType);
// 设置事件监听 - 异步处理识别结果
_setupAsrEventListener(stream);
} catch (e) { } catch (e) {
recognitionStatus.value = '启动连续识别异常: $e'; recognitionStatus.value = '启动连续识别异常: $e';
Logger.error('启动连续识别异常: ${e.toString()}'); Logger.error('启动连续识别异常: ${e.toString()}');
@ -413,4 +413,4 @@ class SpeechTestController extends GetxController {
super.onClose(); super.onClose();
} }
} }

34
lib/modules/translation/controllers/translation_controller.dart

@ -122,7 +122,9 @@ class TranslationController extends GetxController {
// 监听识别结果 // 监听识别结果
if (activeSpeaker.value != 0) { if (activeSpeaker.value != 0) {
isPlayback = false; isPlayback = false;
_asrService.setContinue(); final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType);
lastActiveSpeaker.value = id; lastActiveSpeaker.value = id;
} else { } else {
if (fTFTranslationResult != '' && !isPlayback) { if (fTFTranslationResult != '' && !isPlayback) {
@ -134,7 +136,7 @@ class TranslationController extends GetxController {
} else if ((translationHistory.isEmpty || } else if ((translationHistory.isEmpty ||
!translationHistory.last.isIntermediate) && !translationHistory.last.isIntermediate) &&
currentMode.value == 'faceToFace') { currentMode.value == 'faceToFace') {
_asrService.setPause(); _asrService.stopContinuousRecognition();
} }
} }
} }
@ -305,6 +307,12 @@ class TranslationController extends GetxController {
await _asrService.initialize( await _asrService.initialize(
supportedLanguages: asrSupportedLanguages, supportedLanguages: asrSupportedLanguages,
); );
// 获取识别事件流 - 这启动了异步识别过程
var recognitionStream = await _asrService.recognizeCallback();
// 监听识别结果
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
final bool storage = await _requestStoragePermission(); final bool storage = await _requestStoragePermission();
if (!storage) { if (!storage) {
Logger.d("Permission", "未授予存储权限,无法提供录音功能"); Logger.d("Permission", "未授予存储权限,无法提供录音功能");
@ -325,6 +333,7 @@ class TranslationController extends GetxController {
void onClose() { void onClose() {
stopRecognition(); stopRecognition();
_recognitionSubscription?.cancel(); _recognitionSubscription?.cancel();
_recognitionSubscription = null;
_voiceInteractionSubscription?.cancel(); _voiceInteractionSubscription?.cancel();
_translationDebounceTimer?.cancel(); _translationDebounceTimer?.cancel();
scrollController.dispose(); scrollController.dispose();
@ -425,22 +434,18 @@ class TranslationController extends GetxController {
_audioSourceType = false; _audioSourceType = false;
isTtsEnabled.value = true; isTtsEnabled.value = true;
} }
final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType);
if (currentMode.value == 'faceToFace') { if (currentMode.value == 'faceToFace') {
_asrService.setPause(); _asrService.stopContinuousRecognition();
} else { } else {
await _ttsService.setAudioOutputDevice(0); await _ttsService.setAudioOutputDevice(0);
_asrService.setContinue(); final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType);
} }
isRecognizing.value = true; isRecognizing.value = true;
if (isRecording.value && isRecognizing.value) { if (isRecording.value && isRecognizing.value) {
startRecording(); startRecording();
} }
// 监听识别结果
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
} catch (e) { } catch (e) {
isRecognizing.value = false; isRecognizing.value = false;
Logger.error('启动语音识别失败: ${e.toString()}'); Logger.error('启动语音识别失败: ${e.toString()}');
@ -473,10 +478,9 @@ class TranslationController extends GetxController {
if (!isRecognizing.value) return; if (!isRecognizing.value) return;
try { try {
restoreOriginalAudioState();
await _asrService.stopContinuousRecognition(); await _asrService.stopContinuousRecognition();
_recognitionSubscription?.cancel();
_recognitionSubscription = null;
isRecognizing.value = false; isRecognizing.value = false;
// 如果当前有中间结果,将其作为最终结果处理 // 如果当前有中间结果,将其作为最终结果处理
@ -523,7 +527,7 @@ class TranslationController extends GetxController {
); );
// 添加新项后滚动到底部 // 添加新项后滚动到底部
if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) { if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) {
_asrService.setPause(); _asrService.stopContinuousRecognition();
} else { } else {
translationHistory.add(newItem); translationHistory.add(newItem);
} }
@ -563,7 +567,7 @@ class TranslationController extends GetxController {
_scrollToBottom(); _scrollToBottom();
saveTranslationHistory(); saveTranslationHistory();
if (activeSpeaker.value == 0 && currentMode.value == 'faceToFace') { if (activeSpeaker.value == 0 && currentMode.value == 'faceToFace') {
_asrService.setPause(); _asrService.stopContinuousRecognition();
} }
// 使用项目的时间戳进行翻译 // 使用项目的时间戳进行翻译
translateText(text, translationHistory.last.timestamp, isFinal: true); translateText(text, translationHistory.last.timestamp, isFinal: true);
@ -582,7 +586,7 @@ class TranslationController extends GetxController {
isIntermediate: false, isIntermediate: false,
); );
if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) { if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) {
_asrService.setPause(); _asrService.stopContinuousRecognition();
} //else { } //else {
translationHistory.add(newItem); translationHistory.add(newItem);
translationHistory.refresh(); translationHistory.refresh();

676
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

File diff suppressed because it is too large

86
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentServicePlugin.kt

@ -15,7 +15,8 @@ import kotlinx.coroutines.launch
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, AgentServiceListener { class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler,
AgentServiceListener {
private val TAG = "AgentServicePlugin" private val TAG = "AgentServicePlugin"
private lateinit var context: Context private lateinit var context: Context
private lateinit var methodChannel: MethodChannel private lateinit var methodChannel: MethodChannel
@ -24,12 +25,16 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext context = flutterPluginBinding.applicationContext
methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.agent_service") methodChannel =
MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.agent_service")
methodChannel.setMethodCallHandler(this) methodChannel.setMethodCallHandler(this)
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.agent_service/events") eventChannel = EventChannel(
flutterPluginBinding.binaryMessenger,
"com.yunqiinnovation.agent_service/events"
)
eventChannel.setStreamHandler(this) eventChannel.setStreamHandler(this)
// 添加监听器 // 添加监听器
AgentService.addListener(this) AgentService.addListener(this)
@ -45,37 +50,42 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("INVALID_ARGUMENTS", "配置参数不能为空", null) result.error("INVALID_ARGUMENTS", "配置参数不能为空", null)
return return
} }
Log.d(TAG, "启动BleMediaService,配置参数: $config") Log.d(TAG, "启动BleMediaService,配置参数: $config")
// 创建Intent启动BleMediaService // 创建Intent启动BleMediaService
val intent = Intent(context, BleMediaService::class.java) val intent = Intent(context, BleMediaService::class.java)
// 将参数添加到Intent // 将参数添加到Intent
config["azureSpeechKey"]?.toString()?.let { intent.putExtra("azureSpeechKey", it) } config["azureSpeechKey"]?.toString()
config["azureSpeechRegion"]?.toString()?.let { intent.putExtra("azureSpeechRegion", it) } ?.let { intent.putExtra("azureSpeechKey", it) }
config["azureSpeechRegion"]?.toString()
?.let { intent.putExtra("azureSpeechRegion", it) }
config["openaiApiKey"]?.toString()?.let { intent.putExtra("openaiApiKey", it) } config["openaiApiKey"]?.toString()?.let { intent.putExtra("openaiApiKey", it) }
config["openaiBaseUrl"]?.toString()?.let { intent.putExtra("openaiBaseUrl", it) } config["openaiBaseUrl"]?.toString()
?.let { intent.putExtra("openaiBaseUrl", it) }
config["openaiModel"]?.toString()?.let { intent.putExtra("openaiModel", it) } config["openaiModel"]?.toString()?.let { intent.putExtra("openaiModel", it) }
config["openaivisionModel"]?.toString()?.let { intent.putExtra("openaivisionModel", it) } config["openaivisionModel"]?.toString()
?.let { intent.putExtra("openaivisionModel", it) }
config["systemPrompt"]?.toString()?.let { intent.putExtra("systemPrompt", it) } config["systemPrompt"]?.toString()?.let { intent.putExtra("systemPrompt", it) }
config["mcpServer"]?.toString()?.let { intent.putExtra("mcpServer", it) } config["mcpServer"]?.toString()?.let { intent.putExtra("mcpServer", it) }
config["volcanoAppId"]?.toString()?.let { intent.putExtra("volcanoAppId", it) } config["volcanoAppId"]?.toString()?.let { intent.putExtra("volcanoAppId", it) }
config["volcanoToken"]?.toString()?.let { intent.putExtra("volcanoToken", it) } config["volcanoToken"]?.toString()?.let { intent.putExtra("volcanoToken", it) }
// 启动前台服务 // 启动前台服务
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent) context.startForegroundService(intent)
} else { } else {
context.startService(intent) context.startService(intent)
} }
result.success(true) result.success(true)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "启动AgentService失败", e) Log.e(TAG, "启动AgentService失败", e)
result.error("SERVICE_START_ERROR", "启动AgentService失败: ${e.message}", null) result.error("SERVICE_START_ERROR", "启动AgentService失败: ${e.message}", null)
} }
} }
"stopAgentService" -> { "stopAgentService" -> {
try { try {
val intent = Intent(context, BleMediaService::class.java) val intent = Intent(context, BleMediaService::class.java)
@ -86,6 +96,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("SERVICE_STOP_ERROR", "停止AgentService失败: ${e.message}", null) result.error("SERVICE_STOP_ERROR", "停止AgentService失败: ${e.message}", null)
} }
} }
"startConversation" -> { "startConversation" -> {
try { try {
val isStarted = AgentService.startRecognition(false) val isStarted = AgentService.startRecognition(false)
@ -95,6 +106,11 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("START_ERROR", "启动对话失败: ${e.message}", null) result.error("START_ERROR", "启动对话失败: ${e.message}", null)
} }
} }
"recognizeCallback" -> {
val isStarted = AgentService.recognizeCallback()
result.success(isStarted)
}
"stopConversation" -> { "stopConversation" -> {
try { try {
AgentService.stopRecognition() AgentService.stopRecognition()
@ -104,45 +120,29 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("STOP_ERROR", "停止对话失败: ${e.message}", null) result.error("STOP_ERROR", "停止对话失败: ${e.message}", null)
} }
} }
"audioContinue" -> {
try {
AgentService.audioContinue()
result.success(true)
} catch (e: Exception) {
Log.e(TAG, "audioContinue失败", e)
result.error("STOP_ERROR", "audioContinue失败: ${e.message}", null)
}
}
"audioPause" -> {
try {
AgentService.audioPause()
result.success(true)
} catch (e: Exception) {
Log.e(TAG, "audioContinue失败", e)
result.error("STOP_ERROR", "audioContinue失败: ${e.message}", null)
}
}
"processTextInput" -> { "processTextInput" -> {
val text = call.argument<String>("text") val text = call.argument<String>("text")
if (text.isNullOrEmpty()) { if (text.isNullOrEmpty()) {
result.error("INVALID_ARGUMENTS", "文本不能为空", null) result.error("INVALID_ARGUMENTS", "文本不能为空", null)
return return
} }
val speakResponse = call.argument<Boolean>("speakResponse") ?: false val speakResponse = call.argument<Boolean>("speakResponse") ?: false
try { try {
val isProcessing = AgentService.processTextInput( val isProcessing = AgentService.processTextInput(
text = text, text = text,
speakResponse = speakResponse speakResponse = speakResponse
) )
result.success(isProcessing) result.success(isProcessing)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "处理文本输入失败", e) Log.e(TAG, "处理文本输入失败", e)
result.error("TEXT_PROCESS_ERROR", "处理文本输入失败: ${e.message}", null) result.error("TEXT_PROCESS_ERROR", "处理文本输入失败: ${e.message}", null)
} }
} }
"processImageInput" -> { "processImageInput" -> {
val imagePath = call.argument<String>("imagePath") val imagePath = call.argument<String>("imagePath")
if (imagePath.isNullOrEmpty()) { if (imagePath.isNullOrEmpty()) {
@ -156,7 +156,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
} }
val text = call.argument<String>("text") ?: "" val text = call.argument<String>("text") ?: ""
val speakResponse = call.argument<Boolean>("speakResponse") ?: false val speakResponse = call.argument<Boolean>("speakResponse") ?: false
try { try {
val isProcessing = AgentService.processImageInput( val isProcessing = AgentService.processImageInput(
imagePath = imagePath, imagePath = imagePath,
@ -164,20 +164,21 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
text = text, text = text,
speakResponse = speakResponse speakResponse = speakResponse
) )
result.success(isProcessing) result.success(isProcessing)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "处理图片输入失败", e) Log.e(TAG, "处理图片输入失败", e)
result.error("IMAGE_PROCESS_ERROR", "处理图片输入失败: ${e.message}", null) result.error("IMAGE_PROCESS_ERROR", "处理图片输入失败: ${e.message}", null)
} }
} }
"speakText" -> { "speakText" -> {
val text = call.argument<String>("text") val text = call.argument<String>("text")
if (text.isNullOrEmpty()) { if (text.isNullOrEmpty()) {
result.error("INVALID_ARGUMENTS", "文本不能为空", null) result.error("INVALID_ARGUMENTS", "文本不能为空", null)
return return
} }
try { try {
AgentService.speakText(text) AgentService.speakText(text)
result.success(true) result.success(true)
@ -186,6 +187,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("TTS_ERROR", "语音合成失败: ${e.message}", null) result.error("TTS_ERROR", "语音合成失败: ${e.message}", null)
} }
} }
"stopTts" -> { "stopTts" -> {
try { try {
AgentService.stopTts() AgentService.stopTts()
@ -195,6 +197,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("STOP_TTS_ERROR", "停止语音合成失败: ${e.message}", null) result.error("STOP_TTS_ERROR", "停止语音合成失败: ${e.message}", null)
} }
} }
"stopAiSteam" -> { "stopAiSteam" -> {
try { try {
AgentService.stopAiStream() AgentService.stopAiStream()
@ -215,6 +218,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("CLEAR_HISTORY_ERROR", "清除聊天历史失败: ${e.message}", null) result.error("CLEAR_HISTORY_ERROR", "清除聊天历史失败: ${e.message}", null)
} }
} }
"interruptCurrentResponse" -> { "interruptCurrentResponse" -> {
try { try {
AgentService.interruptCurrentResponse() AgentService.interruptCurrentResponse()
@ -224,6 +228,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("INTERRUPT_ERROR", "中断响应失败: ${e.message}", null) result.error("INTERRUPT_ERROR", "中断响应失败: ${e.message}", null)
} }
} }
"dispose" -> { "dispose" -> {
try { try {
AgentService.dispose() AgentService.dispose()
@ -233,6 +238,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("DISPOSE_ERROR", "释放资源失败: ${e.message}", null) result.error("DISPOSE_ERROR", "释放资源失败: ${e.message}", null)
} }
} }
else -> { else -> {
result.notImplemented() result.notImplemented()
} }
@ -246,7 +252,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
override fun onCancel(arguments: Any?) { override fun onCancel(arguments: Any?) {
this.eventSink = null this.eventSink = null
} }
/** /**
* 实现AgentServiceListener接口 * 实现AgentServiceListener接口
* 接收来自AgentService的事件,并转发到Flutter * 接收来自AgentService的事件,并转发到Flutter
@ -257,7 +263,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
put("event", eventName) put("event", eventName)
put("data", JSONObject(data)) put("data", JSONObject(data))
} }
// 在主线程发送事件到Flutter // 在主线程发送事件到Flutter
MainScope().launch { MainScope().launch {
eventSink?.success(eventData.toString()) eventSink?.success(eventData.toString())

22
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/BleAgent.kt

@ -65,7 +65,7 @@ object BleAgent : BleService.Callback, AgentServiceListener {
Log.e(TAG, "语音代理未初始化") Log.e(TAG, "语音代理未初始化")
return false return false
} }
Log.d(TAG, "手动启动语音识别: ")
return AgentService.startRecognition(true) return AgentService.startRecognition(true)
} }
@ -73,7 +73,12 @@ object BleAgent : BleService.Callback, AgentServiceListener {
* 停止语音识别 * 停止语音识别
*/ */
fun stopListening() { fun stopListening() {
AgentService.stopRecognition() if (!isInitialized) {
Log.e(TAG, "语音代理未初始化")
return
}
AgentService.stopRecognition()
return
} }
/** /**
@ -121,7 +126,7 @@ object BleAgent : BleService.Callback, AgentServiceListener {
} else if (state == BleConst.STATE_DISCONNECTED) { } else if (state == BleConst.STATE_DISCONNECTED) {
Log.d(TAG, "Ble设备已断开,停止语音服务") Log.d(TAG, "Ble设备已断开,停止语音服务")
// 设备断开连接,停止语音服务 // 设备断开连接,停止语音服务
// stopListening() stopListening()
} }
} }
@ -136,18 +141,19 @@ object BleAgent : BleService.Callback, AgentServiceListener {
* 在收到唤醒信号时启动语音识别 * 在收到唤醒信号时启动语音识别
*/ */
override fun onWakeupSignalReceived() { override fun onWakeupSignalReceived() {
Log.d(TAG, "收到唤醒信号,启动语音识别") Log.d(TAG, "收到唤醒信号,启动语音识别${AgentService.isRecognitionActive}")
// 停止当前TTS,避免冲突 // 停止当前TTS,避免冲突
AgentService.interruptCurrentResponse() AgentService.interruptCurrentResponse()
// AgentService.speakText("我在!") // AgentService.speakText("我在!")
// 启动语音识别 // 启动语音识别
if (AgentService.isRecognitionActive&&AgentService.azureAsrHelper!!.isPaused) {
// 如果已经在识别,则重新启动 // 如果已经在识别,则重新启动
stopListening() //stopListening()
}
startListening() //stopListening()
startListening()
} }
override fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>) { override fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>) {

53
local_plugins/agent_service/lib/agent_service.dart

@ -248,16 +248,16 @@ class AgentService {
} }
} }
/// 停止对话 /// 设备识别器
/// ///
/// 停止语音识别 /// 启动语音识别,开始监听用户语音输入
/// 返回是否成功停止对话 /// 返回是否成功开始对话
static Future<bool> stopConversation() async { static Future<bool> recognizeCallback() async {
try { try {
final bool result = await _channel.invokeMethod('stopConversation'); final bool result = await _channel.invokeMethod('recognizeCallback');
return result; return result;
} on PlatformException catch (e) { } on PlatformException catch (e) {
throw AgentServiceException(e.code, e.message ?? '停止对话失败', e.details); throw AgentServiceException(e.code, e.message ?? '设置识别器失败', e.details);
} }
} }
@ -265,27 +265,40 @@ class AgentService {
/// ///
/// 停止语音识别 /// 停止语音识别
/// 返回是否成功停止对话 /// 返回是否成功停止对话
static Future<bool> audioContinue() async { static Future<bool> stopConversation() async {
try { try {
final bool result = await _channel.invokeMethod('audioContinue'); final bool result = await _channel.invokeMethod('stopConversation');
return result; return result;
} on PlatformException catch (e) { } on PlatformException catch (e) {
throw AgentServiceException(e.code, e.message ?? '停止对话失败', e.details); throw AgentServiceException(e.code, e.message ?? '停止对话失败', e.details);
} }
} }
/// 停止对话 // /// 停止对话
/// // ///
/// 停止语音识别 // /// 停止语音识别
/// 返回是否成功停止对话 // /// 返回是否成功停止对话
static Future<bool> audioPause() async { // static Future<bool> audioContinue() async {
try { // try {
final bool result = await _channel.invokeMethod('audioPause'); // final bool result = await _channel.invokeMethod('audioContinue');
return result; // return result;
} on PlatformException catch (e) { // } on PlatformException catch (e) {
throw AgentServiceException(e.code, e.message ?? '停止对话失败', e.details); // throw AgentServiceException(e.code, e.message ?? '停止对话失败', e.details);
} // }
} // }
// /// 停止对话
// ///
// /// 停止语音识别
// /// 返回是否成功停止对话
// static Future<bool> audioPause() async {
// try {
// final bool result = await _channel.invokeMethod('audioPause');
// return result;
// } on PlatformException catch (e) {
// throw AgentServiceException(e.code, e.message ?? '停止对话失败', e.details);
// }
// }
/// 处理文本输入 /// 处理文本输入
/// ///

824
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

File diff suppressed because it is too large

128
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -255,24 +255,15 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
} }
} }
"setPause" -> {
azureAsrHelper.audioPause();
return
}
"setcontinue" -> {
azureAsrHelper.audioContinue();
return
}
"disableBluetoothAudio" -> { "disableBluetoothAudio" -> {
azureAsrHelper.disableBluetoothAudio(); azureAsrHelper.disableBluetoothAudio();
return return
} }
"restoreOriginalAudioState" -> { "restoreOriginalAudioState" -> {
azureAsrHelper.restoreOriginalAudioState(); azureAsrHelper.restoreOriginalAudioState();
return return
} }
"startContinuousRecognition" -> { "startContinuousRecognition" -> {
@ -296,62 +287,70 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
AzureAsrHelper.AudioSourceType.MICROPHONE AzureAsrHelper.AudioSourceType.MICROPHONE
} }
val success = azureAsrHelper.startContinuousRecognition(object : val success = azureAsrHelper.startContinuousRecognition(audioSourceType)
AzureAsrHelper.ContinuousRecognizeCallback { result.success(true)
override fun onResult(text: String, detectedLanguage: String) { } catch (e: Exception) {
sendAsrEvent( result.error("START_RECOGNITION_ERROR", e.message, null)
mapOf( }
"type" to "result", }
"text" to text,
"detectedLanguage" to detectedLanguage "recognizeCallback" -> {
)
val success = azureAsrHelper.setupEventListeners(object :
AzureAsrHelper.ContinuousRecognizeCallback {
override fun onResult(text: String, detectedLanguage: String) {
sendAsrEvent(
mapOf(
"type" to "result",
"text" to text,
"detectedLanguage" to detectedLanguage
) )
} )
}
override fun onRecognizing( override fun onRecognizing(
recognizing: String, recognizing: String,
detectedLanguage: String detectedLanguage: String
) { ) {
sendAsrEvent( sendAsrEvent(
mapOf( mapOf(
"type" to "recognizing", "type" to "recognizing",
"text" to recognizing, "text" to recognizing,
"detectedLanguage" to detectedLanguage "detectedLanguage" to detectedLanguage
)
) )
} )
}
override fun onSessionStarted() { override fun onSessionStarted() {
sendAsrEvent(mapOf("type" to "sessionStarted")) sendAsrEvent(mapOf("type" to "sessionStarted"))
} }
override fun onSessionStopped() { override fun onSessionStopped() {
sendAsrEvent(mapOf("type" to "sessionStopped")) sendAsrEvent(mapOf("type" to "sessionStopped"))
} }
override fun onCanceled(reason: String, errorDetails: String) { override fun onCanceled(reason: String, errorDetails: String) {
sendAsrEvent(
mapOf( sendAsrEvent(
"type" to "canceled", mapOf(
"reason" to reason, "type" to "canceled",
"errorDetails" to errorDetails "reason" to reason,
) "errorDetails" to errorDetails
) )
} )
}
override fun onError(error: String) { override fun onError(error: String) {
sendAsrEvent( sendAsrEvent(
mapOf( mapOf(
"type" to "error", "type" to "error",
"message" to error "message" to error
)
) )
} )
}, audioSourceType) }
result.success(success) })
} catch (e: Exception) { FileLogger.d(tag, "recognizeCallback: ${success}") //
result.error("START_RECOGNITION_ERROR", e.message, null) result.success(success)
}
} }
"stopContinuousRecognition" -> { "stopContinuousRecognition" -> {
@ -378,7 +377,8 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
"音频数据不能为空", "音频数据不能为空",
null null
) )
azureAsrHelper.pushAudioData(data)
azureAsrHelper.audioStream?.saveAudioDataTo(data)
result.success(true) result.success(true)
} }
@ -525,16 +525,16 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
"setAudioOutputDevice" -> { "setAudioOutputDevice" -> {
val type = call.argument<Int>("type") val type = call.argument<Int>("type")
var success =false var success = false
if (type == 0) { if (type == 0) {
// 默认(如果有耳机选耳机,否则使用系统扬声器) // 默认(如果有耳机选耳机,否则使用系统扬声器)
success=azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.DEFAULT) success = azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.DEFAULT)
} else if (type == 1) { } else if (type == 1) {
// 强制使用声器 // 强制使用声器
success=azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.SPEAKER) success = azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.SPEAKER)
} else if (type == 2) { } else if (type == 2) {
// 强制使用耳机 // 强制使用耳机
success=azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.HEADPHONES) success = azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.HEADPHONES)
} }
@ -583,7 +583,7 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
override fun onAudioDataReceived(data: ByteArray) { override fun onAudioDataReceived(data: ByteArray) {
if (azureAsrHelper.audioSourceType == AzureAsrHelper.AudioSourceType.EXTERNAL) { if (azureAsrHelper.audioSourceType == AzureAsrHelper.AudioSourceType.EXTERNAL) {
azureAsrHelper.pushAudioData(data) azureAsrHelper.audioStream?.saveAudioDataTo(data)
} }
// 可选:处理音频数据 // 可选:处理音频数据
} }

3
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/RecordFile.kt

@ -64,7 +64,7 @@ object RecordFile {
writeThread = Thread { writeThread = Thread {
try { try {
while (isWriting.get() || writeQueue.isNotEmpty()) { while (isWriting.get() || writeQueue.isNotEmpty()) {
val data = writeQueue.poll() ?: continue val data = writeQueue.poll() ?: continue
fos?.write(data) fos?.write(data)
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -248,6 +248,7 @@ object RecordFile {
} finally { } finally {
fos = null fos = null
currentAudioFile = null currentAudioFile = null
writeThread = null
} }
return false return false
} }

55
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -1,5 +1,7 @@
package com.yunqiinnovation.ble_service package com.yunqiinnovation.ble_service
import android.Manifest
import android.annotation.SuppressLint
import android.bluetooth.* import android.bluetooth.*
import android.bluetooth.le.* import android.bluetooth.le.*
import android.content.Context import android.content.Context
@ -22,6 +24,7 @@ import java.util.concurrent.LinkedBlockingQueue
import android.os.Handler import android.os.Handler
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import android.util.Log import android.util.Log
import androidx.annotation.RequiresPermission
/** /**
* BLE服务类:提供蓝牙低功耗设备的扫描、连接和通信功能 * BLE服务类:提供蓝牙低功耗设备的扫描、连接和通信功能
@ -61,7 +64,7 @@ object BleService {
// 上下文 // 上下文
private lateinit var context: Context private lateinit var context: Context
private val commandQueue: Queue<ByteArray> = LinkedList()
// 蓝牙相关组件 // 蓝牙相关组件
private lateinit var bluetoothManager: BluetoothManager private lateinit var bluetoothManager: BluetoothManager
private var bluetoothAdapter: BluetoothAdapter? = null private var bluetoothAdapter: BluetoothAdapter? = null
@ -83,6 +86,10 @@ object BleService {
// 是否是ota模式 // 是否是ota模式
var isEnterOta = false var isEnterOta = false
// 是否回复指令
var isReply = true
// 是否回复指令码
var cmdReplyType =0xff
// 连接状态 // 连接状态
val connectionState = MutableLiveData(BleConst.STATE_DISCONNECTED) val connectionState = MutableLiveData(BleConst.STATE_DISCONNECTED)
@ -93,9 +100,6 @@ object BleService {
private var opusManager: OpusManager? = null private var opusManager: OpusManager? = null
private val mainHandler = Handler(Looper.getMainLooper()) private val mainHandler = Handler(Looper.getMainLooper())
// ota解码器实例
// var otaManager: OTAManager? = null
// 初始化状态 // 初始化状态
private var isInitialized = false private var isInitialized = false
@ -663,6 +667,7 @@ object BleService {
private fun processAudioData(data: ByteArray) { private fun processAudioData(data: ByteArray) {
try { try {
if (opusManager?.isDecodeStream == true) { if (opusManager?.isDecodeStream == true) {
recordfile?.saveAudioDataToWav(data) recordfile?.saveAudioDataToWav(data)
opusManager?.writeAudioStream(data) opusManager?.writeAudioStream(data)
} else { } else {
@ -677,6 +682,8 @@ object BleService {
/** /**
* 处理设备响应数据 - 帧头为0xBB * 处理设备响应数据 - 帧头为0xBB
*/ */
@SuppressLint("StaticFieldLeak")
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
private fun processDeviceResponse(data: ByteArray) { private fun processDeviceResponse(data: ByteArray) {
Log.d( Log.d(
TAG, TAG,
@ -741,7 +748,21 @@ object BleService {
} }
}" }"
) )
isReply=true;
cmdReplyType=cmdType;
if(!commandQueue.isEmpty())
{
val nextCmd = commandQueue.poll()
writeChar!!.value=nextCmd
var isSeccess = bluetoothGatt?.writeCharacteristic(writeChar)
if(isSeccess==true)
{
Log.d(
TAG,
"收到设备响应:"
)
}
}
// 根据命令类型处理不同的响应 // 根据命令类型处理不同的响应
when (cmdType) { when (cmdType) {
BleConst.CMD_GET_VERSION -> { BleConst.CMD_GET_VERSION -> {
@ -1005,7 +1026,7 @@ object BleService {
BleConst.CMD_WAKE_UP -> { BleConst.CMD_WAKE_UP -> {
Log.i(TAG, "收到唤醒指令") Log.i(TAG, "收到唤醒指令")
// 接收到唤醒信号,打开编码器 (设备侧) // 接收到唤醒信号,打开编码器 (设备侧)
openEncoder()
notifyWakeupSignalReceived() notifyWakeupSignalReceived()
} }
@ -1316,6 +1337,7 @@ object BleService {
* @param cmdData 命令数据 * @param cmdData 命令数据
* @return 是否成功发送 * @return 是否成功发送
*/ */
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
private fun sendCommand(cmdType: Byte, cmdData: ByteArray = byteArrayOf()): Boolean { private fun sendCommand(cmdType: Byte, cmdData: ByteArray = byteArrayOf()): Boolean {
if (!checkConn()) { if (!checkConn()) {
Log.e(TAG, "发送命令失败: 设备未连接") Log.e(TAG, "发送命令失败: 设备未连接")
@ -1358,7 +1380,26 @@ object BleService {
// 发送数据 // 发送数据
writeChar!!.value = cmdPacket writeChar!!.value = cmdPacket
return bluetoothGatt?.writeCharacteristic(writeChar) ?: false // 加入队列并触发发送
if(isReply)
{
var isSeccess = bluetoothGatt?.writeCharacteristic(writeChar)
if(isSeccess == true)
{
isReply=false;
cmdType;
return true
}
else
return false
}
else
{
commandQueue.offer(cmdPacket)
}
return true
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "发送命令异常: ${e.message}", e) Log.e(TAG, "发送命令异常: ${e.message}", e)
return false return false

Loading…
Cancel
Save