Recorder

Project Url: xiangyuecn/Recorder
Introduction: html5 录音,JavaScript getUserMedia,支持 pc 和 Android、ios 部分浏览器,微信也是支持的
More: Author   ReportBugs   
Tags:

源 GitHub 仓库】 | 【Gitee 镜像库】如果本文档图片没有显示,请手动切换到 Gitee 镜像库阅读文档。

支持在大部分已实现getUserMedia的移动端、PC 端浏览器麦克风录音、实时处理,主要包括:Chrome、Firefox、Safari、iOS 14.3+、Android WebView、腾讯 Android X5 内核(QQ、微信、小程序 WebView)、Electron、大部分 2021 年后更新的 Android 手机自带浏览器、2024 年后更新的 Android 版 UC 内核(支付宝、钉钉);不支持:Android 版 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 片段列表

  1. 【Demo 库】【格式转换】-mp3 等格式解码转成其他格式
  2. 【Demo 库】【格式转换】-wav 格式转成其他格式
  3. 【Demo 库】【格式转换】-amr 格式转成其他格式
  4. 【教程】【音频流】【上传】实时转码并上传-通用版
  5. 【教程】【音频流】【上传】实时转码并上传-mp3 专版
  6. 【教程】【音频流】【上传】实时转码并上传-pcm 固定帧大小
  7. 【教程】【音频流】【播放】实时解码播放音频片段
  8. 【教程】【ASR】实时语音识别、音频文件转文字-阿里云版
  9. 【教程】实时录制处理 audio、video 标签的 captureStream 流
  10. 【Demo 库】【文件合并】-mp3 多个片段文件合并
  11. 【Demo 库】【文件合并】-wav 多个片段文件合并
  12. 【教程】实时多路音频混音
  13. 【教程】变速变调音频转换
  14. 【教程】新录音从老录音接续、或录制中途插入音频
  15. 【教程】DTMF(电话拨号按键信号)解码、编码
  16. 【Demo 库】PCM 采样率提升
  17. 【Demo 库】【信号处理】IIR 低通、高通滤波
  18. 【测试】【信号处理】FFT 频域分析 ECharts 频谱曲线图
  19. 【测试】WebM 格式解析并提取音频
  20. 【测试】G711、G72X 编码和解码播放
  21. 【Demo 库】js 二进制转换-Base64/Hex/Int16Array/ArrayBuffer/Blob
  22. 【测试】音乐合成-用波形函数将歌曲简谱文本转成 PCM
  23. 【测试】音频可视化相关插件测试

App Demo

Android Demo App : 下载 APK(40kb,删除.zip 后缀, 源码

iOS Demo App :下载源码 自行编译

【QQ 群】交流与支持

欢迎加 QQ 群:①群 781036591、②群 748359095、③群 450721519,纯小写口令:recorder

Recorder logo

Stars Forks npm Downloads npm Version License JsDelivr CDN unpkg CDN 51LA

:open_book:快速使用

你可以通过阅读和运行QuickStart.html文件来快速入门学习,直接将QuickStart.htmlcopy 到你的(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:JsDelivrunpkg

<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 ,如果直接 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:import("https://unpkg.com/recorder-core/recorder.mp3.min.js").then(()=>console.log("import ok"))
//②可视化插件和显示:import("https://unpkg.com/recorder-core/src/extensions/waveview.js").then(()=>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,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)等

            //可实时绘制波形(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();
};

/**结束录音**/
function recStop(){
    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);});

【附】Android App - WebView 中录音示例

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

  1. AndroidManifest.xml声明需要用到的两个权限

    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
    
  2. WebChromeClient中实现onPermissionRequest网页授权请求

    @Override
    public void onPermissionRequest(PermissionRequest request) {
     //需判断 request.getResources()中包含了 PermissionRequest.RESOURCE_AUDIO_CAPTURE 才进行权限处理,否则不认识的请求直接 deny()
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
         //录音是敏感权限,必须 app 先有录音权限后,网页才会有录音权限,伪代码:
         App 的系统录音权限请求()
             .用户已授权(()->{
                 //直接静默授权,或者再弹个确认对话框让用户确认是否允许此网页录音后 grant|deny
                 request.grant(request.getResources());
             })
             .用户拒绝授权(()->{
                 request.deny();
             })
     }
    }
    

注:如果应用的腾讯 X5 内核,除了上面两个权限外,还必须提供android.permission.CAMERA权限。另外无法重写此onPermissionRequest方法,默认他会自己弹框询问(如果点了拒绝就很惨了),可以通过调用webView.setWebChromeClientExtension来重写 X5 的IX5WebChromeClientExtension.onPermissionRequest方法来进行权限处理,参考此篇X5 集成文章最后面的网页权限处理代码。

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

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

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

【附】iOS App - WebView 中录音示例

在 iOS App WebView 中使用本库来录音,需要在 App 源码 Info.plist 中声明使用麦克风 NSMicrophoneUsageDescription,无需其他处理,WebView 会自己处理好录音权限;注意:iOS App 需要在项目 Background Modes 中勾选 Audio 才能在后台保持录音,不然 App 切到后台后立马会停止录音。

iOS 14.3+以上版本才支持 WebView 中进行 H5 录音;iOS 15+提供了静默授权支持,参考WKUIDelegate中的 Requesting Permissions -> requestMediaCapturePermissionFor,默认未实现,会导致 WebView 每次打开后第一次录音时、或长时间无操作再打开录音时均会弹出录音权限对话框。

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

【附】微信小程序中录音集成参考

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 部分版本上(不同系统版本问题不重样),好在这玩意能更新

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 群内1048506792190451148开发者反馈研究发现,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 的是麦克风
            ,noiseSuppression:true //降噪(ANS)开关,不设置时由浏览器控制(一般为默认打开),设为 true 明确打开,设为 false 明确关闭
            ,echoCancellation:true //回声消除(AEC)开关,取值和降噪开关一样
            ,autoGainControl:true //自动增益(AGC)开关,取值和降噪开关一样
        }*/
                //普通麦克风录音时 getUserMedia 方法的 audio 配置参数;注意:不同浏览器的支持程度不同,提供的任何配置值都不一定会生效
                //回声消除、降噪开关这两个参数浏览器一般默认为打开, 注意:移动端打开降噪、回声消除可能会表现的很怪异(包括系统播放音量变小),但 iOS 上如果关闭又可能导致录音没有声音,如需更改配置请 Android 和 iOS 分别配置,并测试好,PC 端没有这些问题
                //由于麦克风是全局共享的,所以新配置后需要 close 掉以前的再重新 open
                //更多参考: 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.com19-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+FormDataWebSocket直接发送到服务器,或者用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(不允许修改已处理过的,不允许增删第一维数组,允许将第二维数组任意修改替换成空数组也可以);以此可以简单有限的实现实时静音、降噪、混音等处理。

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

  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.dataType

stop 时返回的录音数据类型,取值:blobarraybuffer,默认为blob(返回 Blob 文件对象);非浏览器环境中一般需要设为arraybuffer(返回 ArrayBuffer 对象),或直接设置全局属性Recorder.DefaultDataType

【属性】rec.srcSampleRate

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

【方法】rec.envIn(pcmData,pcmAbsSum)

本方法是一个内部使用的最为核心方法,如果你不知道用途,请勿随意调用,配套的有私有方法envStart(mockEnvInfo,sampleRate)(私有方法请自行阅读源码),这两方法控制着录音的开启、实时音频输入逻辑,起到隔离平台环境差异的作用(Recorder、RecordApp 共享使用了本机制,实现了录音过程和平台环境无关)。

通过调用本方法,会在当前正在录制的录音中追加进新的 pcm 数据,每次调用本方法都会触发 onProcess 回调;从而可以做到:在录音过程中插入音频数据、在新的录音中注入之前老的录音的 buffers 数据可以做到接续录音 等业务逻辑,可参考上面的 Demo 片段列表中的新录音从老录音接续、或录制中途插入音频例子。

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 时尝试创建新的非全局对象并返回,失败时依旧返回全局的;成功时返回新的,注意用完必须自己调用Recorder.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 时返回的录音数据类型,取值:blobarraybuffer,默认为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 方法可转换成音频文件,比如实时转换成小片段语音文件。

注意:本方法只会将高采样率的 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 中的偏移的下一个位置

    //可定义,指定的一个滤波配置:默认使用 Recorder.IIRFilter 低通滤波(可有效抑制混叠产生的杂音,新采样率大于 pcm 采样率的 75%时不默认滤波),如果提供了配置但 fn 为 null 时将不滤波;sr 为此滤波函数对应的初始化采样率,当采样率和 pcmSampleRate 参数不一致时将重新设为默认函数
    filter:null||{fn:fn(sample),sr:pcmSampleRate}

    //仅作为返回值
    frameNext:null||[Int16,...] 下一帧的部分数据,frameSize 设置了的时候才可能会有
    sampleRate:16000 结果的采样率,<=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目录内为插件支持库,这些插件库默认都没有合并到生成代码中,需单独引用(distsrc中的)才能使用。

【可移植】大部分可视化插件均可以移植到其他语言环境,比如: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 流中,完成将连续的音频片段文件转换成流。

此插件仅支持在浏览器环境中使用。

可以用于

  1. Recorder onProcess 等实时处理中,将实时处理好的音频片段转直接换成 MediaStream,此流可以作为 WebRTC 的 local 流发送到对方,或播放出来;
  2. 接收到的音频片段文件的实时播放,比如:WebSocket 接收到的录音片段文件播放、WebRTC remote 流(Recorder 支持对这种流进行实时处理)实时处理后的播放;
  3. 单个音频文件的实时播放处理,比如:播放一段音频,并同时进行可视化绘制(其实自己解码+播放绘制比直接调用这个更有趣,但这个省事、配套功能多点)。

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);

//暂停播放,暂停后:实时模式下会丢弃所有 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/NodeJsServer_asr.aliyun.short.js)。
  • 也支持在非浏览器环境中使用,比如微信小程序、uni-app,提供compatibleWebSocket进行适配即可。

本插件单次语音识别时虽长无限制,最佳使用场景还是 1-5 分钟内的语音识别;60 分钟以上的语音识别本插件也能胜任(需自行进行重试容错处理),但太长的识别场景不太适合使用阿里云一句话识别(阿里云单次一句话识别最长 60 秒,本插件自带拼接过程,所以无时长限制);为什么采用一句话识别:因为便宜。

【关于腾讯云版的对接说明】 腾讯云一句话语音识别(不支持实时特性),前端基本上没有什么需要做的,仅需让后端提供一个录音文件上传接口(很容易),前端将录制好 1 分钟内的语音文件直接上传给服务器,由后端调用腾讯云语一句话音识别接口,然后返回结果即可。暂不提供插件、测试代码。

相较于阿里云的一句话语音识别:前端直接对接阿里云很容易(后端对接会很难,音频数据前端直连阿里云,无需走后端),后端对接腾讯云很容易(前端无法直连腾讯云,音频数据必须走后端);根据自己的业务需求选择合适的云进行对接,避免多走弯路。

对接流程

  1. 到阿里云开通 一句话识别 服务(可试用一段时间,正式使用时应当开通商用版,很便宜),得到 AccessKey、Secret,参考:https://help.aliyun.com/document_detail/324194.html
  2. 到阿里云智能语音交互控制台创建相应的语音识别项目,并配置好项目,得到 Appkey,每个项目可以设置一种语言模型,要支持多种语言就创建多个项目;
  3. 需要后端提供一个 Token 生成接口(用到上面的 Key 和 Secret),可直接参考或本地运行此 NodeJs 后端测试程序:/assets/demo-asr/NodeJsServer_asr.aliyun.short.js,配置好代码里的阿里云账号后,在目录内直接命令行执行node NodeJsServer_asr.aliyun.short.js即可运行提供本地测试接口;
  4. 前端调用 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 代码测试原版效果。

本插件支持

  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 返回

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.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,此时的检查次数
        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 文件,pcm 片段文件合并+可移植源码:PCMMerge

Recorder.pcm2wav(data,True,False)

已实现的一个把 pcm 转成 wav 格式来播放的方法,data = { sampleRate:16000 pcm 的采样率 , bitRate:16 pcm 的位数 取值:8 或 16 , blob:pcm 的 blob 对象 }True=fn(wavBlob,duration)。要使用此方法需要带上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

简单将多段小的 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),采样率只支持 8000True=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:其它功能介绍

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

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

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

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

:star:捐赠

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

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

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools