|
- 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<SoundRecordScene> {
- 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<String> _logs = [];
- List<ITaskTrans> _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<Uint8List> _audioDataStreamController = StreamController<Uint8List>.broadcast();
- //暴露音频数据流
- Stream<Uint8List> 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; // 高音量
- }
- }
- }
|