Recorder
【源 GitHub 仓库】 | 【Gitee 镜像库】本文档中默认使用github.io 部署的链接,如果无法访问,请直接下载仓库源码到本地然后双击打开 html 文件访问。
支持在大部分已实现getUserMedia
的移动端、PC 端浏览器麦克风录音、实时处理,主要包括:Chrome、Firefox、Safari、iOS 14.3+、Android WebView、腾讯 Android X5 内核(QQ、微信、小程序 WebView)、Electron、大部分 2021 年后更新的 Android 手机自带浏览器、2024 年后更新的 Android 版 UC 内核(UC、支付宝、钉钉);不支持:老旧国产手机自带浏览器、老旧 iOS(11.0-14.2)上除 Safari 外的其他任何形式的浏览器。
支持在非浏览器环境中使用部分功能(如 nodejs、各种使用 js 来构建的程序),使用RecordApp可在微信小程序、uni-app 中直接录音。
支持对任意MediaStream
进行音频录制、实时处理,包括:getUserMedia 返回的流
、WebRTC 中的 remote 流
、audio、video 标签的 captureStream 方法返回的流
、自己创建的流
等等。
提供多个插件功能支持,拥有丰富的音频可视化、变速变调处理、语音识别、音频流播放等;搭配上强大的实时处理支持,可用于各种网页应用:从简单的录音,到复杂的实时语音识别(ASR),甚至音频相关的游戏,都能从容应对;提供转码支持,允许将录制的 buffers 数据或任意 pcm 数据转码成你需要的格式(参考rec.mock
方法)。
主要用于语音录制,因此仅对单声道进行支持(未适配双声道),支持超长时间录音(参考rec.buffers
);默认输出 mp3 格式,另外可选 wav、pcm、g711a、g711u、ogg、amr、webm(beta)格式,支持任意格式扩展(前提有相应编码器);使用 recorder.mp3.min.js(150kb)即可录制 mp3,使用 recorder.wav.min.js(25kb)即可录制 wav;均支持实时转码和实时传输。
音频文件的上传和播放:可直接使用常规的Audio HTML 标签
来播放完整的音频文件,参考文档下面的【快速使用】部分,有上传和播放例子;上传了的录音直接将音频链接赋值给audio.src
即可播放;本地的blob 音频文件
可通过URL.createObjectURL
来生成本地链接赋值给audio.src
即可播放,或者将 blob 对象直接赋值给audio.srcObject
(兼容性没有 src 高)。实时的音频片段文件播放,可以使用本库自带的BufferStreamPlayer
插件来播放,简单高效,或者采用别的途径播放。
如需录音功能定制开发,网站、App、小程序、前端后端开发等需求,请加本文档下面的 QQ 群,联系群主(即作者),谢谢~
Recorder H5 : [ H5 在线测试 ] [ H5 QuickStart ] [ H5 vue ] [ H5 ts ] [ 旧版本测试 ]
Recorder App : [ RecordApp 测试 ] [ App QuickStart ] [ App vue ] [ Android、iOS App 源码 ] [ 微信小程序源码 ] [ uni-app 源码 ]
工具集 : [ Recorder 代码运行和静态分发 ] [ PCM 转 WAV 播放测试和转码 ] [ 无用户操作测试 ] [ Can I Use 查看浏览器支持情况 ]
Demo 片段列表
- 【教程】【PCM 基础】buffers 转 pcm、转采样率、pcm 转成其他格式等
- 【教程】【音频流】【上传】实时转码上传-实时帧回调版
- 【教程】【音频流】【上传】实时转码上传-pcm 固定帧大小
- 【教程】【音频流】【上传】实时转码上传-通用转码旧版
- 【教程】【音频流】【播放】实时解码播放音频片段
- 【教程】【播放】【可视化】实时录制处理 audio、video 播放流
- 【教程】【ASR】实时语音识别、音频文件转文字-阿里云版
- 【Demo 库】【格式转换】-mp3 等格式解码转成其他格式
- 【Demo 库】【格式转换】-wav 格式转成其他格式
- 【Demo 库】【格式转换】-amr 格式转成其他格式
- 【Demo 库】【文件合并】-mp3 多个片段文件合并
- 【Demo 库】【文件合并】-wav 多个片段文件合并
- 【教程】实时多路音频混音
- 【教程】变速变调音频转换
- 【教程】新录音从老录音接续、或录制中途插入音频
- 【教程】DTMF(电话拨号按键信号)解码、编码
- 【测试】PCM 采样率转换测试
- 【Demo 库】【信号处理】IIR 低通、高通滤波
- 【测试】【信号处理】FFT 频域分析 ECharts 频谱曲线图
- 【测试】WebM 格式解析并提取音频
- 【测试】G711、G72X 编码和解码播放
- 【Demo 库】js 二进制转换-Base64/Hex/Int16Array/ArrayBuffer/Blob
- 【测试】音乐合成-用波形函数将歌曲简谱文本转成 PCM
- 【测试】音频可视化相关插件测试
App Demo
Android Demo App : 下载 APK(40kb,删除.zip 后缀, 源码)
iOS Demo App :下载源码 自行编译
【QQ 群】交流与支持
欢迎加 QQ 群:①群 781036591、②群 748359095、③群 450721519,纯小写口令:recorder
:open_book:快速使用
你可以通过阅读和运行QuickStart.html文件来快速入门学习,直接将QuickStart.html
copy 到你的(https、localhost)网站中,无需其他文件,就能正常开始测试了;注意:需要在 https、localhost 等安全环境下才能进行录音。
https 环境搭建最佳实践:建议给自己的域名申请一个泛域名通配符证书(*.xxx.com),然后线上、本地开发均可使用此证书;本地开发环境直接分配一个三级域名(dev.xxx.com、local.xxx.com、192-168-1-123.xxx.com)解析 A 记录到电脑局域网的 IP 地址(192.168.1.123、127.0.0.1),方便本地开发跨端调试(本地如何配置 https 请针对自己的开发环境自行搜索,很容易)。
获取泛域名通配符证书推荐:在线免费申请(ZeroSSL、Let’s Encrypt);不建议自己生成根证书来签发域名证书,一个是流程复杂,每个设备均要导入根证书,致命的是很多现代浏览器不再信任用户目录下导入的根证书(Android)。
如果必须 http 访问,Chrome 中可尝试打开
chrome://flags/#unsafely-treat-insecure-origin-as-secure
,启用Insecure origins treated as secure
,把你的地址含端口配置进去,然后重启浏览器。
【1】加载框架
Recorder 的所有 js 文件均为手动引入(内部不会自动引用),因此未被你引入的文件均可删除来精简源码大小。
方式一:使用 script 标签引入
在需要录音功能的页面引入压缩好的 recorder.xxx.min.js 文件即可(CDN:JsDelivr、unpkg)
<script src="recorder.mp3.min.js"></script> <!--已包含 recorder-core 和 mp3 格式支持, CDN 不稳定仅供测试: https://cdn.jsdelivr.net/gh/xiangyuecn/Recorder@latest/recorder.mp3.min.js-->
或者直接使用源码(src 内的为源码、dist 内的为压缩后的),可以引用 src 目录中的 recorder-core.js+相应类型的实现文件,比如要 mp3 录音:
<script src="src/recorder-core.js"></script> <!--必须引入的录音核心,CDN 不稳定仅供测试: https://cdn.jsdelivr.net/gh/xiangyuecn/Recorder@latest/src/recorder-core.js-->
<script src="src/engine/mp3.js"></script> <!--相应格式支持文件;如果需要多个格式支持,把这些格式的编码引擎 js 文件放到后面统统加载进来即可-->
<script src="src/engine/mp3-engine.js"></script> <!--如果此格式有额外的编码引擎(*-engine.js)的话,必须要加上-->
<script src="src/extensions/waveview.js"></script> <!--可选的插件支持项,把需要的插件按需引入进来即可-->
方式二:通过 import/require 引入
通过 npm 进行安装 npm install recorder-core --registry=https://registry.npmmirror.com/
,如果直接 clone 的源码下面文件路径调整一下即可
//必须引入的核心,换成 require 也是一样的。注意:recorder-core 会自动往 window 下挂载名称为 Recorder 对象,全局可调用 window.Recorder,也许可自行调整相关源码清除全局污染
import Recorder from 'recorder-core' //注意如果未引用 Recorder 变量,可能编译时会被优化删除(如 vue3 tree-shaking),请改成 import 'recorder-core',或随便调用一下 Recorder.a=1 保证强引用
//引入相应格式支持文件;如果需要多个格式支持,把这些格式的编码引擎 js 文件放到后面统统引入进来即可
import 'recorder-core/src/engine/mp3'
import 'recorder-core/src/engine/mp3-engine' //如果此格式有额外的编码引擎(*-engine.js)的话,必须要加上
//以上三个也可以合并使用压缩好的 recorder.xxx.min.js
//比如 import Recorder from 'recorder-core/recorder.mp3.min' //已包含 recorder-core 和 mp3 格式支持
//可选的插件支持项,把需要的插件按需引入进来即可
import 'recorder-core/src/extensions/waveview'
//ts import 提示:npm 包内已自带了.d.ts 声明文件(不过是 any 类型)
【2】调用录音,播放结果
这里假设只录 3 秒,录完后立即播放,在线编辑运行此代码>>。录音结束后得到的是 Blob 二进制文件对象,可以下载保存成文件、用FileReader
读取成ArrayBuffer
或者Base64
给 js 处理,或者参考下一节上传示例直接上传。
//简单控制台直接测试方法:在任意(无 CSP 限制)页面内加载需要的 js,加载成功后再执行一次本代码立即会有效果
//①加载 Recorder+mp3:await import("https://unpkg.com/recorder-core/recorder.mp3.min.js"); console.log("import ok")
//②可视化插件和显示:await import("https://unpkg.com/recorder-core/src/extensions/waveview.js"); console.log("import ok"); div=document.createElement("div");div.innerHTML='<div style="height:100px;width:300px;" class="recwave"></div>';document.body.prepend(div);
var rec,processTime,wave;
/**调用 open 打开录音请求好录音权限**/
var recOpen=function(success){//一般在显示出录音按钮或相关的录音界面时进行此方法调用,后面用户点击开始录音时就能畅通无阻了
rec=Recorder({ //本配置参数请参考下面的文档,有详细介绍
type:"mp3",sampleRate:16000,bitRate:16 //mp3 格式,指定采样率 hz、比特率 kbps,其他参数使用默认配置;注意:是数字的参数必须提供数字,不要用字符串;需要使用的 type 类型,需提前把格式支持文件加载进来,比如使用 wav 格式需要提前加载 wav.js 编码引擎
,onProcess:function(buffers,powerLevel,bufferDuration,bufferSampleRate,newBufferIdx,asyncEnd){
//录音实时回调,大约 1 秒调用 12 次本回调,buffers 为开始到现在的所有录音 pcm 数据块(16 位小端 LE)
//可利用 extensions/sonic.js 插件实时变速变调,此插件计算量巨大,onProcess 需要返回 true 开启异步模式
//可实时上传(发送)数据,配合 Recorder.SampleData 方法,将 buffers 中的新数据连续的转换成 pcm 上传,或使用 mock 方法将新数据连续的转码成其他格式上传,可以参考文档里面的:Demo 片段列表 -> 实时转码并上传-通用版;基于本功能可以做到:实时转发数据、实时保存数据、实时语音识别(ASR)等
processTime=Date.now();
//可实时绘制波形(extensions 目录内的 waveview.js、wavesurfer.view.js、frequency.histogram.view.js 插件功能)
wave&&wave.input(buffers[buffers.length-1],powerLevel,bufferSampleRate);
}
});
rec.open(function(){//打开麦克风授权获得相关资源
//rec.start() 此处可以立即开始录音,但不建议这样编写,因为 open 是一个延迟漫长的操作,通过两次用户操作来分别调用 open 和 start 是推荐的最佳流程
//创建可视化,指定一个要显示的 div
if(Recorder.WaveView)wave=Recorder.WaveView({elem:".recwave"});
success&&success();
},function(msg,isUserNotAllow){//用户拒绝未授权或不支持
console.log((isUserNotAllow?"UserNotAllow,":"")+"无法录音:"+msg);
});
};
/**开始录音**/
function recStart(){//打开了录音后才能进行 start、stop 调用
rec.start();
//【稳如老狗 WDT】可选的,监控是否在正常录音有 onProcess 回调,如果长时间没有回调就代表录音不正常
var wdt=rec.watchDogTimer=setInterval(function(){
if(!rec || wdt!=rec.watchDogTimer){ clearInterval(wdt); return } //sync
if(Date.now()<rec.wdtPauseT) return; //如果暂停录音了就不检测:puase 时赋值 rec.wdtPauseT=Date.now()*2(永不监控),resume 时赋值 rec.wdtPauseT=Date.now()+1000(1 秒后再监控)
if(Date.now()-(processTime||startTime)>1500){ clearInterval(wdt);
console.error(processTime?"录音被中断":"录音未能正常开始");
// ... 错误处理,关闭录音,提醒用户
}
},1000);
var startTime=Date.now(); rec.wdtPauseT=0; processTime=0;
};
/**结束录音**/
function recStop(){
rec.watchDogTimer=0; //停止监控 onProcess 超时
rec.stop(function(blob,duration){
//简单利用 URL 生成本地文件地址,注意不用了时需要 revokeObjectURL,否则霸占内存
//此地址只能本地使用,比如赋值给 audio.src 进行播放,赋值给 a.href 然后 a.click()进行下载(a 需提供 download="xxx.mp3"属性)
var localUrl=(window.URL||webkitURL).createObjectURL(blob);
console.log(blob,localUrl,"时长:"+duration+"ms");
rec.close();//释放录音资源,当然可以不释放,后面可以连续调用 start;但不释放时系统或浏览器会一直提示在录音,最佳操作是录完就 close 掉
rec=null;
//已经拿到 blob 文件对象想干嘛就干嘛:立即播放、上传、下载保存
/*** 【立即播放例子】 ***/
var audio=document.createElement("audio");
document.body.prepend(audio);
audio.controls=true;
audio.src=localUrl;
audio.play();
},function(msg){
console.log("录音失败:"+msg);
rec.close();//可以通过 stop 方法的第 3 个参数来自动调用 close
rec=null;
});
};
//这里假设立即运行,只录 3 秒,录完后立即播放,本段代码 copy 到控制台内可直接运行
recOpen(function(){
recStart();
setTimeout(recStop,3000);
});
【附】录音上传示例
var TestApi="/test_request";//用来在控制台 network 中能看到请求数据,测试的请求结果无关紧要
var rec=Recorder();rec.open(function(){rec.start();setTimeout(function(){rec.stop(function(blob,duration){
//-----↓↓↓以下才是主要代码↓↓↓-------
//本例子假设使用 jQuery 封装的请求方式,实际使用中自行调整为自己的请求方式
//录音结束时拿到了 blob 文件对象,可以用 FileReader 读取出内容,或者用 FormData 上传
var api=TestApi;
/***方式一:将 blob 文件转成 base64 纯文本编码,使用普通 application/x-www-form-urlencoded 表单上传***/
var reader=new FileReader();
reader.onloadend=function(){
$.ajax({
url:api //上传接口地址
,type:"POST"
,data:{
mime:blob.type //告诉后端,这个录音是什么格式的,可能前后端都固定的 mp3 可以不用写
,upfile_b64:(/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result)||[])[1] //录音文件内容,后端进行 base64 解码成二进制
//...其他表单参数
}
,success:function(v){
console.log("上传成功",v);
}
,error:function(s){
console.error("上传失败",s);
}
});
};
reader.readAsDataURL(blob);
/***方式二:使用 FormData 用 multipart/form-data 表单上传文件***/
var form=new FormData();
form.append("upfile",blob,"recorder.mp3"); //和普通 form 表单并无二致,后端接收到 upfile 参数的文件,文件名为 recorder.mp3
//...其他表单参数
$.ajax({
url:api //上传接口地址
,type:"POST"
,contentType:false //让 xhr 自动处理 Content-Type header,multipart/form-data 需要生成随机的 boundary
,processData:false //不要处理 data,让 xhr 自动处理
,data:form
,success:function(v){
console.log("上传成功",v);
}
,error:function(s){
console.error("上传失败",s);
}
});
//-----↑↑↑以上才是主要代码↑↑↑-------
},function(msg){console.log("录音失败:"+msg);});},3000);},function(msg){console.log("无法录音:"+msg);});
【附】js 中的二进制基础知识:Uint8Array Int16Array ArrayBuffer Blob/File
更多 js 二进制数据基础知识请阅读:js 二进制转换-Base64/Hex/Int16Array/ArrayBuffer/Blob
//XMLHttpRequest、WebSocket 等一般都能直接 send(TypedArray/ArrayBuffer/Blob)到服务器,服务器会收到二进制数据
//TypedArray/ArrayBuffer 在绝大部分 js 运行环境中都支持,Blob 只在浏览器中支持
//Recorder 的 buffers 是 pcm 数组,单个 pcm 是 Int16Array(TypedArray 子类)
var arrayBuffer=pcm.buffer; //pcm 的 Int16Array 转 ArrayBuffer
var pcm=new Int16Array(arrayBuffer); //pcm 的 ArrayBuffer 转成 Int16Array(共享内存,arrayBuffer.slice(0)截取新的不共享)
//Recorder 的 takeoffEncodeChunk 得到的 chunkBytes 是 Uint8Array(TypedArray 子类,类似于无符号 byte[])
var arrayBuffer=chunkBytes.buffer; //chunkBytes 的 Uint8Array 转 ArrayBuffer
var chunkBytes=new Uint8Array(arrayBuffer); //任意 ArrayBuffer 均能转成 Uint8Array
//ArrayBuffer 与 Blob/File 可以相互转换,File 是 Blob 的一个特例(子类)
var blob=new Blob([arrayBuffer],{type:"audio/mp3"});
var file=new File([arrayBuffer],"xxx.mp3");
var arrayBuffer=reader.result; //用 FileReader 的 readAsArrayBuffer 方法读取 Blob/File 成 ArrayBuffer
//pcm 二进制拼接合并,下面 Int16Array 换成 Uint8Array 等其他 TypedArray 也是一样的
var pcm1=new Int16Array([1,2]),pcm2=new Int16Array([3,4,5,6]);
var pcm=new Int16Array(pcm1.length+pcm2.length);
pcm.set(pcm1,0); //写入 pcm1 到开头位置
pcm.set(pcm2,pcm1.length); //写入 pcm2 到 pcm1 后面,完成拼接
//二进制封装包头,比如要发送特定封包格式的数据包
var bytes=new Uint8Array(pcm.buffer); //需要封装的音频二进制数据
var header=new Uint8Array(2); //假设 2 字节包头
header[0]=0xEF; header[1]=0x01; //填充任意需要的内容
var packet=new Uint8Array(header.length+bytes.length); //拼接
packet.set(header,0);
packet.set(bytes,header.length); //拼接完成
【附】Android App - WebView 中录音示例
app-support-sample/demo_android目录中提供了 Android 测试项目源码(如果不想自己打包可以用打包好的 apk 来测试,demo_android 目录下的app-debug.apk.zip
,自行去掉.zip 后缀)。
在 Android App WebView 中使用本库来 H5 录音,需要在 App 源码中实现以下几部分:
在
AndroidManifest.xml
声明需要用到的两个权限,第二个也必须的<uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
WebView 的 WebChromeClient 中实现
onPermissionRequest
网页授权请求
如果未实现此方法,WebView 默认会拒绝 H5 录音权限;onPermissionRequest 中需要先获取 App 的系统录音权限(Activity 里面必须先用 this.checkSelfPermission 检测权限,否则直接 this.requestPermissions 会造成 WebView 触发 touchcancel 打断长按),然后再 grant 网页权限,可参考 Android Demo 中的MainActivity.java中的实现代码。
注:如果应用的腾讯 X5 内核
,可能还须提供android.permission.CAMERA
权限,和调用webView.setWebChromeClientExtension
来重写 X5 的IX5WebChromeClientExtension.onPermissionRequest
方法来进行权限处理
- 如需后台录音,还需要实现 Android 后台录音保活服务
自Android 9
开始,为了保护用户隐私,锁屏或进入后台一段时间后 App 可能会被禁止访问麦克风、摄像头等功能,导致无法录音、或录音数据全部是静音,因此需要使用保活机制才能在后台录音,详细请参考app-support-sample/demo_android,里面专门有一个章节讲解保活。
【附】iOS App - WebView 中录音示例
app-support-sample/demo_ios目录中提供了 iOS 测试项目源码(需自行用 xcode 编译运行)。
在 iOS App WebView 中使用本库来录音,需要在 App 源码 Info.plist
中声明使用麦克风 NSMicrophoneUsageDescription
,无需其他处理,WebView 会自己处理好录音权限;注意:iOS App 需要在项目 Background Modes 中勾选 Audio 才能在后台保持录音,不然 App 切到后台后立马会停止录音。
iOS 14.3+以上版本才支持 WebView 中进行 H5 录音。
iOS 15+以上版本提供了静默授权支持,需要 WKWebView 的 uiDelegate 实现WKUIDelegate requestMediaCapturePermissionFor 接口,可参考 iOS Demo 中的MainView.swift中的代码;如果未实现,会导致 H5 录音每次打开页面后第一次录音时、或长时间无操作再打开录音时均会弹出录音权限对话框。
iOS 11.0-14.2:纯粹的 H5 录音在 iOS WebView 中是不支持的,需要有 Native 层的支持,具体参考 RecordApp 中的app-support-sample/demo_ios测试项目源码。
【附】微信小程序中录音集成参考
RecordApp
支持在微信小程序中直接进行录音,请参考小程序 Demo 项目:miniProgram-wx。
[不推荐] 如果仅使用Recorder
的话,将只能在 H5 页面中使用,然后在小程序的web-view
中显示这个 H5,和普通浏览器没有区别。
【附】Electron 程序中录音集成参考
在 Electron BrowserWindow 中使用本库来录音,需要程序本身先获得录音权限;在调用open
打开录音前,需要先到主进程使用 systemPreferences.getMediaAccessStatus('microphone')
判断程序是否有录音权限,如果是未申请权限,需要调用 systemPreferences.askForMediaAccess('microphone')
来请求权限;程序有权限后,即可和普通网页一样正常的录音。
【附】uni-app 集成参考
RecordApp
支持在 uni-app 中直接进行录音,支持编译成:H5、Android App、iOS App、微信小程序,请参考 Demo 项目和文档:demo_UniApp,对应的组件可到DCloud 插件市场下载。
[不推荐] 如果仅使用Recorder
的话,因为 uni-app 中的renderjs
是直接运行在视图层 WebView 中的,因此可以通过在renderjs
中加载 Recorder 来进行录音;此方法支持 App、H5,但不支持小程序。同时需要注意在开发 App 平台的代码时,需在调用rec.open
前,在原生层获取到录音权限;和上面的 Android 和 iOS 一样先配置好录音权限声明,再调用权限请求接口(iOS 的 WebView 会自动处理可以不请求),在逻辑层中编写 js 权限处理代码。
:open_book:需要注意的细节和问题
本库期待的使用场景是语音录制,因此音质只要不比高品质的感觉差太多就行;1 分钟的语音进行编码是很快的,但如果录制超长的录音,比如 10 分钟以上,不同类型格式的编码可能会花费比较长的时间,大部分编码器支持边录边转码(Worker),因此录音时转码速度极快。另外未找到双声道语音录制存在的意义(翻倍录音数据大小,并且拉低音质),因此特意仅对单声道进行支持。
浏览器 Audio Media兼容性mp3 最好,wav 还行,其他要么不支持播放,要么不支持编码;因此本库最佳推荐使用 mp3、wav 格式,代码也是优先照顾这两种格式。
getUserMedia
的浏览器支持情况和兼容性,可到Can I use查看。
留意中途来电话:在移动端录音时,如果录音中途来电话,或者通话过程中打开录音,是不一定能进行录音的;经过简单测试发现,iOS 上 Safari 将暂停返回音频数据,直到通话结束才开始继续有音频数据返回;小米上 Chrome 不管是来电还是通话中开始录音都能对麦克风输入的声音进行录音;只是简单测试,更多机器和浏览器并未做测试,不过整体上来看来电话或通话中进行录音的可行性并不理想,也不赞成在这种过程中进行录音;但只要通话结束后录音还是会正常进行,影响基本不大。
录音时对播放音频的影响:仅在移动端,如果录音配置中未禁用降噪+回声消除(浏览器默认开启降噪+回声),打开录音后,如果同时播放音频,此时系统播放音量可能会变得很小,关闭录音后一般可恢复音量;PC 上 和 禁用降噪+回声消除后 似乎无此影响,但 iOS 上如果禁用又可能会导致无法正常录音,详细请阅读配置文档中的audioTrackSet
参数说明。
移动端锁屏录音:手机锁屏后浏览器的运行状态是一个玄学,是否能录音不可控;不同手机、甚至同一手机在不同状态下,有可能能录又有可能不能录,且无法检测;可以调用 navigator.wakeLock
来阻止手机自动锁屏,不支持的直接简单粗暴的 循环+静音 播放一段视频,来阻止锁屏,就是有点费电,具体实现可参考 H5 在线测试页面内的wakeLockClick
方法。
特别注:如果在iframe
里面调用的录音功能,并且和上层的网页是不同的域(跨域了),如果未设置相应策略,权限永远是被拒绝的,参考此处。另外如果要在非跨域的 iframe
里面使用,最佳实践应该是让 window.top 去加载 Recorder(异步加载 js),iframe 里面使用 top.Recorder,免得各种莫名其妙。
已知问题
此处已清除 7 个已知问题,大部分无法解决的问题会随着时间消失;问题主要集中在 iOS 部分版本上(不同系统版本问题不重样),好在这玩意能更新
2025-03-06 已知现有版本的 Recorder.SampleData 在部分条件下,录音实时转换采样率时,如果两个采样率不是倍数关系时(44100 和 16000 互转),内部的每次转换会引入杂音,大部分情况下并不明显,有些蓝牙耳机录制的 44100 采样率数据(实际只有 24000),经过转换后杂音会非常明显;后续版本会尝试修正这个转换错误。
2024-09-09 #230 已知 open 源码中的trackSet[sampleRateTxt]=...
强制设置采样率会导致部分设备无法正常打开麦克风,导致录音无声(或无法使用蓝牙等外设),需要修改源码去掉这行源码,同时设置 241020 版本开始已移除此代码进行了修复,并增强了 MediaRecorder 的支持。Recorder.ConnectEnableWebM=false
,
2024-09-09 部分 iOS 上(含新系统)第二次打开录音后无法正常开始录音,在开始录音前调用一次,241020 版本开始将不再保留打开状态的 Recorder.Ctx。Recorder.Destroy()
清除掉已打开的AudioContext
可能会恢复正常
2023-12-06 iOS 上反复弹出的权限弹框:据 QQ 群内1806152243
开发者反馈,进入页面打开一次录音并关闭后,如果没有在页面上进行任何用户交互操作(点击、触摸、滑动等),大约 35 秒左右之后,重新打开录音时,浏览器将会再次弹出录音权限对话框;iOS Safari 上稳定复现;这是浏览器自己的行为,js 无法控制,设计交互逻辑时应当注意。
2023-02-22 iPhone 14:有部分开发者反馈 iPhone14 上关闭录音后再次打开录音,会出现无法录音的情况,目前并不清楚是只有 iPhone14 上有问题,还是 iOS16 均有问题;估计是新的 WebKit 改了相关源码印度阿三没有测试,js 没办法解决此问题,静候 iOS 更新,也许下一个系统更新就自动修复了;建议针对 iOS 环境,全局只 open 一次,不要 close,挂在那里录音,可减少 iOS 系统问题带来的影响(负优化+耗电)。 2023-7-1,此问题已修复,原因出在 AudioContext 上,iOS 新版本上似乎不能共用一个 AudioContext(新版本每次 open 均会创建新的 AudioContext),并且 iOS 上 AudioContext 的 resume 行为和其他浏览器不相同,如果不是通过用户操作(触摸、点击等)进行调用,将无法 resume,参考 ztest_AudioContext_resume.html 测试用例。
2020-04-26 Safari Bug:据 QQ 群内1048506792
、190451148
开发者反馈研究发现,IOS ?-13.X Safari 内打开录音后,如果切换到了其他标签、或其他 App 并且播放了任何声音,此时将会中断已打开的录音(系统级的?),切换回正在录音的页面,这个页面的录音功能将会彻底失效,并且刷新也无法恢复录音;表现为关闭录音后再次打开录音,能够正常获得权限,但浏览器返回的采集到的音频为静默的 PCM,此时地址栏也并未显示出麦克风图标,刷新这个标签也也是一样不能正常获得录音,只有关掉此标签新打开页面才可正常录音。如果打开录音后关闭了录音,然后切换到其他标签或 App 播放声音,然后返回录音页面,不会出现此问题。此为 Safari 的底层 Bug。使用长按录音类似的用户交互可大幅度避免踩到这坨翔。
:open_book:方法文档
【构造】rec=Recorder(set)
构造函数,拿到Recorder
的实例,然后可以进行请求获取麦克风权限和录音。
set
参数为配置对象,默认配置值如下:
set={
type:"mp3" //输出类型:mp3,wav 等,使用一个类型前需要先引入对应的编码引擎
,bitRate:16 //比特率,必须是数字 wav(位):16、8,MP3(单位 kbps):8kbps 时文件大小 1k/s,16kbps 2k/s,录音文件很小
,sampleRate:16000 //采样率,必须是数字,wav 格式(8 位)文件大小=sampleRate*时间;mp3 此项对低比特率文件大小有影响,高比特率几乎无影响。
//wav 任意值,mp3 取值范围:48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000
,onProcess:NOOP //接收到录音数据时的回调函数:fn(buffers,powerLevel,bufferDuration,bufferSampleRate,newBufferIdx,asyncEnd)
//返回值:onProcess 如果返回 true 代表开启异步模式,在某些大量运算的场合异步是必须的,必须在异步处理完成时调用 asyncEnd(不能真异步时需用 setTimeout 包裹);返回其他值或者不返回为同步模式(需避免在回调内执行耗时逻辑);如果开启异步模式,在 onProcess 执行后新增的 buffer 会全部替换成空数组,因此本回调开头应立即将 newBufferIdx 到本次回调结尾位置的 buffer 全部保存到另外一个数组内,处理完成后写回 buffers 中本次回调的结尾位置。
//buffers=[[Int16,...],...]:缓冲的 PCM 数据块(16 位小端 LE),为从开始录音到现在的所有 pcm 片段,每次回调可能增加 0-n 个不定量的 pcm 片段。
//注意:buffers 数据的采样率为 bufferSampleRate,它和 set.sampleRate 不一定相同,可能为浏览器提供的原始采样率 rec.srcSampleRate,也可能为已转换好的采样率 set.sampleRate;如需浏览器原始采样率的数据,请使用 rec.buffers 原始数据,而不是本回调的参数;如需明确和 set.sampleRate 完全相同采样率的数据,请在 onProcess 中自行连续调用采样率转换函数 Recorder.SampleData(),配合 mock 方法可实现实时转码和压缩语音传输;修改或替换 buffers 内的数据将会改变最终生成的音频内容(注意不能改变第一维数组长度),比如简单有限的实现实时静音、降噪、混音等处理,详细参考下面的 rec.buffers
//powerLevel:当前缓冲的音量级别 0-100。
//bufferDuration:已缓冲时长。
//bufferSampleRate:buffers 缓存数据的采样率(当 type 支持边录边转码(Worker)时,此采样率和设置的采样率相同,否则不一定相同)。
//newBufferIdx:本次回调新增的 buffer 起始索引。
//asyncEnd:fn() 如果 onProcess 是异步的(返回值为 true 时),处理完成时需要调用此回调,如果不是异步的请忽略此参数,此方法回调时必须是真异步(不能真异步时需用 setTimeout 包裹)。
//如果需要绘制波形之类功能,需要实现此方法即可,使用以计算好的 powerLevel 可以实现音量大小的直观展示,使用 buffers 可以达到更高级效果
//如果需要实时上传(发送)之类的,可以配合 Recorder.SampleData 方法,将 buffers 中的新数据连续的转换成 pcm,或使用 mock 方法将新数据连续的转码成其他格式,可以参考文档里面的:Demo 片段列表 -> 实时转码并上传-通用版;基于本功能可以做到:实时转发数据、实时保存数据、实时语音识别(ASR)等
//*******高级设置******
//,sourceStream:MediaStream Object
//可选直接提供一个媒体流,从这个流中录制、实时处理音频数据(当前 Recorder 实例独享此流);不提供时为普通的麦克风录音,由 getUserMedia 提供音频流(所有 Recorder 实例共享同一个流)
//比如:audio、video 标签 dom 节点的 captureStream 方法(实验特性,不同浏览器支持程度不高)返回的流;WebRTC 中的 remote 流;自己创建的流等
//注意:流内必须至少存在一条音轨(Audio Track),比如 audio 标签必须等待到可以开始播放后才会有音轨,否则 open 会失败
//注意:在 rec.close 时不会主动关闭此流,需自行管理关闭
//,runningContext:AudioContext
//可选提供一个 state 为 running 状态的 AudioContext 对象(ctx);默认会在 rec.open 时自动创建一个新的 ctx,无用户操作(触摸、点击等)时调用 rec.open 的 ctx.state 可能为 suspended,会在 rec.start 时尝试进行 ctx.resume,如果也无用户操作 ctx.resume 可能不会恢复成 running 状态(目前仅 iOS 上有此兼容性问题),导致无法去读取媒体流,这时请提前在用户操作时调用 Recorder.GetContext(true)来得到一个 running 状态 AudioContext(用完需调用 CloseNewCtx(ctx)关闭)
/*,audioTrackSet:{
deviceId:"",groupId:"" //指定设备的麦克风,通过 navigator.mediaDevices.enumerateDevices 拉取设备列表,其中 kind 为 audioinput 的是麦克风
,echoCancellation:true //回声消除(AEC)开关,不设置时由浏览器控制(一般为默认自动打开),设为 true 明确打开,设为 false 明确关闭
,noiseSuppression:true //降噪(ANS)开关,取值和回声消除开关一样
,autoGainControl:true //自动增益(AGC)开关,取值和回声消除开关一样
}*/
//普通麦克风录音时 getUserMedia 方法的 audio 配置参数;注意:不同浏览器的支持程度不同,提供的任何配置值都不一定会生效
//回声消除、降噪开关这些参数浏览器一般默认自动打开,移动端未明确禁用时可能会降低系统播放音量(关闭录音后可恢复)和仅提供 16k 采样率的音频流(不需要回声消除时可明确配置成禁用来获得 48k 高音质的流)
//由于麦克风是全局共享的,所以新配置后需要 close 掉以前的再重新 open
//同样可配置 videoTrackSet,更多参考: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints
//,disableEnvInFix:false 内部参数,禁用设备卡顿时音频输入丢失补偿功能,如果不清楚作用请勿随意使用
//,takeoffEncodeChunk:NOOP //fn(chunkBytes) chunkBytes=[Uint8,...]:实时编码环境下接管编码器输出,当编码器实时编码出一块有效的二进制音频数据时实时回调此方法;参数为二进制的 Uint8Array,就是编码出来的音频数据片段,所有的 chunkBytes 拼接在一起即为完整音频。本实现的想法最初由 QQ2543775048 提出。
//当提供此回调方法时,将接管编码器的数据输出,编码器内部将放弃存储生成的音频数据;如果当前编码器或环境不支持实时编码处理,将在 open 时直接走 fail 逻辑
//因此提供此回调后调用 stop 方法将无法获得有效的音频数据,因为编码器内没有音频数据,因此 stop 时返回的 blob 将是一个字节长度为 0 的 blob
//大部分录音格式编码器都支持实时编码(边录边转码),比如 mp3 格式:会实时的将编码出来的 mp3 片段通过此方法回调,所有的 chunkBytes 拼接到一起即为完整的 mp3,此种拼接的结果比 mock 方法实时生成的音质更加,因为天然避免了首尾的静默
//不支持实时编码的录音格式不可以提供此回调(wav 格式不支持,因为 wav 文件头中需要提供文件最终长度),提供了将在 open 时直接走 fail 逻辑
}
注意:set 内是数字的明确传数字,不要传字符串之类的导致不可预测的异常,其他有配置的地方也是一样(感谢214282049@qq.com
19-01-10 发的反馈邮件)。
注:如果录音结束后生成的音频文件的比特率和采样率和 set 中的不同,将会把 set 中的 bitRate、sampleRate 更新成音频文件的。
【方法】rec.open(success,fail)
普通的麦克风录音时:请求打开录音资源,如果浏览器不支持录音、用户拒绝麦克风权限、或者非安全环境(非 https、file 等)将会调用fail
;打开后需要调用close
来关闭,因为浏览器或设备的系统可能会显示正在录音。
直接提供的流set.sourceStream
时:会连接上此流进行录制,打开后同样需要调用close
来断开连接,但不会主动关闭此流,需自行管理此 sourceStream 的关闭。
success
=fn();
fail
=fn(errMsg,isUserNotAllow); 如果是用户主动拒绝的录音权限,除了有错误消息外,isUserNotAllow=true,方便程序中做不同的提示,提升用户主动授权概率
注意:此方法回调是可能是同步的(异常、或者已持有资源时)也可能是异步的(浏览器弹出权限请求时);一般使用时打开,用完立即关闭;可重复调用,可用来测试是否能录音;open 和 start 至少有一个应当在用户操作(触摸、点击等)下进行调用,原因参考runningContext
配置。
另外:普通的麦克风录音时,因为此方法会调起用户授权请求,如果仅仅想知道浏览器是否支持录音(比如:如果浏览器不支持就走另外一套录音方案),应使用Recorder.Support()
方法。
【方法】rec.close(success)
关闭释放录音资源,释放完成后会调用success()
回调。如果正在录音或者 stop 调用未完成前调用了 close 将会强制终止当前录音。
注意:普通的麦克风录音时(所有 Recorder 实例共享同一个流),如果创建了多个 Recorder 对象并且调用了 open(应避免同时有多个对象进行了 open),只有最后一个新建的才有权限进行实际的资源释放(和多个对象 close 调用顺序无关),浏览器或设备的系统才会不再显示正在录音的提示;直接提供的流 set.sourceStream 无此问题(当前 Recorder 实例独享此流,会直接释放掉录制此流的相关资源),但不会主动关闭此流,需自行管理此 sourceStream 的关闭。
【方法】rec.start()
开始录音,需先调用open
;未 close 之前可以反复进行调用开始新的录音。注意:open 和 start 至少有一个应当在用户操作(触摸、点击等)下进行调用,原因参考runningContext
配置。
只要 open 成功后,调用此方法是安全的,如果未 open 强行调用导致的内部错误将不会有任何提示,stop 时自然能得到错误;另外 open 操作可能需要花费比较长时间,如果中途调用了 stop,open 完成时(同步)的任何 start 调用将会被自动阻止,也是不会有提示的。
【方法】rec.stop(success,fail,autoClose)
结束录音并返回录音数据blob 文件对象
,拿到 blob 文件对象就可以为所欲为了,不限于立即播放、上传、下载保存。blob 可以用URL.createObjectURL
生成本地链接赋值给audio.src
进行播放,赋值给a.href
然后a.click()
进行下载(a 需提供download="xxx.mp3"
属性);或用XMLHttpRequest+FormData
、WebSocket
直接发送到服务器,或者用FileReader
读取成ArrayBuffer
或者Base64
给 js 处理。
success(blob,duration,mime) //录音成功回调
blob:录音二进制文件数据 audio/mp3|wav...格式;
默认是 Blob 对象,可设置 rec.dataType="arraybuffer"改成 ArrayBuffer(或全局设置 Recorder.DefaultDataType="arraybuffer")
duration:录音时长,单位毫秒
mime:"auido/mp3" blob 数据的类型,方便 ArrayBuffer 时区分类型
fail(errMsg) //录音出错回调
autoClose //可选,是否自动调用`close`,默认为`false`不调用
注意:当创建 rec 时提供了 takeoffEncodeChunk 后,你需要自行实时保存录音文件数据,因此 stop 时返回的 blob 的长度将为 0 字节。
提示:stop 时会进行音频编码,根据类型的不同音频编码花费的时间也不相同。对于支持边录边转码(Worker)的类型,将极速完成编码并回调;对于不支持的 10 几秒录音花费 2 秒左右算是正常,但内部采用了分段编码+setTimeout 来处理,界面卡顿不明显。
【方法】rec.pause()
暂停录音。
【方法】rec.resume()
恢复继续录音。
【属性】rec.buffers
此数据为从开始录音到现在为止的所有已缓冲的 PCM 片段列表(16 位小端 LE),buffers
=
[[Int16,...],...]
为二维数组;在没有边录边转码的支持时(mock 调用、非 mp3 等),录音 stop 时会使用此完整数据进行转码成指定的格式。
rec.buffers 中的 PCM 数据为浏览器采集的原始音频数据,采样率为浏览器提供的原始采样率rec.srcSampleRate
;在rec.set.onProcess
回调中buffers
参数就是此数据或者此数据重新采样后的新数据;修改或替换onProcess
回调中buffers
参数可以改变最终生成的音频内容,但修改rec.buffers
不一定会有效,因此你可以在onProcess
中修改或替换buffers
参数里面的内容,注意只能修改或替换上次回调以来新增的 buffer(不允许修改已处理过的,不允许增删第一维数组,允许将第二维数组任意修改替换成空数组也可以);以此可以简单有限的实现实时静音、降噪、混音等处理。
如果你需要长时间实时录音(如长时间语音通话),并且不需要得到最终完整编码的音频文件:
- 未提供 set.takeoffEncodeChunk 时,Recorder 初始化时应当使用一个未知的类型进行初始化(如: type:"unknown",仅仅用于初始化而已,实时转码可以手动转成有效格式,因为有效格式可能内部还有其他类型的缓冲,
unknown
类型onProcess buffers
和rec.buffers
是同一个数组);提供 set.takeoffEncodeChunk 接管了编码器实时输出时,无需特殊处理,因为编码器内部将不会使用缓冲; - 实时在
onProcess
中修改buffers
参数数组,可以只保留最后两个元素,其他元素设为 null(代码:onProcess: buffers[buffers.length-3]=null
),不保留也行,全部设为 null,以释放占用的内存;rec.buffers
将会自动清理,无需手动清理;注意:提供 set.takeoffEncodeChunk 时,应当延迟一下清理,不然 buffers 被清理掉时,这个 buffers 还未推入编码器进行编码; - wav 等不支持实时编码的格式录音结束时可以不用调用
stop
,直接调用close
丢弃所有数据即可,因为只要 buffers[0]==null 时 wav 格式调用stop
永远会直接走 fail 回调;mp3、pcm 等支持实时编码的格式无此限制,且需要调用stop
来编码输出最后一段音频数据。
【属性】rec.dataType
stop 时返回的录音数据类型,取值:blob
、arraybuffer
,默认为blob
(返回 Blob 文件对象);非浏览器环境中一般需要设为arraybuffer
(返回 ArrayBuffer 对象),或直接设置全局属性Recorder.DefaultDataType
。
【属性】rec.srcSampleRate
浏览器提供的原始采样率,只有 start 或 mock 调用后才会有值,此采样率就是 rec.buffers 数据的采样率。
【方法】rec.envIn(pcmData,pcmAbsSum)
本方法是一个内部使用的最为核心方法,如果你不知道用途,请勿随意调用,配套的有私有方法envStart(mockEnvInfo,sampleRate)
(私有方法请自行阅读源码),这两方法控制着录音的开启、实时音频输入逻辑,起到隔离平台环境差异的作用(Recorder、RecordApp 共享使用了本机制,实现了录音过程和平台环境无关)。
通过调用本方法,会在当前正在录制的录音中追加进新的 pcm 数据,每次调用本方法都会触发 onProcess 回调,也会触发 takeoffEncodeChunk 回调;从而可以做到:实时转码 pcm 成其他格式、在录音过程中插入音频数据、在新的录音中注入之前老的录音的 buffers 数据可以做到接续录音 等业务逻辑,可参考上面的 Demo 片段列表中的新录音从老录音接续、或录制中途插入音频
例子;含有 envStart 的完整调用例子可以参考这里面的 testEnvIn 方法。
pcmData
:[Int16,...]
为一维数组,pcm 音频数据的采样率必须是rec.srcSampleRate
(如果不是,请用Recorder.SampleData()
方法先转换好)
pcmAbsSum
:pcmData 所有采样的绝对值的和,用来传递给Recorder.PowerLevel
方法计算音量百分比,最终是给 onProcess 使用,如果不需要计算音量百分比,直接给 0 即可。
【方法】rec.mock(pcmData,pcmSampleRate)
模拟一段录音数据,后面直接调用 stop 进行编码得到音频文件。需提供 pcm 数据 pcmData
=
[Int16,...]
为 Int16Array 一维数组,和 pcm 数据的采样率 pcmSampleRate
。调用本方法后无需调用也无法调用 open、close、start 等方法,只能调用 stop,如果之前已经开始了录音,前面的录音数据全部会被丢弃(一般不要共用一个 rec,直接创建个新的再调用 mock);本方法主要用于音频转码。
提示:在录音实时回调中配合Recorder.SampleData()
方法使用效果更佳,可实时生成小片段语音文件。
注意:pcmData 为一维数组,如果提供二维数组将会产生不可预料的错误;如果需要使用类似onProcess
回调的buffers
或者rec.buffers
这种 pcm 列表(二维数组)时,可自行展开成一维,或者使用Recorder.SampleData()
方法转换成一维。
//提供一个变量,用于连续转换 buffers 数据,注意开始新的转换时需要重置为 null
var prevChunk=null;
//将 onProcess 回调中的 buffers 转成 pcm onProcess(buffers,powerLevel,duration,sampleRate)
//var chunk=Recorder.SampleData(buffers,sampleRate,sampleRate,prevChunk);
//将 rec.buffers 转成 pcm
var chunk=Recorder.SampleData(rec.buffers,rec.srcSampleRate,rec.srcSampleRate,prevChunk);
prevChunk=chunk; //存起来,下次从当前已转换位置继续转换
//这就是上面的代码自动截取到的最新未转换 pcm 数据
//你也可以自行从 buffers 中截取出需要转码的 pcm
//或者从其他地方获取到 pcm 数据,比如用解码其他音频文件得到 pcm,即可实现不同音频格式之间转码
var pcm=chunk.data; //pcm=new Int16Array(pcmArrayBuffer) 可以使用 16 位 pcm 二进制数据直接构造
var sampleRate=chunk.sampleRate;
//调用 mock 方法把 pcm 转码成 mp3 或其他格式
var mockRec=Recorder({ type:"mp3",bitRate:16,sampleRate:16000 });
mockRec.mock(pcm,sampleRate);
mockRec.stop(function(blob,duration){
console.log("pcm 已转码成 mp3",blob,duration);
},function(msg){
console.error("不应该出现的错误:"+msg);
});
【静态方法】Recorder.Support()
判断浏览器是否支持录音,随时可以调用。注意:仅仅是检测浏览器支持情况,不会判断和调起用户授权(rec.open()会判断用户授权),不会判断是否支持特定格式录音。
【静态方法】Recorder.GetContext(tryNew)
获取 AudioContext 对象,如果浏览器不支持将返回 null。tryNew=false 时返回全局的 Recorder.Ctx,Ctx.state 可能是 closed,仅限用于解码等操作。tryNew=true 时会尝试创建新的 ctx(不支持 close 的老浏览器依旧返回全局的),注意用完必须自己调用 CloseNewCtx(ctx)关闭;注意:非用户操作(触摸、点击等)时调用返回的 ctx.state 可能是 suspended 状态,需要在用户操作时调用 ctx.resume 恢复成 running 状态,参考 rec 的 runningContext 配置。
本方法调用一次后,可通过Recorder.Ctx
来获得全局对象,但仅可用于音频文件解码:Recorder.Ctx.decodeAudioData(fileArrayBuffer)
。本方法是从老版本的Recorder.Support()
中剥离出来的,调用 Support 会自动调用一次本方法。已知 iOS16 中全局对象无法多次用于录音,当前 Recorder 打开录音时均会尝试创建新的非全局对象,同时会保留一个全局的对象。
【静态方法】Recorder.IsOpen()
由于 Recorder 持有的普通麦克风录音资源是全局唯一的,可通过此方法检测是否有 Recorder 已调用过 open 打开了麦克风录音功能;注意:此方法无法检测直接提供的流 set.sourceStream 是否已打开,需自行判断。
【静态方法】Recorder.Destroy()
销毁已持有的所有全局资源(AudioContext、Worker),当要彻底移除 Recorder 时需要显式的调用此方法。大部分情况下不调用 Destroy 也不会造成问题。
【静态方法】Recorder.CLog
全局的日志输出函数,可赋值一个空函数来屏蔽 Recorder 的日志输出Recorder.CLog=function(){}
。
【静态属性】Recorder.TrafficImgUrl
流量统计用 1 像素图片地址,在 Recorder 首次被实例化时将往这个地址发送一个请求,请求是通过 Image 对象来发送,安全可靠;默认开启统计,url 为本库的 51la 统计用图片地址,为空响应流量消耗非常小,因此对使用几乎没有影响。
设置为空字符串后将不参与统计,大部分情况下无需关闭统计,如果你网页的 url 私密性要求很高,请在调用 Recorder 之前将此 url 设为空字符串;本功能于 2019-11-09 添加,点此前往 51la 查看统计概况。
【静态属性】Recorder.i18n
内置的简版国际化多语言支持实现,详细请参考下面的 i18n 章节。
【静态属性】Recorder.DefaultDataType
全局默认 stop 时返回的录音数据类型,取值:blob
、arraybuffer
,默认为blob
(返回 Blob 文件对象),非浏览器环境中一般需要设为arraybuffer
(返回 ArrayBuffer 对象);rec 实例的dataType
属性配置优先级更高。
【静态属性】Recorder.BufferSize
普通的麦克风录音时全局的 AudioContext 缓冲大小,默认值为 4096。会影响 H5 录音时的 onProcess 调用速率,相对于 AudioContext.sampleRate=48000 时,4096 接近 12 帧/s(移动端帧率可能会低一些),调节此参数可生成比较流畅的回调动画。
取值 256, 512, 1024, 2048, 4096, 8192, or 16384
注意:取值不能过低,2048 开始不同浏览器可能回调速率跟不上造成音质问题。一般无需调整,调整后需要先 close 掉已打开的录音,再 open 时才会生效。
如果是直接提供的流 set.sourceStream,不是默认的从麦克风录音时,这个属性可以改成由 Recorder 的实例提供,比如 rec.BufferSize=1024,这样就不会受全局干扰。
这个属性在旧版 Recorder 中是放在已废弃的 set.bufferSize 中,后面因为兼容处理 Safari 上 MediaStream 断开后就无法再次进行连接使用的问题(表现为静音),把 MediaStream 连接也改成了全局只连接一次,因此 set.bufferSize 就移出来变成了 Recorder 的属性
【静态属性】Recorder.ConnectEnableWebM
音频采集连接方式:启用时尝试使用 MediaRecorder.WebM.PCM,默认为 true 启用,未启用或者不支持时使用 AudioWorklet 或过时的 ScriptProcessor 来连接;本连接方式仅对普通麦克风录音时有效,直接提供了流(set.sourceStream)时将当做未启用处理。
使用 MediaRecorder 采集到的音频数据比其他方式更好,几乎不存在丢帧现象,所以音质明显会好很多,建议保持开启; 有些浏览器不支持录制 PCM 编码的 WebM,如 FireFox、低版本的 Chrome,将依旧使用 AudioWorklet 或 ScriptProcessor 来连接采集。
可以额外提供一个设置Recorder.ConnectWebMOptions={}
来当做 MediaRecorder 的 options 参数,支持的参数请参考此文档。
本连接实现原理:通过 MediaRecorder 对 MediaStream 进行录制,格式
audio/webm; codecs=pcm
,MediaRecorder 会将实时录制的 PCM 数据(48k+32 位)回传给 js,因此只需要知道 WebM 的封装格式就能提取出 PCM 数据,请参考测试代码:WebM 格式解析并提取音频
【静态属性】Recorder.ConnectEnableWorklet
音频采集连接方式:是否要启用 AudioWorklet (AudioWorkletNode) 来进行连接;默认为 false 禁用,禁用后将使用过时的 ScriptProcessor (AudioContext.createScriptProcessor) 来连接;如果启用了 Recorder.ConnectEnableWebM 并且有效时,本参数将不起作用,否则才会生效。
启用后如果浏览器不支持 AudioWorklet,将只会使用老的 ScriptProcessor 来进行音频采集连接;如果浏览器已停止支持 ScriptProcessor,将永远会尝试启用 AudioWorklet 而忽略此配置值。
未雨绸缪,目前只需要 ScriptProcessor 就能做到 100%兼容所有浏览器;以后就算只能用 AudioWorklet 时,也还是需要保留 ScriptProcessor 用来支持老浏览器;所以默认为禁用,现在实现 AudioWorklet 的目的是让代码更经得起考验。
导致浏览器崩溃:某些浏览器的 AudioWorklet 和 AudioContext 的 resume 一起作用时会产生崩溃现象,错误代码:STATUS_ACCESS_VIOLATION;此坑已填好,复现测试页面。
注意:由于 AudioWorklet 内部1 秒会产生 375 次回调,在移动端可能会有性能问题导致浏览器回调丢失,进而导致录音数据、时长变短,PC 端似乎无此影响,可通过定时 1 分钟录音来检测影响(如果短了 1 秒以上即为有问题);在无明显优势好处的前提下,暂不建议启用。
【静态方法】Recorder.SampleData(pcmDatas,pcmSampleRate,newSampleRate,prevChunkInfo,option)
对 pcm 数据的采样率进行转换,可配合 mock 方法可转换成音频文件,比如实时转换成小片段语音文件。
注意:从 241020 版本开始,支持任意采样率转换;之前的老版本只会将高采样率的 pcm 转成低采样率的 pcm,老版本由低转高时不会进行转换处理。
pcmDatas
: [[Int16,...]] pcm 片段列表,二维数组,比如可以是:rec.buffers、onProcess 中的 buffers;二维数组里面是 Int16Array,也可传 Float32Array(会转成 Int16Array)
pcmSampleRate
:48000 pcm 数据的采样率,比如用:rec.srcSampleRate、onProcess 中的 bufferSampleRate
newSampleRate
:16000 需要转换成的采样率,241020 版本后支持转成任意采样率,之前老版本仅支持转成低采样率<=pcmSampleRate
prevChunkInfo
:{} 可选,上次调用时的返回值,用于连续转换,本次调用将从上次结束位置开始进行处理。或可自行定义一个 ChunkInfo 从 pcmDatas 指定的位置开始进行转换
option
:
option:{ 可选,配置项
frameSize:123456 帧大小,每帧的 PCM Int16 的数量,采样率转换后的 pcm 长度为 frameSize 的整数倍,用于连续转换。目前仅在 mp3 格式时才有用,frameSize 取值为 1152,这样编码出来的 mp3 时长和 pcm 的时长完全一致,否则会因为 mp3 最后一帧录音不够填满时添加填充数据导致 mp3 的时长变长。
frameType:"" 帧类型,一般为 rec.set.type,提供此参数时无需提供 frameSize,会自动使用最佳的值给 frameSize 赋值,目前仅支持 mp3=1152(MPEG1 Layer3 的每帧采采样数),其他类型=1。
以上两个参数用于连续转换时使用,最多使用一个,不提供时不进行帧的特殊处理,提供时必须同时提供 prevChunkInfo 才有作用。最后一段数据处理时无需提供帧大小以便输出最后一丁点残留数据。
}
返回值 ChunkInfo
{
//可定义,从指定位置开始转换到结尾
index:0 pcmDatas 已处理到的索引;比如每次都是单个 pcm 需要连续处理时,可每次调用前重置成 0,pcmDatas 仅需传入`[pcm]`固定一个元素
offset:0.0 已处理到的 index 对应的 pcm 中的偏移的下一个位置(提升采样率时为结果的 pcm)
raisePrev:null 提升采样率时的前一个 pcm 结果采样值
//可定义,指定的一个滤波配置:默认使用 Recorder.IIRFilter 低通滤波(可有效抑制混叠产生的杂音,小采样率大于高采样率的 75%时不默认滤波),如果提供了配置但 fn 为 null 时将不滤波;sr、srn 为此滤波函数对应的初始化采样率,当采样率和参数的不一致时将重新设为默认函数
filter:null||{fn:fn(sample),sr:pcmSampleRate,srn:newSampleRate}
//仅作为返回值
frameNext:null||[Int16,...] 下一帧的部分数据,frameSize 设置了的时候才可能会有
sampleRate:16000 结果的采样率=newSampleRate,241020 版本前的老版本<=newSampleRate
data:[Int16,...] 转换后的 PCM 结果(16 位小端 LE),为一维数组,可直接 new Blob([data],{type:"audio/pcm"})生成 Blob 文件,或者使用 mock 方法转换成其他音频格式;注意:如果是连续转换,并且 pcmDatas 中并没有新数据时,data 的长度可能为 0
}
【静态方法】Recorder.IIRFilter(useLowPass,sampleRate,freq)
IIR 低通、高通滤波;可重新赋值一个函数,来改变 Recorder 的默认行为,比如 SampleData 中的低通滤波。返回的是一个函数,用此函数对 pcm 的每个采样值按顺序进行处理即可(不同 pcm 不可共用);注意此函数返回值可能会越界超过 Int16 范围,自行限制一下即可:Math.min(Math.max(val,-0x8000),0x7FFF)。
useLowPass
: true 或 false,true 为低通滤波,false 为高通滤波
sampleRate
: 待处理 pcm 的采样率
freq
: 截止频率 Hz,最大频率为 sampleRate/2,低通时会切掉高于此频率的声音,高通时会切掉低于此频率的声音,注意滤波并非 100%的切掉不需要的声音,而是减弱频率对应的声音,离截止频率越远对应声音减弱越厉害,离截止频率越近声音就几乎无衰减
【静态方法】Recorder.PowerLevel(pcmAbsSum,pcmLength)
计算音量百分比的一个方法,返回值:0-100,主要当做百分比用;注意:这个不是分贝,因此没用 volume 当做名称。
pcmAbsSum
: pcm Int16 所有采样的绝对值的和
pcmLength
: pcm 长度
【静态方法】Recorder.PowerDBFS(maxSample)
计算音量,单位 dBFS(满刻度相对电平),返回值:-100~0 (最大值 0dB,最小值 -100 代替-∞)。
maxSample
: 为 16 位 pcm 采样的绝对值中最大的一个(计算峰值音量),或者为 pcm 中所有采样的绝对值的平局值
:open_book:Extensions - 插件文档
在src/extensions
目录内为插件支持库,这些插件库默认都没有合并到生成代码中,需单独引用(dist
或src
中的)才能使用。
【可移植】大部分可视化插件均可以移植到其他语言环境,比如:Android、iOS 原生实现,如需定制可联系作者。
【附】部分插件使用效果图(在线运行观看):
WaveView 插件
waveview.js,7kb 大小源码,录音时动态显示波形,具体样子参考演示地址页面。此插件参考MCVoiceWave库编写的,具体代码在https://github.com/HaloMartin/MCVoiceWave/blob/f6dc28975fbe0f7fc6cc4dbc2e61b0aa5574e9bc/MCVoiceWave/MCVoiceWaveView.m
中;本可视化插件可以移植到其他语言环境,如需定制可联系作者。
此插件是在录音时onProcess
回调中使用。基础使用方法:
var wave;
var rec=Recorder({
onProcess:function(buffers,powerLevel,bufferDuration,bufferSampleRate){
wave.input(buffers[buffers.length-1],powerLevel,bufferSampleRate);//输入音频数据,更新显示波形
}
});
rec.open(function(){
wave=Recorder.WaveView({elem:".elem"}); //创建 wave 对象,写这里面浏览器妥妥的
rec.start();
});
【构造】wave=Recorder.WaveView(set)
构造函数,set
参数为配置对象,默认配置值如下:
set={
elem:"css selector" //自动显示到 dom,并以此 dom 大小为显示大小
//或者配置显示大小,手动把 waveviewObj.elem 显示到别的地方
,width:0 //显示宽度
,height:0 //显示高度
//H5 环境以上配置二选一
compatibleCanvas: CanvasObject //提供一个兼容 H5 的 canvas 对象,需支持 getContext("2d"),支持设置 width、height,支持 drawImage(canvas,...)
,width:0 //canvas 显示宽度
,height:0 //canvas 显示高度
//非 H5 环境使用以上配置,比如微信小程序、uni-app
,scale:2 //缩放系数,应为正整数,使用 2(3? no!)倍宽高进行绘制,避免移动端绘制模糊
,speed:9 //移动速度系数,越大越快
,phase:21.8 //相位,调整了速度后,调整这个值得到一个看起来舒服的波形
,fps:20 //绘制帧率,调整后也需调整 phase 值
,keep:true //当停止了 input 输入时,是否保持波形,设为 false 停止后将变成一条线
,lineWidth:3 //线条基础粗细
//渐变色配置:[位置,css 颜色,...] 位置: 取值 0.0-1.0 之间
,linear1:[0,"rgba(150,96,238,1)",0.2,"rgba(170,79,249,1)",1,"rgba(53,199,253,1)"] //线条渐变色 1,从左到右
,linear2:[0,"rgba(209,130,255,0.6)",1,"rgba(53,199,255,0.6)"] //线条渐变色 2,从左到右
,linearBg:[0,"rgba(255,255,255,0.2)",1,"rgba(54,197,252,0.2)"] //背景渐变色,从上到下
}
【方法】wave.input(pcmData,powerLevel,sampleRate)
输入音频数据,更新波形显示。pcmData [Int16,...]
一维数组,为当前的录音数据片段,其他参数和onProcess
回调相同。
WaveSurferView 插件
wavesurfer.view.js,8kb 大小源码,音频可视化波形显示,具体样子参考演示地址页面;本可视化插件可以移植到其他语言环境,如需定制可联系作者。
此插件的使用方式和WaveView
插件完全相同,请参考上面的WaveView
来使用;本插件的波形绘制直接简单的使用 PCM 的采样数值大小来进行线条的绘制,同一段音频绘制出的波形和 Audition 内显示的波形外观上几乎没有差异。
已知问题:iOS 上微信小程序基础库存在 bug,canvas.drawImage(canvas)可能无法绘制,可能会导致本可视化插件在 iOS 小程序上不能正确显示,其他环境下无此兼容性问题。
【构造】surfer=Recorder.WaveSurferView(set)
构造函数,set
参数为配置对象,默认配置值如下:
set={
elem:"css selector" //自动显示到 dom,并以此 dom 大小为显示大小
//或者配置显示大小,手动把 surferObj.elem 显示到别的地方
,width:0 //显示宽度
,height:0 //显示高度
//H5 环境以上配置二选一
compatibleCanvas: CanvasObject //提供一个兼容 H5 的 canvas 对象,需支持 getContext("2d"),支持设置 width、height,支持 drawImage(canvas,...)
,compatibleCanvas_2x: CanvasObject //提供一个宽度是 compatibleCanvas 的 2 倍 canvas 对象
,width:0 //canvas 显示宽度
,height:0 //canvas 显示高度
//非 H5 环境使用以上配置,比如微信小程序、uni-app
,scale:2 //缩放系数,应为正整数,使用 2(3? no!)倍宽高进行绘制,避免移动端绘制模糊
,fps:50 //绘制帧率,不可过高,50-60fps 运动性质动画明显会流畅舒适,实际显示帧率达不到这个值也并无太大影响
,duration:2500 //当前视图窗口内最大绘制的波形的持续时间,此处决定了移动速率
,direction:1 //波形前进方向,取值:1 由左往右,-1 由右往左
,position:0 //绘制位置,取值 -1 到 1,-1 为最底下,0 为中间,1 为最顶上,小数为百分比
,centerHeight:1 //中线基础粗细,如果为 0 不绘制中线,position=±1 时应当设为 0
//波形颜色配置:[位置,css 颜色,...] 位置: 取值 0.0-1.0 之间
,linear:[0,"rgba(0,187,17,1)",0.7,"rgba(255,215,0,1)",1,"rgba(255,102,0,1)"]
,centerColor:"" //中线 css 颜色,留空取波形第一个渐变颜色
}
【方法】surfer.input(pcmData,powerLevel,sampleRate)
输入音频数据,更新波形显示。pcmData [Int16,...]
一维数组,为当前的录音数据片段,其他参数和onProcess
回调相同。
FrequencyHistogramView 插件
frequency.histogram.view.js + lib.fft.js,12kb 大小源码,音频可视化频率直方图显示,具体样子参考演示地址页面。此插件核心算法参考 Java 开源库jmp123的代码编写的,jmp123
版本0.3
;直方图特意优化主要显示 0-5khz 语音部分(线性),其他高频显示区域较小,不适合用来展示音乐频谱,可通过配置 fullFreq 来恢复成完整的线性频谱,或自行修改源码修改成倍频程频谱(伯德图、对数频谱);本可视化插件可以移植到其他语言环境,如需定制可联系作者。
此插件的使用方式和WaveView
插件完全相同,请参考上面的WaveView
来使用;请注意:必须同时引入lib.fft.js
才能正常工作。
【构造】histogram=Recorder.FrequencyHistogramView(set)
构造函数,set
参数为配置对象,默认配置值如下:
set={
elem:"css selector" //自动显示到 dom,并以此 dom 大小为显示大小
//或者配置显示大小,手动把 frequencyObj.elem 显示到别的地方
,width:0 //显示宽度
,height:0 //显示高度
//H5 环境以上配置二选一
compatibleCanvas: CanvasObject //提供一个兼容 H5 的 canvas 对象,需支持 getContext("2d"),支持设置 width、height,支持 drawImage(canvas,...)
,width:0 //canvas 显示宽度
,height:0 //canvas 显示高度
//非 H5 环境使用以上配置,比如微信小程序、uni-app
,scale:2 //缩放系数,应为正整数,使用 2(3? no!)倍宽高进行绘制,避免移动端绘制模糊
,fps:20 //绘制帧率,不可过高
,lineCount:30 //直方图柱子数量,数量的多少对性能影响不大,密集运算集中在 FFT 算法中
,widthRatio:0.6 //柱子线条宽度占比,为所有柱子占用整个视图宽度的比例,剩下的空白区域均匀插入柱子中间;默认值也基本相当于一根柱子占 0.6,一根空白占 0.4;设为 1 不留空白,当视图不足容下所有柱子时也不留空白
,spaceWidth:0 //柱子间空白固定基础宽度,柱子宽度自适应,当不为 0 时 widthRatio 无效,当视图不足容下所有柱子时将不会留空白,允许为负数,让柱子发生重叠
,minHeight:0 //柱子保留基础高度,position 不为±1 时应该保留点高度
,position:-1 //绘制位置,取值 -1 到 1,-1 为最底下,0 为中间,1 为最顶上,小数为百分比
,mirrorEnable:false //是否启用镜像,如果启用,视图宽度会分成左右两块,右边这块进行绘制,左边这块进行镜像(以中间这根柱子的中心进行镜像)
,stripeEnable:true //是否启用柱子顶上的峰值小横条,position 不是 -1 时应当关闭,否则会很丑
,stripeHeight:3 //峰值小横条基础高度
,stripeMargin:6 //峰值小横条和柱子保持的基础距离
,fallDuration:1000 //柱子从最顶上下降到最底部最长时间 ms
,stripeFallDuration:3500 //峰值小横条从最顶上下降到底部最长时间 ms
//柱子颜色配置:[位置,css 颜色,...] 位置: 取值 0.0-1.0 之间
,linear:[0,"rgba(0,187,17,1)",0.5,"rgba(255,215,0,1)",1,"rgba(255,102,0,1)"]
//峰值小横条渐变颜色配置,取值格式和 linear 一致,留空为柱子的渐变颜色
,stripeLinear:null
,shadowBlur:0 //柱子阴影基础大小,设为 0 不显示阴影,如果柱子数量太多时请勿开启,非常影响性能
,shadowColor:"#bbb" //柱子阴影颜色
,stripeShadowBlur:-1 //峰值小横条阴影基础大小,设为 0 不显示阴影,-1 为柱子的大小,如果柱子数量太多时请勿开启,非常影响性能
,stripeShadowColor:"" //峰值小横条阴影颜色,留空为柱子的阴影颜色
,fullFreq:false //是否要绘制所有频率;默认 false 主要绘制 5khz 以下的频率,高频部分占比很少,此时不同的采样率对频谱显示几乎没有影响;设为 true 后不同采样率下显示的频谱是不一样的,因为 最大频率=采样率/2 会有差异
//当发生绘制时会回调此方法,参数为当前绘制的频率数据和采样率,可实现多个直方图同时绘制,只消耗一个 input 输入和计算时间
,onDraw:function(frequencyData,sampleRate){}
}
【方法】histogram.input(pcmData,powerLevel,sampleRate)
输入音频数据,更新直方图显示。pcmData [Int16,...]
一维数组,为当前的录音数据片段,其他参数和onProcess
回调相同。
BufferStreamPlayer 插件
buffer_stream.player.js,31kb 大小源码,实时播放录音片段文件,把片段文件转换成 MediaStream 流,参考此 demo 片段在线测试使用。
BufferStreamPlayer 可以通过 input 方法一次性输入整个音频文件,或者实时输入音频片段文件,然后播放出来;输入支持格式:pcm、wav、mp3 等浏览器支持的音频格式,非 pcm 格式会自动解码成 pcm(播放音质效果比 pcm、wav 格式差点);输入前输入后都可进行处理要播放的音频,比如:混音、变速、变调;输入的音频会写入到内部的 MediaStream 流中,完成将连续的音频片段文件转换成流。
此插件仅支持在浏览器环境中使用。
可以用于
- Recorder onProcess 等实时处理中,将实时处理好的音频片段转直接换成 MediaStream,此流可以作为 WebRTC 的 local 流发送到对方,或播放出来;
- 接收到的音频片段文件的实时播放,比如:WebSocket 接收到的录音片段文件播放、WebRTC remote 流(Recorder 支持对这种流进行实时处理)实时处理后的播放;
- 单个音频文件的实时播放处理,比如:播放一段音频,并同时进行可视化绘制(其实自己解码+播放绘制比直接调用这个更有趣,但这个省事、配套功能多点)。
BufferStreamPlayer 文档
//【构造初始化】
var stream=Recorder.BufferStreamPlayer({
play:true //要播放声音,设为 false 不播放,只提供 MediaStream
,realtime:true /*默认为 true 实时模式,设为 false 为非实时模式
实时模式:设为 true 或 {maxDelay:300,discardAll:false}配置对象
如果有新的 input 输入数据,但之前输入的数据还未播放完的时长不超过 maxDelay 时(缓冲播放延迟默认限制在 300ms 内),如果积压的数据量过大则积压的数据将会被直接丢弃,少量积压会和新数据一起加速播放,最终达到尽快播放新输入的数据的目的;这在网络不流畅卡顿时会发挥很大作用,可有效降低播放延迟;出现加速播放时声音听起来会比较怪异,可配置 discardAll=true 来关闭此特性,少量积压的数据也直接丢弃,不会加速播放;如果你的音频数据块超过 200ms,需要调大 maxDelay(取值 100-800ms)
非实时模式:设为 false
连续完整的播放完所有 input 输入的数据,之前输入的还未播放完又有新 input 输入会加入队列排队播放,比如用于:一次性同时输入几段音频完整播放
*/
//,onInputError:fn(errMsg, inputIndex) //当 input 输入出错时回调,参数为 input 第几次调用和错误消息
//,onUpdateTime:fn() //已播放时长、总时长更新回调(stop、pause、resume 后一定会回调),this.currentTime 为已播放时长,this.duration 为已输入的全部数据总时长(实时模式下意义不大,会比实际播放的长),单位都是 ms
//,onPlayEnd:fn() //没有可播放的数据时回调(stop 后一定会回调),已输入的数据已全部播放完了,可代表正在缓冲中或播放结束;之后如果继续 input 输入了新数据,播放完后会再次回调,因此会多次回调;非实时模式一次性输入了数据时,此回调相当于播放完成,可以 stop 掉,重新创建对象来 input 数据可达到循环播放效果
//,decode:false //input 输入的数据在调用 transform 之前是否要进行一次音频解码成 pcm [Int16,...]
//mp3、wav 等都可以设为 true、或设为{fadeInOut:true}配置对象,会自动解码成 pcm;默认会开启 fadeInOut 对解码的 pcm 首尾进行淡入淡出处理,减少爆音(wav 等解码后和原始 pcm 一致的音频,可以把 fadeInOut 设为 false)
//transform:fn(inputData,sampleRate,True,False)
//将 input 输入的 data(如果开启了 decode 将是解码后的 pcm)转换处理成要播放的 pcm 数据;如果没有解码也没有提供本方法,input 的 data 必须是[Int16,...]并且设置 set.sampleRate
//inputData:any input 方法输入的任意格式数据,只要这个转换函数支持处理;如果开启了 decode,此数据为 input 输入的数据解码后的 pcm [Int16,...]
//sampleRate:123 如果设置了 decode 为解码后的采样率,否则为 set.sampleRate || null
//True(pcm,sampleRate) 回调处理好的 pcm 数据([Int16,...])和 pcm 的采样率
//False(errMsg) 处理失败回调
//sampleRate:16000 //可选 input 输入的数据默认的采样率,当没有设置解码也没有提供 transform 时应当明确设置采样率
//runningContext:AudioContext //可选提供一个 state 为 running 状态的 AudioContext 对象(ctx),默认会在 start 时自动创建一个新的 ctx,这个配置的作用请参阅 Recorder 的 runningContext 配置
});
//创建好后第一件事就是 start 打开流,打开后就会开始播放 input 输入的音频;注意:start 需要在用户操作(触摸、点击等)时进行调用,原因参考 runningContext 配置
stream.start(()=>{
stream.currentTime;//当前已播放的时长,单位 ms,数值变化时会有 onUpdateTime 事件
stream.duration;//已输入的全部数据总时长,单位 ms,数值变化时会有 onUpdateTime 事件;实时模式下意义不大,会比实际播放的长,因为实时播放时卡了就会丢弃部分数据不播放
stream.isStop;//是否已停止,调用了 stop 方法时会设为 true
stream.isPause;//是否已暂停,调用了 pause 方法时会设为 true
stream.isPlayEnd;//已输入的数据是否播放到了结尾(没有可播放的数据了),input 后又会变成 false;可代表正在缓冲中或播放结束,状态变更时会有 onPlayEnd 事件
//如果不要默认的播放,可以设置 set.play 为 false,这种情况下只拿到 MediaStream 来用
stream.getMediaStream() //通过 getMediaStream 方法得到 MediaStream 流,此流可以作为 WebRTC 的 local 流发送到对方,或者直接拿来赋值给 audio.srcObject 来播放(和赋值 audio.src 作用一致);未 start 时调用此方法将会抛异常
stream.getAudioSrc() //【已过时】超低版本浏览器中得到 MediaStream 流的字符串播放地址,可赋值给 audio 标签的 src,直接播放音频;未 start 时调用此方法将会抛异常;新版本浏览器已停止支持将 MediaStream 转换成 url 字符串,调用本方法新浏览器会抛异常,因此在不需要兼容不支持 srcObject 的超低版本浏览器时,请直接使用 getMediaStream 然后赋值给 auido.srcObject 来播放
},(errMsg)=>{
//start 失败,无法播放
});
//随时都能调用 input,会等到 start 成功后播放出来,不停的调用 input,就能持续的播放出声音了,需要暂停播放就不要调用 input 就行了
stream.input(anyData);
//清除已输入但还未播放的数据,一般用于非实时模式打断老的播放;返回清除的音频时长,默认会从总时长 duration 中减去此时长,keepDuration=true 时不减去
stream.clearInput(keepDuration);
//暂停播放,暂停后:实时模式下会丢弃所有 input 输入的数据(resume 时只播放新 input 的数据),非实时模式下所有 input 输入的数据会保留到 resume 时继续播放
stream.pause();
//恢复播放,实时模式下只会从最新 input 的数据开始播放,非实时模式下会从暂停的位置继续播放
stream.resume();
//不要播放了就调用 stop 停止播放,关闭所有资源
stream.stop();
【方法】stream.input(anyData)
输入任意格式的音频数据,未完成 start 前调用会等到 start 成功后生效。
anyData: any 具体类型取决于:
set.decode 为 false 时:
未提供 set.transform,数据必须是 pcm[Int16,...],此时的 set 必须提供 sampleRate;
提供了 set.transform,数据为 transform 方法支持的任意格式。
set.decode 为 true 时:
数据必须是 ArrayBuffer,会自动解码成 pcm[Int16,...];注意输入的每一片数据都应该是完整的一个音频片段文件,否则可能会解码失败。
关于 anyData 的二进制长度:
如果是提供的 pcm、wav 格式数据,数据长度对播放无太大影响,很短的数据也能很好的连续播放。
如果是提供的 mp3 这种必须解码才能获得 pcm 的数据,数据应当尽量长点,测试发现片段有 300ms 以上解码后能很好的连续播放,低于 100ms 解码后可能会有明显的杂音,更低的可能会解码失败;当片段确实太小时,可以将本来会多次 input 调用的数据缓冲起来,等数据量达到了 300ms 再来调用一次 input,能比较显著的改善播放音质。
ASR_Aliyun_Short 插件
asr.aliyun.short.js,29kb 大小源码,ASR,阿里云语音识别(语音转文字),支持实时语音识别、单个音频文件转文字,参考此 demo 片段在线测试使用。
- 本插件通过调用 阿里云-智能语音交互-一句话识别 接口来进行语音识别,无时长限制。
- 识别过程中采用 WebSocket 直连阿里云,语音数据无需经过自己服务器。
- 自己服务器仅需提供一个 Token 生成接口即可(本库已实现一个本地测试 NodeJs 后端程序 /assets/demo-asr)。
- 也支持在非浏览器环境中使用,比如微信小程序、uni-app,提供
compatibleWebSocket
进行适配即可。
本插件单次语音识别时虽长无限制,最佳使用场景还是 1-5 分钟内的语音识别;60 分钟以上的语音识别本插件也能胜任(需自行进行重试容错处理),但太长的识别场景不太适合使用阿里云一句话识别(阿里云单次一句话识别最长 60 秒,本插件自带拼接过程,所以无时长限制);为什么采用一句话识别:因为便宜。
目前暂未提供其他版本的语音识别插件,比如腾讯云、讯飞等,搭配使用 Recorder 的 onProcess 实时处理,可根据自己的业务需求选择对应厂家自行对接即可,如需定制开发请联系作者。
对接流程
- 到阿里云开通 一句话识别 服务(可试用一段时间,正式使用时应当开通商用版,很便宜),得到 AccessKey、Secret,参考:https://help.aliyun.com/document_detail/324194.html ;
- 到阿里云智能语音交互控制台创建相应的语音识别项目,并配置好项目,得到 Appkey,每个项目可以设置一种语言模型,要支持多种语言就创建多个项目;
- 需要后端提供一个 Token 生成接口(用到上面的 Key 和 Secret),可直接参考或本地运行此 NodeJs 后端测试程序:/assets/demo-asr,配置好代码里的阿里云账号后,在目录内直接命令行执行
node NodeJsServer_asr.aliyun.short.js
即可运行提供本地测试接口; - 前端调用 ASR_Aliyun_Short,传入 tokenApi,即可很简单的实现语音识别功能。
ASR_Aliyun_Short 文档
var rec=Recorder(recSet);rec.open(...) //进行语音识别前,先打开录音,获得录音权限
//【构造初始化】
var asr=Recorder.ASR_Aliyun_Short({
tokenApi:"" /*必填,调用阿里云一句话识别需要的 token 获取 api 地址
接口实现请参考本地测试 NodeJs 后端程序:/assets/demo-asr/NodeJsServer_asr.aliyun.short.js
此接口默认需要返回数据格式:
{
c:0 //code,0 接口调用正常,其他数值接口调用出错
,m:"" //message,接口调用出错时的错误消息
,v:{ //value,接口成功调用返回的结果【结果中必须包含下面两个值】
appkey:"aaaa" //lang 语言模型对应的项目 appkey
,token:"bbbb" //语音识别 Access Token
}
}
如果不是返回的这个格式的数据,必须提供 apiRequest 配置,自行请求 api*/
,apiArgs:{ //请求 tokenApi 时要传的参数
action:"token"
,lang:"普通话" //语言模型设置(具体取值取决于 tokenApi 支持了哪些语言)
}
,apiRequest:fn(url,args,success,fail) /*tokenApi 的请求实现方法,默认使用简单的 ajax 实现
如果你接口返回的数据格式和默认格式不一致,必须提供一个函数来自行请求 api
方法参数:
url:"" == tokenApi
args:{} == apiArgs
success:fn(value) 接口调用成功回调,value={appkey:"", token:""}
fail:fn(errMsg) 接口调用出错回调,errMsg="错误消息"
*/
,compatibleWebSocket:null /*提供一个函数返回兼容 WebSocket 的对象,一般也需要提供 apiRequest
如果你使用的环境不支持 WebSocket(如微信小程序、uni-app),需要提供一个函数来返回一个兼容实现对象
方法参数:fn(url) url 为连接地址,返回一个对象,需支持的回调和方法:{
onopen:fn() 连接成功回调
onerror:fn({message}) 连接失败回调
onclose:fn({code, reason}) 连接关闭回调
onmessage:fn({data}) 收到消息回调
connect:fn() 进行连接
close:fn(code,reason) 关闭连接
send:fn(data) 发送数据,data 为字符串或者 arraybuffer
}
binaryType 固定使用 arraybuffer 类型
*/
,asrProcess:fn(text,nextDuration,abortMsg) //当实时接收到语音识别结果时的回调函数(对单个完整音频文件的识别也有效)
//此方法需要返回 true 才会继续识别,否则立即当做识别超时处理,你应当通过 nextDuration 来决定是否继续识别,避免无限制的识别大量消耗阿里云资源额度;如果不提供本回调,默认 1 分钟超时后终止识别(因为没有绑定回调,你不知道已经被终止了)
//text 为中间识别到的内容(并非已有录音片段的最终结果,后续可能会根据语境修整)
//nextDuration 为当前回调时下次即将进行识别的总时长,单位毫秒,通过这个参数来限制识别总时长,超过时长就返回 false 终止识别(第二分钟开始每分钟会多识别前一分钟结尾的 5 秒数据,用于两分钟之间的拼接,相当于第二分钟最多识别 55 秒的新内容)
//abortMsg 如不为空代表识别中途因为某种原因终止了识别(比如超时、接口调用失败),收到此信息时应当立即调用 asr 的 stop 方法得到最终结果,并且终止录音
,log:fn(msg,color) //提供一个日志输出接口,默认只会输出到控制台,color: 1:红色,2 绿色,不为空时为颜色字符串
//高级选项
,fileSpeed:6 //单个文件识别发送速度控制,取值 1-n;1:为按播放速率发送,最慢,识别精度完美;6:按六倍播放速度发送,花 10 秒识别 60 秒文件比较快,精度还行;再快测试发现似乎会缺失内容,可能是发送太快底层识别不过来导致返回的结果缺失。
});
/**asr 创建好后,随时调用 strat,开始进行语音识别,开始后需要调用 input 输入录音数据,结束时调用 stop 来停止识别。如果 start 之前调用了 input 输入数据,这些数据将会等到 start 成功之后进行识别。
建议在 success 回调中开始录音(即 rec.start);当然 asr.start 和 rec.start 同时进行调用,或者任意一个先调用都是允许的,不过当出现 fail 时,需要处理好 asr 和 rec 各自的状态。
无需特殊处理 start 和 stop 的关系,只要调用了 stop,会阻止未完成的 start,不会执行回调。
success:fn()
fail:fn(errMsg)**/
asr.start(function(){//success
rec.start();//一般在 start 成功之后,调用 rec.start()开始录音,此时可以通知用户讲话了
},fail);
/**实时处理输入音频数据,一般是在 rec.set.onProcess 中调用本方法,输入实时录制的音频数据,输入的数据将会发送语音识别;不管有没有 start,都可以调用本方法,start 前输入的数据会缓冲起来等到 start 后进行识别
buffers:[[Int16...],...] pcm 片段列表,为二维数组,第一维数组内存放 1 个或多个 pcm 数据;比如可以是:rec.buffers、onProcess 中的 buffers 截取的一段新二维数组
sampleRate:48000 buffers 中 pcm 的采样率
buffersOffset:0 可选,默认 0,从 buffers 第一维的这个位置开始识别,方便 rec 的 onProcess 中使用**/
asr.input(buffers,sampleRate,buffersOffset);
/**话讲完后,调用 stop 结束语音识别,得到识别到的内容文本,一般在调用了本方法后,下一行代码立即调用录音 rec.stop 结束录音
success:fn(text,abortMsg) text 为识别到的最终完整内容;如果存在 abortMsg 代表识别中途被某种错误停止了,text 是停止前的内容识别到的完整内容,一般早在 asrProcess 中会收到 abort 事件然后要停止录音
fail:fn(errMsg)**/
asr.stop(function(text,abortMsg){//success
//text 为识别到的最终完整内容;如果存在 abortMsg 代表识别中途被某种错误停止了,text 是停止前的内容识别到的完整内容,一般早在 asrProcess 中会收到 abort 事件然后要停止录音
},fail);
//【更多的方法】
asr.inputDuration() //获取 input 已输入的音频数据总时长,单位 ms
asr.sendDuration() //获取已发送识别的音频数据总时长,存在重发重叠部分,因此比 inputDuration 长
asr.asrDuration() //获取已识别的音频数据总时长,去除了 sendDuration 的重叠部分,值<=inputDuration
asr.getText() //获取实时结果文本,如果已 stop 返回的就是最终文本,一般无需调用此方法,因为回调中都提供了此方法的返回值
/**一次性将单个完整音频文件转成文字,无需 start、stop,创建好 asr 后直接调用本方法即可,支持的文件类型由具体的浏览器决定,因此存在兼容性问题,兼容性 mp3 最好,wav 次之,其他格式不一定能够解码。实际就是调用:浏览器解码音频得到 PCM -> start -> input ... input -> stop
audioBlob 音频文件 Blob 对象,如:rec.stop 得到的录音结果、file input 选择的文件、XMLHttpRequest 的 blob 结果、new Blob([TypedArray])创建的 blob
success fn(text,abortMsg) text 为识别到的完整内容,abortMsg 参考 stop
fail:fn(errMsg)**/
asr.audioToText(audioBlob,success,fail)
/**一次性的将单个完整音频转成文字,无需 start、stop,创建好 asr 后直接调用本方法即可。实际就是调用:start -> input ... input -> stop
buffer:[Int16,...] 16 位单声道音频 pcm 数据,一维数组
sampleRate pcm 的采样率
success fn(text,abortMsg) text 为识别到的完整内容,abortMsg 参考 stop
fail:fn(errMsg)**/
asr.pcmToText(buffer,sampleRate,success,fail)
Sonic 插件
sonic.js,38kb 大小源码,音频变速变调转换,参考此 demo 片段在线测试使用。此插件从Sonic.java移植,并做了适当精简。
可到assets/sonic-java目录运行 java 代码测试原版效果。
本插件支持
Pitch
:变调不变速(会说话的汤姆猫),男女变声,只调整音调,不改变播放速度Speed
:变速不变调(快放慢放),只调整播放速度,不改变音调Rate
:变速变调,会改变播放速度和音调Volume
:支持调整音量- 支持实时处理,可在 onProcess 中实时处理 PCM(需开启异步),配合 SampleData 方法使用更佳
Sonic 文档
Sonic 有两个构造方法,一个是同步方法,Sonic.Async 是异步方法,同步方法简单直接但处理量大时会消耗大量时间,主要用于一次性的处理;异步方法由 WebWorker 在后台进行运算处理,但异步方法不一定能成功开启(低版本浏览器),主要用于实时处理。异步方法调用后必须调用 flush 方法,否则会产生内存泄露。
注意:由于同步方法转换操作需要占用比较多的 CPU(但比转码小点),因此实时处理时在低端设备上可能会导致性能问题;在一次性处理大量 pcm 时,可采取切片+setTimeout 进行处理,参考上面的 demo 片段。
注意:变速变调会大幅增减 PCM 数据长度,如果需要在 onProcess 中实时处理 PCM,需要在 rec.set 中设置内部参数rec.set.disableEnvInFix=true
来禁用设备卡顿时音频输入丢失补偿功能,否则可能导致错误的识别为设备卡顿。
注意:每次 input 输入的数据量应该尽量的大些,太少容易产生杂音,每次传入 200ms 以上的数据量就几乎没有影响了。
//【构造初始化】
var sonic=Recorder.Sonic(set) //同步调用,用于一次性处理
var sonic=Recorder.Sonic.Async(set) //异步调用,用于实时处理,调用后必须调用 flush 方法,否则会产生内存泄露。
/*set:{
sampleRate:待处理 pcm 的采样率,就是 input 输入的 buffer 的采样率
}*/
//【功能配置调用函数】同步异步通用,以下 num 取值正常为 0.1-2.0,超过这个范围也是可以的,但不推荐
sonic.setPitch(num) //num:0.1-n,变调不变速(会说话的汤姆猫),男女变声,只调整音调,不改变播放速度,默认为 1.0 不调整
sonic.setSpeed(num) //num:0.1-n,变速不变调(快放慢放),只调整播放速度,不改变音调,默认为 1.0 不调整
sonic.setRate(num) //num:0.1-n,变速变调,越小越缓重,越大越尖锐,会改变播放速度和音调,默认为 1.0 不调整
sonic.setVolume(num) //num:0.1-n,调整音量,默认为 1.0 不调整
sonic.setChordPitch(bool) //bool:默认 false,作用未知,不推荐使用
sonic.setQuality(num) //num:0 或 1,默认 0 时会减小输入采样率来提供处理速度,变调时才会用到,不推荐使用
//【同步调用方法】
sonic.input(buffer) //buffer:[Int16,...] 一维数组,输入 pcm 数据,返回转换后的部分 pcm 数据,完整输出需要调用 flush;返回值[Int16,...]长度可能为 0,代表没有数据被转换;此方法是耗时的方法,一次性处理大量 pcm 需要切片+setTimeout 优化
sonic.flush() //将残余的未转换的 pcm 数据完成转换并返回;返回值[Int16,...]长度可能为 0,代表没有数据被转换
//【异步调用方法】
sonic.input(buffer,callback) //callback:fn(pcm),和同步方法相同,只是返回值通过 callback 返回
sonic.flush(callback) //callback:fn(pcm),和同步方法相同,只是返回值通过 callback 返回
NMN2PCM 插件
create-audio.nmn2pcm.js,14kb 大小源码,简单用 正弦波、方波、锯齿波、三角波 函数生成一段音乐简谱的 pcm 数据,主要用于测试时提供音频数据;参考此 demo 片段在线测试使用。
NMN2PCM 文档
var pcmData=Recorder.NMN2PCM(set);
set 配置:{
texts:""|["",""] 简谱格式化文本,如果格式不符合要求,将会抛异常
sampleRate: 生成 pcm 的采样率,默认 48000;取值不能过低,否则会削除高音
timbre: 音色,默认 2.0(使用音符对应频率的一个倍频),取值>=1.0
meterDuration: 一拍时长,毫秒,默认 600ms
muteDuration: 音符之间的静默,毫秒,0 时无静默,默认 meterDur/4(最大 50ms)
beginDuration: 开头的静默时长,毫秒,0 时无静默,默认为 200ms
endDuration: 结尾的静默时长,毫秒,0 时无静默,默认为 200ms
volume: 音量,默认 0.3,取值范围 0.0-1.0(最大值 1)
waveType: 波形发生器类型,默认"sine",取值:sine(正弦波)、square(方波,volume 应当减半)、sawtooth(锯齿波)、triangle(三角波)
}
其中 texts 格式比较复杂,请查看此插件源码中的注释
返回结果:{
pcm: Int16Array,pcm 数据
duration: 123 pcm 的时长,单位毫秒
set: {...} 使用的 set 配置
warns: [] 不适合抛异常的提示消息
}
Recorder.NMN2PCM.GetExamples() 可获取内置的简谱
DTMF 插件
dtmf.decode.js + lib.fft.js、dtmf.encode.js,两个 js 一个解码、一个编码,体积小均不超过 10kb,纯 js 实现易于移植。参考此 demo 片段在线测试使用。
- DTMF(电话拨号按键信号)解码器,解码得到按键值:可实现实时从音频数据流中解码得到电话拨号按键信息,用于电话录音软解,软电话实时提取 DTMF 按键信号等;识别 DTMF 按键准确度高,误识别率低,支持识别 120ms 以上按键间隔+30ms 以上的按键音,纯 js 实现易于移植;请注意:使用 dtmf.decode.js 必须同时引入
lib.fft.js
(由 java 移植过来的)才能正常工作。 - DTMF(电话拨号按键信号)编码生成器,生成按键对应的音频 PCM 信号:可实现生成按键对应的音频 PCM 信号,用于 DTMF 按键信号生成,软电话实时发送 DTMF 按键信号等;生成信号代码、原理简单粗暴,纯 js 实现易于移植,0 依赖。
【方法】Recorder.DTMF_Decode(pcmData,sampleRate,prevChunk)
解码 DTMF 只有这个一个函数,此函数支持连续调用,将上次的返回值当做参数即可实现实时音频流数据的连续解码处理。
参数:
pcmData:[Int16,...] pcm 一维数组,原则上一次处理的数据量不要超过 10 秒,太长的数据应当分段延时处理
sampleRate: 123 pcm 的采样率
prevChunk: null || {} 上次的返回值,用于连续识别,或者当做额外配置对象
返回:
chunk:{
keys:[keyItem,...] 识别到的按键,如果未识别到数组长度为 0
keyItem:{
key:"" //按键值 0-9 #*
time:123 //所在的时间位置,ms
}
//以下用于下次接续识别
lastIs:"" "":mute {}:match 结尾处是什么
lastCheckCount:0 结尾如果是 key,此时的检查次数
prevIs:"" "":null {}:match 上次疑似检测到了什么
totalLen:0 总采样数,相对 4khz
pcm:[Int16,...] 4khz pcm 数据
//可额外配置值,如:DTMF_Decode(.., .., prevChunk||{checkFactor:2})
checkFactor:3 信号检查因子,取值 1,2,3,默认为 3 不支持低于 32ms 的按键音检测,当需要检测时可以设为 2,当信号更恶劣时设为 1,这样将会减少检查的次数,导致错误识别率变高
debug:false 是否开启调试日志
}
【方法】Recorder.DTMF_Encode(key,sampleRate,duration,mute)
本方法用来生成单个按键信号 pcm 数据,属于底层方法,要混合多个按键信号到别的 pcm 中请用封装好的 DTMF_EncodeMix 方法。
参数:
key: 单个按键 0-9#*
sampleRate:123 要生成的 pcm 采样率
duration:100 按键音持续时间
mute:50 按键音前后静音时长
返回:
pcm:[Int16,...],生成单个按键信号
【方法】Recorder.DTMF_EncodeMix(set)
本方法返回 EncodeMix 对象,将输入的按键信号混合到持续输入的 pcm 流中,当.mix(inputPcms)提供的太短的 pcm 会无法完整放下一个完整的按键信号,所以需要不停调用.mix(inputPcms)进行混合。
set={
duration:100 //按键信号持续时间 ms,最小值为 30ms
,mute:25 //按键音前后静音时长 ms,取值为 0 也是可以的
,interval:200 //两次按键信号间隔时长 ms,间隔内包含了 duration+mute*2,最小值为 120ms
}
EncodeMix 对象:
.add(keys)
添加一个按键或多个按键 "0" "123#*",后面慢慢通过 mix 方法混合到 pcm 中,无返回值
.mix(pcms,sampleRate,index)
将已添加的按键信号混合到 pcm 中,pcms:[[Int16,...],...]二维数组,sampleRate:pcm 的采样率,index:pcms 第一维开始索引,将从这个 pcm 开始混合。
返回状态对象:{
newEncodes:[{key:"*",data:[Int16,...]},...] //本次混合新生成的按键信号列表 ,如果没有产生新信号将为空数组
,hasNext:false //是否还有未混合完的信号
}
注意:调用本方法会修改 pcms 中的内容,因此混合结果就在 pcms 内。
:open_book:已有的音频格式编码器
所有音频格式的编码器都在/src/engine
目录中(或/dist/engine
目录的压缩版),每个格式一般有一个同名的 js 文件,如果这个格式有额外的编码引擎文件(*-engine.js
)的话,使用时必须要一起加上。
pcm 格式
依赖文件:pcm.js
,支持实时编码(边录边转码),pcm 编码器输出的数据其实就是 Recorder 中的 buffers 原始数据(经过了重新采样),16 位时为 LE 小端模式(Little Endian),并未经过任何编码处理;pcm 为未封装的原始音频数据,pcm 数据文件无法直接播放,pcm 加上一个 44 字节 wav 头即成 wav 文件,可通过 wav 格式来正常播放。两个参数相同的 pcm 文件直接二进制拼接在一起即可成为长的 pcm 文件。
Recorder.pcm2wav(data,True,False)
已实现的一个把 pcm 转成 wav 格式来播放的方法,data = { sampleRate:16000 pcm 的采样率 , bitRate:16 pcm 的位数 取值:8 或 16 , blob:pcm 的 blob 对象 }
,True=fn(wavBlob,duration)
。要使用此方法需要带上wav
格式编码器。
提示:pcm 二进制数据可以直接在数据开头添加个 wav 头(Recorder.wav_header
)即成 wav 文件;16 位的 pcm 可直接通过new Int16Array(pcmArrayBuffer)
来构造成 Int16Array,然后通过rec.mock
来转码成其他任意格式。
wav (raw pcm format) 格式
依赖文件:wav.js
(或使用根目录的recorder.wav.min.js
一个文件即可),不支持实时编码(因为 wav 文件头中需要提供文件最终长度)只能一次性将所有 pcm 转成 wav 格式,wav 格式编码器是参考网上资料写的,会发现代码和别人家的差不多。源码 4kb 大小。wav 转其他格式参考和测试
wav 转 pcm
生成的 wav 文件内音频数据的编码为未压缩的 pcm 数据(raw pcm),只是在 pcm 数据前面加了一个 44 字节的 wav 头;因此直接去掉前面 44 字节就能得到原始的 pcm 数据,如:blob.slice(44,blob.size,"audio/pcm")
;注意:其他 wav 编码器可能不是 44 字节的头,要从任意 wav 文件中提取 pcm 数据,请参考:assets/runtime-codes/fragment.decode.wav.js
。
Recorder.wav_header(format,numCh,sampleRate,bitRate,dataLength)
生成一个 wav 头(Uint8Array),添加到 pcm 二进制数据开头即成 wav 文件。比如实时保存 pcm 时(追加写入文件),可在文件开头预留 44 字节的位置,等录音结束后再来生成 wav 头并写入到文件开头。使用例子PCM 基础教程。
format: 1 (raw pcm) 2 (ADPCM) 3 (IEEE Float) 6 (g711a) 7 (g711u),比如 pcm 传 1
,numCh: 声道数,比如 1
,sampleRate: 音频数据采样率,比如 16000
,bitRate: 音频数据位数,比如 16
,dataLength: wav 中的音频数据二进制长度,比如 pcm.byteLength
。
简单将多段小的 wav 片段合成长的 wav 文件
由于 RAW 格式的 wav 内直接就是 pcm 数据,因此将小的 wav 片段文件去掉 wav 头后得到的原始 pcm 数据合并到一起,再加上新的 wav 头即可合并出长的 wav 文件;要求待合成的所有 wav 片段的采样率和位数需一致。wav 合并参考和测试+可移植源码
mp3 (CBR) 格式
依赖文件:mp3.js + mp3-engine.js
(或使用根目录的recorder.mp3.min.js
一个文件即可),支持实时编码(边录边转码),采用的是lamejs(LGPL License)这个库的代码,https://github.com/zhuker/lamejs/blob/bfb7f6c6d7877e0fe1ad9e72697a871676119a0e/lame.all.js
这个版本的文件代码;已对 lamejs 源码进行了部分改动,用于精简代码和修复发现的问题。LGPL 协议涉及到的文件:mp3-engine.js
;这些文件也采用 LGPL 授权,不适用 MIT 协议。源码 366kb 大小,压缩后 130kb 左右,开启 gzip 后 60 来 k。mp3 转其他格式参考和测试
简单将多段小的 mp3 片段合成长的 mp3 文件
由于 lamejs CBR 编码出来的 mp3 二进制数据从头到尾全部是大小相同的数据帧(采样率 44100 等无法被 8 整除的部分帧可能存在额外多 1 字节填充),没有其他任何多余信息,通过文件长度可计算出 mp3 的时长fileSize*8/bitRate
(参考),数据帧之间可以直接拼接。因此将小的 mp3 片段文件的二进制数据全部合并到一起即可得到长的 mp3 文件;要求待合成的所有 mp3 片段的采样率和比特率需一致。mp3 合并参考和测试+可移植源码
注:CBR 编码由于每帧数据的时长是固定的,mp3 文件结尾最后这一帧的录音可能不能刚好填满,就会产生填充数据,多出来的这部分数据会导致 mp3 时长变长一点点,在实时转码传输时应当留意,解码成 pcm 后可直接去掉结尾的多余;另外可以通过调节待编码的 pcm 数据长度以达到刚好填满最后一帧来规避此问题,参考Recorder.SampleData
方法提供的连续转码针对此问题的处理。首帧或前两帧可能是 lame 记录的信息帧,本库已去除(但小的 mp3 片段拼接起来停顿导致的杂音还是非常明显,实时处理时使用takeoffEncodeChunk
选项可完全避免此问题),参考上面的已知问题。
g711a g711u 格式
依赖文件:g711x.js
,g711a: G.711 A-law (pcma),g711u: G.711 μ-law (pcmu、mu-law);支持 g711 的编码和解码,支持实时编码(边录边转码),编解码源码移植自:https://github.com/twstx1/codec-for-audio-in-G72X-G711-G723-G726-G729/blob/master/G711_G721_G723/g711.c
;固定为 8000hz 采样率、16 位,每个采样压缩成 8 位存储,音频文件大小为 8000 字节/秒。
Recorder.g711a_encode|g711u_encode(pcm)
编码任意采样率的 pcm 得到 g711x 数据。pcm: Int16Array,任意采样率 pcm 数据(标准采样率为 8000,其它采样率的 pcm 可使用 Recorder.SampleData 方法来转成 8000 采样率)。返回 Uint8Array,g711x 二进制数据(采样率为 pcm 的采样率)。
Recorder.g711a_decode|g711u_decode(bytes)
解码 g711x 得到 pcm,bytes
: Uint8Array,g711x 二进制数据,采样率一般是 8000;返回 Int16Array,为 g711x 的采样率、16 位的 pcm 数据。
Recorder.g711a2wav|g711u2wav(g711xBlob,True,False)
已实现把 g711a、g711u 转成 wav 格式来播放的方法,g711xBlob 为 g711x 音频文件 blob 对象 或 ArrayBuffer(回调也将返回 ArrayBuffer),采样率只支持 8000
,True=fn(wavBlob,duration)
;要使用此方法需要带上wav
格式编码器。
ogg (Vorbis) 格式
依赖文件:beta-ogg.js + beta-ogg-engine.js
,支持实时编码(边录边转码),采用的是ogg-vorbis-encoder-js(MIT License),https://github.com/higuma/ogg-vorbis-encoder-js/blob/7a872423f416e330e925f5266d2eb66cff63c1b6/lib/OggVorbisEncoder.js
这个版本的文件代码。此编码器源码 1.2M,比较大,开启 gzip 后 400KB 左右。对录音的压缩率比 lamejs 高出一倍, 但 Vorbis in Ogg 好像 Safari 不支持(真的假的)。
amr (NB 窄带) 格式
依赖文件:beta-amr.js + beta-amr-engine.js
,支持实时编码(边录边转码),采用的是benz-amr-recorder(MIT License)优化后的amr.js(Unknown License),https://github.com/BenzLeung/benz-amr-recorder/blob/462c6b91a67f7d9f42d0579fb5906fad9edb2c9d/src/amrnb.js
这个版本的文件代码,已对此代码进行过调整更方便使用。支持编码和解码操作。由于最高只有 12.8kbps 的码率(AMR 12.2,8000hz),音质和同比配置的 mp3、ogg 差一个档次。由于支持解码操作,理论上所有支持 Audio 的浏览器都可以播放(需要自己写代码实现)。此编码器源码 600KB,比较大,开启 gzip 后 130KB。优点:录音文件小。
Recorder.amr2wav(amrBlob,True,False)
已实现的一个把 amr 转成 wav 格式来播放的方法,True=fn(wavBlob,duration)
。要使用此方法需要带上上面的wav
格式编码器。仿照此方法可轻松转成别的格式,参考mock
方法介绍那节。
AMR 实时编码解码
使用Recorder.AMR.GetDecoder() | .GetEncoder(12.2)
可以获得 amr 解码器和编码器,支持实时操作(amr 帧、小文件片段连续编解码);如何使用请阅读beta-amr-engine.js
源码开头部分代码,Recorder.amr2wav
底层调用的Recorder.AMR.decode
方法有用到。
beta-webm 格式
依赖文件:beta-webm.js
,不支持实时编码,这个编码器时通过查阅 MDN 编写的一个玩意,没多大使用价值:录几秒就至少要几秒来编码。。。原因是:未找到对已有 pcm 数据进行快速编码的方法。数据导入到 MediaRecorder,音频有几秒就要等几秒,类似边播放边收听形。(想接原始录音 Stream?我不可能给的!)输出音频虽然可以通过比特率来控制文件大小,但音频文件中的比特率并非设定比特率,采样率由于是我们自己采样的,到这个编码器随他怎么搞。只有比较新的浏览器支持(需实现浏览器 MediaRecorder),压缩率和 mp3 差不多。源码 3kb 大小。
【附】其他音频格式支持办法
//比如增加 aac 格式支持 (可参考/src/engine/wav.js 的简单实现;如果要实现边录边转码应该参考 mp3 的实现,需实现的接口比较多)
//新增一个 aac.js,编写以下格式代码即可实现这个类型
Recorder.prototype.aac=function(pcmData,successCall,failCall){
//通过 aac 编码器把 pcm[Int16,...]数据转成 aac 格式数据,通过 this.set 拿到传入的配置数据
... pcmData->aacData
//返回数据
successCall(new Blob([aacData.buffer],{type:"audio/aac"}));
}
//调用
Recorder({type:"aac"})
//如仅需要得到 aac 格式文件,你可以尝试直接用 Recorder 录制 wav 格式,然后调用 https://github.com/salomvary/fdk-aac.js 库 来将 wav 转码成 aac,详细请参考 assets/手动测试脚本.js 里面有可用的测试代码
:open_book:国际化多语言支持(i18n)
Recorder 内置的简版国际化多语言支持实现,通过设置 Recorder.i18n.lang="en-US"
可切换 Recorder 内所有输出文本为英文(如:错误消息、控制台日志),默认为zh-CN
简体中文。
语言文件在 /src/i18n 目录内,这些语言文件是通过/src/package-i18n.js
脚本从 src 目录内所有源码文件中提取出文本自动生成的,如果修改了源码,需要重新运行一次脚本来生成语言文件,并对新增或修改的语句进行翻译;目前采用的是 google 翻译+百度翻译,由中文翻译成目标语言,翻译水平有限准确度一般。
en-US.js
:English, 英文,lang="en-US" 或 lang="en"es.js
:Spanish, Español, 西班牙语,lang="es"fr.js
:French, Français, 法语,lang="fr"Template.js
:模板文件,当你需要的语言没有对应文件时,可以复制这个文件并改名,然后自行翻译成你需要的语言
切换语言
//比如切换成英文,在引入 Recorder 之后再进行语言文件的 import
import 'recorder-core/src/i18n/en-US.js'
//<script src="src/i18n/en-US.js"></script>
Recorder.i18n.lang="en-US" //切换成对应的语言,此后 Recorder 的所有输出文本均为此语言的文本
i18n 文档
var $T=Recorder.i18n.$T; //可以直接用变量引用$T 多语言文本处理函数
//返回一个当前语言对应的文本,key 必须唯一(可以用随机字符串);key 如果不存在将会把文本存入当前语言实例中,存在时将返回已存在的文本;文本中可以用{1-n}提供参数;文本详细格式定义请阅读 Recorder.i18n 源码中的注释
$T("key-xx1::测试文本,{1},{2}",":Test Text, {1}, {2}",0,"args1","args2");
//可以直接在代码中提供多个语言文本;第一个默认 zh-CN 可以不写语言,第二个默认 en-US 可以不写语言,后面更多的必须写明是哪个语言
//Recorder.i18n.lang="ru" //设置当前语言为俄语
$T("key-xx2:zh-CN:俄语 {1}","en-US:Russian {1}","ru:Русский язык {1}",0,"args1");
//可以直接获取 key 对应的文本,第二个参数是文本中的参数数组,key 如果不存在将返回"";$T.G 这个函数主要用于一些预定义好的文本,免得多处出现相同的文本反复去翻译
$T.G("key-xx1",["v1","v2"]);
$T.G("key-xx2",["v1"], "ru"); //明确返回俄语,不管当前 lang 设置的是什么
:open_book:其它功能介绍
测试用的本地服务器
在 /assets/node-localServer 目录内提供了一个 nodejs 服务,实现了 http 上传接口、websocket 接口。多个 demo 中有使用到,方便测试。
WebSocket 语音通话聊天 demo
在线测试 Demo中包含了一个 WebSocket 版的语音通话聊天的测试功能,测试时需先运行上面这个本地服务器。用两个设备(浏览器打开两个标签也可以)打开 demo,勾选实时语音通话聊天对讲,配置好双方标识,连接上服务器即可通话和聊天。手机上测试时,应当使用 wss 连接,本地服务器里面配置上 ssl 证书开启 wss。
对应的源码在 /assets/zdemo.index.realtime_voice.js,实时传输 pcm 格式的音频数据(未经过任何编码),用 BufferStreamPlayer 插件播放。
WebRTC 语音通话聊天 demo
在线测试 Demo中还包含了一个 WebRTC 版的语音通话聊天的测试功能,支持局域网内一对一语音无需服务器。用两个设备(浏览器打开两个标签也可以)打开 demo,勾选实时语音通话聊天对讲再切换到 WebRTC,按提示交换两个设备的信息即可成功进行 P2P 连接,然后进行语音。实际使用时数据传输用 WebSocket 的会简单好多。
早期编写本语音测试的目的在于验证 H5 录音实时转码、传输的可行性,并验证实时转码 mp3 格式小片段文件接收后的可播放性。经测试发现:除了移动端可能存在设备性能低下的问题以外,录音后实时转码 mp3 并传输给对方是可行的,对方接收后播放也能连贯的播放(效果还是要看播放代码写的怎么样,目前没有比较完美的播放代码,用 BufferStreamPlayer 插件播放效果会好点)。另外(16kbps,16khz)MP3 开语音 15 分钟大概 3M 的流量,wav、pcm 15 分钟要 37M 多流量。
另外除 wav、pcm 外 MP3 等格式编码出来的音频的播放时间比 PCM 原始数据要长一些或短一些,如果涉及到解码或拼接时,这个地方需要注意(如果类型支持,实时处理时使用takeoffEncodeChunk
选项可完全避免此问题)。
:star:捐赠
如果这个库有帮助到您,请 Star 一下。
您也可以使用支付宝或微信打赏作者: