import 'dart:async'; import 'dart:typed_data'; import 'package:deep_voice/data/models/appconfig.dart'; import 'package:flutter/services.dart'; import 'package:get_storage/get_storage.dart'; import '../../../core/utils/logger.dart'; import 'package:get/get.dart'; import '../asr_service.dart'; /// 音频源类型 enum AudioSourceType { microphone, // 使用设备麦克风 external // 使用外部提供的音频数据 } /// Azure 语音识别服务 /// /// 该服务提供了通过平台通道与原生 Microsoft Speech SDK 交互的接口 class AzureAsrService extends GetxService implements AsrService { static final AzureAsrService to = Get.put(AzureAsrService()); static const MethodChannel _channel = MethodChannel('azure_speech/asr'); static const EventChannel _eventChannel = EventChannel('azure_speech/asr_events'); // final GetStorage _storage = GetStorage(); bool _isInitialized = false; late final String _subscriptionKey; late final String _serviceRegion; final List _defaultSupportedLanguages = ['zh-CN', 'en-US']; @override List get supportedLanguages => _defaultSupportedLanguages; // 连续识别相关 bool _isContinuousRecognitionActive = false; StreamController? _eventStreamController; StreamSubscription? _eventSubscription; // 最新的识别结果 String _latestRecognizedText = ''; String get latestRecognizedText => _latestRecognizedText; // 最新检测到的语言 String _latestDetectedLanguage = ''; String get latestDetectedLanguage => _latestDetectedLanguage; // 当前音频源类型 AudioSourceType _audioSourceType = AudioSourceType.microphone; AzureAsrService() { _loadConfig(); } /// 从环境变量加载配置 void _loadConfig() { // final _env = _storage.read("ENV") as Map; _subscriptionKey = AppConfig.env('AZURE_SPEECH_KEY') ?? ''; _serviceRegion = AppConfig.env('AZURE_SPEECH_REGION') ?? ''; if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) { throw Exception( '未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION'); } } /// 设置事件通道 void _setupEventChannel() { _eventSubscription?.cancel(); _eventSubscription = _eventChannel.receiveBroadcastStream().listen((event) { if (event is Map) { _handleRecognitionEvent(event); } }, onError: _handleRecognitionError); } @override Future initialize({ List? supportedLanguages, bool useExternalAudio = false, bool useEchoCancellation = false, }) async { try { final List languages = supportedLanguages ?? _defaultSupportedLanguages; // 检查是否需要重新初始化 if (_isInitialized) { await dispose(); } // 设置音频源类型 _audioSourceType = useExternalAudio ? AudioSourceType.external : AudioSourceType.microphone; final bool result = await _channel.invokeMethod('initialize', { 'subscriptionKey': _subscriptionKey, 'region': _serviceRegion, 'supportedLanguages': languages, 'audioSourceType': _audioSourceType.toString().split('.').last, 'useEchoCancellation': useEchoCancellation, }); _setupEventChannel(); _isInitialized = result; Logger.info('Azure 语音识别服务初始化${result ? '成功' : '失败'}'); return result; } catch (e) { Logger.error('Azure 语音识别服务初始化失败: $e'); _isInitialized = false; rethrow; } } @override Future recognizeOnce() async { if (!_isInitialized) { await initialize(); } try { // 调用同步的recognizeOnce方法 final result = await _channel.invokeMethod('recognizeOnce'); if (result is Map) { // 处理新格式返回 final String text = result['text'] as String? ?? ''; final String detectedLanguage = result['detectedLanguage'] as String? ?? ''; // 更新最新识别结果 _latestRecognizedText = text; _latestDetectedLanguage = detectedLanguage; return RecognitionEvent.finalResult( text: text, detectedLanguage: detectedLanguage, ); } else if (result is String) { // 兼容旧版本 _latestRecognizedText = result; return RecognitionEvent.finalResult(text: result); } throw Exception('无效的识别结果格式'); } catch (e) { Logger.error('语音识别失败: $e'); return RecognitionEvent.error(e.toString()); } } @override Future> startContinuousRecognition( bool audioSourceType) async { if (!_isInitialized) { await initialize(); } if (_isContinuousRecognitionActive) { await stopContinuousRecognition(); } try { _eventStreamController = StreamController.broadcast(); // 开始连续识别 final bool result = await _channel.invokeMethod('startContinuousRecognition', { 'audioSourceType': audioSourceType, }); if (!result) { _cleanupEventStream(); throw Exception('启动连续识别失败'); } _isContinuousRecognitionActive = true; return _eventStreamController!.stream; } catch (e) { Logger.error('开始连续语音识别失败: $e'); _cleanupEventStream(); rethrow; } } @override Future stopContinuousRecognition() async { if (!_isInitialized || !_isContinuousRecognitionActive) { return true; } try { final bool result = await _channel.invokeMethod('stopContinuousRecognition'); _cleanupEventStream(); return result; } catch (e) { Logger.error('停止连续语音识别失败: $e'); _cleanupEventStream(); rethrow; } } @override bool isContinuousRecognitionActive() { return _isContinuousRecognitionActive; } /// 处理来自原生端的识别事件 void _handleRecognitionEvent(dynamic event) { if (event is! Map || _eventStreamController == null) return; final Map eventMap = event; final String eventType = eventMap['type'] as String? ?? ''; // 添加日志帮助调试 Logger.debug('收到语音识别事件: $eventType, 数据: $eventMap'); switch (eventType) { case 'result': final String text = eventMap['text'] as String? ?? ''; final String detectedLanguage = eventMap['detectedLanguage'] as String? ?? ''; _latestRecognizedText = text; _latestDetectedLanguage = detectedLanguage; _eventStreamController?.add(RecognitionEvent( type: RecognitionEventType.finalResult, text: text, detectedLanguage: detectedLanguage, )); break; case 'recognizing': final String text = eventMap['text'] as String? ?? ''; final String detectedLanguage = eventMap['detectedLanguage'] as String? ?? ''; _eventStreamController?.add(RecognitionEvent( type: RecognitionEventType.intermediateResult, text: text, detectedLanguage: detectedLanguage, )); break; case 'sessionStarted': _eventStreamController?.add(RecognitionEvent( type: RecognitionEventType.sessionStarted, )); break; case 'sessionStopped': _isContinuousRecognitionActive = false; _eventStreamController?.add(RecognitionEvent( type: RecognitionEventType.sessionStopped, )); break; case 'canceled': _isContinuousRecognitionActive = false; final String reason = eventMap['reason'] as String? ?? ''; final String errorDetails = eventMap['errorDetails'] as String? ?? ''; if (reason.isNotEmpty || errorDetails.isNotEmpty) { Logger.error('识别取消: $reason - $errorDetails'); } _eventStreamController?.add(RecognitionEvent( type: RecognitionEventType.canceled, error: '$reason: $errorDetails', )); break; case 'error': final String error = eventMap['message'] as String? ?? ''; Logger.error('识别错误: $error'); _eventStreamController?.add(RecognitionEvent( type: RecognitionEventType.error, error: error, )); break; } } /// 处理识别事件流错误 void _handleRecognitionError(Object error) { Logger.error('识别事件流错误: $error'); _eventStreamController?.addError(error); _cleanupEventStream(); } /// 清理事件流资源 void _cleanupEventStream() { _eventStreamController?.close(); _eventStreamController = null; _isContinuousRecognitionActive = false; } @override Future dispose() async { try { if (_isContinuousRecognitionActive) { await stopContinuousRecognition(); } // 取消事件订阅 await _eventSubscription?.cancel(); _eventSubscription = null; // 通知原生端释放资源 await _channel.invokeMethod('dispose'); _isInitialized = false; Logger.info('Azure 语音识别资源已释放'); } catch (e) { Logger.error('释放语音识别资源失败: $e'); _cleanupEventStream(); _isInitialized = false; } } @override Future pushAudioData(Uint8List data) async { if (!_isInitialized) { await initialize(useExternalAudio: true); } if (_audioSourceType != AudioSourceType.external) { Logger.error('当前非外部音频模式,不能推送音频数据'); return false; } try { final bool result = await _channel.invokeMethod('pushAudioData', { 'audioData': data // Flutter 会自动将 Uint8List 转换为 ByteBuffer }); if (!result) { throw Exception('推送音频数据失败'); } return true; } catch (e) { Logger.error('推送音频数据失败: $e'); return false; } } }