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.toString()}'); _isInitialized = false; rethrow; } } @override Future recognizeOnce() async { if (!_isInitialized) { await initialize(); } try { throw Exception('无效的识别结果格式'); } catch (e) { Logger.error('语音识别失败: ${e.toString()}'); return RecognitionEvent.error(e.toString()); } } // @override // Future> startContinuousRecognition( // bool audioSourceType) 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.toString()}'); // 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.toString()}'); }, onDone: () { isConnected = false; Logger.error('WebSocket 连接已关闭!'); }, cancelOnError: true, ); } catch (e) { Logger.error("初始化异常----------------${e.toString()}"); } } //处理数据 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.toString()}"); } } ///获取链接地址 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.toString()}'); 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.toString()}'); _isInitialized = false; } } @override Future enableRecord(bool audioSourceType, String filePath, {bool acceptAudioData = false}) { // TODO: implement enableRecord throw UnimplementedError(); } @override Future stopRecord(bool isSave) { // TODO: implement stopRecord throw UnimplementedError(); } @override Future pauseRecord() { // TODO: implement pauseRecord throw UnimplementedError(); } @override Future moveFile(String sourcePath, String destPath) { // TODO: implement moveFile throw UnimplementedError(); } @override Future renameFile(String filePath, String newName) { // TODO: implement renameFile throw UnimplementedError(); } @override Future disableBluetoothAudio() { // TODO: implement disableBluetoothAudio throw UnimplementedError(); } @override Future restoreOriginalAudioState() { // TODO: implement restoreOriginalAudioState throw UnimplementedError(); } @override Future> recognizeCallback() { // TODO: implement recognizeCallback throw UnimplementedError(); } @override Future startContinuousRecognition( bool audioSourceType, { bool isRemoveFirstPunctuation = true, }) { // TODO: implement startContinuousRecognition throw UnimplementedError(); } @override Future setAudioConfig({int sampleRate = 16000, int channels = 1}) { // TODO: implement setAudioConfig throw UnimplementedError(); } @override Future resumeRecord() { // TODO: implement resumeRecord throw UnimplementedError(); } @override Stream? getAudioDataStream() { // TODO: implement getAudioDataStream throw UnimplementedError(); } }