设备端做录音 + 实时语音流 的踩坑总结。
uni-app 做录音,为什么拿不到实时音频帧?
在工控平板、自助终端、语音交互项目里,需求经常是这样一句话:
“录音,实时把声音发给后端做语音识别。”
听起来很合理,但只要你用过 uni.getRecorderManager,就会立刻遇到一个冷冰冰的事实:
App 端不支持实时回调,不支持录音分片。
这篇文章把 uni-app 里所有音频方案的真实能力,一次性讲清楚。
一、先说结论(避免白忙活)
| 方案 | 能否实时拿 PCM / 帧 | 能否存本地 | 适合场景 |
|---|---|---|---|
uni.getRecorderManager |
❌ | ✅ | 录音文件上传 |
plus.audio.getRecorder |
❌(只能轮询文件) | ✅ | 简单录音 |
| 原生插件(如 leven-audio) | ❌ | ✅ | 播放 + 录音文件 |
WebView MediaRecorder |
❌(App 里没有) | — | 仅 H5 |
👉 uni-app 官方 API 里,没有任何一个能直接给你“实时音频帧”
二、uni.getRecorderManager:看起来最像,其实最坑
标准用法
const recorderManager = uni.getRecorderManager();
recorderManager.onStop(res => {
console.log(res.tempFilePath); // 录音结束后才给你
});
recorderManager.start({
format: 'mp3',
sampleRate: 16000,
frameSize: '1KB' // ⚠️ 写了也没用
});
文档里没写清楚的一行注释
recorderManager.onFrameRecorded((frameBuffer, isLastFrame) => {
// 分片,app不支持
});
在:
- ✅ 微信小程序:有用
- ❌ App(Android / iOS):完全不触发
也就是说:
frameSize在 App 端是“假参数”
三、plus.audio:原生录音,但依然是“文件模式”
plus.audio.getRecorder 是更接近 Android 原生的方案。
基本用法
const recorder = plus.audio.getRecorder();
recorder.record(
{
format: 'amr',
samplerate: '8000',
filename: '_doc/audio/' + Date.now() + '.amr'
},
filePath => {
console.log('录音文件:', filePath);
},
err => {
console.error(err);
}
);
支持什么?
- ✅ 选格式:amr / wav / aac
- ✅ 选采样率
- ✅ 拿到真实文件路径
- ✅ 权限可控
不支持什么?
- ❌ 没有
onFrame - ❌ 不会边录边吐 buffer
四、一种“伪实时”:轮询录音文件(能跑,但不优雅)
文档里给了一个很工控的写法:
startRealTimeCallback() {
let lastSize = 0;
this.intervalId = setInterval(() => {
plus.io.resolveLocalFileSystemURL(this.audioFilePath, entry => {
entry.file(file => {
if (file.size > lastSize) {
lastSize = file.size;
this.readChunk(file);
}
});
});
}, 300);
}
readChunk(file) {
const reader = new plus.io.FileReader();
reader.onloadend = e => {
const buffer = e.target.result; // ArrayBuffer
// 发 WebSocket / 处理
};
reader.readAsArrayBuffer(file);
}
这是什么意思?
- 录音 → 写文件
- 前端定时读文件 → 发现变大了 → 读全部
- 不是增量,是重复读
问题很明显
| 问题 | 原因 |
|---|---|
| 数据重复 | 每次都读整个文件 |
| 延迟 | 300ms 起步 |
| 格式难搞 | AMR / AAC 不是裸 PCM |
| CPU 浪费 | 频繁 IO |
👉 只适合“对实时性要求极低”的设备端场景。
五、原生插件:播放很爽,录音还是“文件模式”
文档里重点测试了这个插件:
leven-audio(DCloud 插件市场 ID: 14803)
它能做什么?
✅ 音频播放
- 播放本地 MP3
- 进度监听
- seek / pause / resume
✅ 录音
- 请求麦克风权限
- 录音到指定目录
const recordModule = uni.requireNativePlugin('leven-audio-RecordModule');
recordModule.start({
saveDir: '/storage/emulated/0/leven_file'
}, res => {
console.log(res);
});
它不能做什么?
❌ 实时音频帧
❌ 录音回调 buffer
❌ 边录边推流
本质上:封装了 Android MediaRecorder,不是 AudioRecord。
六、权限配置,是 80% 报错的来源
插件文档里把 Android 权限写得很全,这里直接给你一份“抄作业版”。
manifest.json(Android)
"android": {
"permissions": [
"android.permission.INTERNET",
"android.permission.RECORD_AUDIO",
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.MODIFY_AUDIO_SETTINGS"
],
"permissionExternalStorage": {
"request": "always"
},
"permissionPhoneState": {
"request": "always"
}
}
运行时权限(Android 11+ 必写)
plus.android.requestPermissions(
['android.permission.RECORD_AUDIO'],
() => start(),
() => uni.showToast({ title: '麦克风权限被拒绝', icon: 'none' })
);
七、为什么 uni-app 就是拿不到实时音频?
这不是 bug,是架构选择。
微信小程序
- 有
onFrameRecorded - 底层是微信自己改的引擎
uni-app App
- 基于 WebView + 原生桥
MediaRecorder不存在- 原生层只暴露了:
- 开始
- 停止
- 文件路径
👉 没有暴露 AudioRecord → PCM → JSBridge 的链路
八、那设备端“实时语音识别”怎么做?
给你 3 条真实可行的路。
✅ 方案 1:录音文件 + 分段上传(最简单)
- 每次录 3 秒
stop()→ 拿到文件 → 上传- 再
start()
recorderManager.stop();
// 等 onStop
// 上传 → 再 start
✅ 稳定
❌ 延迟 3s+
✅ 方案 2:原生插件自己写(专业)
自己写个 uts / native 插件:
- Android:
AudioRecord→ PCM - 16kHz → 16000 × 2 × 2 = 64KB/s
- PCM → opus → WebSocket
这是语音助手、工控终端的标准做法。
✅ 方案 3:云端长语音(绕开前端)
- 前端只负责录文件
- 后端用阿里 / 腾讯 / 讯飞 长语音接口
- 后端负责 VAD + 流式识别
👉 前端压力最小,成本在云端。
九、格式选择建议(别乱选)
| 场景 | 格式 |
|---|---|
| 语音识别 | wav(16kHz / 16bit / mono) |
| 存储省空间 | aac |
| 老 Android 兼容 | amr(但识别效果差) |
| 云端识别 | wav 或 opus |
⚠️ 注意:
plus.audio 在部分设备不支持 wav,要实测。
十、总结
uni-app 能录音,但不能“流录音”。
uni.getRecorderManager→ 文件plus.audio→ 文件- 原生插件 → 还是文件
如果你要做:
- 实时语音交互
- ASR 流式识别
- 对讲系统
👉 要么接受延迟,要么写原生插件
浙公网安备 33010602011771号