import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; import 'package:crypto/crypto.dart'; import 'package:record/record.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import '../../../core/utils/logger.dart'; import 'package:get/get.dart'; import '../asr_service.dart'; /// 讯飞 语音识别服务 /// class XunfeiAsrService extends GetxService implements AsrService { final String baseUrl = 'wss://rtasr.xfyun.cn/v1/ws'; final int _chunkSize = 1280; // 每次发送的数据大小 final String appId = '43d87d22'; final String apiKey = '83f063755e8d342670002f50a4471d42'; AudioRecorder? recorder; Timer? _timer; WebSocketChannel? _channel; bool isConnected = false; //是否链接 bool _isInitialized = false; final List _defaultSupportedLanguages = ['zh-CN']; String _languageStr = 'cn'; // 连续识别相关 bool _isContinuousRecognitionActive = false; StreamController? _eventStreamController; @override List get supportedLanguages => _defaultSupportedLanguages; Uint8List _buff = Uint8List(0); //音频缓存区 BytesBuilder _originaBytes = BytesBuilder(); //原始音频 bool isRecording = false; @override Future initialize({ List? supportedLanguages, }) async { try { final List languages = supportedLanguages ?? _defaultSupportedLanguages; String languagetoStr = languages.first.split('-')[1].toLowerCase(); _languageStr = languagetoStr; _isInitialized = true; Logger.info('Azure 语音识别服务初始化成功'); return true; } catch (e) { Logger.error('Azure 语音识别服务初始化失败: $e'); _isInitialized = false; rethrow; } } @override Future recognizeOnce() async { if (!_isInitialized) { await initialize(); } try { throw Exception('无效的识别结果格式'); } catch (e) { Logger.error('语音识别失败: $e'); return RecognitionEvent.error(e.toString()); } } @override Future> startContinuousRecognition() async { if (!_isInitialized) { await initialize(); } try { _eventStreamController = StreamController.broadcast(); await recorder?.dispose(); recorder = AudioRecorder(); Stream? 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'); rethrow; } } //写入音频数据到缓存区中 Uint8List _appendToBuffer(Uint8List newData) { var newBuffer = Uint8List(_buff.length + newData.length); newBuffer.setAll(0, _buff); newBuffer.setAll(_buff.length, newData); return newBuffer; } //读取缓存区中一帧数据 Uint8List _getAudioData() { if (_buff.length >= _chunkSize) { // 从缓冲区中读取1280字节的数据 var data = _buff.sublist(0, _chunkSize); // 移除已读取的数据 _buff = _buff.sublist(_chunkSize); return data; } else { return _buff; } } //尝试连接 Future _tryConnecting() async { try { final url = _geturl(); _channel = WebSocketChannel.connect(Uri.parse(url)); _channel?.stream.timeout(const Duration(seconds: 10)); //设置超时时间 await _channel?.ready; isConnected = true; _channel?.stream.listen( (message) { _handle(message); }, onError: (error) { isConnected = false; Logger.error('连接失败: $error'); }, onDone: () { isConnected = false; Logger.error('WebSocket 连接已关闭!'); }, cancelOnError: true, ); } catch (e) { Logger.error("初始化异常----------------$e"); } } //处理数据 void _handle(data) { try { final result = jsonDecode(data); if (result['code'] == "0" && result['data'] != "") { final data = jsonDecode(result['data']); String msg = ""; String role = ''; List rtList = data['cn']['st']['rt']; for (var rt in rtList) { for (var ws in rt['ws']) { for (var ws in ws['cw']) { msg += ws['w']; if (ws['rl'] != "0") { role = ws['rl']; } } } } Logger.info( "识别到的内容 type:${data['cn']['st']['type']} msg:$msg rl:$role"); if (msg.isNotEmpty) { if (data['cn']['st']['type'] == "0") { _eventStreamController?.add(RecognitionEvent( type: RecognitionEventType.finalResult, text: msg, role: role, audio: _originaBytes.toBytes(), )); _originaBytes.clear(); } else { _eventStreamController?.add(RecognitionEvent( type: RecognitionEventType.intermediateResult, text: msg, role: role, )); } } } } catch (e) { Logger.error("接受消息异常 $e"); } } ///获取链接地址 String _geturl() { // 生成时间戳 String ts = (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(); // 计算 baseString String baseString = md5.convert(utf8.encode(appId + ts)).toString(); // 计算 signa var hmacSha1 = Hmac(sha1, utf8.encode(apiKey)); var signa = hmacSha1.convert(utf8.encode(baseString)); String signaBase64 = base64Encode(signa.bytes); String url = ""; // 构建 WebSocket URL url = '$baseUrl?appid=$appId&ts=$ts&lang=$_languageStr&roleType=2&signa=${Uri.encodeComponent(signaBase64)}'; return url; } @override Future stopContinuousRecognition() async { try { await recorder?.stop(); isRecording = false; _timer?.cancel(); _buff = Uint8List(0); _originaBytes = BytesBuilder(); if (isConnected) { _channel?.sink.add(utf8.encode('{"end": true}')); //结束 } _isContinuousRecognitionActive = false; return true; } catch (e) { Logger.error('停止连续语音识别失败: $e'); rethrow; } } @override bool isContinuousRecognitionActive() { return _isContinuousRecognitionActive; } @override Future dispose() async { try { await stopContinuousRecognition(); recorder?.dispose(); _channel?.sink.close(); _eventStreamController?.close(); _eventStreamController = null; _isInitialized = false; Logger.info('Azure 语音识别资源已释放'); } catch (e) { Logger.error('释放语音识别资源失败: $e'); _isInitialized = false; } } }