夜雨聆风学习资料网

ARTICLE · 1090558

从 H5 到 uni-app:Recorder 前端录音库跨端技术解析

从 H5 到 uni-app:Recorder 前端录音库跨端技术解析

项目地址:https://github.com/xiangyuecn/Recorder在线测试:https://xiangyuecn.github.io/Recorder/

uni-app 支持情况(先行结论):支持,且是官方主推的落地形态之一。有两条路线——RecordApp(推荐) 可一套代码编译到 H5、Android App、iOS App、微信小程序;Recorder + renderjs 仅覆盖 App 与 H5,不支持小程序。详见第九节。

一、它解决的是什么问题

网页录音这件事,表面上看只是 getUserMedia 一行 API,真正落地时却是一连串的坑:

•
浏览器返回的原始 PCM 数据体积巨大,直接上传或存储都不现实;
•
MediaRecorder 原生只产出 webm/ogg,后端和 iOS 往往不认;
•
iOS WebView 在 14.3 之前根本不支持 H5 录音,国产 App 的 X5 内核又要单独处理权限;
•
想要"边录边传"、实时波形、实时语音识别,就必须在 PCM 层面拿到数据并自行处理。

Recorder 是国产开发者 xiangyuecn 维护的纯前端录音库,核心价值在于:把麦克风采集到的 PCM 流完整暴露给 JS 层,同时提供一套可插拔的编码器与实时处理机制,让"录音"从"生成一个文件"升级为"一条可编程的音频流水线"。

它的定位很清楚——为语音录制而生:单声道、小体积、可实时处理,而不是音乐级录音棚方案。

二、两个入口,别混淆

入口
作用
GitHub 仓库
源码、README 完整文档、QuickStart.html、Android/iOS/小程序/uni-app Demo 源码
xiangyuecn.github.io/Recorder
H5 在线测试页,可直接录音试效果,页面底部会打印当前浏览器的 API 支持情况

在线测试页的一个实用技巧:录音时观察灰色区域是否有绿色音量跳动。没有跳动说明没拿到音频数据;如果 mp3 无声而 wav 有声,基本可以判定是内置 lamejs 编码器的问题。这套自检流程能快速区分"权限问题""采集问题"和"编码问题"。

三、整体架构:一条被拆开的三段式流水线

Recorder 的设计可以拆成三层理解:

关键在于 envIn(pcmData, pcmAbsSum) 这个内部方法:它是平台环境层与核心录制层之间的唯一入口。不管是浏览器麦克风、Android WebView、iOS WKWebView 还是微信小程序,最终都只需要把 PCM 数据喂给 envIn,后续的回调、编码、输出逻辑完全一致。这也解释了为什么同一个核心能同时支撑 Recorder(H5)和 RecordApp(App/小程序)两条产品线。

四、五分钟跑通最小闭环

npm install recorder-core --registry=https://registry.npmmirror.com/
import Recorder from 'recorder-core'import 'recorder-core/src/engine/mp3'import 'recorder-core/src/engine/mp3-engine'let recfunction recOpen() {  rec = Recorder({    type: 'mp3',    sampleRate: 16000,   // 必须是数字,不要传字符串    bitRate: 16,         // kbps,16kbps ≈ 2KB/s    onProcess(buffers, powerLevel, duration, sampleRate, newBufferIdx) {      // 约每秒 12 次回调,buffers 为 [[Int16]] 二维数组      // 这里可以:绘制波形 / 实时上传 / 实时 ASR / 释放内存    }  })  rec.open(() => { /* 已获得麦克风资源 */ },           (msg, isUserNotAllow) => { console.warn(msg, isUserNotAllow) })}function recStop() {  rec.stop((blob, duration) => {    const url = URL.createObjectURL(blob)   // 本地播放    const form = new FormData()    form.append('upfile', blob, 'recorder.mp3')  // 或直接上传    rec.close()   // 用完就关,否则系统一直显示"正在录音"  })}

两个必须记住的前提:

1
必须在 https 或 localhost 下运行,否则 getUserMedia 直接被拒;
2
open() 和 start() 至少有一个发生在用户手势中(点击/触摸)。这不是建议而是硬性要求——iOS 上 AudioContext 若非用户操作触发,resume() 不会生效,录出来就是一段静音。

五、音频数据的生命周期与关键配置

一次录音的数据流向:

麦克风/MediaStream → 采集连接 → buffers 累积      → onProcess 回调(可改写数据)      → 编码器(Worker 边录边转码)      → takeoffEncodeChunk 实时吐块 / stop() 返回完整 Blob
配置项
默认值
说明
typemp3
输出格式,需提前引入对应编码引擎
sampleRate16000
mp3 可选 8000–48000 离散档位;wav 任意值
bitRate16
mp3 为 kbps;wav 为位深(16/8)
onProcess
空
实时回调,返回 true 开启异步模式
takeoffEncodeChunk
空
接管编码器输出,适合实时传输
sourceStream
空
直接提供 MediaStream(WebRTC remote 流、captureStream 等)
audioTrackSet
空
透传给 getUserMedia,控制 AEC/ANS/AGC

几个容易忽略的细节:

•
所有 js 文件都是手动引入的,内部不会自动引用——不用的文件直接删掉即可瘦身。
•
buffers 里的采样率是浏览器给的原始采样率 rec.srcSampleRate,不一定等于set.sampleRate。要严格对齐目标采样率,需在 onProcess 里自己调 Recorder.SampleData()。
•
stop() 会触发编码。支持 Worker 边录边转码的格式几乎瞬时返回;不支持的格式十几秒录音花 2 秒左右属正常。

六、格式怎么选

作者主推 mp3 和 wav,代码也优先照顾这两种。单声道 + 16kbps/16kHz 的组合下,压缩比约 16 倍,语音可懂度完全够用。

七、实时处理:这才是它的分水岭

1. 流式重采样

let prevChunk = nullonProcess(buffers, powerLevel, duration, sampleRate) {  const chunk = Recorder.SampleData(buffers, sampleRate, 16000, prevChunk, {    frameType: 'mp3'   // 自动取 1152 帧,避免 mp3 尾帧填充导致时长变长  })  prevChunk = chunk  // chunk.data 即 16kHz 的一维 PCM,可实时上传}

SampleData 支持任意采样率互转与流式连续转换。注意 1.3.26070800 之前版本的流式转换存在缺陷会引入杂音(非整数倍转换如 44100↔16000 时尤其明显,蓝牙耳机场景可能非常突出),该版本已修复,做实时重采样务必升级。

2. 接管编码器输出

提供 takeoffEncodeChunk 后,编码器不再内部缓存,每产出一块二进制数据就回调一次,所有 chunkBytes 拼接即完整音频。这有两个好处:内存占用恒定,且天然避免片段拼接的首尾静音。代价是 stop() 返回的 blob 长度为 0,数据需自行保存。注意 wav 不支持该回调(文件头需要最终长度),配置了会在 open 时直接走 fail。

3. 长录音的内存释放

for (let i = clearBufferIdx || 0; i < newBufferIdx; i++) buffers[i] = nullclearBufferIdx = newBufferIdx

在 onProcess 里把已处理的 buffer 置 null,即可支撑小时级连续录音。若要彻底避免编码器内部缓冲,可用 type:"unknown" 初始化并自行转码。

4. 看门狗(Watchdog)

移动端录音中断是常态。官方 Demo 给出的做法是起一个 1 秒轮询,若超过 1500ms 没有 onProcess 回调,就判定"录音被中断"并做容错处理:

if (Date.now() - (processTime || startTime) > 1500) {  console.error(processTime ? '录音被中断' : '录音未能正常开始')}

5. 纯转码场景

rec.mock(pcmData, sampleRate) 可以把任意 PCM 灌进去再 stop() 得到目标格式文件——不碰麦克风,用于音频格式转换、文件合并等后端式操作。

八、跨平台落地要点

Android 后台录音需要专门的保活机制:自 Android 9 起,锁屏或进入后台一段时间后系统可能禁止访问麦克风,导致录音数据全为静音——demo_android 中有专门章节讲解。

九、uni-app 集成:两条路线怎么选

结论先行:支持。 官方提供了 demo_UniApp 示例项目,封装好的组件可在 DCloud 插件市场直接下载。但 uni-app 里有两条完全不同的技术路线,选错会在编译到小程序时直接翻车。

路线一:RecordApp(官方推荐)

RecordApp 是 Recorder 的"跨端外壳",内部复用同一个录音核心(共享 envIn/envStart 机制),但把平台差异收敛到了 App 层。

编译目标
支持
H5
✅
Android App
✅
iOS App
✅
微信小程序
✅

适用场景:一套 uni-app 代码要同时发 App 和小程序,或者明确需要小程序端录音。这是唯一能覆盖小程序的路线。

路线二:Recorder + renderjs(有限支持)

uni-app 的 renderjs 运行在视图层 WebView 中,因此可以在 renderjs 里加载 Recorder 完成录音:

编译目标
支持
H5
✅
Android App
✅
iOS App
✅
微信小程序
❌ 不支持

官方明确标注为 [不推荐]。它适合只需覆盖 App 端、想复用现成 H5 录音代码的场景;一旦后续要加小程序端,录音模块需要推倒重来用 RecordApp 重写。

App 端的权限前置处理(两条路线都要做)

在 App 平台,必须在调用 rec.open()之前于原生层拿到录音权限:

1
声明权限:Android 在 AndroidManifest.xml 声明录音权限(X5 内核还需 CAMERA);iOS 在 Info.plist 声明 NSMicrophoneUsageDescription。
2
请求权限:在逻辑层编写 JS 权限处理代码,调起原生权限请求接口。iOS 的 WebView 会自动处理,可以不主动请求。
3
后台录音:iOS 需在 Background Modes 勾选 Audio;Android 9+ 需要保活服务,否则切后台后录出来全是静音。

选型决策

•
要小程序 → 只能 RecordApp;
•
只做 App + H5、已有 H5 录音代码 → 可以 renderjs,但要接受未来扩展成本;
•
新项目、目标端未定 → 直接上 RecordApp,避免二次改造。

十、插件生态

插件默认不合并进主包,按需引入:

插件
体积
用途
waveview.js
7KB
录音动态波形
wavesurfer.view.js
8KB
Audition 风格波形
frequency.histogram.view.js
 + lib.fft.js
12KB
频率直方图(已针对语音频段优化)
buffer_stream.player.js
31KB
音频片段转 MediaStream 实时播放
asr.aliyun.short.js
29KB
阿里云实时/文件语音识别
sonic.js
38KB
变速、变调、变声
dtmf.decode.js
 / dtmf.encode.js
<10KB
电话拨号按键信号编解码
create-audio.nmn2pcm.js
14KB
简谱生成 PCM(测试用)

ASR 插件值得一提:它直连阿里云 WebSocket,语音数据不经过自己的服务器,后端只需提供一个 Token 生成接口;底层用"一句话识别"(便宜)配合插件自带的拼接逻辑来突破 60 秒限制。

十一、生产环境避坑清单

1
用户手势:open/start 至少一个在手势内调用,否则 iOS 静音。
2
HTTPS:非安全环境 open 直接 fail;file:// 也不行。
3
降噪与回声消除:移动端默认开启,会压低系统播放音量,且只给 16kHz 的流;但 iOS 上关掉又可能录不了,需按机型权衡。不要回声消除时可显式关闭以换取 48kHz 高音质流。
4
iOS 权限弹框:打开录音并关闭后若约 35 秒无用户交互,再次打开会重新弹权限框,这是浏览器行为,JS 无法干预——交互设计上要避开。
5
锁屏:手机锁屏后能否录音不可控,可用 navigator.wakeLock 阻止自动锁屏兜底。
6
跨域 iframe:权限必被拒;同源 iframe 建议让 window.top 加载 Recorder,子页面用 top.Recorder。
7
通话中录音:iOS Safari 会暂停返回音频数据直到通话结束,不建议在此过程录音。
8
AudioWorklet:默认禁用(ConnectEnableWorklet=false),因为移动端 1 秒 375 次回调可能造成丢帧;采集优先走 MediaRecorder.WebM.PCM(ConnectEnableWebM 默认开启),音质明显更好。
9
统计请求:首次实例化会向 51la 发一个 1 像素请求,隐私要求高的页面可把 Recorder.TrafficImgUrl 置空。
10
uni-app 路线:Recorder + renderjs 编译到小程序端无法录音,且官方标注不推荐;要覆盖小程序请直接用 RecordApp(见第九节)。

十二、选型建议与边界

适合:网页语音留言、语音输入、在线客服录音、实时对讲、ASR 转写、语音验证码、音频可视化教学 Demo——尤其是需要在微信/QQ 内置浏览器、国产 App WebView 和 uni-app 多端项目里跑的场景,Recorder 在这块的适配深度是同类库中少见的。

不适合:立体声录制(它只支持单声道,且明确表示"未找到双声道语音录制的意义")、音乐级高保真录音、需要在 iOS 11–14.2 的非 Safari 浏览器里工作的项目。

一句话总结:如果你的需求是"在浏览器里可靠地录一段能上传、能播放、能转文字的语音",Recorder 基本是国内生态下最省心的选择;仓库里 30 多个可运行的 Demo 片段(实时上传、多路混音、变速变调、DTMF、FFT 频谱等)几乎覆盖了二次开发会踩到的所有点,动手前先跑一遍 QuickStart.html 是最高效的入门路径。

相关学习资料