简介:面向Flutter开发者的录音功能实现资源包,基于flutter-sound与flutter-sound-record封装,适用于iOS、Android及Web端快速集成录音能力,可支撑语音备忘录、课堂录音、社交通讯等常见场景,对初学者与中级开发者均友好。压缩包共2000个文件,大小约769MB,文件类型涵盖Dart核心逻辑、XML与Properties系统配置、JSON数据、Markdown开发说明、Java/Objective-C原生桥接代码,另含Web插件注册与音频播放辅助模块,工程结构清晰完整,便于按模块查阅。已有2148人学习下载。资源附带可运行的完整工程,覆盖录音器初始化、音频编码格式与比特率/采样率设置、开始与停止录音、保存路径以及Android/iOS麦克风权限配置等核心环节,支持WAV、MP3、AAC等常见格式,并演示录音状态管理、文件保存与音频播放衔接,可直接参考集成或二次修改。配合audio player与插件注册文件,可进一步理解跨端音频处理与依赖注入方式,帮助梳理Flutter原生通信与插件工程组织逻辑,适合有一定Flutter基础、希望快速落地录音功能的开发者。 做Flutter项目遇到录音功能,第一反应肯定是在pub.dev上翻插件。我当初就是这么入坑的,翻了半天发现录音相关的库其实就那几个,record、flutter_sound、audio_streamer,还有个老牌的recorder。实话说,能把录音这件事做得完整、文档又不太离谱的,flutter_sound算一个。而标题里说的flutter_sound-record,其实就是flutter_sound库里负责录音这一块的模块。
这篇文章我不打算给你抄一段官方文档就算完事,而是把我从集成到上线整个过程中遇到的问题、参数怎么调、坑踩在哪里,全部捋一遍。不管你是刚开始接触Flutter录音,还是已经集成到一半卡住了,这篇内容应该都能帮上忙。
1. 项目概述与方案选型
1.1 需求场景与核心痛点
先说说我手里的项目场景。当时做的是一个语音笔记类App,用户需要短按录音、松手停止,然后上传到服务端转文字。需求听起来很简单,但真正落地的时候涉及的问题一点都不少:权限申请、音频会话管理、录音格式选择、文件保存路径、录音时长回调、后台时录音会不会被系统打断、Android和iOS两端的权限逻辑差异,这些如果不提前想清楚,后面改起来非常痛苦。
我在技术选型时对比了以下几个方案:
- 直接用原生平台通道(MethodChannel)写原生录音,再通过Flutter调用。
- 使用社区录音库
record。 - 使用
flutter_sound(包含flutter_sound_record与flutter_sound_play)。 - 使用
audio_streamer自己处理音频流。
直接写原生通道的好处是完全可控,但坏处是两边代码量都不小,而且音频焦点处理、生命周期管理这些逻辑要自己维护,维护成本不低。record库使用起来确实简单,但功能上偏轻量,想要精细控制编码器、采样率、音量回调、后台录音,会有点力不从心。audio_streamer更多面向实时音频流处理场景,和普通录音需求不太匹配。
最终我选了flutter_sound,主要原因是它内部封装了录音和播放两套完整能力,对Android的AudioRecord和MediaRecorder、iOS的AVAudioRecorder/AVAudioPlayer都有比较好的抽象,而且支持丰富的编码格式和流式回调。最关键的是,它支持在录音过程中拿到分贝数据,做录音波形动画很顺手,这对语音笔记类场景是刚需。
1.2 版本选择与兼容性注意
flutter_sound目前版本已经迭代了很多轮,比如9.x。选定这个库之后,面临的第一个头疼问题就是版本兼容。Flutter本身的版本如果不和插件版本对齐,很容易出现依赖解析失败。
我当时的Flutter版本是3.x,使用flutter_sound的9.x版本没问题。这里特别提醒一句:如果你用的是很老的Flutter版本(比如2.x),就不要强行上9.x,否则编译的时候会报一堆莫名其妙的错——不是你代码写错了,而是API签名变了。优先去pub.dev上看插件官方声明的Flutter SDK最低版本要求,老老实实对上再说。
另外,flutter_sound在9.x版本之后,录音和播放功能的API结构做过调整,FlutterSoundRecorder这个类被拆到flutter_sound_record模块下,你需要分开导入。刚开始集成的时候,很多人还按老博客的写法import 'package:flutter_sound/flutter_sound.dart';,结果发现类找不到。在新版本里,需要像下面这样引入:
import 'package:flutter_sound_record/flutter_sound_record.dart';这个看似很小的细节,卡了我不少时间。
2. 环境准备与依赖配置
2.1 依赖安装与版本锁定
在pubspec.yaml里添加依赖时,我建议你锁定一个具体的版本号,而不是直接写^9.2.0这样带脱字符的宽松版本。原因很简单,flutter_sound每次小版本升级都有可能调整原生依赖,稍不注意就拉出一个新版本,导致本机编译需要重新拉取资源,还可能出现依赖冲突。我当时锁定的版本是:
dependencies: flutter_sound: ^9.2.13添加完依赖后,在项目根目录执行:
flutter pub get如果在国内网络环境下,依赖包拉不下来是很常见的问题。不止flutter_sound,很多插件在首次拉取的时候都会卡住。这时候可以在项目根目录或者全局Flutter配置里加镜像源,用国内可访问的Flutter镜像,这样下载依赖会顺畅很多。还有个小技巧,如果某个插件依赖一直拉不下来,先看看是不是因为缺少某个传递依赖(比如path_provider这类基础库),先把基础库的版本对齐,再回来加flutter_sound,很多时候问题就解决了。
顺便说一句,很多GitHub上star数很高的Flutter项目,其实内部都封装了flutter_sound。如果你不想从零开始写,直接找个成熟的开源录音App项目,把它的录音服务层代码拿过来看,比自己摸索API快得多。但要注意,不同项目的封装方式差别很大,代码能不能直接套用,还是要看你自己的业务场景——比如你只是录个音发到聊天消息里,那就不需要把整个波形录制组件都搬过来。
2.2 平台权限与构建配置
权限这块,Android和iOS两边都要做,漏一个都会导致录音静默失败。
Android端
在android/app/src/main/AndroidManifest.xml中添加录音权限:
<uses-permission android:name="android.permission.RECORD_AUDIO" />如果你的App需要保存录音到外部存储,还需要加上存储权限(Android 11以上建议使用媒体库方式,具体看你App的targetSdk版本)。除了权限声明,Android 6.0以上还需要在运行时动态申请权限,flutter_sound本身不处理权限申请,你需要用permission_handler这个插件在调用录音前发起请求,否则直接调用startRecorder,你会发现它静默失败或者根本不会弹出授权框。
iOS端
在ios/Runner/Info.plist中添加:
<key>NSMicrophoneUsageDescription</key> <string>需要使用麦克风录制语音内容</string>如果不添加这个描述,调用录音初始化时App会直接崩溃,而且崩溃日志不会很明确,你很容易误认为是代码逻辑问题,实际上就是一个描述字符串缺失。
另外在模拟器上测试时,麦克风权限会让很多人一脸懵。模拟器默认会弹权限框,但它使用的可能是Mac电脑的麦克风,或者干脆就是无声源。建议录音相关功能一定要真机自测,模拟器只能用来验证UI布局和基本逻辑。
构建配置还有一个容易踩的坑:Flutter 3.x之后对Gradle插件的应用方式做了调整,如果你在项目里用老写法直接applyFlutter的Gradle插件,构建时会看到类似“You are applying Flutter's main Gradle plugin imperatively using the apply script method”的警告。这个警告不影响普通项目运行,但如果你同时改了Gradle版本,就可能导致构建失败。稳妥的做法是按照Flutter新版模板,改用插件声明方式配置settings.gradle和build.gradle,避免老写法与新Gradle版本不兼容。
3. 核心API解析与录音流程实现
3.1 录音会话初始化
FlutterSoundRecorder用起来的第一步,不是直接调startRecorder(),而是要先打开音频会话。这一步很多新手会忽略,导致后面所有操作都没反应。
录音器初始化大致是这样的流程:
final recorder = FlutterSoundRecorder(); Future<void> initRecorder() async { await recorder.openAudioSession(); await recorder.setAudioSource(AudioSource.microphone); }openAudioSession()用来创建一个音频会话,在iOS上会对应AVAudioSession的激活,在Android上会创建AudioRecord并准备好音频焦点。如果你不调用这个方法直接录音,大概率会得到一个底层错误。
setAudioSource()是用来指定音频源的,默认就是麦克风,但显式设置一次可以避免某些机型上的默认值差异。如果你做的是通话场景,可能需要设置为voiceCommunication,它会启用回声消除和降噪,录制效果会明显不同。
3.2 录音开始与停止的完整流程
录音开始很简单,但有几个参数值得认真说:
await recorder.startRecorder( toFile: filePath, codec: Codec.aacLc, bitRate: 128000, sampleRate: 44100, numChannels: 1, );toFile:录音保存的完整文件路径,注意是路径和文件名组合,不能只传文件名。codec:编码格式,常用aacLc,兼顾音质和文件体积。bitRate:码率,语音类128kbps足够,音乐类建议192kbps以上。sampleRate:采样率,语音识别类服务建议16000或44100,看你的后续处理需求。numChannels:声道数,语音笔记一般单声道就够,文件也更小。
停止录音时,需要用stopRecorder(),并确保把录音器的会话释放掉:
await recorder.stopRecorder(); await recorder.closeAudioSession();我见过不少人忘记调用closeAudioSession(),导致再次进页面录音时麦克风无响应。因为在iOS上,音频会话没有被正确释放时,前一次录音占用的资源还挂着,新一次录音就开不起来。
为了稳妥,我封装了一个录音服务类,里面用枚举管理当前状态,防止用户在录音过程中疯狂点击按钮导致状态错乱:
bool _isRecording = false; Future<void> toggleRecording() async { if (_isRecording) { await stopAndSave(); } else { await startNewRecording(); } }状态管理这件事,在录音功能里非常重要。录音不是瞬时操作,用户可能在不同页面间切换,如果没有一个全局的录音状态,很容易出现“上一个页面录着音,下一个页面又来一次startRecorder”这种低级但致命的bug。
3.3 录音参数与格式选择的经验
flutter_sound_record对编码格式的支持是它的一大优势,但支持多不代表每个都要懂,这里分享我实测下来的一套经验:
| 编码格式 | 文件体积 | 音质 | 适用场景 |
|---|---|---|---|
| aacLc | 较小 | 良好 | 通用录音,推荐 |
| opus | 最小 | 良好 | 网络传输、聊天语音 |
| pcm16 | 很大 | 最好 | 音频分析、波形显示、后期处理 |
| aacHE | 小 | 一般 | 低比特率语音场景 |
我最终选择aacLc,是因为文件大小和音质比较均衡,而且服务端转文字时兼容性好。如果你的App有语音识别需求,建议先确认服务商支持什么编码格式,别录音录完发现服务端不能解析。
采样率方面,如果你录制的是语音,16000是一个很常见的选项,很多语音识别API默认接受16kHz单声道的PCM或压流格式。如果既要录音又要播放,44100更通用。
还有一个冷门但实用的API——录音过程中的分贝回调。flutter_sound支持在录音同时监听分贝变化:
recorder.onProgress?.listen((data) { // data.duration 录音时长 // data.decibels 分贝值,范围一般在-160到0之间 });这个回调可以做录音波形动画,也可以用来判断说话音量是否过低,是一个非常实用的扩展点。我当初做“说话太轻”的提示功能,就依赖这个回调。
4. 常见问题与排查技巧实录
4.1 权限弹窗不出现或录制后无声
这个问题我遇到的次数最多,而且原因也各不相同。
情况一:iOS没有权限描述
表现是点击录音按钮后App直接崩溃。解决办法就是在Info.plist里补上NSMicrophoneUsageDescription,这个前面已经提过。
情况二:Android权限已授予,但startRecorder报错
这种情况多半是音频焦点被占用。比如你在录音的同时,系统正在播放音乐,或者另一款App占用了麦克风,AudioRecord初始化就会失败。解决办法是录音开始前使用audio_focus插件获取音频焦点,或者至少监听焦点变化并在失去焦点时自动停止录音。
情况三:权限都正常,录音文件也有,但播放没有声音
这其实不是麦克风的问题,而是文件路径不对,或者录音没有写入到文件中。toFile参数如果传了一个不存在的目录,Android端可能会静默失败,但iOS端会直接抛异常。建议录音前先确保父目录已创建:
final dir = await getApplicationDocumentsDirectory(); final filePath = '${dir.path}/voice_${DateTime.now().millisecondsSinceEpoch}.m4a';4.2 Gradle构建报错与依赖下载失败
集成时如果遇到Gradle相关问题,大概率是Flutter版本和Gradle版本不匹配。我自己遇到过的报错包括:
“You are applying Flutter's main Gradle plugin imperatively using the apply script method…”
这属于Flutter模板更新后,项目里的android/build.gradle没有同步更新导致的。解决办法是在settings.gradle中正确引入Flutter插件,并将build.gradle中的应用方式改成插件声明式。依赖下载卡死或超时
这种情况几乎都出在首次拉取或网络环境不稳的场景。除了配置国内镜像源,还可以删除pub缓存后重新flutter pub get。如果某一两个具体包拉不下来,少用版本脱字符,把版本写死,能有效减少解析时长。报错提示找不到
flutter_sound中的某个类
大概率是版本不统一。比如你项目里某个其他插件依赖了一个老版本的flutter_sound,而你在pubspec.yaml里写的是新版本,实际编译时会冲突。检查一下pubspec.lock,确认一下解析出来的版本和代码里用的API是否一致。
4.3 录音时长与后台状态处理
录音过程中用户按Home键退回桌面,或者切到其他App,录音会不会中断,这是语音类App必须处理的问题。受系统限制,Android上MediaRecorder在App退到后台时通常可以继续录制,但iOS上默认不支持后台录音,你需要在Info.plist中声明UIBackgroundModes并包含audio一行。
<key>UIBackgroundModes</key> <array> <string>audio</string> </array>但要注意,这个配置提交App Store审核时,苹果会询问后台音频的实际使用场景。如果你的App并没有后台播放需求,只为了录音去声明,存在审核被拒的风险。我当时的处理方式是:录音时如果检测到App进入后台,自动加一条系统通知,提示用户“录音仍在进行中”,这样既满足合规要求,又不会因为长时间静默后台录音导致被系统杀死。
录音时长限制也建议做成可配置的。flutter_sound没有内置最大时长限制,你需要在onProgress回调里自己判断时长并停止录音。我当时设置了一个5分钟上限,到时间自动保存并通知用户,这比让用户无限录下去要稳妥得多。
4.4 小技巧:录音后立即播放的坑
录音停止后,如果你立刻用一个FlutterSoundPlayer去播放同一个文件,偶尔会遇到打不开的情况,尤其是刚写完文件就马上播放。这个问题的根源是文件句柄尚未完全释放。解决方法是停止录音后,等待约300毫秒再初始化播放器:
await recorder.stopRecorder(); await Future.delayed(const Duration(milliseconds: 300)); await player.startPlayer(fromFile: filePath);你别小看这300毫秒,它能省掉你一堆“明明文件存在,却播放失败”的排查时间。
再补充一点,录音文件尽量不要和临时文件混放在一起。我在项目里单独建了一个records目录,每个用户的录音按日期分文件夹存放,后面调试定位问题时特别方便。
5. 延伸思考与个人体会
录音功能从表面看,就是一个startRecorder和stopRecorder的事情,真正做透了才发现这里面牵扯的东西太多:平台权限策略的差异、音频会话的生命周期、文件存储策略、后台任务处理、编码格式兼容、播放端的联动……每一个点都能写一篇专项文章。
flutter_sound这个库帮我节省了大量底层工作,但也不能完全依赖它,尤其在做跨平台App时,Android真机和iOS真机上的行为差异一定要尽早测试。我自己吃过亏的地方是:在Android上跑得好好的录音格式,到iOS上文件播放正常但时长对不上,最后排查发现是采样率和声道设置不一致导致的。所以,换平台必测录音,换格式必测播放,这两个动作千万别省。
如果你打算在自己的项目里集成录音,我最后再分享一个实用建议:不要一上来就把录音功能封装成一个巨大的插件,先用最简单的方式把“录音→存文件→播放”这条链路跑通,然后再逐步加波形、加水印、加上传、加后台录音。链路越短,出问题时越好定位。等基础跑通了,再去折腾高级功能,你会觉得顺很多。
本文还有配套的精品资源,点击获取