Files
2026-01-16 14:13:44 +08:00
..
2026-01-16 14:13:44 +08:00
2026-01-16 14:13:44 +08:00
2026-01-16 14:13:44 +08:00
2026-01-16 14:13:44 +08:00
2026-01-16 14:13:44 +08:00
2026-01-16 14:13:44 +08:00
2026-01-16 14:13:44 +08:00
2026-01-16 14:13:44 +08:00
2026-01-16 14:13:44 +08:00
2026-01-16 14:13:44 +08:00

📖Recorder用于html5录音

在线测试,支持大部分已实现getUserMedia的移动端、PC端浏览器主要包括Chrome、Firefox、Safari、Android WebView、腾讯Android X5内核(QQ、微信);不支持:UC系内核典型的支付宝大部分国产手机厂商自研套壳娱乐浏览器IOS上除Safari外的其他任何形式的浏览器含PWA、WebClip、任何App内网页。快捷方式: 【QuickStart】【RecordApp测试】【vue+webpack测试】【Android、IOS App Demo】【工具】Recorder代码运行和静态分发【工具】裸(RAW、WAV)PCM转WAV播放测试和转码 【无用户操作测试】【Can I Use】查看浏览器支持情况

录音默认输出mp3格式另外可选wav格式有限支持ogg(beta)、webm(beta)、amr(beta)格式;支持任意格式扩展(前提有相应编码器)。

mp3默认16kbps的比特率2kb每秒的录音大小音质还可以如果使用8kbps可达到1kb每秒不过音质太渣。主要用于语音录制双声道语音没有意义特意仅对单声道进行支持。mp3和wav格式支持边录边转码录音结束时转码速度极快支持实时转码成小片段文件和实时传输demo中已实现一个语音通话聊天下面有介绍其他格式录音结束时可能需要花费比较长的时间进行转码。

mp3使用lamejs编码(CBR)压缩后的recorder.mp3.min.js文件150kb左右开启gzip后54kb。如果对录音文件大小没有特别要求可以仅仅使用录音核心+wav编码器(raw pcm format录音文件超大)压缩后的recorder.wav.min.js不足5kb。录音得到的mp3(CBR)、wav(PCM),均可简单拼接小的二进制录音片段文件来生成长的音频文件,具体参考下面这两种编码器的详细介绍。

如需在Hybrid App内使用支持IOS、Android或提供IOS微信的支持请参阅app-support-sample目录。

IOS、国产厂商自研套壳娱乐浏览器上的使用限制等问题和兼容请参阅下面的知识库部分打开录音后对音频播放的影响、录音中途来电话等问题也参阅下面的知识库。

Recorder logo

Basic: Stars Forks npm Version License

Traffic: npm Downloads cnpmcnpm JsDelivr CDN cnpm

📖快速使用

你可以通过阅读和运行QuickStart.html文件来快速入门学习,直接将QuickStart.htmlcopy到你的(https、localhost)网站中无需其他文件就能正常开始测试了注意需要在https、localhost等安全环境下才能进行录音。

【1】加载框架

方式一使用script标签引入

在需要录音功能的页面引入压缩好的recorder.xxx.min.js文件即可JsDelivr CDN

<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/dist/recorder-core.js-->

<script src="src/engine/mp3.js"></script> <!--相应格式支持文件如果需要多个格式支持把这些格式的编码引擎js文件放到后面统统加载进来即可-->
<script src="src/engine/mp3-engine.js"></script> <!--如果此格式有额外的编码引擎的话,也要加上-->

<script src="src/extensions/waveview.js"></script>  <!--可选的扩展支持项-->

方式二通过import/require引入

通过 npm/cnpm 进行安装 npm install recorder-core 如果直接clone的源码下面文件路径调整一下即可

//必须引入的核心换成require也是一样的。注意recorder-core会自动往window下挂载名称为Recorder对象全局可调用window.Recorder也许可自行调整相关源码清除全局污染
import Recorder from 'recorder-core'

//需要使用到的音频格式编码引擎的js文件统统加载进来
import 'recorder-core/src/engine/mp3'
import 'recorder-core/src/engine/mp3-engine'

//以上三个也可以合并使用压缩好的recorder.xxx.min.js
//比如 import Recorder from 'recorder-core/recorder.mp3.min' //已包含recorder-core和mp3格式支持

//可选的扩展支持项
import 'recorder-core/src/extensions/waveview'

【2】调用录音

这里假设只录3秒录完后立即播放在线编辑运行此代码>>

//简单控制台直接测试方法:在任意(无CSP限制)页面内加载Recorder加载成功后再执行一次本代码立即会有效果import("https://xiangyuecn.github.io/Recorder/recorder.mp3.min.js").then(function(s){console.log("import ok")}).catch(function(e){console.error("import fail",e)})

var rec;
/**调用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次本回调
            //可利用extensions/waveview.js扩展实时绘制波形
            //可利用extensions/sonic.js扩展实时变速变调此扩展计算量巨大onProcess需要返回true开启异步模式
        }
    });

    //var dialog=createDelayDialog(); 我们可以选择性的弹一个对话框为了防止移动端浏览器存在第三种情况用户忽略并且或者国产系统UC系浏览器没有任何回调此处demo省略了弹窗的代码
    rec.open(function(){//打开麦克风授权获得相关资源
        //dialog&&dialog.Cancel(); 如果开启了弹框,此处需要取消
        //rec.start() 此处可以立即开始录音但不建议这样编写因为open是一个延迟漫长的操作通过两次用户操作来分别调用open和start是推荐的最佳流程
        
        success&&success();
    },function(msg,isUserNotAllow){//用户拒绝未授权或不支持
        //dialog&&dialog.Cancel(); 如果开启了弹框,此处需要取消
        console.log((isUserNotAllow?"UserNotAllow":"")+"无法录音:"+msg);
    });
};

/**开始录音**/
function recStart(){//打开了录音后才能进行start、stop调用
    rec.start();
};

/**结束录音**/
function recStop(){
    rec.stop(function(blob,duration){
        console.log(blob,(window.URL||webkitURL).createObjectURL(blob),"时长:"+duration+"ms");
        rec.close();//释放录音资源当然可以不释放后面可以连续调用start但不释放时系统或浏览器会一直提示在录音最佳操作是录完就close掉
        rec=null;
        
        //已经拿到blob文件对象想干嘛就干嘛立即播放、上传
        
        /*** 【立即播放例子】 ***/
        var audio=document.createElement("audio");
        audio.controls=true;
        document.body.appendChild(audio);
        //简单利用URL生成播放地址注意不用了时需要revokeObjectURL否则霸占内存
        audio.src=(window.URL||webkitURL).createObjectURL(blob);
        audio.play();
    },function(msg){
        console.log("录音失败:"+msg);
        rec.close();//可以通过stop方法的第3个参数来自动调用close
        rec=null;
    });
};


//我们可以选择性的弹一个对话框为了防止移动端浏览器存在第三种情况用户忽略并且或者国产系统UC系浏览器没有任何回调
/*伪代码:
function createDelayDialog(){
    if(Is Mobile){//只针对移动端
        return new Alert Dialog Component
            .Message("录音功能需要麦克风权限,请允许;如果未看到任何请求,请点击忽略~")
            .Button("忽略")
            .OnClick(function(){//明确是用户点击的按钮,此时代表浏览器没有发起任何权限请求
                //此处执行fail逻辑
                console.log("无法录音:权限请求被忽略");
            })
            .OnCancel(NOOP)//自动取消的对话框不需要任何处理
            .Delay(8000); //延迟8秒显示这么久还没有操作基本可以判定浏览器有毛病
    };
};
*/


//这里假设立即运行只录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 headermultipart/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);});

【附】问题排查

  • 打开Demo页面试试看,是不是也有同样的问题。
  • 检查是不是在https之类的安全环境下调用的。
  • 检查是不是IOS系统确认caniuseIOS对getUserMedia的支持情况。
  • 检查上面第1步是否把框架加载到位Demo页面有应该加载哪些js的提示。
  • 提交Issue热心网友帮你解答。

【QQ群】交流与支持

欢迎加QQ群781036591纯小写口令recorder

案例演示

【在线Demo完整版】

https://xiangyuecn.github.io/Recorder/

2019-3-27 在QQ和微信打开时发现这个网址被屏蔽了尝试申诉了一下。2019-4-7晚上又发现被屏蔽了,小米浏览器也一样报危险网站,尝试打开一下别人的github.io发现全是这样,看来是github.io的问题,被波及了,不过第二天又自己好了。

【Demo片段列表】

  1. 【Demo库】【格式转换】-mp3格式转成其他格式
  2. 【Demo库】【格式转换】-wav格式转成其他格式
  3. 【Demo库】【格式转换】-amr格式转成其他格式
  4. 【教程】实时转码并上传-通用版
  5. 【教程】实时转码并上传-mp3专版
  6. 【Demo库】【文件合并】-mp3多个片段文件合并
  7. 【Demo库】【文件合并】-wav多个片段文件合并
  8. 【教程】实时多路音频混音
  9. 【教程】变速变调音频转换
  10. 【教程】DTMF电话拨号按键信号解码、编码
  11. 【Demo库】PCM采样率提升
  12. 【测试】音频可视化相关扩展测试

【祝福贺卡助手】

使用到这个库用于祝福语音的录制已开通网页版和微信小程序版。专门针对IOS的微信中进行了兼容处理IOS上微信环境中调用的微信的api小程序、公众号api。小程序地址;网页地址:

【注】

如果你的项目用到了这个库也想展示到这里可以发个isuse注明使用介绍和访问方式我们收录在这里。

📖知识库

本库期待的使用场景是语音录制因此音质只要不比高品质的感觉差太多就行1分钟的语音进行编码是很快的但如果录制超长的录音比如10分钟以上不同类型的编码可能会花费比较长的时间因为只有边录边转码(Worker)支持的类型才能进行极速转码。另外未找到双声道语音录制存在的意义(翻倍录音数据大小,并且拉低音质),因此特意仅对单声道进行支持。

浏览器Audio Media兼容性mp3最好wav还行其他要么不支持播放要么不支持编码因此本库最佳推荐使用mp3、wav格式代码也是优先照顾这两种格式。

特别注IOS(11.X、12.X、13.X)上只有Safari支持getUserMediaIOS上其他浏览器均不支持唯一有点卵用的Safari getUserMedia 底层实现bug奇多严重关切他们团队水准临时工少发工资了吧参考下面的已知问题。

特别注大部分国产手机厂商的浏览器系统浏览器都用的UC内核虽然支持getUserMedia方法但并不能使用表现为直接返回拒绝或者干脆没有任何回调UC系列目测全部阵亡含支付宝

留意中途来电话在移动端录音时如果录音中途来电话或者通话过程中打开录音是不一定能进行录音的经过简单测试发现IOS上Safari将暂停返回音频数据直到通话结束才开始继续有音频数据返回小米上Chrome不管是来电还是通话中开始录音都能对麦克风输入的声音进行录音听筒中的并不能录到扬声器外放的会被明显降噪只是简单测试更多机器和浏览器并未做测试不过整体上来看来电话或通话中进行录音的可行性并不理想也不赞成在这种过程中进行录音但只要通话结束后录音还是会正常进行影响基本不大。

录音时对播放音频的影响仅在移动端录音过程中尽量不要去播放音频正在播放的也应该暂停播放否则不同手机系统、浏览器环境可能表现会出乎意料已知IOS Safari上录音打开后如果播放音频声音会变得非常小Android上也有可能声音被切换到听筒播放而不是扬声器大喇叭上播放导致声音也会变小更多可能的情况需要更多设备、浏览器的测试数据才能发掘PC上似乎无此影响。

特别注:如果在iframe里面调用的录音功能,并且和上层的网页是不同的域(跨域了),如果未设置相应策略,权限永远是被拒绝的,参考此处。另外如果要在非跨域的iframe里面使用最佳实践应该是让window.top去加载Recorder异步加载jsiframe里面使用top.Recorder免得各种莫名其妙比如微信里面的各种渣渣功能搞多了就习惯了

如果需要最大限度的兼容IOS仅增加微信支持可以使用RecordApp,它已包含Recorder,源码在src/app-supportapp-support-sample但此兼容库需要服务器端提供微信JsSDK的签名、下载素材接口涉及微信公众订阅号的开发。

支持 Recorder RecordApp
PC浏览器
Android Chrome Firefox
Android微信(含小程序)
Android Hybrid App
Android其他浏览器 未知 未知
IOS Safari
IOS微信(含小程序)
IOS Hybrid App
IOS其他浏览器
开发难度 简单 复杂
第三方依赖 依赖微信公众号

已知问题

2018-09-19 caniuse 注明IOS 11.X - 12.X13.X 上 只有Safari支持调用getUserMedia其他App下WKWebView(UIWebView?)(相关资料)均不支持。经用户测试验证IOS 12上chrome、UC都无法录音部分IOS 12 Safari可以获取到并且能正常录音但部分不行原因未知参考ios 12 支不支持录音了。在IOS上不支持录音的环境下应该采用其他解决方案参考案例演示关于微信JsSDK部分。

2019-02-28 issues#14 如果getUserMedia返回的MediaStreamTrack.readyState == "ended""ended" which indicates that the input is not giving any more data and will never provide new data. ,导致无法录音。如果产生这种情况,目前在rec.open方法调用时会正确检测到,并执行fail回调。造成issues#14 ended原因是App源码中AndroidManifest.xml中没有声明android.permission.MODIFY_AUDIO_SETTINGS权限导致腾讯X5不能正常录音。

2019-03-09 在Android上QQ、微信里请求授权使用麦克风的提示经过长时间观察发现他们的表现很随机、很奇特。可能每次在调用getUserMedia时候都会弹选择也可能选择一次就不会再弹提示也可能重启App后又会弹。如果用户拒绝了可能第二天又会弹或者永远都不弹了要么重置(装)App。使用腾讯X5内核的App测试也是一样奇特表现拒绝权限后可能必须要重置(装)。这个问题貌似跟X5内核自动升级的版本有关。QQ浏览器更加惨不忍睹2019-08-16测试发现卸载重装、拒绝权限后永远无法弹出授权通过浏览器设置-清理-清理地理位置授权才能恢复,重启、重装、清理系统垃圾、删除根目录文件夹(腾讯那个大文件不敢删,毒瘤)垃圾均无效,奇葩。

2019-06-14#29反馈稍微远程真机测试了部分厂商的比较新的Android手机系统浏览器的录音支持情况华为直接返回拒绝小米没有回调OPPO好像是没有回调vivo好像是没有回调另外专门测试了一下UC最新版支付宝直接返回拒绝。另参考。也许他们都商量好了或者本身都是用的UC至于没有任何回调的此种浏览器没有良心。

2019-07-22#34反馈研究后发现,问题一:macOS、IOS的Safari对连续调用录音中途未调用close是有问题的但只要调用close后再重复录音就没有问题已通过特殊手段解决。问题二IOS上如果录音之前先播放了任何Audio录音过程可能会变得很诡异但如果先录音就不存在此问题19-09-18 Evan:QQ1346751357反馈发现本问题并非必现功能页面但本库的Demo内却必现原因不明。chrome、firefox正常的很。目测这两个问题是非我等屌丝能够解决的于是报告给苹果家程序员看看因此发了个帖子,顺手在Feedback Assistant提交了bug report但好几天过去了没有任何回应顺带给微软一个好评。问题一目前已通过全局共享一个MediaStream连接来解决原因在于Safari上MediaStream断开后就无法再次进行连接使用表现为静音改成了全局只连接一次就避免了此问题全局处理也有利于屏蔽底层细节start时无需再调用底层接口提升兼容、可靠性。

2019-10-26 针对#51的问题研究后发现如果录音时设备偶尔出现很卡的情况下CPU被其他程序大量占用浏览器采集到的音频是断断续续的导致10秒的录音可能就只返回了5秒的数据量这个时候最终编码得到的音频时长明显变短播放时的效果就像快放一样。此问题能够稳定复现使用别的程序大量占用CPU来模拟目前已在envIn内部函数中进行了补偿处理在浏览器两次传入PCM数据之间填充一段静默数据已弥补丢失的时长最终编码得到的音频时长将和实际录音时长基本一致消除了快放效果但由于丢失的音频已被静默数据代替听起来就是数据本身的断断续续的效果。在设备不卡时录音没有此问题。

2019-11-03 lamejs原版编码器编码出来的mp3文件首尾存在填充数据并且会占据一定时长这种数据播放时静默记录的信息数据或者填充同一录音mp3格式的时长会比wav格式的时长要长0-100ms左右大部分情况下不会有影响但如果涉及到实时转码并传输的话这些数据将会造成多段mp3片段的总时长比实际录音要长最终播放时会均匀的感觉到停顿并且mp3片段越小越明显。本库已对lamejs编码出来的mp3文件进行了处理去掉了头部的非音频数据但由于编码出来的mp3每一帧数据都有固定时长文件结尾最后一帧可能录音的时长不能刚好填满就会产生填充数据因此本库编码出来的mp3文件会比wav格式长0-30ms左右多出来的时长在mp3的结尾处mp3解码出来的pcm数据直接去掉结尾多出来的部分就和wav中的pcm数据基本一致了另外可以通过调节待编码的pcm数据长度以达到刚好填满最后一帧来规避此问题参考Recorder.SampleData方法提供的连续转码针对此问题的处理但小的mp3片段拼接起来停顿导致的杂音还是非常明显实时处理时使用takeoffEncodeChunk选项可完全避免此问题)。参考wiki

2020-04-26 Safari Bug据QQ群内1048506792190451148开发者反馈研究发现IOS ?-13.X Safari内打开录音后如果切换到了其他标签、或其他App并且播放了任何声音此时将会中断已打开的录音系统级的切换回正在录音的页面这个页面的录音功能将会彻底失效并且刷新也无法恢复录音表现为关闭录音后再次打开录音能够正常获得权限但浏览器返回的采集到的音频为静默的PCM此时地址栏也并未显示出麦克风图标刷新这个标签也也是一样不能正常获得录音只有关掉此标签新打开页面才可正常录音。如果打开录音后关闭了录音然后切换到其他标签或App播放声音然后返回录音页面不会出现此问题。此为Safari的底层Bug也许是少给临时工工钱了吧无能为力。使用长按录音类似的用户交互可大幅度避免踩到这坨翔。

📖方法文档

【构造】rec=Recorder(set)

构造函数,拿到Recorder的实例,然后可以进行请求获取麦克风权限和录音。

set参数为配置对象,默认配置值如下:

set={
    type:"mp3" //输出类型mp3,wav等使用一个类型前需要先引入对应的编码引擎
    ,bitRate:16 //比特率,必须是数字 wav(位):16、8MP3(单位kbps)8kbps时文件大小1k/s16kbps 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数据为从开始录音到现在的所有pcm片段每次回调可能增加0-n个不定量的pcm片段。
                //powerLevel当前缓冲的音量级别0-100。
                //bufferDuration已缓冲时长。
                //bufferSampleRate缓冲使用的采样率当type支持边录边转码(Worker)时,此采样率和设置的采样率相同,否则不一定相同)。
                //newBufferIdx:本次回调新增的buffer起始索引。
                //asyncEndfn() 如果onProcess是异步的(返回值为true时)处理完成时需要调用此回调如果不是异步的请忽略此参数此方法回调时必须是真异步不能真异步时需用setTimeout包裹                //如果需要绘制波形之类功能需要实现此方法即可使用以计算好的powerLevel可以实现音量大小的直观展示使用buffers可以达到更高级效果
                //注意buffers数据的采样率和set.sampleRate不一定相同可能为浏览器提供的原始采样率rec.srcSampleRate也可能为已转换好的采样率set.sampleRate如需浏览器原始采样率的数据请使用rec.buffers原始数据而不是本回调的参数如需明确和set.sampleRate完全相同采样率的数据请在onProcess中自行连续调用采样率转换函数Recorder.SampleData()配合mock方法可实现实时转码和压缩语音传输修改或替换buffers内的数据将会改变最终生成的音频内容注意不能改变第一维数组长度比如简单有限的实现实时静音、降噪、混音等处理详细参考下面的rec.buffers
    
    //*******高级设置******
        //,disableEnvInFix:false 内部参数,禁用设备卡顿时音频输入丢失补偿功能,如果不清楚作用请勿随意使用
        
        //,takeoffEncodeChunk:NOOP //fn(chunkBytes) chunkBytes=[Uint8,...]实时编码环境下接管编码器输出当编码器实时编码出一块有效的二进制音频数据时实时回调此方法参数为二进制的Uint8Array就是编码出来的音频数据片段所有的chunkBytes拼接在一起即为完整音频。本实现的想法最初由QQ2543775048提出。
                //当提供此回调方法时将接管编码器的数据输出编码器内部将放弃存储生成的音频数据环境要求比较苛刻如果当前环境不支持实时编码处理将在open时直接走fail逻辑
                //因此提供此回调后调用stop方法将无法获得有效的音频数据因为编码器内没有音频数据因此stop时返回的blob将是一个字节长度为0的blob
                //目前只有mp3格式实现了实时编码在支持实时处理的环境中将会实时的将编码出来的mp3片段通过此方法回调所有的chunkBytes拼接到一起即为完整的mp3此种拼接的结果比mock方法实时生成的音质更加因为天然避免了首尾的静默
                //目前除mp3外其他格式不可以提供此回调提供了将在open时直接走fail逻辑
}

注意set内是数字的明确传数字,不要传字符串之类的导致不可预测的异常,其他有配置的地方也是一样(感谢214282049@qq.com19-01-10发的反馈邮件

如果录音结束后生成的音频文件的比特率和采样率和set中的不同将会把set中的bitRate、sampleRate更新成音频文件的。

【方法】rec.open(success,fail)

请求打开录音资源如果浏览器不支持录音、用户拒绝麦克风权限、或者非安全环境非https、file等将会调用fail;打开后需要调用close来关闭,因为浏览器或设备的系统可能会显示正在录音。

注意:此方法回调是可能是同步的(异常、或者已持有资源时)也可能是异步的(浏览器弹出权限请求时);一般使用时打开,用完立即关闭;可重复调用,可用来测试是否能录音。

另外:因为此方法会调起用户授权请求,如果仅仅想知道浏览器是否支持录音(比如:如果浏览器不支持就走另外一套录音方案),应使用Recorder.Support()方法。

注意打开录音后如果未调用close关闭可能会影响audio音频的播放表现为移动端audio播放有明显的杂音麦克风的电流音因此如果你录音后有别的操作尽量录完音就立即调用close关闭录音。

特别注: 鉴于UC系浏览器大部分国产手机厂商系统浏览器大概率表面支持录音但永远不会有任何回调、或者此浏览器支持第三种情况用户忽略 并且 此浏览器认为此种情况不需要回调 并且程序员完美实现了);如果当前环境是移动端,可以在调用此方法8秒后如果未收到任何回调,弹出一个自定义提示框(只需要一个按钮),提示内容范本:录音功能需要麦克风权限,请允许;如果未看到任何请求,请点击忽略~,按钮文本:忽略;当用户点击了按钮,直接手动执行fail逻辑,因为此时浏览器压根就没有弹移动端特有的模态话权限请求对话框;但如果收到了回调(可能是同步的,因此弹框必须在rec.open调用前准备好随时取消需要把我们弹出的提示框自动关掉不需要用户做任何处理。pc端的由于不是模态化的请求对话框可能会被用户误点所以尽量要判断一下是否是移动端。

success=fn();

fail=fn(errMsg,isUserNotAllow); 如果是用户主动拒绝的录音权限除了有错误消息外isUserNotAllow=true方便程序中做不同的提示提升用户主动授权概率

【方法】rec.close(success)

关闭释放录音资源,释放完成后会调用success()回调。如果正在录音或者stop调用未完成前调用了close将会强制终止当前录音。

注意如果创建了多个Recorder对象并且调用了open应避免同时有多个对象进行了open只有最后一个新建的才有权限进行实际的资源释放和多个对象close调用顺序无关浏览器或设备的系统才会不再显示正在录音的提示。

【方法】rec.start()

开始录音,需先调用open未close之前可以反复进行调用开始新的录音。

只要open成功后调用此方法是安全的如果未open强行调用导致的内部错误将不会有任何提示stop时自然能得到错误另外open操作可能需要花费比较长时间如果中途调用了stopopen完成时同步的任何start调用将会被自动阻止也是不会有提示的。

【方法】rec.stop(success,fail,autoClose)

结束录音并返回录音数据blob对象拿到blob对象就可以为所欲为了不限于立即播放、上传

success(blob,duration)blob录音数据audio/mp3|wav...格式,duration:录音时长,单位毫秒

fail(errMsg):录音出错回调

autoClosefalse 可选,是否自动调用close,默认为false不调用

提示stop时会进行音频编码根据类型的不同音频编码花费的时间也不相同。对于支持边录边转码(Worker)的类型将极速完成编码并回调对于不支持的10几秒录音花费2秒左右算是正常但内部采用了分段编码+setTimeout来处理界面卡顿不明显。

【方法】rec.pause()

暂停录音。

【方法】rec.resume()

恢复继续录音。

【属性】rec.buffers

此数据为从开始录音到现在为止的所有已缓冲的PCM片段列表buffers = [[Int16,...],...] 为二维数组在没有边录边转码的支持时mock调用、非mp3等录音stop时会使用此完整数据进行转码成指定的格式。

buffers中的PCM数据为浏览器采集的原始音频数据采样率为浏览器提供的原始采样率rec.srcSampleRate;在rec.set.onProcess回调中buffers参数就是此数据或者此数据重新采样后的新数据;修改或替换onProcess回调中buffers参数可以改变最终生成的音频内容,但修改rec.buffers不一定会有效,因此你可以在onProcess中修改或替换buffers参数里面的内容注意只能修改或替换上次回调以来新增的buffer不允许修改已处理过的不允许增删第一维数组允许将第二维数组任意修改替换成空数组也可以以此可以简单有限的实现实时静音、降噪、混音等处理。

如果你需要长时间实时录音(如长时间语音通话),并且不需要得到最终完整编码的音频文件:

  1. 未提供set.takeoffEncodeChunk时Recorder初始化时应当使用一个未知的类型进行初始化如: type:"unknown",仅仅用于初始化而已,实时转码可以手动转成有效格式,因为有效格式可能内部还有其他类型的缓冲,unknown类型onProcess buffersrec.buffers是同一个数组提供set.takeoffEncodeChunk接管了编码器实时输出时无需特殊处理因为编码器内部将不会使用缓冲
  2. 实时在onProcess中修改buffers参数数组可以只保留最后两个元素其他元素设为null代码onProcess: buffers[buffers.length-3]=null不保留也行全部设为null以释放占用的内存rec.buffers将会自动清理无需手动清理注意提供set.takeoffEncodeChunk时应当延迟一下清理不然buffers被清理掉时这个buffers还未推入编码器进行编码
  3. 录音结束时可以不用调用stop,直接调用close丢弃所有数据即可。只要buffers[0]==null时调用stop永远会直接走fail回调。

【属性】rec.srcSampleRate

浏览器提供的原始采样率只有start或mock调用后才会有值此采样率就是rec.buffers数据的采样率。

【方法】rec.mock(pcmData,pcmSampleRate)

模拟一段录音数据后面可以调用stop进行编码。需提供pcm数据 pcmData = [Int16,...] 为一维数组和pcm数据的采样率 pcmSampleRate

提示:在录音实时回调中配合Recorder.SampleData()方法使用效果更佳,可实时生成小片段语音文件。

注意pcmData为一维数组如果提供二维数组将会产生不可预料的错误;如果需要使用类似onProcess回调的buffers或者rec.buffers这种pcm列表二维数组可自行展开成一维或者使用Recorder.SampleData()方法转换成一维。

本方法可用于将一个音频解码出来的pcm数据方便的转换成另外一个格式

var amrBlob=...;//amr音频blob对象
var amrSampleRate=8000;//amr音频采样率

//解码amr得到pcm数据
var reader=new FileReader();
reader.onload=function(){
    Recorder.AMR.decode(new Uint8Array(reader.result),function(pcm){
        transformOgg(pcm);
    });
};
reader.readAsArrayBuffer(amrBlob);

//将pcm转成ogg
function transformOgg(pcmData){
    Recorder({type:"ogg",bitRate:64,sampleRate:32000})
        .mock(pcmData,amrSampleRate)
        .stop(function(blob,duration){
            //我们就得到了新采样率和比特率的ogg文件
            console.log(blob,duration);
        });
};

【静态方法】Recorder.Support()

判断浏览器是否支持录音随时可以调用。注意仅仅是检测浏览器支持情况不会判断和调起用户授权rec.open()会判断用户授权),不会判断是否支持特定格式录音。

【静态方法】Recorder.IsOpen()

由于Recorder持有的录音资源是全局唯一的可通过此方法检测是否有Recorder已调用过open打开了录音功能。

【静态方法】Recorder.Destroy()

销毁已持有的所有全局资源AudioContext、Worker当要彻底移除Recorder时需要显式的调用此方法。大部分情况下不调用Destroy也不会造成问题。

【静态属性】Recorder.TrafficImgUrl

流量统计用1像素图片地址在Recorder首次被实例化时将往这个地址发送一个请求请求是通过Image对象来发送安全可靠默认开启统计url为本库的51la统计用图片地址为空响应流量消耗非常小因此对使用几乎没有影响。

设置为空字符串后将不参与统计大部分情况下无需关闭统计如果你网页的url私密性要求很高请在调用Recorder之前将此url设为空字符串本功能于2019-11-09添加点此前往51la查看统计概况。

【静态属性】Recorder.BufferSize

录音时的AudioContext缓冲大小默认值为4096。会影响H5录音时的onProcess调用速率相对于AudioContext.sampleRate=48000时4096接近12帧/s调节此参数可生成比较流畅的回调动画。

取值256, 512, 1024, 2048, 4096, 8192, or 16384

注意取值不能过低2048开始不同浏览器可能回调速率跟不上造成音质问题。一般无需调整调整后需要先close掉已打开的录音再open时才会生效。

这个属性在旧版Recorder中是放在已废弃的set.bufferSize中后面因为兼容处理Safari上MediaStream断开后就无法再次进行连接使用的问题表现为静音把MediaStream连接也改成了全局只连接一次因此set.bufferSize就移出来变成了Recorder的属性

【静态方法】Recorder.SampleData(pcmDatas,pcmSampleRate,newSampleRate,prevChunkInfo,option)

对pcm数据的采样率进行转换配合mock方法使用效果更佳比如实时转换成小片段语音文件。

注意本方法只会将高采样率的pcm转成低采样率的pcm当newSampleRate>pcmSampleRate想转成更高采样率的pcm时本方法将不会进行转换处理由低的采样率转成高的采样率没有存在的意义在特殊场合下如果确实需要提升采样率比如8k必须转成16k可参考【Demo库】PCM采样率提升自行编写代码转换一下即可。

pcmDatas: Int16,... pcm片段列表二维数组比如可以是rec.buffers、onProcess中的buffers

pcmSampleRate:48000 pcm数据的采样率比如用rec.srcSampleRate、onProcess中的bufferSampleRate

newSampleRate:16000 需要转换成的采样率newSampleRate>=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已处理到的索引
    offset:0.0 已处理到的index对应的pcm中的偏移的下一个位置
    
    //仅作为返回值
    frameNext:null||[Int16,...] 下一帧的部分数据frameSize设置了的时候才可能会有
    sampleRate:16000 结果的采样率<=newSampleRate
    data:[Int16,...] 转换后的PCM结果为一维数组如果是连续转换并且pcmDatas中并没有新数据时data的长度可能为0
}

【静态方法】Recorder.PowerLevel(pcmAbsSum,pcmLength)

计算音量百分比的一个方法返回值0-100主要当做百分比用注意这个不是分贝因此没用volume当做名称。

pcmAbsSum: pcm Int16所有采样的绝对值的和

pcmLength: pcm长度

📖压缩合并一个自己需要的js文件

可参考/src/package-build.js中如何合并的一个文件比如mp3是由recorder-core.js,engine/mp3.js,engine/mp3-engine.js组成的。

除了recorder-core.js其他引擎文件都是可选的,可以把全部编码格式合到一起也,也可以只合并几种,然后就可以支持相应格式的录音了。

可以修改/src/package-build.js后在src目录内执行压缩

cnpm install
npm start

📖关于现有编码器

如果你有其他格式的编码器并且想贡献出来可以提交新增格式文件的PR文件放到/src/engine中我们升级它。

wav (raw pcm format)

wav格式编码器时参考网上资料写的会发现代码和别人家的差不多。源码2kb大小。wav转其他格式参考和测试

wav转pcm

生成的wav文件内音频数据的编码为未压缩的pcm数据raw pcm只是在pcm数据前面加了一个44字节的wav头因此直接去掉前面44字节就能得到原始的pcm数据blob.slice(44,blob.size,"audio/pcm");

简单将多段小的wav片段合成长的wav文件

由于RAW格式的wav内直接就是pcm数据因此将小的wav片段文件去掉wav头后得到的原始pcm数据合并到一起再加上新的wav头即可合并出长的wav文件要求待合成的所有wav片段的采样率和位数需一致。wav合并参考和测试+可移植源码

mp3 (CBR)

采用的是lamejs(LGPL License)这个库的代码,https://github.com/zhuker/lamejs/blob/bfb7f6c6d7877e0fe1ad9e72697a871676119a0e/lame.all.js这个版本的文件代码已对lamejs源码进行了部分改动用于精简代码和修复发现的问题。LGPL协议涉及到的文件mp3-engine.js这些文件也采用LGPL授权不适用MIT协议。源码518kb大小压缩后150kb左右开启gzip后50来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选项可完全避免此问题),参考上面的已知问题。

beta-ogg (Vorbis)

采用的是ogg-vorbis-encoder-js(MIT License)https://github.com/higuma/ogg-vorbis-encoder-js/blob/7a872423f416e330e925f5266d2eb66cff63c1b6/lib/OggVorbisEncoder.js这个版本的文件代码。此编码器源码2.2M超级大压缩后1.6M开启gzip后327K左右。对录音的压缩率比lamejs高出一倍, 但Vorbis in Ogg好像Safari不支持真的假的)。

beta-webm

这个编码器时通过查阅MDN编写的一个玩意没多大使用价值录几秒就至少要几秒来编码。。。原因是未找到对已有pcm数据进行快速编码的方法。数据导入到MediaRecorder音频有几秒就要等几秒类似边播放边收听形。(想接原始录音Stream我不可能给的!)输出音频虽然可以通过比特率来控制文件大小但音频文件中的比特率并非设定比特率采样率由于是我们自己采样的到这个编码器随他怎么搞。只有比较新的浏览器支持需实现浏览器MediaRecorder压缩率和mp3差不多。源码2kb大小。

beta-amr (NB 窄带)

采用的是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.28000hz)音质和同比配置的mp3、ogg差一个档次。由于支持解码操作理论上所有支持Audio的浏览器都可以播放需要自己写代码实现。源码1M多蛮大压缩后445K开启gzip后136K。优点录音文件小。

Recorder.amr2wav(amrBlob,True,False)

已实现的一个把amr转成wav格式来播放的方法True=fn(wavBlob,duration)。要使用此方法需要带上上面的wav格式编码器。仿照此方法可轻松转成别的格式,参考mock方法介绍那节。

📖其他音频格式支持办法

//比如增加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"})

📖扩展

src/extensions目录内为扩展支持库,这些扩展库默认都没有合并到生成代码中,需单独引用(distsrc中的)才能使用。

【附】部分扩展使用效果图(在线运行观看

WaveView扩展

waveview.js4kb大小源码录音时动态显示波形具体样子参考演示地址页面。此扩展参考MCVoiceWave库编写的,具体代码在https://github.com/HaloMartin/MCVoiceWave/blob/f6dc28975fbe0f7fc6cc4dbc2e61b0aa5574e9bc/MCVoiceWave/MCVoiceWaveView.m中。

此扩展是在录音时onProcess回调中使用;Recorder.BufferSize会影响绘制帧率越小越流畅但越消耗cpu默认配置的大概12帧/s。基础使用方法

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 //显示高度
    
    //以上配置二选一
    
    ,scale:2 //缩放系数应为正整数使用2(3? no!)倍宽高进行绘制,避免移动端绘制模糊
    ,speed:8 //移动速度系数,越大越快
    
    ,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.js7kb大小源码音频可视化波形显示具体样子参考演示地址页面。

此扩展的使用方式和WaveView扩展完全相同,请参考上面的WaveView来使用本扩展的波形绘制直接简单的使用PCM的采样数值大小来进行线条的绘制同一段音频绘制出的波形和Audition内显示的波形外观上几乎没有差异。

【构造】surfer=Recorder.WaveSurferView(set)

构造函数,set参数为配置对象,默认配置值如下:

set={
    elem:"css selector" //自动显示到dom并以此dom大小为显示大小
        //或者配置显示大小手动把surferObj.elem显示到别的地方
    ,width:0 //显示宽度
    ,height:0 //显示高度
    
    //以上配置二选一
    
    ,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.js12kb大小源码音频可视化频率直方图显示具体样子参考演示地址页面。此扩展核心算法参考Java开源库jmp123的代码编写的,jmp123版本0.3直方图特意优化主要显示0-5khz语音部分其他高频显示区域较小不适合用来展示音乐频谱。

此扩展的使用方式和WaveView扩展完全相同,请参考上面的WaveView来使用;请注意:必须同时引入lib.fft.js才能正常工作。

【构造】histogram=Recorder.FrequencyHistogramView(set)

构造函数,set参数为配置对象,默认配置值如下:

set={
    elem:"css selector" //自动显示到dom并以此dom大小为显示大小
        //或者配置显示大小手动把frequencyObj.elem显示到别的地方
    ,width:0 //显示宽度
    ,height:0 //显示高度
    
    //以上配置二选一
    
    ,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:"" //峰值小横条阴影颜色,留空为柱子的阴影颜色
    
    //当发生绘制时会回调此方法参数为当前绘制的频率数据和采样率可实现多个直方图同时绘制只消耗一个input输入和计算时间
    ,onDraw:function(frequencyData,sampleRate){}
}

【方法】histogram.input(pcmData,powerLevel,sampleRate)

输入音频数据更新直方图显示。pcmData [Int16,...] 一维数组,为当前的录音数据片段,其他参数和onProcess回调相同。

Sonic扩展

sonic.js37kb大小源码(压缩版gzip后4.5kb),音频变速变调转换,参考此demo片段在线测试使用。此扩展从Sonic.java移植,并做了适当精简。

可到assets/sonic-java目录运行java代码测试原版效果。

本扩展支持

  1. Pitch:变调不变速(会说话的汤姆猫),男女变声,只调整音调,不改变播放速度
  2. Speed:变速不变调(快放慢放),只调整播放速度,不改变音调
  3. Rate:变速变调,会改变播放速度和音调
  4. Volume:支持调整音量
  5. 支持实时处理可在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返回

DTMF扩展

dtmf.decode.js + lib.fft.jsdtmf.encode.js两个js一个解码、一个编码体积小均不超过10kb纯js实现易于移植。参考此demo片段在线测试使用

  1. DTMF电话拨号按键信号解码器解码得到按键值可实现实时从音频数据流中解码得到电话拨号按键信息用于电话录音软解软电话实时提取DTMF按键信号等识别DTMF按键准确度高误识别率低支持识别120ms以上按键间隔+30ms以上的按键音纯js实现易于移植请注意使用dtmf.decode.js必须同时引入lib.fft.js由java移植过来的才能正常工作。
  2. 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此时的检查次数
        totalLen:0 总采样数相对4khz
        pcm:[Int16,...] 4khz pcm数据
    }

【方法】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,...],...]二维数组sampleRatepcm的采样率indexpcms第一维开始索引将从这个pcm开始混合
        返回状态对象{
            newEncodes:[{key:"*",data:[Int16,...]},...] //本次混合新生成的按键信号列表 ,如果没有产生新信号将为空数组
            ,hasNext:false //是否还有未混合完的信号
        }
        注意调用本方法会修改pcms中的内容因此混合结果就在pcms内

📖兼容性

对于支持录音的浏览器能够正常录音并返回录音数据对于不支持的浏览器引入js和执行相关方法都不会产生异常并且进入相关的fail回调。一般在open的时候就能检测到是否支持或者被用户拒绝可在用户开始录音之前提示浏览器不支持录音或授权。

📖Android Hybrid App中录音示例

在Android Hybrid App中使用本库来录音需要在App源码中实现以下两步分

  1. AndroidManifest.xml声明需要用到的两个权限
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
  1. WebChromeClient中实现onPermissionRequest网页授权请求
@Override
public void onPermissionRequest(PermissionRequest request) {
    ...此处应包裹一层系统权限请求
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        request.grant(request.getResources());
    }
}

注:如果应用的腾讯X5内核,除了上面两个权限外,还必须提供android.permission.CAMERA权限。另外无法重写此onPermissionRequest方法他会自己弹框询问如果被拒绝了就看X5脸色了随着X5不停更新什么时候恢复弹框天知地知就是你不知参考已知问题部分。

如果不出意外App内显示的网页就能正常录音了。

备忘小插曲

排查 #46 Android WebView内长按录音不能收到touchend问题时发现touch事件会被打断反复折腾最终发现是每次检测权限都会调用Activity.requestPermissions,而requestPermissions会造成WebView打断touch事件进而产生H5、AppNative原生录都会产生此问题最后老实把精简掉的checkSelfPermission加上检测一下是否已授权,就没有此问题了,囧。

附带测试项目

app-support-sample/demo_android目录中提供了Android测试源码如果不想自己打包可以用打包好的apk来测试文件名为app-debug.apk.zip,自行去掉.zip后缀

📖IOS Hybrid App中录音示例

纯粹的H5录音在IOS WebView中是不支持的需要有Native层的支持具体参考RecordApp中的app-support-sample/demo_ios含IOS App源码。

📖语音通话聊天demo实时编码、传输与播放验证

线测试Demo中包含了一个语音通话聊天的测试功能没有服务器支持所以仅支持局域网内一对一语音。用两个设备浏览器打开两个标签也可以打开demo勾选H5版语音通话聊天按提示交换两个设备的信息即可成功进行P2P连接然后进行语音。实际使用时数据传输可以用WebSocket会简单好多。

编写本语音测试的目的在于验证H5录音实时转码、传输的可行性并验证实时转码mp3格式小片段文件接收后的可播放性。经测试发现除了移动端可能存在设备性能低下的问题以外录音后实时转码mp3并传输给对方是可行的对方接收后播放也能连贯的播放效果还是要看播放代码写的怎么样目前没有比较完美的播放代码。另外16kbps,16khzMP3开语音15分钟大概3M的流量wav 15分钟要37M多流量。

另外除wav外MP3等格式编码出来的音频的播放时间比PCM原始数据要长一些或短一些如果涉及到解码或拼接时这个地方需要注意如果类型支持实时处理时使用takeoffEncodeChunk选项可完全避免此问题)。

📖工具代码运行和静态分发Runtime

在线访问本工具提供在线运行和测试代码的能力本库的大部分小demo将由此工具来进行开发和承载。本工具提供代码片段的分发功能代码存储在url中因此简单可靠额外提供了一套源码作者的身份认证机制。

我们不传输、不存储数据,我们只是代码的可靠搬运工。看图:

📖工具:裸(RAW、WAV)PCM转WAV播放测试和转码

在线访问本工具用来对原始的PCM音频数据进行封装、播放、转码操作极其简单免去了动用二进制编辑工具操作的麻烦。比如加工一下Android AudioRecord(44100)采集的音频。源码在assets/工具-裸PCM转WAV播放测试.html;

📖关于微信JsSDK和RecordApp

微信内浏览器他家的JsSDK也支持录音涉及笨重难调的公众号开发光sdk初始化就能阻碍很多新奇想法的产生signature限制太多只能满足最基本的使用大部分情况足够了。获取音频数据必须绕一个大圈录好音了->上传到微信服务器->自家服务器请求微信服务器多进行媒体下载->保存录音微信小程序以前也是二逼路子现在稍微好点能实时拿到录音mp3数据

[2018]由于微信IOS上不支持原生JS录音Android上又支持为了兼容而去兼容的事情我是拒绝的而且是仅仅为了兼容IOS上面的微信其实也算不上去兼容因为微信JsSDK中的接口完全算是另外一种东西接入的话对整个录音流程都会产生完全不一样的变化还不如没有进入录音流程之前就进行分支判断处理。

[2019]大动干戈仅为兼容IOS而生不得不向大厂低头我还是为兼容而去兼容了IOS微信对不支持录音的IOS微信浏览器小程序web-view进行了兼容使用微信JsSDK来录音并以前未开源的兼容代码基础上重写了RecordApp,源码在app-support-samplesrc/app-support内。

最后如果要兼容IOS可以自行接入JsSDK或使用RecordApp(没有公众号开个订阅号又不要钱),基本上可以忽略兼容性问题,就是麻烦点。

捐赠

如果这个库有帮助到您,请 Star 一下。

您也可以使用支付宝或微信打赏作者: