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. 8
      lib/modules/speech_test/controllers/speech_test_controller.dart
  12. 34
      lib/modules/translation/controllers/translation_controller.dart
  13. 404
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  14. 56
      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. 810
      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. 1
      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,
);
/// 停止连续语音识别
Future<bool> stopContinuousRecognition();
/// 停止音频输入
Future<void> setPause();
/// 继续音频输入
Future<void> setContinue();
/// 设置识别回掉
///
/// 返回一个包含识别事件的流
Future<Stream<RecognitionEvent>> recognizeCallback();
/// 恢复原始音频状态
Future<void> restoreOriginalAudioState();

38
lib/data/services/ble_manager.dart

@ -472,37 +472,13 @@ class BleManager extends GetxService {
firmwareProductColor: '',
);
}
final commands = [
_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(),
// ]);
// 并行获取所有设备信息,不使用延迟
final results = await Future.wait([
_bleService.getBatteryInfo(),
_bleService.getDeviceVersion(),
_bleService.getProductId(),
]);
Logger.info(
'设备信息获取结果: 电池=${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
Future<Stream<RecognitionEvent>> startContinuousRecognition(
bool audioSourceType) async {
Future<bool> startContinuousRecognition(bool audioSourceType) async {
if (!_isInitialized) {
await initialize();
}
@ -161,8 +160,6 @@ class AzureAsrService extends GetxService implements AsrService {
}
try {
_eventStreamController = StreamController<RecognitionEvent>.broadcast();
// 开始连续识别
final bool result =
await _channel.invokeMethod('startContinuousRecognition', {
@ -170,15 +167,13 @@ class AzureAsrService extends GetxService implements AsrService {
});
if (!result) {
_cleanupEventStream();
throw Exception('启动连续识别失败');
}
_isContinuousRecognitionActive = true;
return _eventStreamController!.stream;
return result;
} catch (e) {
Logger.error('开始连续语音识别失败: ${e.toString()}');
_cleanupEventStream();
rethrow;
}
}
@ -192,11 +187,30 @@ class AzureAsrService extends GetxService implements AsrService {
try {
final bool result =
await _channel.invokeMethod('stopContinuousRecognition');
_cleanupEventStream();
return result;
} catch (e) {
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;
}
}
@ -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
Future<void> disableBluetoothAudio() async {
// TODO: implement disableBluetoothAudio

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

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

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

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

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

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

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

@ -66,6 +66,7 @@ class MeetingRecordController extends GetxController
_initializeComponents();
_setupFromArguments();
generateWaveData();
_reinitializeAsrService();
}
@override
@ -74,6 +75,18 @@ class MeetingRecordController extends GetxController
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 {
try {
@ -132,6 +145,7 @@ class MeetingRecordController extends GetxController
void _cleanupResources() {
_timer?.cancel();
_recognitionSubscription?.cancel();
_recognitionSubscription = null;
titleEditingController.dispose();
titleFocusNode.dispose();
if (audioType.value != 0) {
@ -170,7 +184,13 @@ class MeetingRecordController extends GetxController
}
/// 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
void toggleMarked() => isMarked.toggle();
@ -211,10 +231,7 @@ class MeetingRecordController extends GetxController
// Initialize speech recognition
final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType);
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
_asrService.setContinue();
// Prepare storage directory
final appDir = await appDocDir;
final dir = Directory("${appDir.path}/MeetingAudio/");
@ -263,9 +280,7 @@ class MeetingRecordController extends GetxController
Future<void> _resumeRecording() async {
final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType);
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
startTimer();
}

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

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

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

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

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

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

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

@ -58,7 +58,7 @@ object AgentService : CoroutineScope {
private val listeners = Collections.synchronizedSet(mutableSetOf<AgentServiceListener>())
// Azure服务
var azureAsrHelper: AzureAsrHelper? = null
private var azureAsrHelper: AzureAsrHelper? = null
// 使用BytedanceTTS作为唯一的TTS实现
private var ttsService: AzureTtsHelper? = null
@ -102,7 +102,8 @@ object AgentService : CoroutineScope {
// AI流生成相关
private var currentAiJob: Job? = null
private var currsessionId:String = ""
private var currsessionId: String = ""
// 空闲检测相关
private var idleCheckJob: Job? = null
private val maxIdleSeconds = 10 // 最大空闲秒数
@ -111,7 +112,8 @@ object AgentService : CoroutineScope {
private var lastInterruptTime = 0L
private val INTERRUPT_DEBOUNCE_MS = 300L
private var audioSourceType= AzureAsrHelper.AudioSourceType.MICROPHONE
var audioSourceType = AzureAsrHelper.AudioSourceType.MICROPHONE
// 语气词过滤列表(中英文)
private val FILLER_WORDS = setOf(
// 中文语气词
@ -122,6 +124,7 @@ object AgentService : CoroutineScope {
"um", "uh", "ah", "oh", "eh", "er", "hmm", "huh", "wow",
"well", "like", "you know", "i mean", "so", "actually"
)
/**
* 初始化系统提示词
*/
@ -142,6 +145,7 @@ object AgentService : CoroutineScope {
你与用户的交流风格应始终温暖、亲切、充满同理心,随时表达关怀与鼓励,让用户感受到真诚的陪伴与支持。
""".trimIndent()
}
/**
* 初始化
* @param appContext 应用上下文
@ -149,7 +153,11 @@ object AgentService : CoroutineScope {
* @param config 配置参数,包含所需的所有API密钥和设置
* @return 是否初始化成功
*/
fun initialize(appContext: Context, config: Map<String, Any>, isExternalActive: Boolean): Boolean {
fun initialize(
appContext: Context,
config: Map<String, Any>,
isExternalActive: Boolean
): Boolean {
if (isInitialized) return true
try {
@ -169,13 +177,24 @@ object AgentService : CoroutineScope {
}
// 初始化Azure ASR
azureAsrHelper = AzureAsrHelper(context).apply {
initialize(
subscriptionKey = config["azureSpeechKey"]?.toString() ?: "",
region = config["azureSpeechRegion"]?.toString() ?: "",
audioSourceType = if (isExternalActive) AzureAsrHelper.AudioSourceType.EXTERNAL
else AzureAsrHelper.AudioSourceType.MICROPHONE
)
// 使用协程处理异步初始化
CoroutineScope(Dispatchers.IO).launch {
val asrInitSuccess = initialize(
subscriptionKey = config["azureSpeechKey"]?.toString() ?: "",
region = config["azureSpeechRegion"]?.toString() ?: "",
audioSourceType = if (isExternalActive) AzureAsrHelper.AudioSourceType.EXTERNAL
else AzureAsrHelper.AudioSourceType.MICROPHONE
)
if (asrInitSuccess) {
withContext(Dispatchers.Main) {
recognizeCallback()
}
} else {
Log.w(TAG, "Azure ASR初始化失败")
}
}
}
// 初始化TTS服务
initializeTtsEngine(config)
@ -189,6 +208,8 @@ object AgentService : CoroutineScope {
config["mcpServer"]?.toString() ?: ""
)
// 加载最近的聊天记录
loadChatHistory()
@ -274,45 +295,55 @@ object AgentService : CoroutineScope {
restartIdleCheck()
sendEvent("tts_started", mapOf("status" to "started"))
}
TtsEventType.SYNTHESIS_COMPLETED -> {
_isTtsSpeaking.set(false)
restartIdleCheck()
sendEvent("tts_completed", mapOf("status" to "completed"))
}
TtsEventType.SYNTHESIS_CANCELED -> {
_isTtsSpeaking.set(false)
restartIdleCheck()
sendEvent("tts_canceled", mapOf("status" to "canceled"))
}
TtsEventType.PLAYBACK_STARTED -> { //开始AI播报内容
restartIdleCheck()
sendEvent("playback_started", mapOf("status" to "playback_started"))
// 发送AI说话开始广播
val intent = Intent("com.yunqiinnovation.music_service.AI_SAY_START").apply {
setPackage(context.packageName)
}
val intent =
Intent("com.yunqiinnovation.music_service.AI_SAY_START").apply {
setPackage(context.packageName)
}
context.sendBroadcast(intent)
}
TtsEventType.PLAYBACK_COMPLETED -> { //结束AI播报内容
restartIdleCheck()
sendEvent("playback_completed", mapOf("status" to "playback_completed"))
// 发送AI说话开始广播
val intent = Intent("com.yunqiinnovation.music_service.AI_SAY_END").apply {
setPackage(context.packageName)
}
val intent =
Intent("com.yunqiinnovation.music_service.AI_SAY_END").apply {
setPackage(context.packageName)
}
context.sendBroadcast(intent)
}
TtsEventType.ERROR -> {
_isTtsSpeaking.set(false)
restartIdleCheck()
val params = event.params
val code = params["errorCode"] as? String ?: "UNKNOWN_ERROR"
val message = params["errorMessage"] as? String ?: "未知错误"
sendEvent("error", mapOf(
"code" to "TTS_ERROR",
"message" to "语音合成错误: $code, $message"
))
sendEvent(
"error", mapOf(
"code" to "TTS_ERROR",
"message" to "语音合成错误: $code, $message"
)
)
}
else -> {
// 处理其他类型的事件
}
@ -359,8 +390,11 @@ object AgentService : CoroutineScope {
delay(maxIdleSeconds * 1000L)
if (isActive && isRecognitionActive && !hasSpeechDetected && !isTtsSpeaking && !isAiStreaming) {
//stopRecognition()
sendEvent("auto_stop", mapOf("reason" to "idle_timeout", "seconds" to maxIdleSeconds))
stopRecognition()
sendEvent(
"auto_stop",
mapOf("reason" to "idle_timeout", "seconds" to maxIdleSeconds)
)
}
} catch (e: CancellationException) {
// 协程被取消,正常行为
@ -396,14 +430,26 @@ object AgentService : CoroutineScope {
Log.d(TAG, "服务未初始化")
return false
}
if(!(azureAsrHelper!!.isPaused)&&isExternalActive)
{
Log.d(TAG, "麦克风音频未暂停,无法设置")
BleService.closeCodec()
return false
}
if (isRecognitionActive) return true
Log.d("TAG", "开始语音识别startRecognition?")
stopRecognition()
BleService.openEncoder()
// if (isRecognitionActive&&audioSourceType==AzureAsrHelper.AudioSourceType.MICROPHONE){
// Log.d(TAG, "开始语音识别stopRecognition")
// // BleService.closeCodec()
// stopRecognition()
// }
// if((isExternalActive&&audioSourceType==AzureAsrHelper.AudioSourceType.MICROPHONE)||!isRecognitionActive)
// {
// Log.d(TAG, "开始语音识别BleService.openEncoder")
// BleService.openEncoder()
// }
// if(isRecognitionActive)
// {
// Log.d(TAG, "开始语音识别azureAsrHelper?.startContinuousRecognition(audioSourceType)")
// return true
// }
// 检查录音权限
if (!isExternalActive && !checkRecordAudioPermission()) {
Log.e(TAG, "需要录音权限")
@ -415,12 +461,33 @@ object AgentService : CoroutineScope {
_hasSpeechDetected.set(false)
try {
audioSourceType = if (isExternalActive) {
audioSourceType = if (isExternalActive) {
AzureAsrHelper.AudioSourceType.EXTERNAL
} else {
AzureAsrHelper.AudioSourceType.MICROPHONE
}
azureAsrHelper?.startContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback {
Log.d("TAG", "azureAsrHelper?.startContinuousRecognition")
azureAsrHelper?.startContinuousRecognition(audioSourceType)
return true
} catch (e: Exception) {
_isRecognitionActive.set(false)
Log.e(TAG, "启动语音识别失败: ${e.message}")
sendEvent(
"error", mapOf(
"code" to "RECOGNITION_START_ERROR",
"message" to e.message.toString()
)
)
return false
}
}
fun recognizeCallback(): Boolean {
try {
Log.d(TAG, "recognizeCallback")
val success = azureAsrHelper?.setupEventListeners(object :
AzureAsrHelper.ContinuousRecognizeCallback {
override fun onRecognizing(recognizing: String, detectedLanguage: String) {
if (recognizing.isNotEmpty()) {
// 检测到语音,更新状态
@ -431,10 +498,12 @@ object AgentService : CoroutineScope {
restartIdleCheck()
}
sendEvent("recognizing", mapOf(
"text" to recognizing,
"language" to detectedLanguage
))
sendEvent(
"recognizing", mapOf(
"text" to recognizing,
"language" to detectedLanguage
)
)
// 如果TTS正在播放或AI正在生成,检查是否需要打断
if (isTtsSpeaking || isAiStreaming) {
@ -465,10 +534,12 @@ object AgentService : CoroutineScope {
override fun onResult(result: String, detectedLanguage: String) {
if (result.isNotEmpty()) {
sendEvent("recognition_result", mapOf(
"text" to result,
"language" to detectedLanguage
))
sendEvent(
"recognition_result", mapOf(
"text" to result,
"language" to detectedLanguage
)
)
processWithChatApi(result)
}
@ -485,14 +556,12 @@ object AgentService : CoroutineScope {
override fun onSessionStarted() {
sendEvent("recognition_started", mapOf("status" to "started"))
startIdleCheck()
Log.d(TAG, "onSessionStarted")
audioPlayer?.playAudio(R.raw.start)
}
override fun onSessionStopped() {
sendEvent("recognition_stopped", mapOf("status" to "stopped"))
_isRecognitionActive.set(false)
stopIdleCheck()
audioPlayer?.playAudio(R.raw.stop)
@ -501,41 +570,41 @@ object AgentService : CoroutineScope {
override fun onCanceled(reason: String, errorDetails: String) {
_isRecognitionActive.set(false)
stopIdleCheck()
Log.d(TAG, "recognition_canceled:")
recognizeCallback()
BleService.closeCodec()
sendEvent("recognition_canceled", mapOf(
"reason" to reason,
"details" to errorDetails
))
sendEvent(
"recognition_canceled", mapOf(
"reason" to reason,
"details" to errorDetails
)
)
}
override fun onError(error: String) {
_isRecognitionActive.set(false)
stopIdleCheck()
recognizeCallback()
BleService.closeCodec()
Log.e(TAG, "语音识别错误: $error")
sendEvent("error", mapOf(
"code" to "RECOGNITION_ERROR",
"message" to error
))
Log.d(TAG, "语音识别错误: $error")
sendEvent(
"error", mapOf(
"code" to "RECOGNITION_ERROR",
"message" to error
)
)
}
}, audioSourceType)
})
return true
} catch (e: Exception) {
_isRecognitionActive.set(false)
Log.e(TAG, "启动语音识别失败: ${e.message}")
sendEvent("error", mapOf(
"code" to "RECOGNITION_START_ERROR",
"message" to e.message.toString()
))
return false
}
}
fun pushAudioData(data: ByteArray) {
azureAsrHelper?.pushAudioData(data)
azureAsrHelper?.audioStream?.saveAudioDataTo(data)
}
/**
* 停止语音识别
*/
@ -547,67 +616,22 @@ object AgentService : CoroutineScope {
BleService.closeCodec()
_isRecognitionActive.set(false)
stopIdleCheck()
return
} catch (e: Exception) {
Log.e(TAG, "停止语音识别异常: ${e.message}")
_isRecognitionActive.set(false)
stopIdleCheck()
return
}
}
/**
* 继续音频输入
*/
fun audioContinue() {
try {
if(audioSourceType== AzureAsrHelper.AudioSourceType.EXTERNAL)
{
BleService.closeCodec()
stopRecognition()
startRecognition(false)
}
// if (!isRecognitionActive) {
// }
azureAsrHelper?.audioContinue()
//BleService.closeCodec()
//_isRecognitionActive.set(false)
//stopIdleCheck()
} catch (e: Exception) {
Log.e(TAG, "暂停音频输入异常: ${e.message}")
// _isRecognitionActive.set(false)
//stopIdleCheck()
}
}
/**
* 暂停音频输入
*/
fun audioPause() {
if (!isRecognitionActive) return
try {
azureAsrHelper?.audioPause()
Log.d(TAG, "暂停音频输入")
BleService.closeCodec()
//_isRecognitionActive.set(false)
//stopIdleCheck()
} catch (e: Exception) {
Log.e(TAG, "暂停音频输入异常: ${e.message}")
// _isRecognitionActive.set(false)
//stopIdleCheck()
}
}
/**
* 打断当前响应
* 停止TTS播放和AI流输出
*/
fun interruptCurrentResponse() {
if(isAiStreaming || isTtsSpeaking) {
if (isAiStreaming || isTtsSpeaking) {
val startTime = System.currentTimeMillis()
// 并行执行停止操作,加快响应速度
@ -617,10 +641,12 @@ object AgentService : CoroutineScope {
// 记录打断耗时
val duration = System.currentTimeMillis() - startTime
sendEvent("response_interrupted", mapOf(
"status" to "interrupted",
"duration_ms" to duration
))
sendEvent(
"response_interrupted", mapOf(
"status" to "interrupted",
"duration_ms" to duration
)
)
}
}
@ -628,7 +654,7 @@ object AgentService : CoroutineScope {
/**
* 停止AI流输出
*/
fun stopAiStream() {
fun stopAiStream() {
if (isAiStreaming) {
try {
// 先更新状态,避免回调时的状态不一致
@ -743,11 +769,11 @@ object AgentService : CoroutineScope {
currsessionId = UUID.randomUUID().toString()
// 设置状态为正在流式输出
_isAiStreaming.set(true)
Log.d(TAG, "processWithChatApiInternal")
audioPlayer?.playAudio(R.raw.await, true,0.3f)
Log.d(TAG, "processWithChatApiInternal")
audioPlayer?.playAudio(R.raw.await, true, 0.3f)
// 使用历史记录作为上下文发送到OpenAI
val responseBuilder = StringBuilder()
var aiMetadata:String = ""
var aiMetadata: String = ""
var userMetadata = JSONObject()
var reply = true; //是否回复
var broadcast = true //是否播发
@ -761,16 +787,16 @@ object AgentService : CoroutineScope {
val messagesWithSystemPrompt = JSONArray()
// 先添加系统提示词
if (systemPrompt.isNotEmpty()) {
val location = LocationService.getCachedLocation()?: mapOf(
"user_location" to "定位未开启",
"user_lat" to "定位未开启",
"user_lng" to "定位未开启",
)
val _systemPrompt = Utils.fillTemplate(systemPrompt,location)
messagesWithSystemPrompt.put(createSystemMessage(_systemPrompt))
// Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}")
}
if (systemPrompt.isNotEmpty()) {
val location = LocationService.getCachedLocation() ?: mapOf(
"user_location" to "定位未开启",
"user_lat" to "定位未开启",
"user_lng" to "定位未开启",
)
val _systemPrompt = Utils.fillTemplate(systemPrompt, location)
messagesWithSystemPrompt.put(createSystemMessage(_systemPrompt))
// Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}")
}
// 再添加历史消息
for (i in 0 until historyMessages.length()) {
@ -810,9 +836,9 @@ object AgentService : CoroutineScope {
currsessionId,
messagesWithSystemPrompt,
object : StreamCallback {
override fun onToken(sessionid:String,token: String) {
override fun onToken(sessionid: String, token: String) {
try {
if (sessionid != currsessionId){
if (sessionid != currsessionId) {
return
}
responseBuilder.append(token)
@ -828,7 +854,7 @@ object AgentService : CoroutineScope {
}
}
override fun onComplete(sessionid:String) {
override fun onComplete(sessionid: String) {
try {
// 视情况决定是否朗读回复
if (speakResponse && reply && broadcast && sessionid == currsessionId){
@ -867,7 +893,7 @@ object AgentService : CoroutineScope {
}
}
override fun onError(sessionid:String,e: Exception) {
override fun onError(sessionid: String, e: Exception) {
try {
Log.e(TAG, "AI处理出错", e)
sendEvent(
@ -880,20 +906,22 @@ object AgentService : CoroutineScope {
// 标记AI流式输出已完成
_isAiStreaming.set(false)
currentAiJob = null
}catch (e: Exception) {
} catch (e: Exception) {
Log.e(TAG, "liwei--------------- AI Call onError 异常", e)
}
}
override fun onFunctionCall(sessionid:String,call: JSONObject) {
override fun onFunctionCall(sessionid: String, call: JSONObject) {
try {
// audioPlayer?.playAudio(R.raw.calling, true)
val name = call.getString("name")
sendEvent("function_call", mapOf(
"name" to name,
"arguments" to call.toString()
))
if(name == "exit_interaction") {
sendEvent(
"function_call", mapOf(
"name" to name,
"arguments" to call.toString()
)
)
if (name == "exit_interaction") {
stopRecognition()
}
} catch (e: Exception) {
@ -901,7 +929,11 @@ object AgentService : CoroutineScope {
}
}
override fun onFunctionCallResult(sessionid:String,functionCall: JSONObject, functionCallResult: JSONObject) {
override fun onFunctionCallResult(
sessionid: String,
functionCall: JSONObject,
functionCallResult: JSONObject
) {
try {
audioPlayer?.stopAudio()
val name = functionCall.get("name") as String;
@ -917,7 +949,11 @@ object AgentService : CoroutineScope {
)
)
} catch (e: Exception) {
Log.e(TAG, "liwei--------------- AI Call onFunctionCallResult 异常", e)
Log.e(
TAG,
"liwei--------------- AI Call onFunctionCallResult 异常",
e
)
}
}
}
@ -926,10 +962,12 @@ object AgentService : CoroutineScope {
} catch (e: Exception) {
val errorType = if (hasImage) "AI_IMAGE_PROCESS_ERROR" else "AI_PROCESS_ERROR"
Log.e(TAG, "liwei--------------- AI处理出错", e)
sendEvent("error", mapOf(
"code" to errorType,
"message" to e.message.toString()
))
sendEvent(
"error", mapOf(
"code" to errorType,
"message" to e.message.toString()
)
)
// 确保状态被重置
_isAiStreaming.set(false)
@ -1019,7 +1057,12 @@ object AgentService : CoroutineScope {
/**
* 保存聊天消息
*/
private fun saveChatMessage(userMessage: String, assistantMessage: String,aiMetadata : String,userMetadata : String) {
private fun saveChatMessage(
userMessage: String,
assistantMessage: String,
aiMetadata: String,
userMetadata: String
) {
// 显式指定IO调度器,数据库操作应在后台线程执行,即使父协程在Main上下文中
launch(Dispatchers.IO) {
try {
@ -1167,7 +1210,12 @@ object AgentService : CoroutineScope {
/**
* 处理图片输入
*/
fun processImageInput(imagePath: String, imageUrl: String,text: String = "", speakResponse: Boolean = false): Boolean {
fun processImageInput(
imagePath: String,
imageUrl: String,
text: String = "",
speakResponse: Boolean = false
): Boolean {
if (!isInitialized) {
Log.e(TAG, "服务未初始化")
sendEvent("error", mapOf("code" to "NOT_INITIALIZED", "message" to "服务未初始化"))
@ -1181,10 +1229,12 @@ object AgentService : CoroutineScope {
}
// 通知开始处理图片
sendEvent("image_processing", mapOf(
"status" to "processing",
"imagePath" to imagePath
))
sendEvent(
"image_processing", mapOf(
"status" to "processing",
"imagePath" to imagePath
)
)
// 处理图片
launch {
@ -1204,19 +1254,23 @@ object AgentService : CoroutineScope {
val displayText = text.ifEmpty { "[图片]" }
// 通知图片准备完成
sendEvent("image_ready", mapOf(
"status" to "ready",
"imagePath" to imagePath
))
sendEvent(
"image_ready", mapOf(
"status" to "ready",
"imagePath" to imagePath
)
)
Log.d(TAG, "liwei--------- 图像Mesage: ${userMessage}")
// 处理消息并传入存储路径
processWithChatApiInternal(userMessage, displayText, speakResponse, true, imagePath)
} catch (e: Exception) {
Log.e(TAG, "处理图片失败: ${e.message}")
sendEvent("error", mapOf(
"code" to "IMAGE_PROCESSING_ERROR",
"message" to e.message.toString()
))
sendEvent(
"error", mapOf(
"code" to "IMAGE_PROCESSING_ERROR",
"message" to e.message.toString()
)
)
}
}
return true
@ -1254,7 +1308,7 @@ object AgentService : CoroutineScope {
fun playAudio(resId: Int, isLooping: Boolean = false, volume: Float = 1.0f) {
synchronized(playbackLock) {
try {
Log.d(TAG, "playAudio${isLooping}")
Log.d(TAG, "playAudio${isLooping}")
// 强制停止并释放之前的资源
forceStop()
@ -1298,7 +1352,7 @@ object AgentService : CoroutineScope {
fun stopAudio() {
synchronized(playbackLock) {
try {
Log.d(TAG, "stopAudio")
Log.d(TAG, "stopAudio")
mediaPlayer?.apply {
if (isMediaPlayerPlaying()) {
stop()
@ -1422,7 +1476,10 @@ object AgentService : CoroutineScope {
/*
* 自动播放音乐
* */
fun autoHandleFcunCallResult(toolname:String,functionCallResult: JSONObject): Map<String,Any>{
fun autoHandleFcunCallResult(
toolname: String,
functionCallResult: JSONObject
): Map<String, Any> {
println("协议工具返回数据 检查是否存在卡片! functionCallResult: ${functionCallResult}")
try {
val contentStr = functionCallResult.optString("context")
@ -1435,10 +1492,10 @@ object AgentService : CoroutineScope {
var notreply = false;
if (textStr.isNotEmpty()) {
val meta = JSONObject(textStr)
if (meta.has("broadcast")){
if (meta.has("broadcast")) {
broadcast = meta["broadcast"] as Boolean
}
if (meta.has("notreply")){
if (meta.has("notreply")) {
notreply = meta["notreply"] as Boolean
}
metadata[toolname] = meta // 现在可以赋值
@ -1501,13 +1558,13 @@ object AgentService : CoroutineScope {
val end = cardNavigation.optString("end", "")
processNavigation(start, end);
}
return mapOf("meta" to metaStr, "broadcast" to broadcast,"notreply" to notreply);
return mapOf("meta" to metaStr, "broadcast" to broadcast, "notreply" to notreply);
}
} catch (e: Exception) {
// 捕获其他类型的异常(可选)
println("为解析到卡片数据!")
}
return mapOf("meta" to "", "broadcast" to true,"notreply" to false);
return mapOf("meta" to "", "broadcast" to true, "notreply" to false);
}
/**
@ -1520,7 +1577,7 @@ object AgentService : CoroutineScope {
* @param singer 歌手名称,默认为空
* @return 是否成功开始处理
*/
fun processMusicPlay(song: Map<String,Any>){
fun processMusicPlay(song: Map<String, Any>) {
// 在其他 Service、BroadcastReceiver 或 Application 中调用
Log.i(TAG, "播放音乐: ${song}")
MusicServiceStarter.startServiceWithCommand(context, command = "play", song = song)
@ -1535,10 +1592,14 @@ object AgentService : CoroutineScope {
* @param singer 歌手,默认为false
* @return 是否成功开始处理
*/
fun processMusicPlayList(songs:List<Map<String,Any>>){
fun processMusicPlayList(songs: List<Map<String, Any>>) {
// 如果 playlist 中有有效的歌曲,开始播放
if (songs.isNotEmpty()) {
MusicServiceStarter.startServiceWithPlaylist(context, command = "playlist", songs = songs)
MusicServiceStarter.startServiceWithPlaylist(
context,
command = "playlist",
songs = songs
)
} else {
Log.e(TAG, "音乐列表为空,未启动播放服务")
}
@ -1552,10 +1613,15 @@ object AgentService : CoroutineScope {
* @param singer 歌手,默认为false
* @return 是否成功开始处理
*/
fun processNavigation(start:String,end:String){
fun processNavigation(start: String, end: String) {
// 如果 playlist 中有有效的歌曲,开始播放
if (!start.isNullOrEmpty() && !end.isNullOrEmpty()) {
NavigationServiceHelper.startNavigation(context,"start", startpos = start, endpos = end)
NavigationServiceHelper.startNavigation(
context,
"start",
startpos = start,
endpos = end
)
} else {
Log.i(TAG, "启动导航服务失败")
}

56
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.os.Build
class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, AgentServiceListener {
class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler,
AgentServiceListener {
private val TAG = "AgentServicePlugin"
private lateinit var context: Context
private lateinit var methodChannel: MethodChannel
@ -24,10 +25,14 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.agent_service")
methodChannel =
MethodChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.agent_service")
methodChannel.setMethodCallHandler(this)
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.yunqiinnovation.agent_service/events")
eventChannel = EventChannel(
flutterPluginBinding.binaryMessenger,
"com.yunqiinnovation.agent_service/events"
)
eventChannel.setStreamHandler(this)
// 添加监听器
@ -52,12 +57,16 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
val intent = Intent(context, BleMediaService::class.java)
// 将参数添加到Intent
config["azureSpeechKey"]?.toString()?.let { intent.putExtra("azureSpeechKey", it) }
config["azureSpeechRegion"]?.toString()?.let { intent.putExtra("azureSpeechRegion", it) }
config["azureSpeechKey"]?.toString()
?.let { intent.putExtra("azureSpeechKey", it) }
config["azureSpeechRegion"]?.toString()
?.let { intent.putExtra("azureSpeechRegion", 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["openaivisionModel"]?.toString()?.let { intent.putExtra("openaivisionModel", it) }
config["openaivisionModel"]?.toString()
?.let { intent.putExtra("openaivisionModel", it) }
config["systemPrompt"]?.toString()?.let { intent.putExtra("systemPrompt", it) }
config["mcpServer"]?.toString()?.let { intent.putExtra("mcpServer", it) }
config["volcanoAppId"]?.toString()?.let { intent.putExtra("volcanoAppId", it) }
@ -76,6 +85,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("SERVICE_START_ERROR", "启动AgentService失败: ${e.message}", null)
}
}
"stopAgentService" -> {
try {
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)
}
}
"startConversation" -> {
try {
val isStarted = AgentService.startRecognition(false)
@ -95,6 +106,11 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("START_ERROR", "启动对话失败: ${e.message}", null)
}
}
"recognizeCallback" -> {
val isStarted = AgentService.recognizeCallback()
result.success(isStarted)
}
"stopConversation" -> {
try {
AgentService.stopRecognition()
@ -104,24 +120,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
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" -> {
val text = call.argument<String>("text")
if (text.isNullOrEmpty()) {
@ -143,6 +142,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("TEXT_PROCESS_ERROR", "处理文本输入失败: ${e.message}", null)
}
}
"processImageInput" -> {
val imagePath = call.argument<String>("imagePath")
if (imagePath.isNullOrEmpty()) {
@ -171,6 +171,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("IMAGE_PROCESS_ERROR", "处理图片输入失败: ${e.message}", null)
}
}
"speakText" -> {
val text = call.argument<String>("text")
if (text.isNullOrEmpty()) {
@ -186,6 +187,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("TTS_ERROR", "语音合成失败: ${e.message}", null)
}
}
"stopTts" -> {
try {
AgentService.stopTts()
@ -195,6 +197,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("STOP_TTS_ERROR", "停止语音合成失败: ${e.message}", null)
}
}
"stopAiSteam" -> {
try {
AgentService.stopAiStream()
@ -215,6 +218,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("CLEAR_HISTORY_ERROR", "清除聊天历史失败: ${e.message}", null)
}
}
"interruptCurrentResponse" -> {
try {
AgentService.interruptCurrentResponse()
@ -224,6 +228,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("INTERRUPT_ERROR", "中断响应失败: ${e.message}", null)
}
}
"dispose" -> {
try {
AgentService.dispose()
@ -233,6 +238,7 @@ class AgentServicePlugin : FlutterPlugin, MethodCallHandler, EventChannel.Stream
result.error("DISPOSE_ERROR", "释放资源失败: ${e.message}", null)
}
}
else -> {
result.notImplemented()
}

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, "语音代理未初始化")
return false
}
Log.d(TAG, "手动启动语音识别: ")
return AgentService.startRecognition(true)
}
@ -73,7 +73,12 @@ object BleAgent : BleService.Callback, AgentServiceListener {
* 停止语音识别
*/
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) {
Log.d(TAG, "Ble设备已断开,停止语音服务")
// 设备断开连接,停止语音服务
// stopListening()
stopListening()
}
}
@ -136,18 +141,19 @@ object BleAgent : BleService.Callback, AgentServiceListener {
* 在收到唤醒信号时启动语音识别
*/
override fun onWakeupSignalReceived() {
Log.d(TAG, "收到唤醒信号,启动语音识别")
Log.d(TAG, "收到唤醒信号,启动语音识别${AgentService.isRecognitionActive}")
// 停止当前TTS,避免冲突
AgentService.interruptCurrentResponse()
// AgentService.speakText("我在!")
// 启动语音识别
if (AgentService.isRecognitionActive&&AgentService.azureAsrHelper!!.isPaused) {
// 如果已经在识别,则重新启动
stopListening()
}
//stopListening()
startListening()
//stopListening()
startListening()
}
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 {
final bool result = await _channel.invokeMethod('stopConversation');
final bool result = await _channel.invokeMethod('recognizeCallback');
return result;
} 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 {
final bool result = await _channel.invokeMethod('audioContinue');
final bool result = await _channel.invokeMethod('stopConversation');
return result;
} on PlatformException catch (e) {
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);
}
}
// /// 停止对话
// ///
// /// 停止语音识别
// /// 返回是否成功停止对话
// static Future<bool> audioContinue() async {
// try {
// final bool result = await _channel.invokeMethod('audioContinue');
// return result;
// } on PlatformException catch (e) {
// 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);
// }
// }
/// 处理文本输入
///

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

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

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

Loading…
Cancel
Save