|
- 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:just_audio/just_audio.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();
- @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) {
- _processAudioData(audioData);
- // 这里可以进一步处理音频数据,例如保存到文件或上传到服务器
- });
- setState(() {
- _audioFilePath = tempPath;
- _isRecording = true;
- });
- _log('录音开始');
- } catch (e) {
- _log('录音开始 异常: $e');
- }
- }
-
- // 处理音频数据的方法
- Uint8List _audioBuffer = Uint8List(0); // 缓存音频数据
- void _processAudioData(Uint8List newData) {
- // 将新数据追加到缓存中
- _audioBuffer = Uint8List.fromList([..._audioBuffer, ...newData]);
-
- // 每次处理一帧数据(1280 字节)
- int frameSize = 1280; // 每帧的大小
- while (_isSpeaking && _audioBuffer.length >= frameSize) {
- // 取出一帧数据
- Uint8List frame = _audioBuffer.sublist(0, frameSize);
- _audioBuffer = _audioBuffer.sublist(frameSize); // 移除已处理的数据
-
- // 将帧数据传递给任务
- _lasttran.addAudioData(frame);
- }
-
- // 如果录音结束且缓存中还有剩余数据,则作为最后一帧发送
- if (!_isRecording && _audioBuffer.isNotEmpty) {
- _lasttran.addAudioData(_audioBuffer);
- _audioBuffer = Uint8List(0); // 清空缓存
- }
- }
-
- // 停止录音
- void _stopRecording() async {
- try {
- if (!_isRecording) return; // 防止重复调用
- _lasttran.endpuish();
- await _soundRecorder?.stopRecorder();
- await _soundRecorder?.closeRecorder();
- 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(_taskchange);
- _trans.add(_lasttran);
- } 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.endpuish();
- }
- }
- } else {
- // 音量恢复到阈值以上,重置计时器
- _lastBelowThresholdTime = null;
- }
- _stateSpeak = 2;
- }
-
- //任务状态变化
- void _taskchange(ITaskTrans task) {
- if (task.state() == 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,
- );
- }
- });
- }
-
- // 播放音频流
- void playAudioStream(Stream<Uint8List> audioStream) async {
- try {
- await for (var chunk in audioStream) {
- // 每次接收到音频数据块后,播放它
- await _audioPlayer?.setUrl(Uri.dataFromBytes(chunk).toString());
- await _audioPlayer?.play();
- }
- } catch (e) {
- print("音频流播放错误: $e");
- }
- }
-
- @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: _trans.length,
- itemBuilder: (context, index) {
- // 语音消息
- return _buildAudioMessage(_trans[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,
- ),
- ),
- ),
- ]))
- ],
- ),
- );
- }
-
- // 构建语音消息
- Widget _buildAudioMessage(ITaskTrans msg) {
- return Padding(
- padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // 音频时长显示
- Text(
- '2秒',
- style: TextStyle(fontSize: 14, color: Colors.grey),
- ),
- SizedBox(height: 5),
- // 音频播放按钮
- GestureDetector(
- onTap: () {
- // 这里可以实现点击播放音频的功能
- // print("播放音频: ${message['audioUrl']}");
- },
- child: Container(
- padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20),
- decoration: BoxDecoration(
- color: Colors.green,
- borderRadius: BorderRadius.circular(30),
- ),
- child: Row(
- children: [
- Icon(
- Icons.play_arrow,
- color: Colors.white,
- ),
- SizedBox(width: 10),
- Text(
- '播放音频',
- style: TextStyle(color: Colors.white),
- ),
- ],
- ),
- ),
- ),
- SizedBox(height: 5),
- // 文字内容
- Text(
- msg.originalText(),
- style: TextStyle(fontSize: 16),
- ),
- ],
- ),
- );
- }
- }
|