html5錄音支持pc和Android、ios部分瀏覽器,微信也是支持的,JavaScript getUserMedia


以前在前人基礎上重復造了一個網頁錄音的輪子,順帶把github倉庫使用研究了一下,扔到了github上。

優勢在於結構簡單,可插拔式的錄音格式支持,幾乎可以支持任意格式(前提有相應的編碼器);默認提供實時音量反饋、有一個波形顯示擴展支持。錄音結果非常容易立即播放錄音或者上傳錄音到服務器(提供參考源碼)。

2018-05-16首發,2019-04-21更新

GitHub地址:https://github.com/xiangyuecn/Recorder

在線測試demo傳送門:https://xiangyuecn.github.io/Recorder/

效果圖

自述

以前准備做一個網頁版聊天界面,表情啊、圖片啊、上傳文件啊都應該要有,視頻就算了,語音還是要的。

當下環境html5的錄音功能支持情況大為良好(IOS上偏落后點)
2019-04瀏覽器支持情況,棒棒噠

如是,就有了這個輪子。

以下內容copy自README

Recorder用於html5錄音

在線測試,支持大部分已實現getUserMedia的移動端、PC端瀏覽器,包括騰訊Android X5內核(QQ、微信)。點此查看瀏覽器支持情況。

錄音默認輸出mp3格式,另外可選wav格式(此格式錄音文件超大);有限支持ogg(beta)、webm(beta)、amr(beta)格式;支持任意格式擴展(前提有相應編碼器)。

mp3默認16kbps的比特率,2kb每秒的錄音大小,音質還可以(如果使用8kbps可達到1kb每秒,不過音質太渣)。

mp3使用lamejs編碼,壓縮后的recorder.mp3.min.js文件150kb左右(開啟gzip后54kb)。如果對錄音文件大小沒有特別要求,可以僅僅使用錄音核心+wav編碼器,源碼不足300行,壓縮后的recorder.wav.min.js不足4kb。

瀏覽器Audio Media兼容性mp3最好,wav還行,其他要么不支持播放,要么不支持編碼。

特別注:IOS(11.X、12.X)上只有Safari支持getUserMedia,其他瀏覽器均不支持,參考下面的已知問題。

快速使用

【1】加載框架

在需要錄音功能的頁面引入壓縮好的recorder..min.js文件即可 (注意:需要在https等安全環境下才能進行錄音*)

<script src="recorder.mp3.min.js"></script>

或者直接使用源碼(src內的為源碼、dist內的為壓縮后的),可以引用src目錄中的recorder-core.js+相應類型的實現文件,比如要mp3錄音:

<script src="src/recorder-core.js"></script> <!--必須引入的錄音核心-->
<script src="src/engine/mp3.js"></script> <!--相應格式支持文件-->
<script src="src/engine/mp3-engine.js"></script> <!--如果此格式有額外的編碼引擎的話,也要加上-->

【2】調用錄音

然后使用,假設立即運行,只錄3秒

var rec=Recorder();//使用默認配置,mp3格式

rec.open(function(){//打開麥克風授權獲得相關資源
    rec.start();//開始錄音
    
    setTimeout(function(){
        rec.stop(function(blob,duration){//到達指定條件停止錄音
            console.log(URL.createObjectURL(blob),"時長:"+duration+"ms");
            rec.close();//釋放錄音資源
            //已經拿到blob文件對象想干嘛就干嘛:立即播放、上傳
            
            /*立即播放例子*/
            var audio=document.createElement("audio");
            audio.controls=true;
            document.body.appendChild(audio);
            //簡單的一嗶
            audio.src=URL.createObjectURL(blob);
            audio.play();
            
        },function(msg){
            console.log("錄音失敗:"+msg);
        });
    },3000);
},function(msg,isUserNotAllow){//用戶拒絕未授權或不支持
    console.log((isUserNotAllow?"UserNotAllow,":"")+"無法錄音:"+msg);
});

【附】錄音上傳示例

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

【附】問題排查

  • 打開Demo頁面試試看,是不是也有同樣的問題。
  • 檢查是不是在https之類的安全環境下調用的。
  • 檢查是不是IOS系統,確認caniuseIOS對getUserMedia的支持情況。
  • 檢查上面第1步是否把框架加載到位,在Demo頁面有應該加載哪些js的提示。
  • 提交Issue,熱心網友幫你解答。

方法文檔

【構造】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格式文件大小=sampleRate*時間;mp3此項對低比特率文件大小有影響,高比特率幾乎無影響。
                //wav任意值,mp3取值范圍:48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000
    
    ,bufferSize:4096 //AudioContext緩沖大小。會影響onProcess調用速度,相對於AudioContext.sampleRate=48000時,4096接近12幀/s,調節此參數可生成比較流暢的回調動畫。
                //取值256, 512, 1024, 2048, 4096, 8192, or 16384
                //注意,取值不能過低,2048開始不同瀏覽器可能回調速率跟不上造成音質問題(低端瀏覽器→說的就是騰訊X5)
    
    ,onProcess:NOOP //接收到錄音數據時的回調函數:fn(this.buffer,powerLevel,bufferDuration,bufferSampleRate) 
                //buffer=[緩沖PCM數據,...],powerLevel:當前緩沖的音量級別0-100,bufferDuration:已緩沖時長,bufferSampleRate:緩沖使用的采樣率
                //如果需要繪制波形之類功能,需要實現此方法即可,使用以計算好的powerLevel可以實現音量大小的直觀展示,使用buffer可以達到更高級效果
}

注意:set內是數字的明確傳數字,不要傳字符串之類的導致不可預測的異常,其他有配置的地方也是一樣(感謝214282049@qq.com19-01-10發的反饋郵件)。

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

請求打開錄音資源,如果瀏覽器不支持錄音或用戶拒絕麥克風權限將會調用fail,打開后需要調用close

注意:此方法是異步的;一般使用時打開,用完立即關閉;可重復調用,可用來測試是否能錄音。

另外:因為此方法會調起用戶授權請求,如果僅僅想知道瀏覽器是否支持錄音(比如:如果瀏覽器不支持就走另外一套錄音方案),應使用Recorder.Support()方法。

success=fn();

fail=fn(errMsg,isUserNotAllow); 如果是用戶主動拒絕的錄音權限,除了有錯誤消息外,isUserNotAllow=true,方便程序中做不同的提示,提升用戶主動授權概率

【方法】rec.close(success)

關閉釋放錄音資源,釋放完成后會調用success()回調

【方法】rec.start()

開始錄音,需先調用open;如果不支持、錯誤,不會有任何提示,因為stop時自然能得到錯誤。

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

結束錄音並返回錄音數據blob對象,拿到blob對象就可以為所欲為了,不限於立即播放、上傳

success(blob,duration)blob:錄音數據audio/mp3|wav...格式,duration:錄音時長,單位毫秒

fail(errMsg):錄音出錯回調

提示:stop時會進行音頻編碼,音頻編碼可能會很慢,10幾秒錄音花費2秒左右算是正常,編碼並未使用Worker方案(文件多),內部采取的是分段編碼+setTimeout來處理,界面卡頓不明顯。

【方法】rec.pause()

暫停錄音。

【方法】rec.resume()

恢復繼續錄音。

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

模擬一段錄音數據,后面可以調用stop進行編碼。需提供pcm數據int[],和pcm數據的采樣率。

可用於將一個音頻解碼出來的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打開了錄音功能。

壓縮合並一個自己需要的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

關於現有編碼器

如果你有其他格式的編碼器並且想貢獻出來,可以提交新增格式文件的pull(文件放到/src/engine中),我們升級它。

wav

wav格式編碼器時參考網上資料寫的,會發現代碼和別人家的差不多。源碼2kb大小。

mp3

采用的是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。

beta-ogg

采用的是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

采用的是benz-amr-recorder(MIT License)優化后的amr.js(Unknown License),https://github.com/BenzLeung/benz-amr-recorder/blob/462c6b91a67f7d9f42d0579fb5906fad9edb2c9d/src/amrnb.js這個版本的文件代碼,已對此代碼進行過調整更方便使用。支持編碼和解碼操作。由於最高只有12.8kbps的碼率,音質和同比配置的mp3、ogg差一個檔次。由於支持解碼操作,理論上所有支持Audio的瀏覽器都可以播放(需要自己寫代碼實現)。源碼1M多,蠻大,壓縮后445K,開啟gzip后136K。優點:錄音文件小。

Recorder.amr2wav(amrBlob,True,False)

已實現的一個把amr轉成wav格式來播放的方法,True=fn(wavBlob,duration)。要使用此方法需要帶上上面的wav格式編碼器。仿照此方法可輕松轉成別的格式,參考mock方法介紹那節。

其他音頻格式支持辦法

//比如增加aac格式支持 (可參考/src/engine/mp3.js實現)

//新增一個aac.js,編寫以下格式代碼即可實現這個類型
Recorder.prototype.aac=function(pcmData,successCall,failCall){
    //通過aac編碼器把pcm數據轉成aac格式數據,通過this.set拿到傳入的配置數據
    ... pcmData->aacData
    
    //返回數據
    successCall(new Blob(aacData,{type:"audio/aac"}));
}

//調用
Recorder({type:"aac"})

擴展

src/extensions目錄內為擴展支持庫,這些擴展庫默認都沒有合並到生成代碼中,需單獨引用(distsrc中的)才能使用。

WaveView擴展

waveview.js,4kb大小源碼,錄音時動態顯示波形,具體樣子參考演示地址頁面。此擴展參考MCVoiceWave庫編寫的,具體代碼在https://github.com/HaloMartin/MCVoiceWave/blob/f6dc28975fbe0f7fc6cc4dbc2e61b0aa5574e9bc/MCVoiceWave/MCVoiceWaveView.m中。

此擴展是在錄音時onProcess回調中使用;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:2 //線條基礎粗細
            
    //漸變色配置:[位置,css顏色,...] 位置: 取值0.0-1.0之間
    ,linear1:[0,"rgba(150,97,236,1)",1,"rgba(54,197,252,1)"] //線條漸變色1,從左到右
    ,linear2:[0,"rgba(209,130,253,0.6)",1,"rgba(54,197,252,0.6)"] //線條漸變色2,從左到右
    ,linearBg:[0,"rgba(255,255,255,0.2)",1,"rgba(54,197,252,0.2)"] //背景漸變色,從上到下
}

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

輸入音頻數據,更新波形顯示,這個方法調用的越快,波形越流暢。pcmData為當前的錄音數據片段,其他參數和onProcess回調相同。

兼容性

對於支持錄音的瀏覽器能夠正常錄音並返回錄音數據;對於不支持的瀏覽器,引入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方法,他會自己彈框詢問,如果被拒絕了就永遠拒絕了,參考已知問題部分。

如果不出意外,App內顯示的網頁就能正常錄音了。

附帶測試項目

.assets/android_test目錄中提供了Android測試源碼(如果不想自己打包可以用打包好的apk來測試,位於.assets/android_test/app-debug-xxx.apk)。提供了系統自帶WebView、和騰訊X5內核兩個測試界面。

關於微信JsSDK

微信內瀏覽器他家的JsSDK也支持錄音,涉及笨重難調的公眾號開發(光sdk初始化就能阻礙很多新奇想法的產生,signature限制太多),只能滿足最基本的使用(大部分情況足夠了)。如果JsSDK錄完音能返回音頻數據,這個SDK將好用10000倍,如果能實時返回音頻數據,將好用100000倍。關鍵是他們家是拒絕給這種簡單好用的功能的,必須繞一個大圈:錄好音了->上傳到微信服務器->自家服務器請求微信服務器多進行媒體下載->保存錄音(微信小程序以前也是二逼路子,現在稍微好點能實時拿到錄音mp3數據),如果能升級:錄好音了拿到音頻數據->上傳保存錄音,目測對最終結果是沒有區別的,還簡單不少,對微信自家也算是非常經濟實用。[2018]由於微信IOS上不支持原生JS錄音,Android上又支持,為了兼容而去兼容的事情我是拒絕的(而且是僅僅為了兼容IOS上面的微信),其實也算不上去兼容,因為微信JsSDK中的接口完全算是另外一種東西,接入的話對整個錄音流程都會產生完全不一樣的變化,還不如沒有進入錄音流程之前就進行分支判斷處理。

最后:如果是在微信上用的多,應優先直接接入他家的JsSDK(沒有公眾號開個訂閱號又不要錢),基本上可以忽略兼容性問題,就是麻煩點。

已知問題

2018-09-19 caniuse 注明IOS 11.X - 12.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內核自動升級的版本有關。

如果這個庫有幫助到您,請 Star 一下。GitHub:https://github.com/xiangyuecn/Recorder


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM