Browse Source

接通翻译

weicu
fdp 2 years ago
parent
commit
804255cf24
  1. 5
      lib/data/services/asr_service.dart
  2. 18
      lib/data/services/speech_impl/azure_asr_service.dart
  3. 95
      lib/data/services/speech_impl/flutter_asr_service.dart
  4. 6
      lib/data/services/speech_impl/xunfei_asr_service.dart
  5. 192
      lib/modules/translation/controllers/translation_controller.dart
  6. 32
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  7. 16
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  8. 2
      local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/JieliPlugin.kt
  9. 2
      local_plugins/jieli/lib/jieli.dart
  10. 2
      local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt

5
lib/data/services/asr_service.dart

@ -16,6 +16,11 @@ abstract class AsrService {
/// 返回识别的最终结果 /// 返回识别的最终结果
Future<RecognitionEvent> recognizeOnce(); Future<RecognitionEvent> recognizeOnce();
/// 开始连续语音识别
///
/// 推送音频数据到连续识别中
Future<void> pushAudioData(Uint8List data);
/// 开始连续语音识别 /// 开始连续语音识别
/// ///
/// 返回一个包含识别事件的流 /// 返回一个包含识别事件的流

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

@ -288,4 +288,22 @@ class AzureAsrService extends GetxService implements AsrService {
_isInitialized = false; _isInitialized = false;
} }
} }
@override
Future<void> pushAudioData(Uint8List data) async {
try {
// 开始连续识别
final bool result = await _channel.invokeMethod('pushAudioData', {
'AudioData': data // Flutter 会自动将 Uint8List 转换为 ByteBuffer
});
if (!result) {
_cleanupEventStream();
throw Exception('开始pushAudioData失败');
}
} catch (e) {
Logger.error('开始pushAudioData失败: $e');
rethrow;
}
}
} }

95
lib/data/services/speech_impl/flutter_asr_service.dart

@ -131,7 +131,8 @@ class FlutterAsrService extends GetxService implements AsrService {
_isListening = false; _isListening = false;
// 在连续识别模式下,当一次识别完成后立即开始下一次识别 // 在连续识别模式下,当一次识别完成后立即开始下一次识别
if (_continuousRecognitionController != null && !_continuousRecognitionController!.isClosed) { if (_continuousRecognitionController != null &&
!_continuousRecognitionController!.isClosed) {
_restartContinuousRecognition(); _restartContinuousRecognition();
} }
break; break;
@ -144,12 +145,13 @@ class FlutterAsrService extends GetxService implements AsrService {
_asrState.value = AsrState.error; _asrState.value = AsrState.error;
// 向连续识别控制器发送错误事件(如果存在) // 向连续识别控制器发送错误事件(如果存在)
_continuousRecognitionController?.add( _continuousRecognitionController
RecognitionEvent.error(error.errorMsg) ?.add(RecognitionEvent.error(error.errorMsg));
);
// 在连续识别模式下,尝试从错误中恢复(非永久性错误) // 在连续识别模式下,尝试从错误中恢复(非永久性错误)
if (!error.permanent && _continuousRecognitionController != null && !_continuousRecognitionController!.isClosed) { if (!error.permanent &&
_continuousRecognitionController != null &&
!_continuousRecognitionController!.isClosed) {
_restartContinuousRecognition(); _restartContinuousRecognition();
} }
} }
@ -181,17 +183,18 @@ class FlutterAsrService extends GetxService implements AsrService {
// 启动语音识别 // 启动语音识别
final result = await _speech.listen( final result = await _speech.listen(
onResult: onResult, onResult: onResult,
localeId: _currentLocale.value, localeId: _currentLocale.value,
listenFor: listenFor, listenFor: listenFor,
pauseFor: pauseFor, pauseFor: pauseFor,
listenOptions: stt.SpeechListenOptions( listenOptions: stt.SpeechListenOptions(
partialResults: true, partialResults: true,
autoPunctuation: autoPunctuation, autoPunctuation: autoPunctuation,
// 使用独白模式,减少与系统交互冲突 // 使用独白模式,减少与系统交互冲突
listenMode: stt.ListenMode.dictation, listenMode: stt.ListenMode.dictation,
), ),
) ?? false; ) ??
false;
if (!result) { if (!result) {
Logger.error('语音识别启动失败'); Logger.error('语音识别启动失败');
@ -210,7 +213,8 @@ class FlutterAsrService extends GetxService implements AsrService {
Future<void> _restartContinuousRecognition() async { Future<void> _restartContinuousRecognition() async {
try { try {
// 确保控制器有效 // 确保控制器有效
if (_continuousRecognitionController == null || _continuousRecognitionController!.isClosed) { if (_continuousRecognitionController == null ||
_continuousRecognitionController!.isClosed) {
Logger.info('连续识别控制器已关闭,跳过重启'); Logger.info('连续识别控制器已关闭,跳过重启');
return; return;
} }
@ -225,7 +229,8 @@ class FlutterAsrService extends GetxService implements AsrService {
await Future.delayed(const Duration(milliseconds: 1000)); await Future.delayed(const Duration(milliseconds: 1000));
// 再次检查状态 // 再次检查状态
if (_continuousRecognitionController == null || _continuousRecognitionController!.isClosed) { if (_continuousRecognitionController == null ||
_continuousRecognitionController!.isClosed) {
Logger.info('延迟期间控制器已关闭,跳过重启'); Logger.info('延迟期间控制器已关闭,跳过重启');
return; return;
} }
@ -244,17 +249,14 @@ class FlutterAsrService extends GetxService implements AsrService {
Logger.info('连续识别重启成功'); Logger.info('连续识别重启成功');
} else { } else {
Logger.error('连续识别重启失败'); Logger.error('连续识别重启失败');
_continuousRecognitionController?.add( _continuousRecognitionController
RecognitionEvent.error('重启语音识别失败,可能存在系统资源冲突') ?.add(RecognitionEvent.error('重启语音识别失败,可能存在系统资源冲突'));
);
} }
} catch (e) { } catch (e) {
Logger.error('重启连续识别失败: $e'); Logger.error('重启连续识别失败: $e');
// 发送错误给控制器 // 发送错误给控制器
_continuousRecognitionController?.add( _continuousRecognitionController?.add(RecognitionEvent.error('重启失败: $e'));
RecognitionEvent.error('重启失败: $e')
);
} }
} }
@ -264,20 +266,16 @@ class FlutterAsrService extends GetxService implements AsrService {
if (_continuousRecognitionController != null) { if (_continuousRecognitionController != null) {
if (result.finalResult) { if (result.finalResult) {
Logger.info('收到最终识别结果: "${result.recognizedWords}"'); Logger.info('收到最终识别结果: "${result.recognizedWords}"');
_continuousRecognitionController!.add( _continuousRecognitionController!.add(RecognitionEvent.finalResult(
RecognitionEvent.finalResult( text: result.recognizedWords,
text: result.recognizedWords, detectedLanguage: currentLocale,
detectedLanguage: currentLocale, ));
)
);
} else { } else {
_continuousRecognitionController!.add( _continuousRecognitionController!.add(RecognitionEvent(
RecognitionEvent( type: RecognitionEventType.intermediateResult,
type: RecognitionEventType.intermediateResult, text: result.recognizedWords,
text: result.recognizedWords, detectedLanguage: currentLocale,
detectedLanguage: currentLocale, ));
)
);
} }
} }
} }
@ -352,7 +350,7 @@ class FlutterAsrService extends GetxService implements AsrService {
// 设置较短的超时时间 // 设置较短的超时时间
return await completer.future.timeout( return await completer.future.timeout(
const Duration(seconds: 20), // 略长于listenFor以确保获取到结果 const Duration(seconds: 20), // 略长于listenFor以确保获取到结果
onTimeout: () { onTimeout: () {
_speech.stop(); _speech.stop();
return RecognitionEvent.error('语音识别超时'); return RecognitionEvent.error('语音识别超时');
@ -380,12 +378,12 @@ class FlutterAsrService extends GetxService implements AsrService {
} }
// 创建新的流控制器 // 创建新的流控制器
_continuousRecognitionController = StreamController<RecognitionEvent>.broadcast(); _continuousRecognitionController =
StreamController<RecognitionEvent>.broadcast();
// 开始会话 // 开始会话
_continuousRecognitionController!.add( _continuousRecognitionController!
RecognitionEvent(type: RecognitionEventType.sessionStarted) .add(RecognitionEvent(type: RecognitionEventType.sessionStarted));
);
// 开始连续监听 // 开始连续监听
_asrState.value = AsrState.listening; _asrState.value = AsrState.listening;
@ -397,9 +395,8 @@ class FlutterAsrService extends GetxService implements AsrService {
if (!success) { if (!success) {
// 添加错误事件,但保留流 // 添加错误事件,但保留流
_continuousRecognitionController!.add( _continuousRecognitionController!
RecognitionEvent.error('启动连续语音识别失败') .add(RecognitionEvent.error('启动连续语音识别失败'));
);
// 返回已有的流控制器 // 返回已有的流控制器
return _continuousRecognitionController!.stream; return _continuousRecognitionController!.stream;
@ -434,9 +431,8 @@ class FlutterAsrService extends GetxService implements AsrService {
_isListening = false; _isListening = false;
// 发送会话结束事件 // 发送会话结束事件
_continuousRecognitionController?.add( _continuousRecognitionController
RecognitionEvent(type: RecognitionEventType.sessionStopped) ?.add(RecognitionEvent(type: RecognitionEventType.sessionStopped));
);
// 关闭流控制器 // 关闭流控制器
await _continuousRecognitionController?.close(); await _continuousRecognitionController?.close();
@ -478,4 +474,7 @@ class FlutterAsrService extends GetxService implements AsrService {
Logger.error('释放语音识别资源失败: $e'); Logger.error('释放语音识别资源失败: $e');
} }
} }
@override
Future<void> pushAudioData(Uint8List data) async {}
} }

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

@ -269,4 +269,10 @@ class XunfeiAsrService extends GetxService implements AsrService {
_isInitialized = false; _isInitialized = false;
} }
} }
@override
Future<void> pushAudioData(Uint8List data) {
// TODO: implement pushAudioData
throw UnimplementedError();
}
} }

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

@ -10,14 +10,37 @@ import '../../../core/utils/logger.dart';
import '../../../data/models/events/voice_interaction_event.dart'; import '../../../data/models/events/voice_interaction_event.dart';
import '../../../data/services/voice_interaction_service.dart'; import '../../../data/services/voice_interaction_service.dart';
import '../../../data/services/asr_service.dart'; import '../../../data/services/asr_service.dart';
import 'package:jieli/jieli.dart';
class TranslationController extends GetxController { class TranslationController extends GetxController {
late Jieli jieli;
// 事件订阅
StreamSubscription? _scanSubscription;
StreamSubscription? _connectionSubscription;
StreamSubscription? _wakeupSubscription;
StreamSubscription? _audioSubscription;
StreamSubscription? _commandResultSubscription;
// 状态变量
var isScanning = false.obs;
var deviceInfo = '未获取'.obs;
var connectStatus = '未连接'.obs;
var currentDevice = Rx<BleDevice?>(null);
var scannedDevices = RxList<BleDevice>([]);
var wakeupReceived = false.obs;
var audioDataSize = 0.obs;
var receivedFrames = 0.obs;
var audioDataReceiving = false.obs;
var volumeCommandResult = ''.obs;
// 服务 // 服务
final AsrService _asrService = Get.find<AsrService>(); final AsrService _asrService = Get.find<AsrService>();
final VolcanoTranslationService _translationService = Get.find<VolcanoTranslationService>(); final VolcanoTranslationService _translationService =
Get.find<VolcanoTranslationService>();
final TtsService _ttsService = Get.find<TtsService>(); final TtsService _ttsService = Get.find<TtsService>();
final LanguageManager _languageManager = Get.find<LanguageManager>(); final LanguageManager _languageManager = Get.find<LanguageManager>();
final GetStorage _storage = GetStorage(); final GetStorage _storage = GetStorage();
final VoiceInteractionService _voiceInteractionService = Get.find<VoiceInteractionService>(); final VoiceInteractionService _voiceInteractionService =
Get.find<VoiceInteractionService>();
// 存储相关 // 存储相关
static const String _historyKey = 'translation_history'; static const String _historyKey = 'translation_history';
@ -43,7 +66,8 @@ class TranslationController extends GetxController {
// 兼容旧版视图的计算属性 // 兼容旧版视图的计算属性
Rx<TranslationItem?> get currentItem { Rx<TranslationItem?> get currentItem {
if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate) { if (translationHistory.isNotEmpty &&
translationHistory.last.isIntermediate) {
return Rx<TranslationItem?>(translationHistory.last); return Rx<TranslationItem?>(translationHistory.last);
} }
return Rx<TranslationItem?>(null); return Rx<TranslationItem?>(null);
@ -58,22 +82,26 @@ class TranslationController extends GetxController {
Timer? _translationDebounceTimer; Timer? _translationDebounceTimer;
// 获取支持的语言列表 // 获取支持的语言列表
Map<String, String> get supportedLanguages => _languageManager.getChineseNameToAsrCodeMap(); Map<String, String> get supportedLanguages =>
_languageManager.getChineseNameToAsrCodeMap();
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
jieli = Jieli();
//_setupEventListeners();
// 加载上次保存的语言设置 // 加载上次保存的语言设置
_loadSavedLanguages(); _loadSavedLanguages();
// 初始化语言代码 // 初始化语言代码
final sourceAsrCode = _languageManager.getAsrCodeByChineseName(sourceLanguage.value); final sourceAsrCode =
_languageManager.getAsrCodeByChineseName(sourceLanguage.value);
if (sourceAsrCode != null) { if (sourceAsrCode != null) {
sourceLanguageCode.value = sourceAsrCode; sourceLanguageCode.value = sourceAsrCode;
} }
final targetAsrCode = _languageManager.getAsrCodeByChineseName(targetLanguage.value); final targetAsrCode =
_languageManager.getAsrCodeByChineseName(targetLanguage.value);
if (targetAsrCode != null) { if (targetAsrCode != null) {
targetLanguageCode.value = targetAsrCode; targetLanguageCode.value = targetAsrCode;
} }
@ -90,14 +118,14 @@ class TranslationController extends GetxController {
// 使用微任务在布局完成后自动滚动到底部 // 使用微任务在布局完成后自动滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom(); _scrollToBottom();
}); });
} }
/// 订阅原生服务事件 /// 订阅原生服务事件
void _subscribeToVoiceInteractionService() { void _subscribeToVoiceInteractionService() {
if (_voiceInteractionService.eventStream != null) { if (_voiceInteractionService.eventStream != null) {
_voiceInteractionSubscription = _voiceInteractionService.eventStream!.listen((event) { _voiceInteractionSubscription =
_voiceInteractionService.eventStream!.listen((event) {
if (event is RecognitionStartedEvent) { if (event is RecognitionStartedEvent) {
_handleVoiceInteractionStarted(); _handleVoiceInteractionStarted();
} }
@ -110,11 +138,13 @@ class TranslationController extends GetxController {
final savedSourceLanguage = _storage.read(_sourceLanguageKey); final savedSourceLanguage = _storage.read(_sourceLanguageKey);
final savedTargetLanguage = _storage.read(_targetLanguageKey); final savedTargetLanguage = _storage.read(_targetLanguageKey);
if (savedSourceLanguage != null && supportedLanguages.containsKey(savedSourceLanguage)) { if (savedSourceLanguage != null &&
supportedLanguages.containsKey(savedSourceLanguage)) {
sourceLanguage.value = savedSourceLanguage; sourceLanguage.value = savedSourceLanguage;
} }
if (savedTargetLanguage != null && supportedLanguages.containsKey(savedTargetLanguage)) { if (savedTargetLanguage != null &&
supportedLanguages.containsKey(savedTargetLanguage)) {
targetLanguage.value = savedTargetLanguage; targetLanguage.value = savedTargetLanguage;
} }
} }
@ -125,13 +155,30 @@ class TranslationController extends GetxController {
_storage.write(_targetLanguageKey, targetLanguage.value); _storage.write(_targetLanguageKey, targetLanguage.value);
} }
// /// 设置事件监听器
// void _setupEventListeners() {
// // 监听扫描结果
// _scanSubscription = jieli.scanResults.listen((devices) {
// scannedDevices.assignAll(devices);
// });
// // 监听唤醒信号
// _wakeupSubscription = jieli.wakeupStream.listen((wakeup) {
// wakeupReceived.value = true;
// Get.snackbar('收到唤醒信号', '耳机发送了唤醒信号');
// });
// }
// 初始化服务 // 初始化服务
Future<void> _initServices() async { Future<void> _initServices() async {
try { try {
await _translationService.initialize(); await _translationService.initialize();
// 初始化 ASR 服务时,明确指定需要支持的语言 // 初始化 ASR 服务时,明确指定需要支持的语言
final List<String> asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value]; final List<String> asrSupportedLanguages = [
sourceLanguageCode.value,
targetLanguageCode.value
];
Logger.info('初始化ASR服务,支持语言: $asrSupportedLanguages'); Logger.info('初始化ASR服务,支持语言: $asrSupportedLanguages');
await _asrService.initialize( await _asrService.initialize(
@ -159,14 +206,20 @@ class TranslationController extends GetxController {
// 开始语音识别 // 开始语音识别
Future<void> startRecognition() async { Future<void> startRecognition() async {
Logger.info('开始语音识别');
if (isRecognizing.value) return; if (isRecognizing.value) return;
_voiceInteractionService.pauseVoiceInteraction(); _voiceInteractionService.pauseVoiceInteraction();
try { try {
Logger.info('开始语音识别1');
// 每次开始识别前检查 ASR 服务是否已配置为当前语言对 // 每次开始识别前检查 ASR 服务是否已配置为当前语言对
final List<String> currentLanguages = [sourceLanguageCode.value, targetLanguageCode.value]; final List<String> currentLanguages = [
final bool needsReinit = !_asrService.supportedLanguages.toSet().containsAll(currentLanguages); sourceLanguageCode.value,
targetLanguageCode.value
];
final bool needsReinit =
!_asrService.supportedLanguages.toSet().containsAll(currentLanguages);
if (needsReinit) { if (needsReinit) {
Logger.info('识别开始前发现语言变更,重新初始化ASR服务'); Logger.info('识别开始前发现语言变更,重新初始化ASR服务');
@ -192,11 +245,19 @@ class TranslationController extends GetxController {
// 开始连续语音识别 // 开始连续语音识别
final recognitionStream = await _asrService.startContinuousRecognition(); final recognitionStream = await _asrService.startContinuousRecognition();
Logger.info('翻译开启,开始监');
// 监听音频数据
_audioSubscription = jieli.audioDataStream.listen((data) async {
// Logger.info('翻译开启,开始监听解码后的数据=${data}');
final pushAudioDataStream = await _asrService.pushAudioData(data);
});
isRecognizing.value = true; isRecognizing.value = true;
// 监听识别结果 // 监听识别结果
_recognitionSubscription?.cancel(); _recognitionSubscription?.cancel();
_recognitionSubscription = recognitionStream.listen(_handleRecognitionEvent); _recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
} catch (e) { } catch (e) {
isRecognizing.value = false; isRecognizing.value = false;
Logger.error('启动语音识别失败: $e'); Logger.error('启动语音识别失败: $e');
@ -205,10 +266,12 @@ class TranslationController extends GetxController {
// 处理识别事件 // 处理识别事件
void _handleRecognitionEvent(RecognitionEvent event) { void _handleRecognitionEvent(RecognitionEvent event) {
if (event.type == RecognitionEventType.finalResult && event.text.isNotEmpty) { if (event.type == RecognitionEventType.finalResult &&
event.text.isNotEmpty) {
detectedLanguageCode = event.detectedLanguage; detectedLanguageCode = event.detectedLanguage;
handleFinalResult(event.text); handleFinalResult(event.text);
} else if (event.type == RecognitionEventType.intermediateResult && event.text.isNotEmpty) { } else if (event.type == RecognitionEventType.intermediateResult &&
event.text.isNotEmpty) {
if (event.detectedLanguage.isNotEmpty) { if (event.detectedLanguage.isNotEmpty) {
detectedLanguageCode = event.detectedLanguage; detectedLanguageCode = event.detectedLanguage;
} }
@ -233,14 +296,18 @@ class TranslationController extends GetxController {
isRecognizing.value = false; isRecognizing.value = false;
// 如果当前有中间结果,将其作为最终结果处理 // 如果当前有中间结果,将其作为最终结果处理
if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) { if (translationHistory.isNotEmpty &&
translationHistory.last.isIntermediate &&
translationHistory.last.sourceText.isNotEmpty) {
// 标记为最终结果 // 标记为最终结果
translationHistory.last.isIntermediate = false; translationHistory.last.isIntermediate = false;
translationHistory.refresh(); translationHistory.refresh();
// 如果还没有翻译结果,尝试翻译 // 如果还没有翻译结果,尝试翻译
if (translationHistory.last.translatedText.isEmpty) { if (translationHistory.last.translatedText.isEmpty) {
await translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: true); await translateText(translationHistory.last.sourceText,
translationHistory.last.timestamp,
isFinal: true);
} }
saveTranslationHistory(); saveTranslationHistory();
@ -265,7 +332,8 @@ class TranslationController extends GetxController {
targetLanguageCode: targetLanguageCode.value, targetLanguageCode: targetLanguageCode.value,
timestamp: DateTime.now(), timestamp: DateTime.now(),
sessionId: currentSessionId!, sessionId: currentSessionId!,
isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, isFirstInSession: translationHistory.isEmpty ||
translationHistory.last.sessionId != currentSessionId,
isIntermediate: true, isIntermediate: true,
); );
@ -283,8 +351,12 @@ class TranslationController extends GetxController {
// 防抖处理翻译请求 // 防抖处理翻译请求
_translationDebounceTimer?.cancel(); _translationDebounceTimer?.cancel();
_translationDebounceTimer = Timer(const Duration(milliseconds: 300), () { _translationDebounceTimer = Timer(const Duration(milliseconds: 300), () {
if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) { if (translationHistory.isNotEmpty &&
translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: false); translationHistory.last.isIntermediate &&
translationHistory.last.sourceText.isNotEmpty) {
translateText(translationHistory.last.sourceText,
translationHistory.last.timestamp,
isFinal: false);
} }
}); });
} }
@ -312,8 +384,10 @@ class TranslationController extends GetxController {
sourceLanguageCode: sourceLanguageCode.value, sourceLanguageCode: sourceLanguageCode.value,
targetLanguageCode: targetLanguageCode.value, targetLanguageCode: targetLanguageCode.value,
timestamp: DateTime.now(), timestamp: DateTime.now(),
sessionId: currentSessionId ?? DateTime.now().millisecondsSinceEpoch.toString(), sessionId: currentSessionId ??
isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, DateTime.now().millisecondsSinceEpoch.toString(),
isFirstInSession: translationHistory.isEmpty ||
translationHistory.last.sessionId != currentSessionId,
isIntermediate: false, isIntermediate: false,
); );
@ -322,14 +396,14 @@ class TranslationController extends GetxController {
_scrollToBottom(); _scrollToBottom();
saveTranslationHistory(); saveTranslationHistory();
// 使用新项目的时间戳进行翻译 // 使用新项目的时间戳进行翻译
translateText(text, newItem.timestamp, isFinal: true); translateText(text, newItem.timestamp, isFinal: true);
} }
} }
// 翻译文本 // 翻译文本
Future<String?> translateText(String sourceText, DateTime timestamp, {bool isFinal = false}) async { Future<String?> translateText(String sourceText, DateTime timestamp,
{bool isFinal = false}) async {
if (sourceText.isEmpty) return null; if (sourceText.isEmpty) return null;
isTranslating.value = true; isTranslating.value = true;
@ -338,8 +412,10 @@ class TranslationController extends GetxController {
try { try {
// 根据检测到的语言确定源语言和目标语言 // 根据检测到的语言确定源语言和目标语言
final bool shouldSwap = detectedLanguageCode == sourceLanguageCode.value; final bool shouldSwap = detectedLanguageCode == sourceLanguageCode.value;
final detectedSourceLanguageCode = shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value; final detectedSourceLanguageCode =
final detectedTargetLanguageCode = shouldSwap ? targetLanguageCode.value : sourceLanguageCode.value; shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value;
final detectedTargetLanguageCode =
shouldSwap ? targetLanguageCode.value : sourceLanguageCode.value;
// 调用翻译服务 // 调用翻译服务
translationResult = await _translationService.translateText( translationResult = await _translationService.translateText(
@ -352,13 +428,15 @@ class TranslationController extends GetxController {
// 查找匹配的项目 - 优先使用timestamp查找 // 查找匹配的项目 - 优先使用timestamp查找
int index = -1; int index = -1;
index = translationHistory.indexWhere((item) => index = translationHistory
item.timestamp.isAtSameMomentAs(timestamp)); .indexWhere((item) => item.timestamp.isAtSameMomentAs(timestamp));
if (index != -1) { if (index != -1) {
// 更新历史项 // 更新历史项
translationHistory[index].translatedText = translationResult; translationHistory[index].translatedText = translationResult;
translationHistory[index].sourceLanguageCode = detectedSourceLanguageCode; translationHistory[index].sourceLanguageCode =
translationHistory[index].targetLanguageCode = detectedTargetLanguageCode; detectedSourceLanguageCode;
translationHistory[index].targetLanguageCode =
detectedTargetLanguageCode;
translationHistory.refresh(); translationHistory.refresh();
saveTranslationHistory(); saveTranslationHistory();
// 更新后滚动到底部 // 更新后滚动到底部
@ -370,7 +448,8 @@ class TranslationController extends GetxController {
await playTranslatedText(translationResult); await playTranslatedText(translationResult);
} }
} else { } else {
Logger.info('未找到匹配的历史项 ${sourceText} ${timestamp.toIso8601String() ?? "无时间戳"}'); Logger.info(
'未找到匹配的历史项 ${sourceText} ${timestamp.toIso8601String() ?? "无时间戳"}');
} }
} }
} catch (e) { } catch (e) {
@ -388,8 +467,11 @@ class TranslationController extends GetxController {
try { try {
final bool shouldSwap = detectedLanguageCode == targetLanguageCode.value; final bool shouldSwap = detectedLanguageCode == targetLanguageCode.value;
final detectedTargetLanguageCode = shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value; final detectedTargetLanguageCode =
final voiceName = _languageManager.getTtsVoiceNameByAsrCode(detectedTargetLanguageCode) ?? 'en-US-AriaNeural'; shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value;
final voiceName = _languageManager
.getTtsVoiceNameByAsrCode(detectedTargetLanguageCode) ??
'en-US-AriaNeural';
await _ttsService.setVoice(voiceName); await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(text); await _ttsService.speakOnce(text);
@ -417,7 +499,9 @@ class TranslationController extends GetxController {
if (item.translatedText.isEmpty) return; if (item.translatedText.isEmpty) return;
try { try {
final voiceName = _languageManager.getTtsVoiceNameByAsrCode(item.targetLanguageCode) ?? 'en-US-AriaNeural'; final voiceName =
_languageManager.getTtsVoiceNameByAsrCode(item.targetLanguageCode) ??
'en-US-AriaNeural';
await _ttsService.setVoice(voiceName); await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(item.translatedText); await _ttsService.speakOnce(item.translatedText);
} catch (e) { } catch (e) {
@ -431,8 +515,11 @@ class TranslationController extends GetxController {
try { try {
final voiceName = languageCode != null final voiceName = languageCode != null
? _languageManager.getTtsVoiceNameByAsrCode(languageCode) ?? 'en-US-AriaNeural' ? _languageManager.getTtsVoiceNameByAsrCode(languageCode) ??
: _languageManager.getTtsVoiceNameByAsrCode(targetLanguageCode.value) ?? 'en-US-AriaNeural'; 'en-US-AriaNeural'
: _languageManager
.getTtsVoiceNameByAsrCode(targetLanguageCode.value) ??
'en-US-AriaNeural';
await _ttsService.setVoice(voiceName); await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(text); await _ttsService.speakOnce(text);
@ -443,7 +530,8 @@ class TranslationController extends GetxController {
// 滚动到底部 // 滚动到底部
void _scrollToBottom() { void _scrollToBottom() {
if (scrollController.hasClients && (translationHistory.isNotEmpty || currentSessionId != null)) { if (scrollController.hasClients &&
(translationHistory.isNotEmpty || currentSessionId != null)) {
try { try {
scrollController.animateTo( scrollController.animateTo(
scrollController.position.maxScrollExtent, scrollController.position.maxScrollExtent,
@ -535,7 +623,10 @@ class TranslationController extends GetxController {
Future<void> _reinitializeAsrService() async { Future<void> _reinitializeAsrService() async {
try { try {
// 更新ASR支持的语言 // 更新ASR支持的语言
final List<String> asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value]; final List<String> asrSupportedLanguages = [
sourceLanguageCode.value,
targetLanguageCode.value
];
Logger.info('重新初始化ASR服务,支持语言: $asrSupportedLanguages'); Logger.info('重新初始化ASR服务,支持语言: $asrSupportedLanguages');
@ -556,11 +647,13 @@ class TranslationController extends GetxController {
if (historyJson != null) { if (historyJson != null) {
try { try {
final List<dynamic> historyList = jsonDecode(historyJson); final List<dynamic> historyList = jsonDecode(historyJson);
final List<TranslationItem> allHistory = historyList.map((item) => TranslationItem.fromJson(item)).toList(); final List<TranslationItem> allHistory =
historyList.map((item) => TranslationItem.fromJson(item)).toList();
// 只保留最新的10条记录 // 只保留最新的10条记录
if (allHistory.length > _maxHistoryItems) { if (allHistory.length > _maxHistoryItems) {
translationHistory.value = allHistory.sublist(allHistory.length - _maxHistoryItems); translationHistory.value =
allHistory.sublist(allHistory.length - _maxHistoryItems);
} else { } else {
translationHistory.value = allHistory; translationHistory.value = allHistory;
} }
@ -579,11 +672,13 @@ class TranslationController extends GetxController {
// 保存翻译历史 // 保存翻译历史
void saveTranslationHistory() { void saveTranslationHistory() {
if (translationHistory.length > _maxHistoryItems) { if (translationHistory.length > _maxHistoryItems) {
translationHistory.removeRange(0, translationHistory.length - _maxHistoryItems); translationHistory.removeRange(
0, translationHistory.length - _maxHistoryItems);
} }
try { try {
final List<Map<String, dynamic>> historyJson = translationHistory.map((item) => item.toJson()).toList(); final List<Map<String, dynamic>> historyJson =
translationHistory.map((item) => item.toJson()).toList();
_storage.write(_historyKey, jsonEncode(historyJson)); _storage.write(_historyKey, jsonEncode(historyJson));
} catch (e) { } catch (e) {
Logger.error('保存翻译历史失败: $e'); Logger.error('保存翻译历史失败: $e');
@ -644,7 +739,8 @@ class TranslationItem {
sourceLanguageCode: json['sourceLanguageCode'] ?? 'zh-CN', sourceLanguageCode: json['sourceLanguageCode'] ?? 'zh-CN',
targetLanguageCode: json['targetLanguageCode'] ?? 'en-US', targetLanguageCode: json['targetLanguageCode'] ?? 'en-US',
timestamp: timestamp, timestamp: timestamp,
sessionId: json['sessionId'] ?? DateTime.now().millisecondsSinceEpoch.toString(), sessionId:
json['sessionId'] ?? DateTime.now().millisecondsSinceEpoch.toString(),
isFirstInSession: json['isFirstInSession'] ?? false, isFirstInSession: json['isFirstInSession'] ?? false,
isIntermediate: false, // 从存储加载的都是最终结果 isIntermediate: false, // 从存储加载的都是最终结果
); );
@ -666,12 +762,14 @@ class TranslationItem {
// 获取源语言的中文名称 // 获取源语言的中文名称
String get sourceLanguageName { String get sourceLanguageName {
final languageManager = Get.find<LanguageManager>(); final languageManager = Get.find<LanguageManager>();
return languageManager.getChineseNameByAsrCode(sourceLanguageCode) ?? '未知语言'; return languageManager.getChineseNameByAsrCode(sourceLanguageCode) ??
'未知语言';
} }
// 获取目标语言的中文名称 // 获取目标语言的中文名称
String get targetLanguageName { String get targetLanguageName {
final languageManager = Get.find<LanguageManager>(); final languageManager = Get.find<LanguageManager>();
return languageManager.getChineseNameByAsrCode(targetLanguageCode) ?? '未知语言'; return languageManager.getChineseNameByAsrCode(targetLanguageCode) ??
'未知语言';
} }
} }

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

@ -28,7 +28,7 @@ class AzureAsrHelper(private val context: Context) {
private var supportedLanguages = arrayOf("zh-CN") private var supportedLanguages = arrayOf("zh-CN")
// 是否使用回音消除 - 内部控制常量 // 是否使用回音消除 - 内部控制常量
private val useEchoCancellation = false private val useEchoCancellation = true
// 自定义音频处理相关 // 自定义音频处理相关
private var customAudioProcessor: CustomAudioProcessor? = null private var customAudioProcessor: CustomAudioProcessor? = null
@ -36,6 +36,7 @@ class AzureAsrHelper(private val context: Context) {
private var audioConfig: AudioConfig? = null private var audioConfig: AudioConfig? = null
// 公共方法:允许外部写入音频数据 // 公共方法:允许外部写入音频数据
fun pushAudioData(data: ByteArray) { fun pushAudioData(data: ByteArray) {
FileLogger.d(TAG, "pushAudioData,data=${data},pushStream=${pushStream}")
pushStream?.write(data) // 使用 SDK 的正确方法(如 push) pushStream?.write(data) // 使用 SDK 的正确方法(如 push)
} }
@ -85,6 +86,7 @@ class AzureAsrHelper(private val context: Context) {
// 创建识别器 // 创建识别器
try { try {
if (useEchoCancellation) { if (useEchoCancellation) {
FileLogger.d(TAG, "如果使用回音消除,创建自定义音频输入流")
// 如果使用回音消除,创建自定义音频输入流 // 如果使用回音消除,创建自定义音频输入流
setupCustomAudioProcessing() setupCustomAudioProcessing()
@ -95,13 +97,13 @@ class AzureAsrHelper(private val context: Context) {
recognizer = SpeechRecognizer(speechConfig, audioConfig) recognizer = SpeechRecognizer(speechConfig, audioConfig)
} }
} else { } else {
// 使用默认麦克风输入 // // 使用默认麦克风输入
if (isAutoDetectLanguage) { // if (isAutoDetectLanguage) {
val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList()) // val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
recognizer = SpeechRecognizer(speechConfig, autoDetectConfig) // recognizer = SpeechRecognizer(speechConfig, autoDetectConfig)
} else { // } else {
recognizer = SpeechRecognizer(speechConfig) // recognizer = SpeechRecognizer(speechConfig)
} // }
} }
FileLogger.d(TAG, "Azure 语音服务初始化成功") FileLogger.d(TAG, "Azure 语音服务初始化成功")
@ -140,13 +142,13 @@ class AzureAsrHelper(private val context: Context) {
recognizer = SpeechRecognizer(speechConfig, audioConfig) recognizer = SpeechRecognizer(speechConfig, audioConfig)
} }
} else { } else {
// 使用默认麦克风输入 // // 使用默认麦克风输入
if (isAutoDetectLanguage) { // if (isAutoDetectLanguage) {
val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList()) // val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
recognizer = SpeechRecognizer(speechConfig, autoDetectConfig) // recognizer = SpeechRecognizer(speechConfig, autoDetectConfig)
} else { // } else {
recognizer = SpeechRecognizer(speechConfig) // recognizer = SpeechRecognizer(speechConfig)
} // }
} }
return true return true
} else { } else {

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

@ -4,6 +4,7 @@ import android.content.Context
import android.app.Activity import android.app.Activity
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log
import androidx.annotation.NonNull import androidx.annotation.NonNull
import com.yunqiinnovation.azure_speech.utils.FileLogger import com.yunqiinnovation.azure_speech.utils.FileLogger
@ -91,6 +92,21 @@ class AzureSpeechPlugin: FlutterPlugin {
} catch (e: Exception) { } catch (e: Exception) {
result.error("INITIALIZATION_ERROR", e.message, null) result.error("INITIALIZATION_ERROR", e.message, null)
} }
}
"pushAudioData" -> {
val audioData = call.argument<ByteArray>("AudioData")
try {
Log.d("","AsrMethodHandler,pushAudioData${audioData}")
if (audioData != null) {
val success = azureAsrHelper.pushAudioData(audioData)
result.success(success)
}
} catch (e: Exception) {
result.error("pushAudioData_ERROR", e.message, null)
}
} }
"recognizeOnce" -> { "recognizeOnce" -> {
azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback { azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback {

2
local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/JieliPlugin.kt

@ -92,7 +92,7 @@ class JieliPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, EventChannel
override fun onAudioDataReceived(data: ByteArray) { override fun onAudioDataReceived(data: ByteArray) {
pluginScope.launch(Dispatchers.Main) { pluginScope.launch(Dispatchers.Main) {
Log.d("","数据流onAudioDataReceived${data}") // Log.d("","数据流onAudioDataReceived${data}")
sendEvent(EVENT_TYPE_AUDIO_DATA, data) sendEvent(EVENT_TYPE_AUDIO_DATA, data)
} }
} }

2
local_plugins/jieli/lib/jieli.dart

@ -127,7 +127,7 @@ class Jieli {
break; break;
case EventType.audioData: case EventType.audioData:
if (data is Uint8List) { if (data is Uint8List) {
print('收到ble通道消息: $data'); //print('收到ble通道消息: $data');
_audioController.add(data); _audioController.add(data);
} }
break; break;

2
local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt

@ -324,7 +324,7 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback { opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback {
override fun onDecodeStream(data: ByteArray?) { override fun onDecodeStream(data: ByteArray?) {
if (data != null) { if (data != null) {
Log.d(TAG, "onDecodeStream${data}") //Log.d(TAG, "onDecodeStream${data}")
callback.onAudioDataReceived1(data) callback.onAudioDataReceived1(data)
sendEventOnMainThread(mapOf( sendEventOnMainThread(mapOf(
"event" to "onDecodeStream", "event" to "onDecodeStream",

Loading…
Cancel
Save