20 changed files with 2336 additions and 211 deletions
@ -0,0 +1,107 @@ |
|||
import 'dart:async'; |
|||
import 'dart:typed_data'; |
|||
|
|||
/// 语音识别服务接口 |
|||
abstract class AstService { |
|||
/// 支持的语言 |
|||
List<String> get supportedLanguages; |
|||
|
|||
/// 初始化语音识别服务 |
|||
Future<bool> initialize({required List<String> supportedLanguages}); |
|||
|
|||
/// 开始录音 |
|||
Future<bool> enableRecord(String filePath); |
|||
|
|||
/// 停止录音 |
|||
Future<bool> stopContinuousTranslation(bool isSave); |
|||
|
|||
/// 开始录音 |
|||
Future<bool> path(String filePath); |
|||
} |
|||
|
|||
/// 识别事件类型 |
|||
// enum RecognitionEventType { |
|||
// /// 最终识别结果 |
|||
// finalResult, |
|||
|
|||
// /// 中间识别结果(实时反馈) |
|||
// intermediateResult, |
|||
|
|||
// /// 音频 |
|||
// onAudio, |
|||
|
|||
// /// 会话开始 |
|||
// sessionStarted, |
|||
|
|||
// /// 会话结束 |
|||
// sessionStopped, |
|||
|
|||
// /// 识别取消 |
|||
// canceled, |
|||
|
|||
// /// 识别错误 |
|||
// error, |
|||
// } |
|||
|
|||
/// 识别事件 |
|||
// class RecognitionEvent { |
|||
// /// 事件类型 |
|||
// final RecognitionEventType type; |
|||
|
|||
// /// 识别文本(仅在 finalResult 和 intermediateResult 类型中有效) |
|||
// final String text; |
|||
|
|||
// /// 检测到的语言 |
|||
// final String detectedLanguage; |
|||
|
|||
// /// 角色 |
|||
// final String role; |
|||
|
|||
// /// 原始音频 |
|||
// final Uint8List? audio; |
|||
|
|||
// /// 错误信息(仅在 error 和 canceled 类型中有效) |
|||
// final String error; |
|||
|
|||
// RecognitionEvent({ |
|||
// required this.type, |
|||
// this.text = '', |
|||
// this.detectedLanguage = '', |
|||
// this.role = '', |
|||
// this.audio, |
|||
// this.error = '', |
|||
// }); |
|||
|
|||
// /// 创建最终结果事件的快捷构造函数 |
|||
// factory RecognitionEvent.finalResult({ |
|||
// required String text, |
|||
// String detectedLanguage = '', |
|||
// }) { |
|||
// return RecognitionEvent( |
|||
// type: RecognitionEventType.finalResult, |
|||
// text: text, |
|||
// detectedLanguage: detectedLanguage, |
|||
// ); |
|||
// } |
|||
|
|||
// /// 创建错误事件的快捷构造函数 |
|||
// factory RecognitionEvent.error(String errorMessage) { |
|||
// return RecognitionEvent( |
|||
// type: RecognitionEventType.error, |
|||
// error: errorMessage, |
|||
// ); |
|||
// } |
|||
|
|||
// /// 检查是否为最终结果 |
|||
// bool get isFinalResult => type == RecognitionEventType.finalResult; |
|||
|
|||
// /// 检查是否为错误 |
|||
// bool get isError => |
|||
// type == RecognitionEventType.error || |
|||
// type == RecognitionEventType.canceled; |
|||
|
|||
// @override |
|||
// String toString() { |
|||
// return 'RecognitionEvent{type: $type, text: $text, detectedLanguage: $detectedLanguage, error: $error}'; |
|||
// } |
|||
// } |
|||
@ -0,0 +1,164 @@ |
|||
import 'dart:async'; |
|||
import '../../../data/models/appconfig.dart'; |
|||
import 'package:flutter/services.dart'; |
|||
import '../../../core/utils/logger.dart'; |
|||
import 'package:get/get.dart'; |
|||
import '../ast_service.dart'; |
|||
|
|||
/// 音频源类型 |
|||
enum AudioSourceType { |
|||
microphone, // 使用设备麦克风 |
|||
external // 使用外部提供的音频数据 |
|||
} |
|||
|
|||
/// Azure 语音识别服务 |
|||
/// |
|||
/// 该服务提供了通过平台通道与原生 Microsoft Speech SDK 交互的接口 |
|||
class AzureAstService extends GetxService implements AstService { |
|||
static final AzureAstService to = Get.put(AzureAstService()); |
|||
static const MethodChannel _channel = MethodChannel('azure_speech/ast'); |
|||
static const EventChannel _eventChannel = |
|||
EventChannel('azure_speech/ast_events'); |
|||
// final GetStorage _storage = GetStorage(); |
|||
bool _isInitialized = false; |
|||
late final String _subscriptionKey; |
|||
late final String _serviceRegion; |
|||
late String _baseUrl; |
|||
final String _endpoint = '/'; // 修改为根路径 |
|||
late final String _accessKey; |
|||
late final String _secretKey; |
|||
late final String _region; |
|||
late final String _service; |
|||
|
|||
final List<String> _defaultSupportedLanguages = ['zh-CN', 'en-US']; |
|||
@override |
|||
List<String> get supportedLanguages => _defaultSupportedLanguages; |
|||
|
|||
// 连续识别相关 |
|||
// bool _isContinuousRecognitionActive = false; |
|||
// StreamController<RecognitionEvent>? _eventStreamController; |
|||
// StreamSubscription? _eventSubscription; |
|||
|
|||
// 最新的识别结果 |
|||
String _latestRecognizedText = ''; |
|||
String get latestRecognizedText => _latestRecognizedText; |
|||
|
|||
// 最新检测到的语言 |
|||
String _latestDetectedLanguage = ''; |
|||
String get latestDetectedLanguage => _latestDetectedLanguage; |
|||
|
|||
// 当前音频源类型 |
|||
AudioSourceType _audioSourceType = AudioSourceType.microphone; |
|||
|
|||
AzureAstService() { |
|||
_loadConfig(); |
|||
} |
|||
|
|||
/// 从环境变量加载配置 |
|||
void _loadConfig() { |
|||
// final _env = _storage.read("ENV") as Map<String, String>; |
|||
_subscriptionKey = AppConfig.env('AZURE_SPEECH_KEY') ?? ''; |
|||
_serviceRegion = AppConfig.env('AZURE_SPEECH_REGION') ?? ''; |
|||
_accessKey = AppConfig.env('VOLCANO_TRANSLATION_ACCESS_KEY') ?? ''; |
|||
_secretKey = AppConfig.env('VOLCANO_TRANSLATION_SECRET_KEY') ?? ''; |
|||
_region = AppConfig.env('VOLCANO_TRANSLATION_REGION') ?? 'cn-north-1'; |
|||
_service = 'translate'; |
|||
_baseUrl = 'https://translate.volcengineapi.com'; |
|||
if (_subscriptionKey.isEmpty || _serviceRegion.isEmpty) { |
|||
throw Exception( |
|||
'未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION'); |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<bool> enableRecord(String filePath) async { |
|||
try { |
|||
final bool result = await _channel.invokeMethod('enableRecord', { |
|||
'filePath': filePath, |
|||
}); |
|||
|
|||
return result; |
|||
} catch (e) { |
|||
Logger.error('开始录音: ${e.toString()}'); |
|||
rethrow; |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<bool> path(String filePath) async { |
|||
try { |
|||
final bool result = await _channel.invokeMethod('path', { |
|||
'filePath': filePath, |
|||
}); |
|||
|
|||
return result; |
|||
} catch (e) { |
|||
Logger.error('开始录音: ${e.toString()}'); |
|||
rethrow; |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<bool> stopContinuousTranslation(bool isSave) async { |
|||
try { |
|||
final bool result = |
|||
await _channel.invokeMethod('stopContinuousTranslation', { |
|||
'isSave': isSave, |
|||
}); |
|||
|
|||
return result; |
|||
} catch (e) { |
|||
Logger.error('停止录音: ${e.toString()}'); |
|||
rethrow; |
|||
} |
|||
} |
|||
|
|||
@override |
|||
Future<bool> initialize({ |
|||
List<String>? supportedLanguages, |
|||
bool useExternalAudio = false, |
|||
bool useEchoCancellation = false, |
|||
}) async { |
|||
try { |
|||
final List<String> languages = |
|||
supportedLanguages ?? _defaultSupportedLanguages; |
|||
//底层会初始化前释放 |
|||
// // 检查是否需要重新初始化 |
|||
// if (_isInitialized) { |
|||
// await dispose(); |
|||
// } |
|||
|
|||
// 设置音频源类型 |
|||
_audioSourceType = useExternalAudio |
|||
? AudioSourceType.external |
|||
: AudioSourceType.microphone; |
|||
// Future<bool> initialize({ |
|||
// required String subscriptionKey, |
|||
// required String region, |
|||
// required List<String> supportedLanguages, |
|||
// required String audioSourceType, |
|||
// required String translationAccessKey, |
|||
// required String translationSecretKey, |
|||
// String translationRegion = 'cn-north-1', |
|||
// }); |
|||
|
|||
final bool result = await _channel.invokeMethod('initialize', { |
|||
'subscriptionKey': _subscriptionKey, |
|||
'region': _serviceRegion, |
|||
'supportedLanguages': languages, |
|||
'audioSourceType': _audioSourceType.toString().split('.').last, |
|||
'translationAccessKey': _accessKey, |
|||
'translationSecretKey': _secretKey, |
|||
'translationRegion': _region, |
|||
}); |
|||
|
|||
_isInitialized = result; |
|||
Logger.info('Azure 语音识别服务初始化${result ? '成功' : '失败'}'); |
|||
return result; |
|||
} catch (e) { |
|||
Logger.error('Azure 语音识别服务初始化失败: ${e.toString()}'); |
|||
_isInitialized = false; |
|||
rethrow; |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue