JS實現audio音訊剪裁剪下複製播放與上傳(步驟詳解)
背景是這樣的,使用者上傳音訊檔案,可能只需要幾十秒就夠了,但是常規的音樂都要3~5分鐘,80%的流量都是不需要的,要是就這麼傳上去,其實是流量的浪費,如果可以在前端就進行剪裁,也就是隻取前面一段時間的音訊,豈不是可以給公司省很多流量費用,前端的業務價值就體現了。
關鍵如何實現呢?
下面,就以“擷取使用者上傳音訊前3秒內容”的需求示意下如何藉助Web Audio API實現音訊的部分複製與播放功能。
一、不嗶嗶,直接正題
實現步驟如下。
1. File物件轉ArrayBuffer
在Web網頁中,使用者選擇的檔案是個file物件,我們可以將這個檔案物件轉換成Blob、ArrayBuffer或者Base64。
在音訊處理這裡,都是使用ArrayBuffer這個資料型別。
程式碼如下所示,假設file型別的檔案選擇框的id是 'file'
。
file.onchange = function (event) { var file = event.target.files[0]; // 開始識別 var reader = new FileReader(); reader.onload = function (event) { var arrBuffer = event.target.result; // arrBuffer就是包含音訊資料的ArrayBuffer物件 }); reader.readAsArrayBuffer(file); };
使用的是 readAsArrayBuffer()
方法,無論是MP3格式、OGG格式還是WAV格式,都可以轉換成ArrayBuffer型別。
2. ArrayBuffer轉AudioBuffer
這裡的ArrayBuffer相對於把音訊檔案陣列化了,大家可以理解為把音訊檔案分解成一段一段的,塞進了一個一個有地址的小屋子裡,在計算機領域稱為“緩衝區”,就是單詞Buffer的意思。
所謂音訊的剪裁,其實就是希望可以複製音訊前面一段時間的內容。
但是問題來了,ArrayBuffer裡面的資料並沒有分類,統一分解了,想要準確提取某一截音訊資料,提取不出來。
所以,才需要轉換成AudioBuffer,純粹的音訊資料,方便提取。
AudioBuffer是一個僅僅包含音訊資料的資料物件,是Web Audio API中的一個概念。
既然說到了Web Audio API,那我們就順便……順便……,想了想,還是不展開,因為太龐雜了,這Web Audio API至少比Web Animation API複雜了10倍,API之多,體量之大,世間罕見,想要完全吃透了,沒有三年五載,啃不下來。
如果大家不是想要立志成為音視訊處理專家,僅僅是臨時解決一點小毛小病的問題,則不必深入,否則腦坑疼,使用MDN文件中的一些案例東拼西湊,基本的效果也能弄出來。
扯遠了,回到這裡。
AudioBuffer大家可以理解為音樂資料,那為什麼叫AudioBuffer,不叫AudioData呢?
因為Buffer是個專有名詞,直譯為緩衝區,大家可以理解為高速公路,AudioBuffer處理資料更快,而且還有很多延伸的API,就像是高速公路上的服務區,有吃有喝還有加油的地方。
AudioData一看名字就是鄉下土鱉,雖然接地氣,但是,處理好幾兆的資料的時候,就有些帶不動了,就好像騎小電驢,在公速公路和鄉道縣道沒多大區別,但是如果是開跑車,嘖嘖,鄉下路就帶不動了。
如何才能轉換成AudioBuffer呢?
使用AudioContext物件的 decodeAudioData()
方法,程式碼如下:
var audioCtx = new AudioContext(); audioCtx.decodeAudioData(arrBuffer,function(audioBuffer) { // audioBuffer就是AudioBuffer });
3. 複製AudioBuffer前3秒資料
AudioBuffer物件是一個音訊專用Buffer物件,包含很多音訊資訊,包括:
duration numberOfChannels sampleRate
等。
包括一些音訊聲道資料處理方法,例如:
getChannelData() copyFromChannel() copyToChannel()
文件見這裡: https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer
所以,實現的原理很簡單,建立一個空的AudioBuffer,複製現有的通道資料前3秒的資料,然後複製的內容寫入到這個空的AudioBuffer,於是我們就得到了一個剪裁後的音訊Buffer資料了。
程式碼如下:
// 聲道數量和取樣率 var channels = audioBuffer.numberOfChannels; var rate = audioBuffer.sampleRate; // 擷取前3秒 var startOffset = 0; var endOffset = rate * 3; // 3秒對應的幀數 var frameCount = endOffset - startOffset; // 建立同樣採用率、同樣聲道數量,長度是前3秒的空的AudioBuffer var newAudioBuffer = new AudioContext().createBuffer(channels,endOffset - startOffset,rate); // 建立臨時的Array存放複製的buffer資料 var anotherArray = new Float32Array(frameCount); // 聲道的資料的複製和寫入 var offset = 0; for (var channel = 0; channel < channels; channel++) { audioBuffer.copyFromChannel(anotherArray,channel,startOffset); newAudioBuffer.copyToChannel(anotherArray,offset); } // newAudioBuffer就是全新的複製的3秒長度的AudioBuffer物件
上面JavaScript程式碼中的變數 newAudioBuffer
就是全新的複製的3秒長度的AudioBuffer物件。
4. 使用newAudioBuffer做點什麼?
其實應該是有了AudioBuffer物件後我們可以做點什麼。
能做很多事情。
1) 如果希望直接播放
我們可以直接把AudioBuffer的資料作為音訊資料進行播放
// 建立AudioBufferSourceNode物件 var source = audioCtx.createBufferSource(); // 設定AudioBufferSourceNode物件的buffer為複製的3秒AudioBuffer物件 source.buffer = newAudioBuffer; // 這一句是必須的,表示結束,沒有這一句沒法播放,沒有聲音 // 這裡直接結束,實際上可以對結束做一些特效處理 source.connect(audioCtx.destination); // 資源開始播放 source.start();
2) 如果希望在<audio>元素中播放
這個還挺麻煩的。
從 <audio>
的src屬性獲取音訊資源,再進行處理是簡單的,網上的案例也很多。
但是,想要處理後的AudioBuffer再變成src讓 <audio>
元素播放,嘿嘿,就沒那麼容易了。
我 (張鑫旭) 找了一圈,沒有看到Web Audio API中有專門的“逆轉錄”方法。
唯一可行的路數就是根據AudioBuffer資料,重新構建原始的音訊資料。研究了一番,轉成WAV格式相對容易,想要轉換成MP3格式比較麻煩,這裡有個專案: https://github.com/higuma/mp3-lame-encoder-js 不過自己沒驗證過,不過看程式碼量,還挺驚人的。
因此,我們的目標還是轉到WAV音訊檔案生成上吧,下面這段方法是從網上找的AudioBuffer轉WAV檔案的方法,以Blob資料格式返回。
// Convert AudioBuffer to a Blob using WAVE representation function bufferToWave(abuffer,len) { var numOfChan = abuffer.numberOfChannels,length = len * numOfChan * 2 + 44,buffer = new ArrayBuffer(length),view = new DataView(buffer),channels = [],i,sample,offset = 0,pos = 0; // write WAVE header // "RIFF" setUint32(0x46464952); // file length - 8 setUint32(length - 8); // "WAVE" setUint32(0x45564157); // "fmt " chunk setUint32(0x20746d66); // length = 16 setUint32(16); // PCM (uncompressed) setUint16(1); setUint16(numOfChan); setUint32(abuffer.sampleRate); // avg. bytes/sec setUint32(abuffer.sampleRate * 2 * numOfChan); // block-align setUint16(numOfChan * 2); // 16-bit (hardcoded in this demo) setUint16(16); // "data" - chunk setUint32(0x61746164); // chunk length setUint32(length - pos - 4); // write interleaved data for(i = 0; i < abuffer.numberOfChannels; i++) channels.push(abuffer.getChannelData(i)); while(pos < length) { // interleave channels for(i = 0; i < numOfChan; i++) { // clamp sample = Math.max(-1,Math.min(1,channels[i][offset])); // scale to 16-bit signed int sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767)|0; // write 16-bit sample view.setInt16(pos,true); pos += 2; } // next source sample offset++ } // create Blob return new Blob([buffer],{type: "audio/wav"}); function setUint16(data) { view.setUint16(pos,data,true); pos += 2; } function setUint32(data) { view.setUint32(pos,true); pos += 4; } }
WAV格式的相容性還是很6的,如下圖所示:
凡事支援Web Audio API的瀏覽器都支援WAV格式,所以,技術上完全可行。
下面這段JS可以得到剪裁後的WAV音訊的Blob資料格式:
var blob = bufferToWave(newAudioBuffer,frameCount);
有了Blob資料,接下來事情就簡單了。
我們可以直接把Blob資料轉換成URL,可以使用 URL.createObjectURL()
生成一個Blob連結。
假設頁面上有如下HTML程式碼:
<audio id="audio" controls=""></audio>
則如下設定,就可以點選上面的 <audio>
元素進行播放了。
audio.src = URL.createObjectURL(blob);
如果要轉換成Base64地址,可以這麼處理:
var reader2 = new FileReader(); reader2.onload = function(event){ audio.src = event.target.result; }; reader2.readAsDataURL(blob);
3) 如果希望上傳剪裁的音訊
有了Blob資料,上傳還不是灑灑水的事情。
可以使用FormData進行傳輸,例如:
var formData = new FormData(); formData.append('audio',blob); // 請求走起 var xhr = new XMLHttpRequest(); xhr.open('POST',this.cgiGetImg,true); // 請求成功 xhr.onload = function () { }; // 傳送資料 xhr.send(formData);
有demo可以進行效果體驗的,您可以狠狠地點選這裡: 使用者上傳的MP3音訊剪裁併播放demo
使用截圖示意如下:
本文地址: https://www.zhangxinxu.com/wordpress/?p=9505
到此這篇關於JS實現audio音訊剪裁剪下複製播放與上傳的文章就介紹到這了,更多相關js audio音訊剪裁內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!