|
|
@ -8,35 +8,36 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; |
|
|
import '../asr_service.dart'; |
|
|
import '../asr_service.dart'; |
|
|
|
|
|
|
|
|
/// 火山语音识别服务实现 |
|
|
/// 火山语音识别服务实现 |
|
|
class VolcanoAsrService extends GetxService implements AsrService { |
|
|
class VolcanoAsrService extends GetxService implements AsrService { |
|
|
/// 方法通道 |
|
|
/// 方法通道 |
|
|
static const MethodChannel _channel = MethodChannel('volcano_speech/asr'); |
|
|
static const MethodChannel _channel = MethodChannel('volcano_speech/asr'); |
|
|
|
|
|
|
|
|
/// 事件通道 |
|
|
/// 事件通道 |
|
|
static const EventChannel _eventChannel = EventChannel('volcano_speech/asr_events'); |
|
|
static const EventChannel _eventChannel = |
|
|
|
|
|
EventChannel('volcano_speech/asr_events'); |
|
|
|
|
|
|
|
|
/// 事件流控制器 |
|
|
/// 事件流控制器 |
|
|
final StreamController<RecognitionEvent> _eventStreamController = |
|
|
final StreamController<RecognitionEvent> _eventStreamController = |
|
|
StreamController<RecognitionEvent>.broadcast(); |
|
|
StreamController<RecognitionEvent>.broadcast(); |
|
|
|
|
|
|
|
|
/// 事件订阅 |
|
|
/// 事件订阅 |
|
|
StreamSubscription? _eventSubscription; |
|
|
StreamSubscription? _eventSubscription; |
|
|
|
|
|
|
|
|
/// 是否初始化完成 |
|
|
/// 是否初始化完成 |
|
|
bool _isInitialized = false; |
|
|
bool _isInitialized = false; |
|
|
|
|
|
|
|
|
/// 是否正在连续识别 |
|
|
/// 是否正在连续识别 |
|
|
bool _isContinuousRecognitionActive = false; |
|
|
bool _isContinuousRecognitionActive = false; |
|
|
|
|
|
|
|
|
/// 当前语言 |
|
|
/// 当前语言 |
|
|
String _currentLanguage = 'zh-CN'; |
|
|
String _currentLanguage = 'zh-CN'; |
|
|
|
|
|
|
|
|
/// 支持的语言列表 |
|
|
/// 支持的语言列表 |
|
|
final List<String> _supportedLanguages = ['zh-CN', 'en-US']; |
|
|
final List<String> _supportedLanguages = ['zh-CN', 'en-US']; |
|
|
|
|
|
|
|
|
@override |
|
|
@override |
|
|
List<String> get supportedLanguages => _supportedLanguages; |
|
|
List<String> get supportedLanguages => _supportedLanguages; |
|
|
|
|
|
|
|
|
/// 构造函数 |
|
|
/// 构造函数 |
|
|
VolcanoAsrService() { |
|
|
VolcanoAsrService() { |
|
|
_setupEventListener(); |
|
|
_setupEventListener(); |
|
|
@ -48,39 +49,42 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
List<String>? supportedLanguages, |
|
|
List<String>? supportedLanguages, |
|
|
}) async { |
|
|
}) async { |
|
|
if (_isInitialized) return true; |
|
|
if (_isInitialized) return true; |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
// 如果提供了支持的语言列表,则替换默认列表 |
|
|
// 如果提供了支持的语言列表,则替换默认列表 |
|
|
if (supportedLanguages != null && supportedLanguages.isNotEmpty) { |
|
|
if (supportedLanguages != null && supportedLanguages.isNotEmpty) { |
|
|
_supportedLanguages.clear(); |
|
|
_supportedLanguages.clear(); |
|
|
_supportedLanguages.addAll(supportedLanguages); |
|
|
_supportedLanguages.addAll(supportedLanguages); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 从.env文件读取配置 |
|
|
// 从.env文件读取配置 |
|
|
final appId = dotenv.env['VOLCANO_SPEECH_APP_ID'] ?? ''; |
|
|
final appId = dotenv.env['VOLCANO_SPEECH_APP_ID'] ?? ''; |
|
|
final appToken = dotenv.env['VOLCANO_SPEECH_APP_TOKEN'] ?? ''; |
|
|
final appToken = dotenv.env['VOLCANO_SPEECH_APP_TOKEN'] ?? ''; |
|
|
final resourceId = dotenv.env['VOLCANO_ASR_RESOURCE_ID'] ?? 'volc.bigasr.sauc.duration'; |
|
|
final resourceId = |
|
|
|
|
|
dotenv.env['VOLCANO_ASR_RESOURCE_ID'] ?? 'volc.bigasr.sauc.duration'; |
|
|
|
|
|
|
|
|
if (appId.isEmpty || appToken.isEmpty) { |
|
|
if (appId.isEmpty || appToken.isEmpty) { |
|
|
debugPrint('火山语音初始化失败:缺少VOLCANO_SPEECH_APP_ID或VOLCANO_SPEECH_APP_TOKEN环境变量'); |
|
|
debugPrint( |
|
|
|
|
|
'火山语音初始化失败:缺少VOLCANO_SPEECH_APP_ID或VOLCANO_SPEECH_APP_TOKEN环境变量'); |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 初始化火山语音ASR |
|
|
// 初始化火山语音ASR |
|
|
final initResult = await _channel.invokeMethod<bool>('initialize', { |
|
|
final initResult = await _channel.invokeMethod<bool>('initialize', { |
|
|
'appId': appId, |
|
|
'appId': appId, |
|
|
'appToken': appToken, |
|
|
'appToken': appToken, |
|
|
'resourceId': resourceId, |
|
|
'resourceId': resourceId, |
|
|
}) ?? false; |
|
|
}) ?? |
|
|
|
|
|
false; |
|
|
|
|
|
|
|
|
if (!initResult) { |
|
|
if (!initResult) { |
|
|
debugPrint('火山语音ASR初始化失败'); |
|
|
debugPrint('火山语音ASR初始化失败'); |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 设置默认语言、VAD参数和启用音量返回 |
|
|
// 设置默认语言、VAD参数和启用音量返回 |
|
|
await _configureAsr(); |
|
|
await _configureAsr(); |
|
|
|
|
|
|
|
|
_isInitialized = true; |
|
|
_isInitialized = true; |
|
|
return true; |
|
|
return true; |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
@ -88,14 +92,13 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// 配置ASR参数 |
|
|
/// 配置ASR参数 |
|
|
Future<void> _configureAsr() async { |
|
|
Future<void> _configureAsr() async { |
|
|
// 设置默认语言 |
|
|
// 设置默认语言 |
|
|
await _channel.invokeMethod<bool>('setLanguage', { |
|
|
await _channel.invokeMethod<bool>('setLanguage', { |
|
|
'language': _currentLanguage, |
|
|
'language': _currentLanguage, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// 设置事件监听 |
|
|
/// 设置事件监听 |
|
|
@ -118,25 +121,25 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
/// 处理ASR事件 |
|
|
/// 处理ASR事件 |
|
|
void _handleAsrEvent(Map<String, dynamic> event) { |
|
|
void _handleAsrEvent(Map<String, dynamic> event) { |
|
|
final eventType = event['type'] as String?; |
|
|
final eventType = event['type'] as String?; |
|
|
|
|
|
|
|
|
switch (eventType) { |
|
|
switch (eventType) { |
|
|
case 'sessionStarted': |
|
|
case 'sessionStarted': |
|
|
_eventStreamController.add(RecognitionEvent( |
|
|
_eventStreamController.add(RecognitionEvent( |
|
|
type: RecognitionEventType.sessionStarted, |
|
|
type: RecognitionEventType.sessionStarted, |
|
|
)); |
|
|
)); |
|
|
break; |
|
|
break; |
|
|
|
|
|
|
|
|
case 'sessionStopped': |
|
|
case 'sessionStopped': |
|
|
_eventStreamController.add(RecognitionEvent( |
|
|
_eventStreamController.add(RecognitionEvent( |
|
|
type: RecognitionEventType.sessionStopped, |
|
|
type: RecognitionEventType.sessionStopped, |
|
|
)); |
|
|
)); |
|
|
_isContinuousRecognitionActive = false; |
|
|
_isContinuousRecognitionActive = false; |
|
|
break; |
|
|
break; |
|
|
|
|
|
|
|
|
case 'recognizing': |
|
|
case 'recognizing': |
|
|
final text = event['text'] as String?; |
|
|
final text = event['text'] as String?; |
|
|
final language = event['language'] as String?; |
|
|
final language = event['language'] as String?; |
|
|
|
|
|
|
|
|
if (text != null) { |
|
|
if (text != null) { |
|
|
debugPrint('接收到中间识别结果: $text'); |
|
|
debugPrint('接收到中间识别结果: $text'); |
|
|
_eventStreamController.add(RecognitionEvent( |
|
|
_eventStreamController.add(RecognitionEvent( |
|
|
@ -146,11 +149,11 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
)); |
|
|
)); |
|
|
} |
|
|
} |
|
|
break; |
|
|
break; |
|
|
|
|
|
|
|
|
case 'result': |
|
|
case 'result': |
|
|
final text = event['text'] as String?; |
|
|
final text = event['text'] as String?; |
|
|
final language = event['language'] as String?; |
|
|
final language = event['language'] as String?; |
|
|
|
|
|
|
|
|
if (text != null) { |
|
|
if (text != null) { |
|
|
debugPrint('接收到最终识别结果: $text'); |
|
|
debugPrint('接收到最终识别结果: $text'); |
|
|
_eventStreamController.add(RecognitionEvent( |
|
|
_eventStreamController.add(RecognitionEvent( |
|
|
@ -160,10 +163,10 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
)); |
|
|
)); |
|
|
} |
|
|
} |
|
|
break; |
|
|
break; |
|
|
|
|
|
|
|
|
case 'error': |
|
|
case 'error': |
|
|
final errorMessage = event['message'] as String?; |
|
|
final errorMessage = event['message'] as String?; |
|
|
|
|
|
|
|
|
debugPrint('接收到识别错误: ${errorMessage ?? "未知错误"}'); |
|
|
debugPrint('接收到识别错误: ${errorMessage ?? "未知错误"}'); |
|
|
_eventStreamController.add(RecognitionEvent( |
|
|
_eventStreamController.add(RecognitionEvent( |
|
|
type: RecognitionEventType.error, |
|
|
type: RecognitionEventType.error, |
|
|
@ -171,11 +174,12 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
)); |
|
|
)); |
|
|
_isContinuousRecognitionActive = false; |
|
|
_isContinuousRecognitionActive = false; |
|
|
break; |
|
|
break; |
|
|
|
|
|
|
|
|
case 'volumeChanged': |
|
|
case 'volumeChanged': |
|
|
// 音量变化事件仅在日志中输出,不发送到事件流 |
|
|
// 音量变化事件仅在日志中输出,不发送到事件流 |
|
|
final volume = event['volume'] as int?; |
|
|
final volume = event['volume'] as int?; |
|
|
if (volume != null && volume > 5) { // 仅记录有意义的音量变化 |
|
|
if (volume != null && volume > 5) { |
|
|
|
|
|
// 仅记录有意义的音量变化 |
|
|
debugPrint('音量变化: $volume'); |
|
|
debugPrint('音量变化: $volume'); |
|
|
} |
|
|
} |
|
|
break; |
|
|
break; |
|
|
@ -199,11 +203,11 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
error: '语音识别服务未初始化', |
|
|
error: '语音识别服务未初始化', |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
// 创建一个Completer用于等待最终结果 |
|
|
// 创建一个Completer用于等待最终结果 |
|
|
final completer = Completer<RecognitionEvent>(); |
|
|
final completer = Completer<RecognitionEvent>(); |
|
|
|
|
|
|
|
|
// 创建一次性事件监听 |
|
|
// 创建一次性事件监听 |
|
|
StreamSubscription? subscription; |
|
|
StreamSubscription? subscription; |
|
|
subscription = _eventStreamController.stream.listen((event) { |
|
|
subscription = _eventStreamController.stream.listen((event) { |
|
|
@ -218,10 +222,11 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
subscription?.cancel(); |
|
|
subscription?.cancel(); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// 启动一次性识别 |
|
|
// 启动一次性识别 |
|
|
final started = await _channel.invokeMethod<bool>('recognizeOnce') ?? false; |
|
|
final started = |
|
|
|
|
|
await _channel.invokeMethod<bool>('recognizeOnce') ?? false; |
|
|
|
|
|
|
|
|
if (!started) { |
|
|
if (!started) { |
|
|
subscription.cancel(); |
|
|
subscription.cancel(); |
|
|
return RecognitionEvent( |
|
|
return RecognitionEvent( |
|
|
@ -229,7 +234,7 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
error: '启动一次性识别失败', |
|
|
error: '启动一次性识别失败', |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 设置超时,防止永久等待 |
|
|
// 设置超时,防止永久等待 |
|
|
Future.delayed(const Duration(seconds: 30), () { |
|
|
Future.delayed(const Duration(seconds: 30), () { |
|
|
if (!completer.isCompleted) { |
|
|
if (!completer.isCompleted) { |
|
|
@ -242,7 +247,7 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
_channel.invokeMethod('stopRecognize'); |
|
|
_channel.invokeMethod('stopRecognize'); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// 等待结果 |
|
|
// 等待结果 |
|
|
return await completer.future; |
|
|
return await completer.future; |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
@ -250,7 +255,7 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
try { |
|
|
try { |
|
|
await _channel.invokeMethod('stopRecognize'); |
|
|
await _channel.invokeMethod('stopRecognize'); |
|
|
} catch (_) {} |
|
|
} catch (_) {} |
|
|
|
|
|
|
|
|
return RecognitionEvent( |
|
|
return RecognitionEvent( |
|
|
type: RecognitionEventType.error, |
|
|
type: RecognitionEventType.error, |
|
|
error: e.toString(), |
|
|
error: e.toString(), |
|
|
@ -268,10 +273,12 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
)); |
|
|
)); |
|
|
return _eventStreamController.stream; |
|
|
return _eventStreamController.stream; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
final success = await _channel.invokeMethod<bool>('startContinuousRecognition') ?? false; |
|
|
final success = |
|
|
|
|
|
await _channel.invokeMethod<bool>('startContinuousRecognition') ?? |
|
|
|
|
|
false; |
|
|
|
|
|
|
|
|
if (success) { |
|
|
if (success) { |
|
|
_isContinuousRecognitionActive = true; |
|
|
_isContinuousRecognitionActive = true; |
|
|
} else { |
|
|
} else { |
|
|
@ -286,7 +293,7 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
error: '启动连续识别异常:$e', |
|
|
error: '启动连续识别异常:$e', |
|
|
)); |
|
|
)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return _eventStreamController.stream; |
|
|
return _eventStreamController.stream; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -296,10 +303,12 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
if (!_isInitialized || !_isContinuousRecognitionActive) { |
|
|
if (!_isInitialized || !_isContinuousRecognitionActive) { |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
final success = await _channel.invokeMethod<bool>('stopContinuousRecognition') ?? false; |
|
|
final success = |
|
|
|
|
|
await _channel.invokeMethod<bool>('stopContinuousRecognition') ?? |
|
|
|
|
|
false; |
|
|
|
|
|
|
|
|
if (success) { |
|
|
if (success) { |
|
|
_isContinuousRecognitionActive = false; |
|
|
_isContinuousRecognitionActive = false; |
|
|
} |
|
|
} |
|
|
@ -310,18 +319,6 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// 推送音频数据到连续识别中 |
|
|
|
|
|
@override |
|
|
|
|
|
Future<void> pushAudioData(Uint8List data) async { |
|
|
|
|
|
if (!_isInitialized || !_isContinuousRecognitionActive) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 注意:此功能需要在引擎初始化时设置音频输入类型为stream |
|
|
|
|
|
// 由于当前实现默认使用麦克风输入,此方法暂时不会实际处理数据 |
|
|
|
|
|
debugPrint('警告:当前火山语音ASR使用麦克风输入,pushAudioData方法不会起作用'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 检查连续识别是否活跃 |
|
|
/// 检查连续识别是否活跃 |
|
|
@override |
|
|
@override |
|
|
bool isContinuousRecognitionActive() { |
|
|
bool isContinuousRecognitionActive() { |
|
|
@ -334,17 +331,17 @@ class VolcanoAsrService extends GetxService implements AsrService { |
|
|
if (_isContinuousRecognitionActive) { |
|
|
if (_isContinuousRecognitionActive) { |
|
|
await stopContinuousRecognition(); |
|
|
await stopContinuousRecognition(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
await _eventSubscription?.cancel(); |
|
|
await _eventSubscription?.cancel(); |
|
|
_eventSubscription = null; |
|
|
_eventSubscription = null; |
|
|
|
|
|
|
|
|
if (!_eventStreamController.isClosed) { |
|
|
if (!_eventStreamController.isClosed) { |
|
|
await _eventStreamController.close(); |
|
|
await _eventStreamController.close(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (_isInitialized) { |
|
|
if (_isInitialized) { |
|
|
await _channel.invokeMethod<bool>('release'); |
|
|
await _channel.invokeMethod<bool>('release'); |
|
|
_isInitialized = false; |
|
|
_isInitialized = false; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|