import 'dart:async'; import 'dart:typed_data'; import 'package:demo001/xunfei/xunfei.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter_sound/flutter_sound.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:permission_handler/permission_handler.dart'; class SoundRecordScene extends StatefulWidget { @override _SoundRecordSceneState createState() => _SoundRecordSceneState(); } class _SoundRecordSceneState extends State { late ISDK _sdk; FlutterSoundRecorder? _soundRecorder; AudioPlayer? _audioPlayer; bool _isRecorderReady = false; bool _isRecording = false; bool _isSpeaking = false; //是否说话 int _stateSpeak = 0; // 说话状态 0 未说话 1开始说话 2 说话中 3结束说话 String? _audioFilePath; double _volumeLevel = 0.0; // 当前音量值 DateTime? _lastBelowThresholdTime; // 上次音量低于阈值的时间 ScrollController _scrollController = ScrollController(); List _logs = []; List _trans = []; late ITaskTrans _lasttran; // 音量阈值 final double _speakingThreshold = 50.0; // 开始说话的阈值 final double _silenceThreshold = 30.0; // 结束说话的阈值 final Duration _silenceDuration = Duration(seconds: 1); // 持续低于阈值的时间 // 采样率和声道数 Codec _audiocodec = Codec.pcm16; final int _sampleRate = 16000; // 16kHz 采样率 final int _numChannels = 1; // 单声道 StreamController _audioDataStreamController = StreamController.broadcast(); //暴露音频数据流 Stream get audioDataStream => _audioDataStreamController.stream; @override void initState() { super.initState(); _sdk = Xunfei( appId: "137dc132", apiKey: "1c1891a475e71250ecd1320303ad6545", apiSecret: "MjZhNDA1NTI1NWZkZDQxOTMxYzMyN2Yw"); _audioPlayer = AudioPlayer(); _requestPermissions(); _initRecorder(); } // 初始化录音器 void _initRecorder() async { try { _soundRecorder = FlutterSoundRecorder(); await _soundRecorder?.openRecorder(); await _soundRecorder?.setSubscriptionDuration(const Duration(milliseconds: 100)); //检查编解码器是否支持 if (!await _soundRecorder!.isEncoderSupported(Codec.pcm16)) { _log("PCM16 codec is not supported on this device."); _audiocodec = Codec.aacADTS; } setState(() { _isRecorderReady = true; }); _log('录音器初始化成功'); } catch (e) { _log('初始化录音器失败: $e'); setState(() { _isRecorderReady = false; }); } } // 请求麦克风权限 void _requestPermissions() async { try { if (await Permission.microphone.request().isGranted) { _log('麦克风权限已授予'); } else { _log('麦克风权限被拒绝'); setState(() { _isRecorderReady = false; }); } } catch (e) { _log('请求麦克风权限失败: $e'); setState(() { _isRecorderReady = false; }); } } // 开始录音 void _startRecording() async { try { if (!_isRecorderReady) { _log('录音器未准备好'); return; } if (_isRecording) return; // 防止重复调用 final directory = await getTemporaryDirectory(); final tempPath = '${directory.path}/recorded_audio.pcm'; _log('录音文件路径: $tempPath'); await _soundRecorder?.startRecorder( codec: _audiocodec, toStream: _audioDataStreamController.sink, // 将音频数据写入到 StreamController sampleRate: _sampleRate, // 设置采样率 numChannels: _numChannels, // 设置声道数 enableVoiceProcessing: true, // 启用音量监听 ); _soundRecorder?.onProgress!.listen((RecordingDisposition event) { // _log('onProgress 回调触发, 分贝: ${event.decibels}'); if (event.decibels != null) { setState(() { _volumeLevel = event.decibels!; //更新音量值 }); _checkSpeakingStatus(); // 检查说话状态 } }); // 监听音频数据流 _audioDataStreamController.stream.listen((Uint8List audioData) { if (_isSpeaking){ // _log('Received audio data: ${audioData.length} bytes'); _lasttran.addAudioData(List.from(audioData)); } // 这里可以进一步处理音频数据,例如保存到文件或上传到服务器 }); setState(() { _audioFilePath = tempPath; _isRecording = true; }); _log('录音开始'); } catch (e) { _log('录音开始 异常: $e'); } } // 停止录音 void _stopRecording() async { try { if (!_isRecording) return; // 防止重复调用 await _soundRecorder?.stopRecorder(); await _soundRecorder?.closeRecorder(); await _lasttran.close(); setState(() { _isRecording = false; _volumeLevel = 0.0; //重置音量值 }); _log('录音停止'); } catch (e) { _log('录音停止 异常: $e'); } } // 播放录音 void _playRecording() async { // try { // if (_audioFilePath != null) { // await _audioPlayer?.play(DeviceFileSource(_audioFilePath!)); // _log('播放录音'); // } // } catch (e) { // _log('播放录音 异常: $e'); // } } // 检查说话状态 _checkSpeakingStatus() { if (_volumeLevel > _speakingThreshold && !_isSpeaking) { // 音量高于阈值,表示开始说话 setState(() { _isSpeaking = true; }); _log('开始说话'); _stateSpeak = 1; _lasttran = _sdk.createTransTask(); } else if (_volumeLevel < _silenceThreshold) { // 音量低于阈值 if (_lastBelowThresholdTime == null) { // 记录第一次低于阈值的时间 _lastBelowThresholdTime = DateTime.now(); } else if (DateTime.now().difference(_lastBelowThresholdTime!) > _silenceDuration) { // 持续低于阈值超过指定时间,表示结束说话 if (_isSpeaking) { setState(() { _isSpeaking = false; }); _log('结束说话'); _stateSpeak = 3; _lasttran.close(); } } } else { // 音量恢复到阈值以上,重置计时器 _lastBelowThresholdTime = null; } _stateSpeak = 2; } // 添加日志信息并自动滚动 void _log(String message) { print("输出日志:${message}"); setState(() { _logs.add(message); // 从顶部插入新日志 }); _scrollToBottom(); } // 滚动到底部 void _scrollToBottom() { WidgetsBinding.instance.addPostFrameCallback((_) { if (_scrollController.hasClients) { _scrollController.animateTo( _scrollController.position.maxScrollExtent, // 滚动到底部 duration: Duration(milliseconds: 200), curve: Curves.easeInOut, ); } }); } @override void dispose() { _soundRecorder?.closeRecorder(); _audioPlayer?.dispose(); _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('录音与播放')), body: Column( children: [ // 滑动面板(日志区域) Expanded( child: Padding( padding: const EdgeInsets.all(8.0), child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.blue), borderRadius: BorderRadius.circular(10), ), child: ListView.builder( controller: _scrollController, itemCount: _logs.length, itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.symmetric( vertical: 0.0), // 设置日志项间的垂直间距 child: ListTile( title: Text(_logs[index]), ), ); }, ), ), ), ), // 底部按钮区域 Padding( padding: const EdgeInsets.all(20.0), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ // 音量图标和音量值 Row( children: [ Icon( Icons.volume_up, size: 30, ), SizedBox(width: 10), // 间距 Text( '${_volumeLevel.toStringAsFixed(2)} dB', //显示音量值,保留两位小数 style: TextStyle(fontSize: 16), ), ], ), SizedBox(width: 20), // 间距 // 按钮区域 GestureDetector( onTapDown: (details) { _startRecording(); // 按下时开始录音 }, onTapUp: (details) { _stopRecording(); // 抬起时停止录音并播放 _playRecording(); // 播放录音 }, child: Container( margin: EdgeInsets.all(20), padding: EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.blue, shape: BoxShape.circle, ), child: Icon( Icons.mic, color: Colors.white, size: 50, ), ), ), ])) ], ), ); } // 根据音量值动态调整图标颜色 Color _getVolumeColor() { if (_volumeLevel < -40) { return Colors.green; // 低音量 } else if (_volumeLevel < -20) { return Colors.yellow; // 中音量 } else { return Colors.red; // 高音量 } } }