依赖:flutter_sound:^9.2.9
地址:https://pub.flutter-io.cn/packages/flutter_sound/install
音频:audio_session: ^0.1.6
地址:https://pub.flutter-io.cn/packages/audio_session
依赖:permission_handler: ^8.1.2
地址:https://pub.flutter-io.cn/packages/permission_handler/versions/8.1.2/install
安卓配置
需要配置 AndroidManifest.xml 录音权限
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
苹果配置
info.plist
<key>NSMicrophoneUsageDescription</key>
<string>需要您的同意,才能访问麦克风</string>
podfile
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'PERMISSION_MICROPHONE=1']
end
end
end
iOS工程中增加libc++.tbd库,Build Phases->Link Binary With...
代码块:
引入头文件
import 'package:audio_session/audio_session.dart'; import >'package:flutter_sound_platform_interface/flutter_sound_recorder_platform_interface.dart'; import 'package:flutter_sound/flutter_sound.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:path_provider/path_provider.dart';
声明对象
FlutterSoundRecorder recorderModule = FlutterSoundRecorder(); FlutterSoundPlayer playerModule = FlutterSoundPlayer();
定义初始化方法
void init() async { //开启录音 await recorderModule.openRecorder(); //设置订阅计时器 await recorderModule .setSubscriptionDuration(const Duration(milliseconds: 10)); //设置音频 final session = await AudioSession.instance; await session.configure(AudioSessionConfiguration( avAudioSessionCategory: AVAudioSessionCategory.playAndRecord, avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.allowBluetooth | AVAudioSessionCategoryOptions.defaultToSpeaker, avAudioSessionMode: AVAudioSessionMode.spokenAudio, avAudioSessionRouteSharingPolicy: AVAudioSessionRouteSharingPolicy.defaultPolicy, avAudioSessionSetActiveOptions: AVAudioSessionSetActiveOptions.none, androidAudioAttributes: const AndroidAudioAttributes( contentType: AndroidAudioContentType.speech, flags: AndroidAudioFlags.none, usage: AndroidAudioUsage.voiceCommunication, ), androidAudioFocusGainType: AndroidAudioFocusGainType.gain, androidWillPauseWhenDucked: true, )); await playerModule.closePlayer(); await playerModule.openPlayer(); await playerModule .setSubscriptionDuration(const Duration(milliseconds: 10)); }
动态权限
Future<bool> getPermissionStatus() async { Permission permission = Permission.microphone; //granted 通过,denied 被拒绝,permanentlyDenied 拒绝且不在提示 PermissionStatus status = await permission.status; if (status.isGranted) { return true; } else if (status.isDenied) { requestPermission(permission); } else if (status.isPermanentlyDenied) { openAppSettings(); } else if (status.isRestricted) { requestPermission(permission); } else {} return false; } ///申请权限 void requestPermission(Permission permission) async { PermissionStatus status = await permission.request(); if (status.isPermanentlyDenied) { openAppSettings(); } }
录音、播放
/// 开始录音 _startRecorder() async { try { var status = await getPermissionStatus(); Directory tempDir = await getTemporaryDirectory(); var time = DateTime.now().millisecondsSinceEpoch; String path = '${tempDir.path}}-$time${ext[Codec.aacADTS.index]}' ; print('===> 准备开始录音'); await recorderModule.startRecorder( toFile: path, codec: Codec.aacADTS, bitRate: 8000,, sampleRate: 8000,, audioSource: AudioSource.microphone); print('===> 开始录音'); /// 监听录音 recorderModule.onProgress!.listen((e) { if (e != null && e.duration != null) { DateTime date = new DateTime.fromMillisecondsSinceEpoch( e.duration.inMilliseconds, isUtc: true); if (date.second >= _maxLength) { print('===> 到达时常停止录音'); _stopRecorder(); } setState(() { print("时间:${date.second}"); print("当前振幅:${e.decibels}"); num = date.second; setState(() { if (e.decibels! > 0 && e.decibels! < 10) { voiceIco = "Asset/message/icon_diyinliang.png"; } else if (e.decibels! > 20 && e.decibels! < 30) { voiceIco = "Asset/message/icon_zhongyinliang.png"; } else if (e.decibels! > 30 && e.decibels! < 40) { voiceIco = "Asset/message/icon_gaoyinliang.png"; } else if (e.decibels! > 40 && e.decibels! < 50) { voiceIco = "Asset/message/icon_zhongyinliang.png"; } else if (e.decibels! > 50 && e.decibels! < 60) { voiceIco = "Asset/message/icon_diyinliang.png"; } else if (e.decibels! > 70 && e.decibels! < 100) { voiceIco = "Asset/message/icon_gaoyinliang.png"; } else { voiceIco = "Asset/message/icon_diyinliang.png"; } }); }); } }); this.setState(() { _state = RecordPlayState.recording; _path = path; print("path == $path"); }); } catch (err) { setState(() { print(err.toString()); _stopRecorder(); _state = RecordPlayState.record; }); } } /// 结束录音 _stopRecorder() async { try { await recorderModule.stopRecorder(); print('stopRecorder===> fliePath:$_path'); widget.stopRecord!(_path, num); } catch (err) { print('stopRecorder error: $err'); } setState(() { _state = RecordPlayState.play; }); } ///开始播放,这里做了一个播放状态的回调 void startPlayer(path, {Function(dynamic)? callBack}) async { try { if (path.contains('http')) { await playerModule.startPlayer( fromURI: path, codec: Codec.mp3, sampleRate: 44000, whenFinished: () { stopPlayer(); callBack!(0); }); } else { //判断文件是否存在 if (await _fileExists(path)) { if (playerModule.isPlaying) { playerModule.stopPlayer(); } await playerModule.startPlayer( fromURI: path, codec: Codec.aacADTS, sampleRate: 44000, whenFinished: () { stopPlayer(); callBack!(0); }); } else {} } //监听播放进度 playerModule.onProgress!.listen((e) {}); callBack!(1); } catch (err) { callBack!(0); } } /// 结束播放 void stopPlayer() async { try { await playerModule.stopPlayer(); } catch (err) {} } ///获取播放状态 Future getPlayState() async { return await playerModule.getPlayerState(); } /// 释放播放器 void releaseFlauto() async { try { await playerModule.closePlayer(); } catch (e) { print(e); } } /// 判断文件是否存在 Future _fileExists(String path) async { return await File(path).exists(); }
释放
@override void dispose() { // TODO: implement dispose super.dispose(); recorderModule.closeRecorder(); playerModule.closePlayer(); }
联调真机运行,安卓录音播放一气呵成没任何问题,但苹果手机上会发现无法录制,在进度监听里返回的date秒数一直是0,这就很苦恼经过资料查找最终找到问题所在并解决
修改机型对应filepath后缀音频格式
String path =GetPlatform.isAndroid ? '${tempDir.path}}-$time.aac' : '${tempDir.path}}-$time.wav';
修改采样、比特率
await recorderModule.startRecorder( toFile: path, codec: GetPlatform.isAndroid ? Codec.aacADTS : Codec.pcm16WAV, bitRate: 1411200, sampleRate: 44100, audioSource: AudioSource.microphone); print('===> 开始录音');
细心的你能发现bitRate由8000换为1411200,sampleRate由8000换为44100,这不是瞎换的,这是为了提升音频的高质量,以采样率为44.1KHz,以16bit采样,声道数为2为例,所以sampleRate:44100,计算出比特率44100162 = 1411200
出现这个问题的根本原因是iOS不兼容.aac,说一句这个插件不支持mp3。